* [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros
@ 2025-12-24 3:59 Jesung Yang
2025-12-24 3:59 ` [PATCH v3 3/4] rust: macros: add private doctests for `Into` derive macro Jesung Yang
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Jesung Yang @ 2025-12-24 3:59 UTC (permalink / raw)
To: Alexandre Courbot, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: rust-for-linux, nouveau, linux-kernel, Jesung Yang
This patch series introduces derive macros for the `TryFrom` and `Into`
traits.
Primary updates in v3:
- Adopt `syn` for improved macro parsing.
- Allow `kernel::num::Bounded` to be specified in helper attributes,
enabling the generation of `TryFrom` and `Into` implementations for
types with restricted bit ranges as required by the nova-core [1].
- Add compile-time overflow assertion to ensure that enum discriminants
fit within the types being converted to or from, preventing silent
overflows.
Note: 1015+ insertions are purely from doctests. Most of them are kept
private to keep the public API documentation clean and simple for
users. This might not be the usual way of doing things, but for now I
don't think I have a better option. Feedback and suggestions are always
appreciated.
[1] https://lore.kernel.org/rust-for-linux/DDHU4LL4GGIY.16OJMIL7ZK58P@nvidia.com/
Best regards,
Jesung
---
Changes in v3:
- Use the vendored `syn` and `quote` crates.
- Support `kernel::num::Bounded`.
- Add compile-time overflow assertion.
- Add a comment about `#[repr(C)]` enums.
- Drop Tested-by and Reviewed-by tags, as the code structure has
changed substantially. (Thanks for the previous reviews and testing!)
- Link to v2: https://lore.kernel.org/rust-for-linux/cover.1755235180.git.y.j3ms.n@gmail.com/
Changes in v2 (no functional changes):
- Split the patch "rust: macros: extend custom `quote!()` macro"
into two separate patches.
- Remove unnecessary spaces between tags.
- Use a consistent commit subject prefix: "rust: macros:".
- Add Tested-by tags.
- Link to v1: https://lore.kernel.org/rust-for-linux/cover.1754228164.git.y.j3ms.n@gmail.com/
---
Jesung Yang (4):
rust: macros: add derive macro for `Into`
rust: macros: add derive macro for `TryFrom`
rust: macros: add private doctests for `Into` derive macro
rust: macros: add private doctests for `TryFrom` derive macro
rust/macros/convert.rs | 1546 ++++++++++++++++++++++++++++++++++++++++
rust/macros/lib.rs | 319 +++++++++
2 files changed, 1865 insertions(+)
create mode 100644 rust/macros/convert.rs
base-commit: 9448598b22c50c8a5bb77a9103e2d49f134c9578
--
2.47.3
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 3/4] rust: macros: add private doctests for `Into` derive macro
2025-12-24 3:59 [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
@ 2025-12-24 3:59 ` Jesung Yang
2025-12-24 3:59 ` [PATCH v3 4/4] rust: macros: add private doctests for `TryFrom` " Jesung Yang
2025-12-25 7:32 ` [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros Alexandre Courbot
2 siblings, 0 replies; 5+ messages in thread
From: Jesung Yang @ 2025-12-24 3:59 UTC (permalink / raw)
To: Alexandre Courbot, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: rust-for-linux, nouveau, linux-kernel, Jesung Yang
Add internal doctests to verify the `Into` derive macro's logic. This
ensures comprehensive testing while keeping the public-facing
documentation compact and readable.
Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
---
rust/macros/convert.rs | 436 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 436 insertions(+)
diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index ceaf089c1ecb..7159fb4dd187 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -529,3 +529,439 @@ fn is_valid_primitive(ident: &Ident) -> bool {
| "isize"
)
}
+
+mod derive_into_tests {
+ /// ```
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(u8)]
+ /// enum Foo {
+ /// // Works with const expressions.
+ /// A = add(0, 0),
+ /// B = 2_isize.pow(1) - 1,
+ /// }
+ ///
+ /// const fn add(a: isize, b: isize) -> isize {
+ /// a + b
+ /// }
+ ///
+ /// assert_eq!(0_u8, Foo::A.into());
+ /// assert_eq!(1_u8, Foo::B.into());
+ /// ```
+ mod works_with_const_expr {}
+
+ /// ```
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(bool)]
+ /// enum Foo {
+ /// A,
+ /// B,
+ /// }
+ ///
+ /// assert_eq!(false, Foo::A.into());
+ /// assert_eq!(true, Foo::B.into());
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(bool)]
+ /// enum Foo {
+ /// // `-1` cannot be represented with `bool`.
+ /// A = -1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(bool)]
+ /// enum Foo {
+ /// // `2` cannot be represented with `bool`.
+ /// A = 2,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_bool {}
+
+ /// ```
+ /// use kernel::{
+ /// macros::Into,
+ /// num::Bounded, //
+ /// };
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i8, 7>)]
+ /// enum Foo {
+ /// A = -1 << 6, // The minimum value of `Bounded<i8, 7>`.
+ /// B = (1 << 6) - 1, // The maximum value of `Bounded<i8, 7>`.
+ /// }
+ ///
+ /// let foo_a: Bounded<i8, 7> = Foo::A.into();
+ /// let foo_b: Bounded<i8, 7> = Foo::B.into();
+ /// assert_eq!(Bounded::<i8, 7>::new::<{ -1_i8 << 6 }>(), foo_a);
+ /// assert_eq!(Bounded::<i8, 7>::new::<{ (1_i8 << 6) - 1 }>(), foo_b);
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i8, 7>)]
+ /// enum Foo {
+ /// // `1 << 6` cannot be represented with `Bounded<i8, 7>`.
+ /// A = 1 << 6,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i8, 7>)]
+ /// enum Foo {
+ /// // `(-1 << 6) - 1` cannot be represented with `Bounded<i8, 7>`.
+ /// A = (-1 << 6) - 1,
+ /// }
+ /// ```
+ ///
+ /// ```
+ /// use kernel::{
+ /// macros::Into,
+ /// num::Bounded, //
+ /// };
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i8, 1>)]
+ /// enum Foo {
+ /// A = -1, // The minimum value of `Bounded<i8, 1>`.
+ /// B, // The maximum value of `Bounded<i8, 1>`.
+ /// }
+ ///
+ /// let foo_a: Bounded<i8, 1> = Foo::A.into();
+ /// let foo_b: Bounded<i8, 1> = Foo::B.into();
+ /// assert_eq!(Bounded::<i8, 1>::new::<{ -1_i8 }>(), foo_a);
+ /// assert_eq!(Bounded::<i8, 1>::new::<{ 0_i8 } >(), foo_b);
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i8, 1>)]
+ /// enum Foo {
+ /// // `1` cannot be represented with `Bounded<i8, 1>`.
+ /// A = 1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i8, 1>)]
+ /// enum Foo {
+ /// // `-2` cannot be represented with `Bounded<i8, 1>`.
+ /// A = -2,
+ /// }
+ /// ```
+ ///
+ /// ```
+ /// use kernel::{
+ /// macros::Into,
+ /// num::Bounded, //
+ /// };
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i32, 32>)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = i32::MIN as i64,
+ /// B = i32::MAX as i64,
+ /// }
+ ///
+ /// let foo_a: Bounded<i32, 32> = Foo::A.into();
+ /// let foo_b: Bounded<i32, 32> = Foo::B.into();
+ /// assert_eq!(Bounded::<i32, 32>::new::<{ i32::MIN }>(), foo_a);
+ /// assert_eq!(Bounded::<i32, 32>::new::<{ i32::MAX }>(), foo_b);
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i32, 32>)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// // `1 << 31` cannot be represented with `Bounded<i32, 32>`.
+ /// A = 1 << 31,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i32, 32>)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// // `(-1 << 31) - 1` cannot be represented with `Bounded<i32, 32>`.
+ /// A = (-1 << 31) - 1,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_signed_bounded {}
+
+ /// ```
+ /// use kernel::{
+ /// macros::Into,
+ /// num::Bounded, //
+ /// };
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<u8, 7>)]
+ /// enum Foo {
+ /// A, // The minimum value of `Bounded<u8, 7>`.
+ /// B = (1 << 7) - 1, // The maximum value of `Bounded<u8, 7>`.
+ /// }
+ ///
+ /// let foo_a: Bounded<u8, 7> = Foo::A.into();
+ /// let foo_b: Bounded<u8, 7> = Foo::B.into();
+ /// assert_eq!(Bounded::<u8, 7>::new::<{ 0 }>(), foo_a);
+ /// assert_eq!(Bounded::<u8, 7>::new::<{ (1_u8 << 7) - 1 }>(), foo_b);
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<u8, 7>)]
+ /// enum Foo {
+ /// // `1 << 7` cannot be represented with `Bounded<u8, 7>`.
+ /// A = 1 << 7,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<u8, 7>)]
+ /// enum Foo {
+ /// // `-1` cannot be represented with `Bounded<u8, 7>`.
+ /// A = -1,
+ /// }
+ /// ```
+ ///
+ /// ```
+ /// use kernel::{
+ /// macros::Into,
+ /// num::Bounded, //
+ /// };
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<u8, 1>)]
+ /// enum Foo {
+ /// A, // The minimum value of `Bounded<u8, 1>`.
+ /// B, // The maximum value of `Bounded<u8, 1>`.
+ /// }
+ ///
+ /// let foo_a: Bounded<u8, 1> = Foo::A.into();
+ /// let foo_b: Bounded<u8, 1> = Foo::B.into();
+ /// assert_eq!(Bounded::<u8, 1>::new::<{ 0 }>(), foo_a);
+ /// assert_eq!(Bounded::<u8, 1>::new::<{ 1 }>(), foo_b);
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<u8, 1>)]
+ /// enum Foo {
+ /// // `2` cannot be represented with `Bounded<u8, 1>`.
+ /// A = 2,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<u8, 1>)]
+ /// enum Foo {
+ /// // `-1` cannot be represented with `Bounded<u8, 1>`.
+ /// A = -1,
+ /// }
+ /// ```
+ ///
+ /// ```
+ /// use kernel::{
+ /// macros::Into,
+ /// num::Bounded, //
+ /// };
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<u32, 32>)]
+ /// #[repr(u64)]
+ /// enum Foo {
+ /// A = u32::MIN as u64,
+ /// B = u32::MAX as u64,
+ /// }
+ ///
+ /// let foo_a: Bounded<u32, 32> = Foo::A.into();
+ /// let foo_b: Bounded<u32, 32> = Foo::B.into();
+ /// assert_eq!(Bounded::<u32, 32>::new::<{ u32::MIN }>(), foo_a);
+ /// assert_eq!(Bounded::<u32, 32>::new::<{ u32::MAX }>(), foo_b);
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<u32, 32>)]
+ /// #[repr(u64)]
+ /// enum Foo {
+ /// // `1 << 32` cannot be represented with `Bounded<u32, 32>`.
+ /// A = 1 << 32,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<u32, 32>)]
+ /// #[repr(u64)]
+ /// enum Foo {
+ /// // `-1` cannot be represented with `Bounded<u32, 32>`.
+ /// A = -1,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_unsigned_bounded {}
+
+ /// ```
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(isize)]
+ /// #[repr(isize)]
+ /// enum Foo {
+ /// A = isize::MIN,
+ /// B = isize::MAX,
+ /// }
+ ///
+ /// assert_eq!(isize::MIN, Foo::A.into());
+ /// assert_eq!(isize::MAX, Foo::B.into());
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(isize)]
+ /// #[repr(usize)]
+ /// enum Foo {
+ /// A = (isize::MAX as usize) + 1
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(i32)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = (i32::MIN as i64) - 1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(i32)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = (i32::MAX as i64) + 1,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_signed_int {}
+
+ /// ```
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(usize)]
+ /// #[repr(usize)]
+ /// enum Foo {
+ /// A = usize::MIN,
+ /// B = usize::MAX,
+ /// }
+ ///
+ /// assert_eq!(usize::MIN, Foo::A.into());
+ /// assert_eq!(usize::MAX, Foo::B.into());
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(usize)]
+ /// #[repr(isize)]
+ /// enum Foo {
+ /// A = -1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(u32)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = (u32::MIN as i64) - 1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(u32)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = (u32::MAX as i64) + 1,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_unsigned_int {}
+
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(Bounded<i8, 7>, i8, i16, i32, i64)]
+ /// #[repr(i8)]
+ /// enum Foo {
+ /// // `i8::MAX` cannot be represented with `Bounded<i8, 7>`.
+ /// A = i8::MAX,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::Into;
+ ///
+ /// #[derive(Into)]
+ /// #[into(i8, i16, i32, i64, Bounded<i8, 7>)]
+ /// #[repr(i8)]
+ /// enum Foo {
+ /// // `i8::MAX` cannot be represented with `Bounded<i8, 7>`.
+ /// A = i8::MAX,
+ /// }
+ /// ```
+ mod any_into_target_overflow_is_rejected {}
+}
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v3 4/4] rust: macros: add private doctests for `TryFrom` derive macro
2025-12-24 3:59 [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
2025-12-24 3:59 ` [PATCH v3 3/4] rust: macros: add private doctests for `Into` derive macro Jesung Yang
@ 2025-12-24 3:59 ` Jesung Yang
2025-12-25 7:32 ` [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros Alexandre Courbot
2 siblings, 0 replies; 5+ messages in thread
From: Jesung Yang @ 2025-12-24 3:59 UTC (permalink / raw)
To: Alexandre Courbot, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich
Cc: rust-for-linux, nouveau, linux-kernel, Jesung Yang
Add internal doctests to verify the `TryFrom` derive macro's logic. This
ensures comprehensive testing while keeping the public-facing
documentation compact and readable.
Signed-off-by: Jesung Yang <y.j3ms.n@gmail.com>
---
rust/macros/convert.rs | 579 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 579 insertions(+)
diff --git a/rust/macros/convert.rs b/rust/macros/convert.rs
index 7159fb4dd187..88a0f0b0df2d 100644
--- a/rust/macros/convert.rs
+++ b/rust/macros/convert.rs
@@ -965,3 +965,582 @@ mod overflow_assert_works_on_unsigned_int {}
/// ```
mod any_into_target_overflow_is_rejected {}
}
+
+mod derive_try_from_tests {
+ /// ```
+ /// use kernel::{
+ /// macros::{
+ /// Into,
+ /// TryFrom, //
+ /// },
+ /// num::Bounded,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, Into, PartialEq, TryFrom)]
+ /// #[into(bool, Bounded<i8, 7>, Bounded<u8, 7>, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize)]
+ /// #[try_from(bool, Bounded<i8, 7>, Bounded<u8, 7>, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize)]
+ /// enum Foo {
+ /// A,
+ /// B,
+ /// }
+ ///
+ /// assert_eq!(false, Foo::A.into());
+ /// assert_eq!(true, Foo::B.into());
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(false));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(true));
+ ///
+ /// let foo_a: Bounded<i8, 7> = Foo::A.into();
+ /// let foo_b: Bounded<i8, 7> = Foo::B.into();
+ /// assert_eq!(Bounded::<i8, 7>::new::<0>(), foo_a);
+ /// assert_eq!(Bounded::<i8, 7>::new::<1>(), foo_b);
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<i8, 7>::new::<0>()));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<i8, 7>::new::<1>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i8, 7>::new::<-1>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i8, 7>::new::<2>()));
+ ///
+ /// let foo_a: Bounded<u8, 7> = Foo::A.into();
+ /// let foo_b: Bounded<u8, 7> = Foo::B.into();
+ /// assert_eq!(Bounded::<u8, 7>::new::<0>(), foo_a);
+ /// assert_eq!(Bounded::<u8, 7>::new::<1>(), foo_b);
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u8, 7>::new::<0>()));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 7>::new::<1>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u8, 7>::new::<2>()));
+ ///
+ /// macro_rules! gen_signed_tests {
+ /// ($($type:ty),*) => {
+ /// $(
+ /// assert_eq!(0 as $type, Foo::A.into());
+ /// assert_eq!(1 as $type, Foo::B.into());
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(0 as $type));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(1 as $type));
+ /// assert_eq!(Err(EINVAL), Foo::try_from((0 as $type) - 1));
+ /// assert_eq!(Err(EINVAL), Foo::try_from((1 as $type) + 1));
+ /// )*
+ /// };
+ /// }
+ /// macro_rules! gen_unsigned_tests {
+ /// ($($type:ty),*) => {
+ /// $(
+ /// assert_eq!(0 as $type, Foo::A.into());
+ /// assert_eq!(1 as $type, Foo::B.into());
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(0 as $type));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(1 as $type));
+ /// assert_eq!(Err(EINVAL), Foo::try_from((1 as $type) + 1));
+ /// )*
+ /// };
+ /// }
+ /// gen_signed_tests!(i8, i16, i32, i64, i128, isize);
+ /// gen_unsigned_tests!(u8, u16, u32, u64, u128, usize);
+ /// ```
+ mod works_with_derive_into {}
+
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(u8)]
+ /// enum Foo {
+ /// // Works with const expressions.
+ /// A = add(0, 0),
+ /// B = 2_isize.pow(1) - 1,
+ /// }
+ ///
+ /// const fn add(a: isize, b: isize) -> isize {
+ /// a + b
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(0_u8));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(1_u8));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(2_u8));
+ /// ```
+ mod works_with_const_expr {}
+
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(bool)]
+ /// enum Foo {
+ /// A,
+ /// B,
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(false));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(true));
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(bool)]
+ /// enum Bar {
+ /// A,
+ /// }
+ ///
+ /// assert_eq!(Ok(Bar::A), Bar::try_from(false));
+ /// assert_eq!(Err(EINVAL), Bar::try_from(true));
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(bool)]
+ /// enum Baz {
+ /// A = 1,
+ /// }
+ ///
+ /// assert_eq!(Err(EINVAL), Baz::try_from(false));
+ /// assert_eq!(Ok(Baz::A), Baz::try_from(true));
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(bool)]
+ /// enum Foo {
+ /// // `-1` cannot be represented with `bool`.
+ /// A = -1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(bool)]
+ /// enum Foo {
+ /// // `2` cannot be represented with `bool`.
+ /// A = 2,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_bool {}
+
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// num::Bounded,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<i8, 7>)]
+ /// enum Foo {
+ /// A = -1 << 6, // The minimum value of `Bounded<i8, 7>`.
+ /// B = (1 << 6) - 1, // The maximum value of `Bounded<i8, 7>`.
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<i8, 7>::new::<{ -1_i8 << 6 }>()));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<i8, 7>::new::<{ (1_i8 << 6) - 1 }>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i8, 7>::new::<{ (-1_i8 << 6) + 1 }>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i8, 7>::new::<{ (1_i8 << 6) - 2 }>()));
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<i8, 7>)]
+ /// enum Foo {
+ /// // `1 << 6` cannot be represented with `Bounded<i8, 7>`.
+ /// A = 1 << 6,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<i8, 7>)]
+ /// enum Foo {
+ /// // `(-1 << 6) - 1` cannot be represented with `Bounded<i8, 7>`.
+ /// A = (-1 << 6) - 1,
+ /// }
+ /// ```
+ ///
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// num::Bounded,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<i8, 1>)]
+ /// enum Foo {
+ /// A = -1, // The minimum value of `Bounded<i8, 1>`.
+ /// B, // The maximum value of `Bounded<i8, 1>`.
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<i8, 1>::new::<{ -1_i8 }>()));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<i8, 1>::new::<{ 0_i8 } >()));
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<i8, 1>)]
+ /// enum Bar {
+ /// A = -1, // The minimum value of `Bounded<i8, 1>`.
+ /// }
+ ///
+ /// assert_eq!(Ok(Bar::A), Bar::try_from(Bounded::<i8, 1>::new::<{ -1_i8 }>()));
+ /// assert_eq!(Err(EINVAL), Bar::try_from(Bounded::<i8, 1>::new::<{ 0_i8 } >()));
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<i8, 1>)]
+ /// enum Baz {
+ /// A, // The maximum value of `Bounded<i8, 1>`.
+ /// }
+ ///
+ /// assert_eq!(Err(EINVAL), Baz::try_from(Bounded::<i8, 1>::new::<{ -1_i8 }>()));
+ /// assert_eq!(Ok(Baz::A), Baz::try_from(Bounded::<i8, 1>::new::<{ 0_i8 } >()));
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<i8, 1>)]
+ /// enum Foo {
+ /// // `1` cannot be represented with `Bounded<i8, 1>`.
+ /// A = 1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<i8, 1>)]
+ /// enum Foo {
+ /// // `-2` cannot be represented with `Bounded<i8, 1>`.
+ /// A = -2,
+ /// }
+ /// ```
+ ///
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// num::Bounded,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<i32, 32>)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = i32::MIN as i64,
+ /// B = i32::MAX as i64,
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<i32, 32>::new::<{ i32::MIN }>()));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<i32, 32>::new::<{ i32::MAX }>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i32, 32>::new::<{ i32::MIN + 1 }>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<i32, 32>::new::<{ i32::MAX - 1 }>()));
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<i32, 32>)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// // `1 << 31` cannot be represented with `Bounded<i32, 32>`.
+ /// A = 1 << 31,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<i32, 32>)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// // `(-1 << 31) - 1` cannot be represented with `Bounded<i32, 32>`.
+ /// A = (-1 << 31) - 1,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_signed_bounded {}
+
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// num::Bounded,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<u8, 7>)]
+ /// enum Foo {
+ /// A, // The minimum value of `Bounded<u8, 7>`.
+ /// B = (1 << 7) - 1, // The maximum value of `Bounded<u8, 7>`.
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u8, 7>::new::<{ 0 }>()));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 7>::new::<{ (1_u8 << 7) - 1 }>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u8, 7>::new::<{ 1 }>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u8, 7>::new::<{ (1_u8 << 7) - 2 }>()));
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<u8, 7>)]
+ /// enum Foo {
+ /// // `1 << 7` cannot be represented with `Bounded<u8, 7>`.
+ /// A = 1 << 7,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<u8, 7>)]
+ /// enum Foo {
+ /// // `-1` cannot be represented with `Bounded<u8, 7>`.
+ /// A = -1,
+ /// }
+ /// ```
+ ///
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// num::Bounded,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<u8, 1>)]
+ /// enum Foo {
+ /// A, // The minimum value of `Bounded<u8, 1>`.
+ /// B, // The maximum value of `Bounded<u8, 1>`.
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u8, 1>::new::<{ 0 }>()));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u8, 1>::new::<{ 1 }>()));
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<u8, 1>)]
+ /// enum Bar {
+ /// A, // The minimum value of `Bounded<u8, 1>`.
+ /// }
+ ///
+ /// assert_eq!(Ok(Bar::A), Bar::try_from(Bounded::<u8, 1>::new::<{ 0 }>()));
+ /// assert_eq!(Err(EINVAL), Bar::try_from(Bounded::<u8, 1>::new::<{ 1 }>()));
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<u8, 1>)]
+ /// enum Baz {
+ /// A = 1, // The maximum value of `Bounded<u8, 1>`.
+ /// }
+ ///
+ /// assert_eq!(Err(EINVAL), Baz::try_from(Bounded::<u8, 1>::new::<{ 0 }>()));
+ /// assert_eq!(Ok(Baz::A), Baz::try_from(Bounded::<u8, 1>::new::<{ 1 }>()));
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<u8, 1>)]
+ /// enum Foo {
+ /// // `2` cannot be represented with `Bounded<u8, 1>`.
+ /// A = 2,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<u8, 1>)]
+ /// enum Foo {
+ /// // `-1` cannot be represented with `Bounded<u8, 1>`.
+ /// A = -1,
+ /// }
+ /// ```
+ ///
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// num::Bounded,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(Bounded<u32, 32>)]
+ /// #[repr(u64)]
+ /// enum Foo {
+ /// A = u32::MIN as u64,
+ /// B = u32::MAX as u64,
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(Bounded::<u32, 32>::new::<{ u32::MIN }>()));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(Bounded::<u32, 32>::new::<{ u32::MAX }>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u32, 32>::new::<{ u32::MIN + 1 }>()));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(Bounded::<u32, 32>::new::<{ u32::MAX - 1 }>()));
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<u32, 32>)]
+ /// #[repr(u64)]
+ /// enum Foo {
+ /// // `1 << 32` cannot be represented with `Bounded<u32, 32>`.
+ /// A = 1 << 32,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<u32, 32>)]
+ /// #[repr(u64)]
+ /// enum Foo {
+ /// // `-1` cannot be represented with `Bounded<u32, 32>`.
+ /// A = -1,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_unsigned_bounded {}
+
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// num::Bounded,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(isize)]
+ /// #[repr(isize)]
+ /// enum Foo {
+ /// A = isize::MIN,
+ /// B = isize::MAX,
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(isize::MIN));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(isize::MAX));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(isize::MIN + 1));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(isize::MAX - 1));
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(isize)]
+ /// #[repr(usize)]
+ /// enum Foo {
+ /// A = (isize::MAX as usize) + 1
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(i32)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = (i32::MIN as i64) - 1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(i32)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = (i32::MAX as i64) + 1,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_signed_int {}
+
+ /// ```
+ /// use kernel::{
+ /// macros::TryFrom,
+ /// num::Bounded,
+ /// prelude::*, //
+ /// };
+ ///
+ /// #[derive(Debug, PartialEq, TryFrom)]
+ /// #[try_from(usize)]
+ /// #[repr(usize)]
+ /// enum Foo {
+ /// A = usize::MIN,
+ /// B = usize::MAX,
+ /// }
+ ///
+ /// assert_eq!(Ok(Foo::A), Foo::try_from(usize::MIN));
+ /// assert_eq!(Ok(Foo::B), Foo::try_from(usize::MAX));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(usize::MIN + 1));
+ /// assert_eq!(Err(EINVAL), Foo::try_from(usize::MAX - 1));
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(usize)]
+ /// #[repr(isize)]
+ /// enum Foo {
+ /// A = -1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(u32)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = (u32::MIN as i64) - 1,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(u32)]
+ /// #[repr(i64)]
+ /// enum Foo {
+ /// A = (u32::MAX as i64) + 1,
+ /// }
+ /// ```
+ mod overflow_assert_works_on_unsigned_int {}
+
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(Bounded<i8, 7>, i8, i16, i32, i64)]
+ /// #[repr(i8)]
+ /// enum Foo {
+ /// // `i8::MAX` cannot be represented with `Bounded<i8, 7>`.
+ /// A = i8::MAX,
+ /// }
+ /// ```
+ ///
+ /// ```compile_fail
+ /// use kernel::macros::TryFrom;
+ ///
+ /// #[derive(TryFrom)]
+ /// #[try_from(i8, i16, i32, i64, Bounded<i8, 7>)]
+ /// #[repr(i8)]
+ /// enum Foo {
+ /// // `i8::MAX` cannot be represented with `Bounded<i8, 7>`.
+ /// A = i8::MAX,
+ /// }
+ /// ```
+ mod any_try_from_target_overflow_is_rejected {}
+}
--
2.47.3
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros
2025-12-24 3:59 [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
2025-12-24 3:59 ` [PATCH v3 3/4] rust: macros: add private doctests for `Into` derive macro Jesung Yang
2025-12-24 3:59 ` [PATCH v3 4/4] rust: macros: add private doctests for `TryFrom` " Jesung Yang
@ 2025-12-25 7:32 ` Alexandre Courbot
2025-12-25 7:56 ` Jesung Yang
2 siblings, 1 reply; 5+ messages in thread
From: Alexandre Courbot @ 2025-12-25 7:32 UTC (permalink / raw)
To: Jesung Yang, Alexandre Courbot, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich
Cc: rust-for-linux, nouveau, linux-kernel
On Wed Dec 24, 2025 at 12:59 PM JST, Jesung Yang wrote:
> This patch series introduces derive macros for the `TryFrom` and `Into`
> traits.
>
> Primary updates in v3:
> - Adopt `syn` for improved macro parsing.
> - Allow `kernel::num::Bounded` to be specified in helper attributes,
> enabling the generation of `TryFrom` and `Into` implementations for
> types with restricted bit ranges as required by the nova-core [1].
> - Add compile-time overflow assertion to ensure that enum discriminants
> fit within the types being converted to or from, preventing silent
> overflows.
>
> Note: 1015+ insertions are purely from doctests. Most of them are kept
> private to keep the public API documentation clean and simple for
> users. This might not be the usual way of doing things, but for now I
> don't think I have a better option. Feedback and suggestions are always
> appreciated.
>
> [1] https://lore.kernel.org/rust-for-linux/DDHU4LL4GGIY.16OJMIL7ZK58P@nvidia.com/
>
> Best regards,
> Jesung
Hi Jesung,
I'm really looking forward to reviewing this one, but for some reason
patches 1 and 2 appear to not have been sent. They don't even appear on
lore: https://lore.kernel.org/all/cover.1766544407.git.y.j3ms.n@gmail.com/
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros
2025-12-25 7:32 ` [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros Alexandre Courbot
@ 2025-12-25 7:56 ` Jesung Yang
0 siblings, 0 replies; 5+ messages in thread
From: Jesung Yang @ 2025-12-25 7:56 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, rust-for-linux, nouveau, linux-kernel
On Thu, Dec 25, 2025 at 4:32 PM Alexandre Courbot <acourbot@nvidia.com> wrote:
>
> I'm really looking forward to reviewing this one, but for some reason
> patches 1 and 2 appear to not have been sent. They don't even appear on
> lore: https://lore.kernel.org/all/cover.1766544407.git.y.j3ms.n@gmail.com/
Hi,
I'm having some issues with Gmail. It's blocking some of the patches
in the series [1]. I will resend the whole series using b4 to avoid further
issues. Apologies for the noise.
[1] https://rust-for-linux.zulipchat.com/#narrow/stream/x/topic/x/near/565266787
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2025-12-25 7:56 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-24 3:59 [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros Jesung Yang
2025-12-24 3:59 ` [PATCH v3 3/4] rust: macros: add private doctests for `Into` derive macro Jesung Yang
2025-12-24 3:59 ` [PATCH v3 4/4] rust: macros: add private doctests for `TryFrom` " Jesung Yang
2025-12-25 7:32 ` [PATCH v3 0/4] rust: add `TryFrom` and `Into` derive macros Alexandre Courbot
2025-12-25 7:56 ` Jesung Yang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox