Skip to content

Commit 288b98c

Browse files
committed
Let macros output all the errors in a single compilation
1 parent 92cb29e commit 288b98c

File tree

10 files changed

+156
-93
lines changed

10 files changed

+156
-93
lines changed

riscv-macros/src/riscv_rt.rs

Lines changed: 82 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,15 @@ pub enum Fn {
1414
impl Fn {
1515
/// Convenience method to generate the token stream for the `post_init` attribute
1616
pub fn post_init(args: TokenStream, input: TokenStream) -> TokenStream {
17-
match Self::PostInit.check_args_empty(args) {
18-
Ok(_) => Self::PostInit.quote_fn(input),
19-
Err(e) => e.to_compile_error().into(),
20-
}
17+
let errors = Self::PostInit.check_args_empty(args).err();
18+
Self::PostInit.quote_fn(input, errors)
2119
}
2220

2321
/// Generate the token stream for the function with the given attribute
24-
fn quote_fn(&self, item: TokenStream) -> TokenStream {
22+
fn quote_fn(&self, item: TokenStream, errors: Option<Error>) -> TokenStream {
2523
let mut func = parse_macro_input!(item as ItemFn);
2624

27-
if let Err(e) = self.check_fn(&func) {
25+
if let Err(e) = self.check_fn(&func, errors) {
2826
return e.to_compile_error().into();
2927
}
3028

@@ -47,67 +45,80 @@ impl Fn {
4745
}
4846

4947
/// Check if the function signature is valid for the given attribute
50-
fn check_fn(&self, f: &ItemFn) -> Result<()> {
48+
fn check_fn(&self, f: &ItemFn, mut errors: Option<Error>) -> Result<()> {
5149
// First, check that the function is private
5250
if f.vis != Visibility::Inherited {
53-
let attr = self.attr_name();
54-
return Err(Error::new(
55-
f.vis.span(),
56-
format!("`#[{attr}]` function must be private"),
57-
));
51+
combine_err(
52+
&mut errors,
53+
Error::new(f.vis.span(), "function must be private"),
54+
);
5855
}
56+
5957
let sig = &f.sig;
58+
// Next, check common aspects of the signature individually to accumulate errors
59+
if let Some(constness) = sig.constness {
60+
combine_err(
61+
&mut errors,
62+
Error::new(constness.span(), "function must not be const"),
63+
);
64+
}
65+
if let Some(asyncness) = sig.asyncness {
66+
combine_err(
67+
&mut errors,
68+
Error::new(asyncness.span(), "function must not be async"),
69+
);
70+
}
71+
if let Some(abi) = &sig.abi {
72+
combine_err(
73+
&mut errors,
74+
Error::new(abi.span(), "ABI must not be specified"),
75+
);
76+
}
77+
if !sig.generics.params.is_empty() {
78+
// Use to_token_stream to get a span covering the entire <...> block
79+
let span = sig.generics.params.span();
80+
combine_err(&mut errors, Error::new(span, "generics are not allowed"));
81+
}
6082

61-
// Next, check common aspects of the signature (constness, asyncness, generics, etc.)
62-
let valid_signature = sig.constness.is_none()
63-
&& sig.asyncness.is_none()
64-
&& sig.abi.is_none()
65-
&& sig.generics.params.is_empty()
66-
&& sig.generics.where_clause.is_none()
67-
&& sig.variadic.is_none();
68-
if !valid_signature {
69-
let attr = self.attr_name();
70-
let expected = self.expected_signature();
71-
return Err(Error::new(
72-
sig.span(),
73-
format!("`#[{attr}]` function signature must be `{expected}`"),
74-
));
75-
}
76-
77-
// Finally, check that input arguments and output type are valid
78-
self.check_inputs(&sig.inputs)?;
79-
self.check_output(&sig.output)
80-
}
83+
// Check input parameters...
84+
self.check_inputs(&sig.inputs, &mut errors);
85+
// ... and variadic arguments (they are at the end of input parameters)
86+
if let Some(variadic) = &sig.variadic {
87+
combine_err(
88+
&mut errors,
89+
Error::new(variadic.span(), "variadic arguments are not allowed"),
90+
);
91+
}
8192

82-
/// Utility method for printing attribute name in error messages
83-
const fn attr_name(&self) -> &'static str {
84-
// Use this match to specify attribute names for different functions in the future
85-
match self {
86-
Self::PostInit => "post_init",
93+
// Check output type...
94+
self.check_output(&sig.output, &mut errors);
95+
// ... and where clause (they are after output type)
96+
if let Some(where_clause) = &sig.generics.where_clause {
97+
combine_err(
98+
&mut errors,
99+
Error::new(where_clause.span(), "where clause is not allowed"),
100+
);
87101
}
88-
}
89102

90-
/// Utility method for printing expected function signature in error messages
91-
const fn expected_signature(&self) -> &'static str {
92-
// Use this match to specify expected signatures for different functions in the future
93-
match self {
94-
Self::PostInit => "[unsafe] fn([usize])",
103+
match errors {
104+
Some(e) => Err(e),
105+
None => Ok(()),
95106
}
96107
}
97108

98109
/// Check if the function has valid input arguments for the given attribute
99-
fn check_inputs(&self, inputs: &Punctuated<FnArg, Comma>) -> Result<()> {
110+
fn check_inputs(&self, inputs: &Punctuated<FnArg, Comma>, errors: &mut Option<Error>) {
100111
// Use this match to specify expected input arguments for different functions in the future
101112
match self {
102-
Self::PostInit => self.check_fn_args(inputs, &["usize"]),
113+
Self::PostInit => self.check_fn_args(inputs, &["usize"], errors),
103114
}
104115
}
105116

106117
/// Check if the function has a valid output type for the given attribute
107-
fn check_output(&self, output: &ReturnType) -> Result<()> {
118+
fn check_output(&self, output: &ReturnType, errors: &mut Option<Error>) {
108119
// Use this match to specify expected output types for different functions in the future
109120
match self {
110-
Self::PostInit => check_output_empty(output),
121+
Self::PostInit => check_output_empty(output, errors),
111122
}
112123
}
113124

@@ -120,7 +131,7 @@ impl Fn {
120131

121132
export_name.map(|name| {
122133
quote! {
123-
#[cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), export_name = #name)]
134+
#[export_name = #name]
124135
}
125136
})
126137
}
@@ -143,11 +154,7 @@ impl Fn {
143154
Ok(())
144155
} else {
145156
let args: TokenStream2 = args.into();
146-
let attr = self.attr_name();
147-
Err(Error::new(
148-
args.span(),
149-
format!("`#[{attr}]` function does not accept any arguments"),
150-
))
157+
Err(Error::new(args.span(), "macro arguments are not allowed"))
151158
}
152159
}
153160

@@ -156,21 +163,29 @@ impl Fn {
156163
&self,
157164
inputs: &Punctuated<FnArg, Comma>,
158165
expected_types: &[&str],
159-
) -> Result<()> {
166+
errors: &mut Option<Error>,
167+
) {
160168
let mut expected_iter = expected_types.iter();
161169
for arg in inputs.iter() {
162170
match expected_iter.next() {
163-
Some(expected) => check_arg_type(arg, expected)?,
171+
Some(expected) => {
172+
if let Err(e) = check_arg_type(arg, expected) {
173+
combine_err(errors, e);
174+
}
175+
}
164176
None => {
165-
let attr = self.attr_name();
166-
return Err(Error::new(
167-
arg.span(),
168-
format!("`#[{attr}]` function has too many input arguments"),
169-
));
177+
combine_err(errors, Error::new(arg.span(), "too many input arguments"));
170178
}
171179
}
172180
}
173-
Ok(())
181+
}
182+
}
183+
184+
/// Combine a new error into an optional accumulator
185+
fn combine_err(acc: &mut Option<Error>, err: Error) {
186+
match acc {
187+
Some(e) => e.combine(err),
188+
None => *acc = Some(err),
174189
}
175190
}
176191

@@ -181,7 +196,7 @@ fn check_arg_type(arg: &FnArg, expected: &str) -> Result<()> {
181196
if !is_correct_type(&argument.ty, expected) {
182197
Err(Error::new(
183198
argument.ty.span(),
184-
format!("argument type must be {expected}"),
199+
format!("argument type must be `{expected}`"),
185200
))
186201
} else {
187202
Ok(())
@@ -221,18 +236,16 @@ fn strip_type_path(ty: &Type) -> Option<Type> {
221236
}
222237

223238
/// Make sure the output type is either `()` or absent
224-
fn check_output_empty(output: &ReturnType) -> Result<()> {
239+
fn check_output_empty(output: &ReturnType, errors: &mut Option<Error>) {
225240
match output {
226-
ReturnType::Default => Ok(()),
241+
ReturnType::Default => {}
227242
ReturnType::Type(_, ty) => match **ty {
228243
Type::Tuple(ref tuple) => {
229-
if tuple.elems.is_empty() {
230-
Ok(())
231-
} else {
232-
Err(Error::new(tuple.span(), "return type must be ()"))
244+
if !tuple.elems.is_empty() {
245+
combine_err(errors, Error::new(tuple.span(), "return type must be ()"));
233246
}
234247
}
235-
_ => Err(Error::new(ty.span(), "return type must be ()")),
248+
_ => combine_err(errors, Error::new(ty.span(), "return type must be ()")),
236249
},
237250
}
238251
}

tests-trybuild/tests/riscv-rt/post_init/fail_arg_count.rs

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests-trybuild/tests/riscv-rt/post_init/fail_arg_count.stderr

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests-trybuild/tests/riscv-rt/post_init/fail_arg_type.rs

Lines changed: 0 additions & 4 deletions
This file was deleted.

tests-trybuild/tests/riscv-rt/post_init/fail_arg_type.stderr

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests-trybuild/tests/riscv-rt/post_init/fail_async.stderr

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#[riscv_rt::post_init(arg)]
2+
pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
3+
where
4+
T: Copy,
5+
{
6+
}
7+
8+
fn main() {}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
error: macro arguments are not allowed
2+
--> tests/riscv-rt/post_init/fail_full.rs:1:23
3+
|
4+
1 | #[riscv_rt::post_init(arg)]
5+
| ^^^
6+
7+
error: function must be private
8+
--> tests/riscv-rt/post_init/fail_full.rs:2:1
9+
|
10+
2 | pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
11+
| ^^^
12+
13+
error: function must not be const
14+
--> tests/riscv-rt/post_init/fail_full.rs:2:5
15+
|
16+
2 | pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
17+
| ^^^^^
18+
19+
error: function must not be async
20+
--> tests/riscv-rt/post_init/fail_full.rs:2:11
21+
|
22+
2 | pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
23+
| ^^^^^
24+
25+
error: ABI must not be specified
26+
--> tests/riscv-rt/post_init/fail_full.rs:2:17
27+
|
28+
2 | pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
29+
| ^^^^^^
30+
31+
error: generics are not allowed
32+
--> tests/riscv-rt/post_init/fail_full.rs:2:46
33+
|
34+
2 | pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
35+
| ^^
36+
37+
error: argument type must be `usize`
38+
--> tests/riscv-rt/post_init/fail_full.rs:2:63
39+
|
40+
2 | pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
41+
| ^^^
42+
43+
error: too many input arguments
44+
--> tests/riscv-rt/post_init/fail_full.rs:2:68
45+
|
46+
2 | pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
47+
| ^^^^
48+
49+
error: variadic arguments are not allowed
50+
--> tests/riscv-rt/post_init/fail_full.rs:2:81
51+
|
52+
2 | pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
53+
| ^
54+
55+
error: return type must be ()
56+
--> tests/riscv-rt/post_init/fail_full.rs:2:89
57+
|
58+
2 | pub const async extern "Rust" fn before_main<'a, T>(_hart_id: u32, _dtb: &'a T, ...) -> !
59+
| ^
60+
61+
error: where clause is not allowed
62+
--> tests/riscv-rt/post_init/fail_full.rs:3:1
63+
|
64+
3 | where
65+
| ^^^^^

tests-trybuild/tests/riscv-rt/post_init/pass_empty.rs renamed to tests-trybuild/tests/riscv-rt/post_init/pass_empty_safe.rs

File renamed without changes.

tests-trybuild/tests/riscv-rt/post_init/fail_async.rs renamed to tests-trybuild/tests/riscv-rt/post_init/pass_empty_unsafe.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
#[riscv_rt::post_init]
2-
async fn before_main() {}
2+
unsafe fn before_main() {}
33

44
fn main() {}

0 commit comments

Comments
 (0)