Skip to content

Commit a7c013f

Browse files
committed
Merge patch series "refactor Rust proc macros with syn"
Gary writes: "This series converts Rust proc macros that we have to use `syn`, and replace the custom `quote!` macro that we have with the vendored `quote!` macro. The `pin-init` macros are not converted yet; Benno has a work in progress in converting them. They're however converted to use `quote` and `proc-macro2` crate so our custom `quote!` macro can be removed. Overall this improves the robustness of the macros as we have precise parsing of the AST rather than relying on heuristics to extract needed information from there. This is also a quality-of-life improvement to those using language servers (e.g. Rust analyzer) as the span information of the proc macros are now preserved which allows the "jump-to-definition" feature to work, even when used on completely custom macros such as `module!`. Miguel gave a very good explanation on why `syn` is a good idea in the patch series that introduced it [1], which I shall not repeat here." The `pin-init` rewrite was merged just before this one. Link: https://lore.kernel.org/rust-for-linux/20251124151837.2184382-1-ojeda@kernel.org/ [1] Link: https://patch.msgid.link/20260112170919.1888584-1-gary@kernel.org Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
2 parents 99ba0fa + 779f6e3 commit a7c013f

11 files changed

Lines changed: 816 additions & 982 deletions

File tree

rust/kernel/kunit.rs

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,6 @@ pub fn is_test_result_ok(t: impl TestResult) -> bool {
189189
}
190190

191191
/// Represents an individual test case.
192-
///
193-
/// The [`kunit_unsafe_test_suite!`] macro expects a `NULL`-terminated list of valid test cases.
194-
/// Use [`kunit_case_null`] to generate such a delimiter.
195192
#[doc(hidden)]
196193
pub const fn kunit_case(
197194
name: &'static kernel::str::CStr,
@@ -212,27 +209,6 @@ pub const fn kunit_case(
212209
}
213210
}
214211

215-
/// Represents the `NULL` test case delimiter.
216-
///
217-
/// The [`kunit_unsafe_test_suite!`] macro expects a `NULL`-terminated list of test cases. This
218-
/// function returns such a delimiter.
219-
#[doc(hidden)]
220-
pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
221-
kernel::bindings::kunit_case {
222-
run_case: None,
223-
name: core::ptr::null_mut(),
224-
generate_params: None,
225-
attr: kernel::bindings::kunit_attributes {
226-
speed: kernel::bindings::kunit_speed_KUNIT_SPEED_NORMAL,
227-
},
228-
status: kernel::bindings::kunit_status_KUNIT_SUCCESS,
229-
module_name: core::ptr::null_mut(),
230-
log: core::ptr::null_mut(),
231-
param_init: None,
232-
param_exit: None,
233-
}
234-
}
235-
236212
/// Registers a KUnit test suite.
237213
///
238214
/// # Safety
@@ -251,7 +227,7 @@ pub const fn kunit_case_null() -> kernel::bindings::kunit_case {
251227
///
252228
/// static mut KUNIT_TEST_CASES: [kernel::bindings::kunit_case; 2] = [
253229
/// kernel::kunit::kunit_case(c"name", test_fn),
254-
/// kernel::kunit::kunit_case_null(),
230+
/// pin_init::zeroed(),
255231
/// ];
256232
/// kernel::kunit_unsafe_test_suite!(suite_name, KUNIT_TEST_CASES);
257233
/// ```

rust/macros/concat_idents.rs

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,36 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use proc_macro::{token_stream, Ident, TokenStream, TokenTree};
3+
use proc_macro2::{
4+
Ident,
5+
TokenStream,
6+
TokenTree, //
7+
};
8+
use syn::{
9+
parse::{
10+
Parse,
11+
ParseStream, //
12+
},
13+
Result,
14+
Token, //
15+
};
416

5-
use crate::helpers::expect_punct;
17+
pub(crate) struct Input {
18+
a: Ident,
19+
_comma: Token![,],
20+
b: Ident,
21+
}
622

7-
fn expect_ident(it: &mut token_stream::IntoIter) -> Ident {
8-
if let Some(TokenTree::Ident(ident)) = it.next() {
9-
ident
10-
} else {
11-
panic!("Expected Ident")
23+
impl Parse for Input {
24+
fn parse(input: ParseStream<'_>) -> Result<Self> {
25+
Ok(Self {
26+
a: input.parse()?,
27+
_comma: input.parse()?,
28+
b: input.parse()?,
29+
})
1230
}
1331
}
1432

15-
pub(crate) fn concat_idents(ts: TokenStream) -> TokenStream {
16-
let mut it = ts.into_iter();
17-
let a = expect_ident(&mut it);
18-
assert_eq!(expect_punct(&mut it), ',');
19-
let b = expect_ident(&mut it);
20-
assert!(it.next().is_none(), "only two idents can be concatenated");
33+
pub(crate) fn concat_idents(Input { a, b, .. }: Input) -> TokenStream {
2134
let res = Ident::new(&format!("{a}{b}"), b.span());
2235
TokenStream::from_iter([TokenTree::Ident(res)])
2336
}

rust/macros/export.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use crate::helpers::function_name;
4-
use proc_macro::TokenStream;
3+
use proc_macro2::TokenStream;
4+
use quote::quote;
55

66
/// Please see [`crate::export`] for documentation.
7-
pub(crate) fn export(_attr: TokenStream, ts: TokenStream) -> TokenStream {
8-
let Some(name) = function_name(ts.clone()) else {
9-
return "::core::compile_error!(\"The #[export] attribute must be used on a function.\");"
10-
.parse::<TokenStream>()
11-
.unwrap();
12-
};
7+
pub(crate) fn export(f: syn::ItemFn) -> TokenStream {
8+
let name = &f.sig.ident;
139

14-
// This verifies that the function has the same signature as the declaration generated by
15-
// bindgen. It makes use of the fact that all branches of an if/else must have the same type.
16-
let signature_check = quote!(
10+
quote! {
11+
// This verifies that the function has the same signature as the declaration generated by
12+
// bindgen. It makes use of the fact that all branches of an if/else must have the same
13+
// type.
1714
const _: () = {
1815
if true {
1916
::kernel::bindings::#name
2017
} else {
2118
#name
2219
};
2320
};
24-
);
2521

26-
let no_mangle = quote!(#[no_mangle]);
27-
28-
TokenStream::from_iter([signature_check, no_mangle, ts])
22+
#[no_mangle]
23+
#f
24+
}
2925
}

rust/macros/fmt.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use proc_macro::{Ident, TokenStream, TokenTree};
43
use std::collections::BTreeSet;
54

5+
use proc_macro2::{Ident, TokenStream, TokenTree};
6+
use quote::quote_spanned;
7+
68
/// Please see [`crate::fmt`] for documentation.
79
pub(crate) fn fmt(input: TokenStream) -> TokenStream {
810
let mut input = input.into_iter();

rust/macros/helpers.rs

Lines changed: 31 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,41 @@
11
// SPDX-License-Identifier: GPL-2.0
22

3-
use proc_macro::{token_stream, Group, Ident, TokenStream, TokenTree};
4-
5-
pub(crate) fn try_ident(it: &mut token_stream::IntoIter) -> Option<String> {
6-
if let Some(TokenTree::Ident(ident)) = it.next() {
7-
Some(ident.to_string())
8-
} else {
9-
None
10-
}
11-
}
12-
13-
pub(crate) fn try_sign(it: &mut token_stream::IntoIter) -> Option<char> {
14-
let peek = it.clone().next();
15-
match peek {
16-
Some(TokenTree::Punct(punct)) if punct.as_char() == '-' => {
17-
let _ = it.next();
18-
Some(punct.as_char())
3+
use proc_macro2::TokenStream;
4+
use quote::ToTokens;
5+
use syn::{
6+
parse::{
7+
Parse,
8+
ParseStream, //
9+
},
10+
Attribute,
11+
Error,
12+
LitStr,
13+
Result, //
14+
};
15+
16+
/// A string literal that is required to have ASCII value only.
17+
pub(crate) struct AsciiLitStr(LitStr);
18+
19+
impl Parse for AsciiLitStr {
20+
fn parse(input: ParseStream<'_>) -> Result<Self> {
21+
let s: LitStr = input.parse()?;
22+
if !s.value().is_ascii() {
23+
return Err(Error::new_spanned(s, "expected ASCII-only string literal"));
1924
}
20-
_ => None,
21-
}
22-
}
23-
24-
pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option<String> {
25-
if let Some(TokenTree::Literal(literal)) = it.next() {
26-
Some(literal.to_string())
27-
} else {
28-
None
25+
Ok(Self(s))
2926
}
3027
}
3128

32-
pub(crate) fn try_string(it: &mut token_stream::IntoIter) -> Option<String> {
33-
try_literal(it).and_then(|string| {
34-
if string.starts_with('\"') && string.ends_with('\"') {
35-
let content = &string[1..string.len() - 1];
36-
if content.contains('\\') {
37-
panic!("Escape sequences in string literals not yet handled");
38-
}
39-
Some(content.to_string())
40-
} else if string.starts_with("r\"") {
41-
panic!("Raw string literals are not yet handled");
42-
} else {
43-
None
44-
}
45-
})
46-
}
47-
48-
pub(crate) fn expect_ident(it: &mut token_stream::IntoIter) -> String {
49-
try_ident(it).expect("Expected Ident")
50-
}
51-
52-
pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
53-
if let TokenTree::Punct(punct) = it.next().expect("Reached end of token stream for Punct") {
54-
punct.as_char()
55-
} else {
56-
panic!("Expected Punct");
29+
impl ToTokens for AsciiLitStr {
30+
fn to_tokens(&self, ts: &mut TokenStream) {
31+
self.0.to_tokens(ts);
5732
}
5833
}
5934

60-
pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String {
61-
try_string(it).expect("Expected string")
62-
}
63-
64-
pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> String {
65-
let string = try_string(it).expect("Expected string");
66-
assert!(string.is_ascii(), "Expected ASCII string");
67-
string
68-
}
69-
70-
pub(crate) fn expect_group(it: &mut token_stream::IntoIter) -> Group {
71-
if let TokenTree::Group(group) = it.next().expect("Reached end of token stream for Group") {
72-
group
73-
} else {
74-
panic!("Expected Group");
75-
}
76-
}
77-
78-
pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
79-
if it.next().is_some() {
80-
panic!("Expected end");
81-
}
82-
}
83-
84-
/// Given a function declaration, finds the name of the function.
85-
pub(crate) fn function_name(input: TokenStream) -> Option<Ident> {
86-
let mut input = input.into_iter();
87-
while let Some(token) = input.next() {
88-
match token {
89-
TokenTree::Ident(i) if i.to_string() == "fn" => {
90-
if let Some(TokenTree::Ident(i)) = input.next() {
91-
return Some(i);
92-
}
93-
return None;
94-
}
95-
_ => continue,
96-
}
35+
impl AsciiLitStr {
36+
pub(crate) fn value(&self) -> String {
37+
self.0.value()
9738
}
98-
None
9939
}
10040

10141
pub(crate) fn file() -> String {
@@ -115,16 +55,7 @@ pub(crate) fn file() -> String {
11555
}
11656
}
11757

118-
/// Parse a token stream of the form `expected_name: "value",` and return the
119-
/// string in the position of "value".
120-
///
121-
/// # Panics
122-
///
123-
/// - On parse error.
124-
pub(crate) fn expect_string_field(it: &mut token_stream::IntoIter, expected_name: &str) -> String {
125-
assert_eq!(expect_ident(it), expected_name);
126-
assert_eq!(expect_punct(it), ':');
127-
let string = expect_string(it);
128-
assert_eq!(expect_punct(it), ',');
129-
string
58+
/// Obtain all `#[cfg]` attributes.
59+
pub(crate) fn gather_cfg_attrs(attr: &[Attribute]) -> impl Iterator<Item = &Attribute> + '_ {
60+
attr.iter().filter(|a| a.path().is_ident("cfg"))
13061
}

0 commit comments

Comments
 (0)