-
-
Notifications
You must be signed in to change notification settings - Fork 354
following up on btoi removal #1297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Astonishing! Do more with less, and even with added docs ❤️.
Just looking at this, what one could actually do is: remove all the generics and traits, just have two functions which parse into i64 und u64 and then do try_into at the call side for the target type. |
I've been attempting that but:
|
I think it's OK to keep these generics as from my experience, To my mind, |
Yeah that sounds right, although it can be shorter/ more readable. I was also wondering why num-traits has a build.rs anyway cause it should really be a light dependency I'd like to think but it seems it wants to keep it rust-num/num-traits#200. diff --git a/gix-quote/src/ansi_c.rs b/gix-quote/src/ansi_c.rs
index 43856f899..58f246f26 100644
--- a/gix-quote/src/ansi_c.rs
+++ b/gix-quote/src/ansi_c.rs
@@ -91,7 +91,7 @@ pub fn undo(input: &BStr) -> Result<(Cow<'_, BStr>, usize), undo::Error> {
.expect("impossible to fail as numbers match");
let byte = gix_utils::btoi::to_unsigned_with_radix(&buf, 8)
.map_err(|e| undo::Error::new(e, original))?;
- out.push(byte);
+ out.push(byte.try_into().unwrap());
input = &input[2..];
consumed += 2;
}
diff --git a/gix-utils/src/btoi.rs b/gix-utils/src/btoi.rs
index c2cc5e28c..df41c6607 100644
--- a/gix-utils/src/btoi.rs
+++ b/gix-utils/src/btoi.rs
@@ -79,8 +79,18 @@ impl std::error::Error for ParseIntegerError {
/// assert!(to_unsigned::<u8>(b"256").is_err()); // overflow
/// ```
#[track_caller]
-pub fn to_unsigned<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError> {
- to_unsigned_with_radix(bytes, 10)
+pub fn to_unsigned<I: std::convert::TryFrom<u64>>(bytes: &[u8]) -> Result<I, ParseIntegerError>
+where
+ <I as std::convert::TryFrom<u64>>::Error: std::fmt::Debug,
+{
+ let unsigned = to_unsigned_with_radix(bytes, 10)?;
+ match unsigned.try_into() {
+ Ok(v) => Ok(v),
+ Err(e) => Err(ParseIntegerError {
+ // TODO distinguish under and overflow here
+ kind: ErrorKind::Overflow,
+ }),
+ }
}
/// Converts a byte slice in a given base to an integer. Signs are not allowed.
@@ -91,13 +101,13 @@ pub fn to_unsigned<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError
///
/// * `bytes` is empty
/// * not all characters of `bytes` are `0-9`, `a-z` or `A-Z`
-/// * not all characters refer to digits in the given `radix`
+/// * not all characters refer to digits in the given `base`
/// * the number overflows `I`
///
/// # Panics
///
-/// Panics if `radix` is not in the range `2..=36` (or in the pathological
-/// case that there is no representation of `radix` in `I`).
+/// Panics if `base` is not in the range `2..=36` (or in the pathological
+/// case that there is no representation of `base` in `I`).
///
/// # Examples
///
@@ -106,22 +116,20 @@ pub fn to_unsigned<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError
/// assert_eq!(Ok(255), to_unsigned_with_radix(b"ff", 16));
/// assert_eq!(Ok(42), to_unsigned_with_radix(b"101010", 2));
/// ```
-pub fn to_unsigned_with_radix<I: MinNumTraits>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError> {
+pub fn to_unsigned_with_radix(bytes: &[u8], base: u32) -> Result<u64, ParseIntegerError> {
assert!(
- (2..=36).contains(&radix),
- "radix must lie in the range 2..=36, found {radix}"
+ (2..=36).contains(&base),
+ "base must lie in the range 2..=36, found {base}"
);
- let base = I::from_u32(radix).expect("radix can be represented as integer");
-
if bytes.is_empty() {
return Err(ParseIntegerError { kind: ErrorKind::Empty });
}
- let mut result = I::ZERO;
+ let mut result: u64 = 0;
for &digit in bytes {
- let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
+ let x = match char::from(digit).to_digit(base).and_then(|num| num.try_into().unwrap()) {
Some(x) => x,
None => {
return Err(ParseIntegerError {
@@ -129,7 +137,7 @@ pub fn to_unsigned_with_radix<I: MinNumTraits>(bytes: &[u8], radix: u32) -> Resu
})
}
};
- result = match result.checked_mul(base) {
+ result = match result.checked_mul(base.into()) {
Some(result) => result,
None => {
return Err(ParseIntegerError {
@@ -137,7 +145,7 @@ pub fn to_unsigned_with_radix<I: MinNumTraits>(bytes: &[u8], radix: u32) -> Resu
})
}
};
- result = match result.checked_add(x) {
+ result = match result.checked_add(x.into()) {
Some(result) => result,
None => {
return Err(ParseIntegerError {
@@ -181,8 +189,19 @@ pub fn to_unsigned_with_radix<I: MinNumTraits>(bytes: &[u8], radix: u32) -> Resu
///
/// assert!(to_signed::<i32>(b" 42").is_err()); // leading space
/// ```
-pub fn to_signed<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError> {
- to_signed_with_radix(bytes, 10)
+pub fn to_signed<I: std::convert::TryFrom<i64>>(bytes: &[u8]) -> Result<I, ParseIntegerError>
+where
+ <I as std::convert::TryFrom<i64>>::Error: std::fmt::Debug,
+{
+ let signed = to_signed_with_radix(bytes, 10)?;
+
+ match signed.try_into() {
+ Ok(v) => Ok(v),
+ Err(e) => Err(ParseIntegerError {
+ // TODO distinguish under and overflow here
+ kind: ErrorKind::Overflow,
+ }),
+ }
}
/// Converts a byte slice in a given base to an integer.
@@ -197,14 +216,14 @@ pub fn to_signed<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError>
/// * `bytes` has no digits
/// * not all characters of `bytes` are `0-9`, `a-z`, `A-Z`, exluding an
/// optional leading sign
-/// * not all characters refer to digits in the given `radix`, exluding an
+/// * not all characters refer to digits in the given `base`, exluding an
/// optional leading sign
/// * the number overflows or underflows `I`
///
/// # Panics
///
-/// Panics if `radix` is not in the range `2..=36` (or in the pathological
-/// case that there is no representation of `radix` in `I`).
+/// Panics if `base` is not in the range `2..=36` (or in the pathological
+/// case that there is no representation of `base` in `I`).
///
/// # Examples
///
@@ -214,97 +233,24 @@ pub fn to_signed<I: MinNumTraits>(bytes: &[u8]) -> Result<I, ParseIntegerError>
/// assert_eq!(Ok(10), to_signed_with_radix(b"+a", 16));
/// assert_eq!(Ok(-42), to_signed_with_radix(b"-101010", 2));
/// ```
-pub fn to_signed_with_radix<I: MinNumTraits>(bytes: &[u8], radix: u32) -> Result<I, ParseIntegerError> {
+pub fn to_signed_with_radix(bytes: &[u8], base: u32) -> Result<i64, ParseIntegerError> {
assert!(
- (2..=36).contains(&radix),
- "radix must lie in the range 2..=36, found {radix}"
+ (2..=36).contains(&base),
+ "base must lie in the range 2..=36, found {base}"
);
- let base = I::from_u32(radix).expect("radix can be represented as integer");
-
if bytes.is_empty() {
return Err(ParseIntegerError { kind: ErrorKind::Empty });
}
- let digits = match bytes[0] {
- b'+' => return to_unsigned_with_radix(&bytes[1..], radix),
- b'-' => &bytes[1..],
- _ => return to_unsigned_with_radix(bytes, radix),
- };
-
- if digits.is_empty() {
- return Err(ParseIntegerError { kind: ErrorKind::Empty });
- }
-
- let mut result = I::ZERO;
-
- for &digit in digits {
- let x = match char::from(digit).to_digit(radix).and_then(I::from_u32) {
- Some(x) => x,
- None => {
- return Err(ParseIntegerError {
- kind: ErrorKind::InvalidDigit,
- })
- }
- };
- result = match result.checked_mul(base) {
- Some(result) => result,
- None => {
- return Err(ParseIntegerError {
- kind: ErrorKind::Underflow,
- })
- }
- };
- result = match result.checked_sub(x) {
- Some(result) => result,
- None => {
- return Err(ParseIntegerError {
- kind: ErrorKind::Underflow,
- })
- }
- };
- }
-
- Ok(result)
-}
-
-/// minimal subset of traits used by [`to_signed_with_radix`] and [`to_unsigned_with_radix`]
-pub trait MinNumTraits: Sized + Copy + TryFrom<u32> {
- /// the 0 value for this type
- const ZERO: Self;
- /// convert from a unsinged 32-bit word
- fn from_u32(n: u32) -> Option<Self> {
- Self::try_from(n).ok()
- }
- /// the checked multiplication operation for this type
- fn checked_mul(self, rhs: Self) -> Option<Self>;
- /// the chekced addition operation for this type
- fn checked_add(self, rhs: Self) -> Option<Self>;
- /// the checked subtraction operation for this type
- fn checked_sub(self, v: Self) -> Option<Self>;
-}
-
-macro_rules! impl_checked {
- ($f:ident) => {
- fn $f(self, rhs: Self) -> Option<Self> {
- Self::$f(self, rhs)
+ match bytes[0] {
+ b'+' => return to_unsigned_with_radix(&bytes[1..], base).map(|num| num.try_into().unwrap()),
+ b'-' => {
+ return to_unsigned_with_radix(&bytes[1..], base).map(|num| {
+ let signed: i64 = num.try_into().unwrap();
+ -signed
+ })
}
- };
-}
-
-macro_rules! min_num_traits {
- ($t:ty) => {
- impl MinNumTraits for $t {
- const ZERO: Self = 0;
- impl_checked!(checked_add);
- impl_checked!(checked_mul);
- impl_checked!(checked_sub);
- }
- };
+ _ => return to_unsigned_with_radix(bytes, base).map(|num| num.try_into().unwrap()),
+ }
}
-
-min_num_traits!(i32);
-min_num_traits!(i64);
-min_num_traits!(u64);
-min_num_traits!(u8);
-min_num_traits!(usize);
|
Quite amazing how much less code is possible here. But I also agree, what's here now isn't bad at all, compiles reasonably fast and doesn't take shortcuts when dealing with errors. So let's keep the current version (I guess ). |
following up on #1292 with some "cleanup" of the trait & macro. feel free to take what you want from this
I changed to the ZERO constant but I also played around with some other changes to the macro I don't know what is more correct/idiomatic tbh