master
rs 465 lines 16.1 KB
Raw
1 // Copyright 2025, Linaro Limited
2 // Author(s): Manos Pitsidianakis <manos.pitsidianakis@linaro.org>
3 // SPDX-License-Identifier: GPL-2.0-or-later
4
5 use quote::quote;
6
7 use super::*;
8
9 macro_rules! derive_compile_fail {
10 ($derive_fn:path, $input:expr, $($error_msg:expr),+ $(,)?) => {{
11 let input: proc_macro2::TokenStream = $input;
12 let error_msg = &[$( quote! { ::core::compile_error! { $error_msg } } ),*];
13 let derive_fn: fn(input: syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> =
14 $derive_fn;
15
16 let input: syn::DeriveInput = syn::parse2(input).unwrap();
17 let result = derive_fn(input);
18 let err = result.unwrap_err().into_compile_error();
19 assert_eq!(
20 err.to_string(),
21 quote! { #(#error_msg)* }.to_string()
22 );
23 }};
24 }
25
26 macro_rules! derive_compile {
27 ($derive_fn:path, $input:expr, $($expected:tt)*) => {{
28 let input: proc_macro2::TokenStream = $input;
29 let expected: proc_macro2::TokenStream = $($expected)*;
30 let derive_fn: fn(input: syn::DeriveInput) -> Result<proc_macro2::TokenStream, syn::Error> =
31 $derive_fn;
32
33 let input: syn::DeriveInput = syn::parse2(input).unwrap();
34 let result = derive_fn(input).unwrap();
35 assert_eq!(result.to_string(), expected.to_string());
36 }};
37 }
38
39 #[test]
40 fn test_derive_device() {
41 // Check that repr(C) is used
42 derive_compile_fail!(
43 derive_device_or_error,
44 quote! {
45 #[derive(Device)]
46 struct Foo {
47 _unused: [u8; 0],
48 }
49 },
50 "#[repr(C)] required for #[derive(Device)]"
51 );
52 // Check that invalid/misspelled attributes raise an error
53 derive_compile_fail!(
54 derive_device_or_error,
55 quote! {
56 #[repr(C)]
57 #[derive(Device)]
58 struct DummyState {
59 #[property(defalt = true)]
60 migrate_clock: bool,
61 }
62 },
63 "Expected one of `bit`, `default` or `rename`"
64 );
65 // Check that repeated attributes are not allowed:
66 derive_compile_fail!(
67 derive_device_or_error,
68 quote! {
69 #[repr(C)]
70 #[derive(Device)]
71 struct DummyState {
72 #[property(rename = "migrate-clk", rename = "migrate-clk", default = true)]
73 migrate_clock: bool,
74 }
75 },
76 "Duplicate argument",
77 "Already used here",
78 );
79 derive_compile_fail!(
80 derive_device_or_error,
81 quote! {
82 #[repr(C)]
83 #[derive(Device)]
84 struct DummyState {
85 #[property(default = true, default = true)]
86 migrate_clock: bool,
87 }
88 },
89 "Duplicate argument",
90 "Already used here",
91 );
92 derive_compile_fail!(
93 derive_device_or_error,
94 quote! {
95 #[repr(C)]
96 #[derive(Device)]
97 struct DummyState {
98 #[property(bit = 0, bit = 1)]
99 flags: u32,
100 }
101 },
102 "Duplicate argument",
103 "Already used here",
104 );
105 // Check that the field name is preserved when `rename` isn't used:
106 derive_compile!(
107 derive_device_or_error,
108 quote! {
109 #[repr(C)]
110 #[derive(Device)]
111 pub struct DummyState {
112 parent: ParentField<DeviceState>,
113 #[property(default = true)]
114 migrate_clock: bool,
115 }
116 },
117 quote! {
118 unsafe impl ::hwcore::DevicePropertiesImpl for DummyState {
119 const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
120 ::hwcore::bindings::Property {
121 name: ::std::ffi::CStr::as_ptr(c"migrate_clock"),
122 info: <bool as ::hwcore::QDevProp>::BASE_INFO,
123 offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize,
124 bitnr: 0,
125 set_default: true,
126 defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 },
127 ..::common::Zeroable::ZERO
128 }
129 ];
130 }
131 }
132 );
133 // Check that `rename` value is used for the property name when used:
134 derive_compile!(
135 derive_device_or_error,
136 quote! {
137 #[repr(C)]
138 #[derive(Device)]
139 pub struct DummyState {
140 parent: ParentField<DeviceState>,
141 #[property(rename = "migrate-clk", default = true)]
142 migrate_clock: bool,
143 }
144 },
145 quote! {
146 unsafe impl ::hwcore::DevicePropertiesImpl for DummyState {
147 const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
148 ::hwcore::bindings::Property {
149 name: ::std::ffi::CStr::as_ptr(c"migrate-clk"),
150 info: <bool as ::hwcore::QDevProp>::BASE_INFO,
151 offset: ::core::mem::offset_of!(DummyState, migrate_clock) as isize,
152 bitnr: 0,
153 set_default: true,
154 defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 },
155 ..::common::Zeroable::ZERO
156 }
157 ];
158 }
159 }
160 );
161 // Check that `bit` value is used for the bit property without default
162 // value (note: though C macro (e.g., DEFINE_PROP_BIT) always requires
163 // default value, Rust side allows to default this field to "0"):
164 derive_compile!(
165 derive_device_or_error,
166 quote! {
167 #[repr(C)]
168 #[derive(Device)]
169 pub struct DummyState {
170 parent: ParentField<DeviceState>,
171 #[property(bit = 3)]
172 flags: u32,
173 }
174 },
175 quote! {
176 unsafe impl ::hwcore::DevicePropertiesImpl for DummyState {
177 const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
178 ::hwcore::bindings::Property {
179 name: ::std::ffi::CStr::as_ptr(c"flags"),
180 info: <u32 as ::hwcore::QDevProp>::BIT_INFO,
181 offset: ::core::mem::offset_of!(DummyState, flags) as isize,
182 bitnr : {
183 const { assert!(3 >= 0 && 3 < u32::BITS as _ , "bit number exceeds type bits range"); }
184 3 as u8
185 },
186 set_default: false,
187 defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: 0 as u64 },
188 ..::common::Zeroable::ZERO
189 }
190 ];
191 }
192 }
193 );
194 // Check that `bit` value is used for the bit property when used:
195 derive_compile!(
196 derive_device_or_error,
197 quote! {
198 #[repr(C)]
199 #[derive(Device)]
200 pub struct DummyState {
201 parent: ParentField<DeviceState>,
202 #[property(bit = 3, default = true)]
203 flags: u32,
204 }
205 },
206 quote! {
207 unsafe impl ::hwcore::DevicePropertiesImpl for DummyState {
208 const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
209 ::hwcore::bindings::Property {
210 name: ::std::ffi::CStr::as_ptr(c"flags"),
211 info: <u32 as ::hwcore::QDevProp>::BIT_INFO,
212 offset: ::core::mem::offset_of!(DummyState, flags) as isize,
213 bitnr : {
214 const { assert!(3 >= 0 && 3 < u32::BITS as _ , "bit number exceeds type bits range"); }
215 3 as u8
216 },
217 set_default: true,
218 defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: true as u64 },
219 ..::common::Zeroable::ZERO
220 }
221 ];
222 }
223 }
224 );
225 // Check that `bit` value is used for the bit property with rename when used:
226 derive_compile!(
227 derive_device_or_error,
228 quote! {
229 #[repr(C)]
230 #[derive(Device)]
231 pub struct DummyState {
232 parent: ParentField<DeviceState>,
233 #[property(rename = "msi", bit = 3, default = false)]
234 flags: u64,
235 }
236 },
237 quote! {
238 unsafe impl ::hwcore::DevicePropertiesImpl for DummyState {
239 const PROPERTIES: &'static [::hwcore::bindings::Property] = &[
240 ::hwcore::bindings::Property {
241 name: ::std::ffi::CStr::as_ptr(c"msi"),
242 info: <u64 as ::hwcore::QDevProp>::BIT_INFO,
243 offset: ::core::mem::offset_of!(DummyState, flags) as isize,
244 bitnr : {
245 const { assert!(3 >= 0 && 3 < u64::BITS as _ , "bit number exceeds type bits range"); }
246 3 as u8
247 },
248 set_default: true,
249 defval: ::hwcore::bindings::Property__bindgen_ty_1 { u: false as u64 },
250 ..::common::Zeroable::ZERO
251 }
252 ];
253 }
254 }
255 );
256 }
257
258 #[test]
259 fn test_derive_object() {
260 derive_compile_fail!(
261 derive_object_or_error,
262 quote! {
263 #[derive(Object)]
264 struct Foo {
265 _unused: [u8; 0],
266 }
267 },
268 "#[repr(C)] required for #[derive(Object)]"
269 );
270 derive_compile!(
271 derive_object_or_error,
272 quote! {
273 #[derive(Object)]
274 #[repr(C)]
275 struct Foo {
276 _unused: [u8; 0],
277 }
278 },
279 quote! {
280 ::common::assert_field_type!(
281 Foo,
282 _unused,
283 ::qom::ParentField<<Foo as ::qom::ObjectImpl>::ParentType>
284 );
285 ::util::module_init! {
286 MODULE_INIT_QOM => unsafe {
287 ::qom::type_register_static(&<Foo as ::qom::ObjectImpl>::TYPE_INFO);
288 }
289 }
290 }
291 );
292 }
293
294 #[test]
295 fn test_derive_tryinto() {
296 derive_compile_fail!(
297 derive_tryinto_or_error,
298 quote! {
299 #[derive(TryInto)]
300 struct Foo {
301 _unused: [u8; 0],
302 }
303 },
304 "#[repr(u8/u16/u32/u64) required for #[derive(TryInto)]"
305 );
306 derive_compile!(
307 derive_tryinto_or_error,
308 quote! {
309 #[derive(TryInto)]
310 #[repr(u8)]
311 enum Foo {
312 First = 0,
313 Second,
314 }
315 },
316 quote! {
317 impl Foo {
318 #[allow(dead_code)]
319 pub const fn into_bits(self) -> u8 {
320 self as u8
321 }
322
323 #[allow(dead_code)]
324 pub const fn from_bits(value: u8) -> Self {
325 match ({
326 const First: u8 = Foo::First as u8;
327 const Second: u8 = Foo::Second as u8;
328 match value {
329 First => core::result::Result::Ok(Foo::First),
330 Second => core::result::Result::Ok(Foo::Second),
331 _ => core::result::Result::Err(value),
332 }
333 }) {
334 Ok(x) => x,
335 Err(_) => panic!("invalid value for Foo"),
336 }
337 }
338 }
339
340 impl core::convert::TryFrom<u8> for Foo {
341 type Error = u8;
342
343 #[allow(ambiguous_associated_items)]
344 fn try_from(value: u8) -> Result<Self, u8> {
345 const First: u8 = Foo::First as u8;
346 const Second: u8 = Foo::Second as u8;
347 match value {
348 First => core::result::Result::Ok(Foo::First),
349 Second => core::result::Result::Ok(Foo::Second),
350 _ => core::result::Result::Err(value),
351 }
352 }
353 }
354 }
355 );
356 }
357
358 #[test]
359 fn test_derive_to_migration_state() {
360 derive_compile_fail!(
361 MigrationStateDerive::expand,
362 quote! {
363 struct MyStruct {
364 #[migration_state(omit, clone)]
365 bad: u32,
366 }
367 },
368 "ToMigrationState: omit cannot be used with other attributes"
369 );
370 derive_compile_fail!(
371 MigrationStateDerive::expand,
372 quote! {
373 struct MyStruct {
374 #[migration_state(into)]
375 bad: u32,
376 }
377 },
378 "unexpected end of input, expected parentheses"
379 );
380 derive_compile_fail!(
381 MigrationStateDerive::expand,
382 quote! {
383 struct MyStruct {
384 #[migration_state(into(String), try_into(String))]
385 bad: &'static str,
386 }
387 },
388 "ToMigrationState: into and try_into attributes cannot be used together"
389 );
390 derive_compile!(
391 MigrationStateDerive::expand,
392 quote! {
393 #[migration_state(rename = CustomMigration)]
394 struct MyStruct {
395 #[migration_state(omit)]
396 runtime_field: u32,
397
398 #[migration_state(clone)]
399 shared_data: String,
400
401 #[migration_state(into(Cow<'static, str>), clone)]
402 converted_field: String,
403
404 #[migration_state(try_into(i8))]
405 fallible_field: u32,
406
407 nested_field: NestedStruct,
408 simple_field: u32,
409 }
410 },
411 quote! {
412 #[derive(Default)]
413 pub struct CustomMigration {
414 pub shared_data: String,
415 pub converted_field: Cow<'static, str>,
416 pub fallible_field: i8,
417 pub nested_field: <NestedStruct as ToMigrationState>::Migrated,
418 pub simple_field: <u32 as ToMigrationState>::Migrated,
419 }
420 impl ToMigrationState for MyStruct {
421 type Migrated = CustomMigration;
422 fn snapshot_migration_state(
423 &self,
424 target: &mut Self::Migrated
425 ) -> Result<(), migration::InvalidError> {
426 target.shared_data = self.shared_data.clone();
427 target.converted_field = self.converted_field.clone().into();
428 target.fallible_field = self
429 .fallible_field
430 .try_into()
431 .map_err(|_| migration::InvalidError)?;
432 self.nested_field
433 .snapshot_migration_state(&mut target.nested_field)?;
434 self.simple_field
435 .snapshot_migration_state(&mut target.simple_field)?;
436 Ok(())
437 }
438 #[allow(clippy::used_underscore_binding)]
439 fn restore_migrated_state_mut(
440 &mut self,
441 source: Self::Migrated,
442 _version_id: u8
443 ) -> Result<(), migration::InvalidError> {
444 let Self::Migrated {
445 shared_data,
446 converted_field,
447 fallible_field,
448 nested_field,
449 simple_field
450 } = source;
451 self.shared_data = shared_data;
452 self.converted_field = converted_field.into();
453 self.fallible_field = fallible_field
454 .try_into()
455 .map_err(|_| migration::InvalidError)?;
456 self.nested_field
457 .restore_migrated_state_mut(nested_field, _version_id)?;
458 self.simple_field
459 .restore_migrated_state_mut(simple_field, _version_id)?;
460 Ok(())
461 }
462 }
463 }
464 );
465 }