Conversation
I'm down to two failing doc tests on number.rs around |
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.
Thanks! Let me know when you fixed the remaining tests.
I left a few inline comments. Aside from that, please add u64
to the partialeq_numeric!
call, and make sure to s/JSON/YAML in a few places in the new number.rs
:)
src/value/ser.rs
Outdated
} else if let Some(f) = n.as_f64() { | ||
serializer.serialize_f64(f) | ||
} else { | ||
panic!("unexpected number") |
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.
I don't think this should panic. It's either unreachable!()
or an error we should propagate.
src/value/de.rs
Outdated
} else if let Some(f) = n.as_f64() { | ||
Unexpected::Float(f) | ||
} else { | ||
panic!("unexpected number") |
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.
I don't think this should panic. It's either unreachable!()
or a new Unexpected case.
src/value/de.rs
Outdated
} else if let Some(f) = n.as_f64() { | ||
visitor.visit_f64(f) | ||
} else { | ||
panic!("unexpected number") |
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.
unreachable!()
?
src/value/from.rs
Outdated
@@ -9,7 +10,7 @@ macro_rules! from_integer { | |||
$( | |||
impl From<$ty> for Value { | |||
fn from(n: $ty) -> Self { | |||
Value::I64(n as i64) | |||
Value::Number(Number::from(n as i64)) |
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.
Also make sure to add u64
in the macro call below
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.
I haven't done much with macro's yet. Would I build a
from_integer! {
i8 i16 i32 i64 isize
}
and a
from_uinteger! {
u8 u16 u32 usize
}
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.
IIUC, you can omit the as i64
on this line as Number
already implements From
for a bunch of integer sizes and just change the call below to
from_integer! {
i8 i16 i32 i64 isize
u8 u16 u32 u64 usize
}
/// | ||
/// // Integers. | ||
/// assert!(!v["b"].is_f64()); | ||
/// assert!(!v["c"].is_f64()); |
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.
These two asserts are failing and I can't reason out how !v["b"].is_f64()
could be returning true
.
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.
Ah, I think I can see what the problem is. v["b"]
returns a Value
, so v["b"].is_f64()
calls Value::is_f64
and not Number::is_f64
. The implementation of Value::is_f64
is pretty simple: It returns true if the value is a number that can be represented as a f64.
I think the best course of action is to change the Value::is_f64
impl to use Number::is_f64
internally. This should work:
match *self {
Value::Number(ref n) => n.is_f64(),
_ => false,
}
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.
YAS!
Would love any comments on how the group would like to see https://travis-ci.org/dtolnay/serde-yaml/jobs/224911514#L203 addressed |
The clippy docs describe this lint as a warning, that you have to be careful and manually make sure that #[cfg(feature = "cargo-clippy", allow(derive_hash_xor_eq))] // This is fine because …
I see you made this an expect now, but: Your |
This is taken straight from serder-json - so I'm not sure on the best path to take forward. That said, do we wish to support
|
Ok, everything is green now. @killercup let me know what you think and now that the ⛰ has been moved we can sweep up the pebbles. |
@drusellers very nice! I've added one more commit to make the new allow attrs depend on the clippy feature. I'll try to do another review later. |
src/value/number.rs
Outdated
@@ -59,7 +59,7 @@ impl Number { | |||
/// # } | |||
/// ``` | |||
#[inline] | |||
#[allow(cast_sign_loss)] | |||
#[cfg_attr(feature = "cargo-clippy", allow(cast_sign_loss))] |
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.
👍
src/value/number.rs
Outdated
@@ -369,7 +369,7 @@ macro_rules! from_signed { | |||
$( | |||
impl From<$signed_ty> for Number { | |||
#[inline] | |||
#[allow(cast_sign_loss)] | |||
#[cfg_attr(feature = "cargo-clippy", allow(cast_sign_loss))] |
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.
👍
Awesome, let me know if there is anything else I can do to help. |
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.
Okay, looking at http://yaml.org/type/float.html, we need to support NaN
, inf
and -inf
.
Otherwise, I'd really like to replace the all expect
, unwrap
, panic!
, and unreachable!
calls with return Err(_)
.
src/value/de.rs
Outdated
} | ||
|
||
fn visit_f64<E>(self, f: f64) -> Result<Value, E> | ||
where E: SError, | ||
{ | ||
Ok(Value::F64(f)) | ||
Ok(Value::Number(Number::from_f64(f).expect("wrapping an existing float"))) |
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.
This does not work for infinite values! cf. http://yaml.org/type/float.html for valid syntax. I locally added this example to the as_f64
docs of value:
/// ```rust
/// # use std::f64;
/// # fn yaml(i: &str) -> serde_yaml::Value { serde_yaml::from_str(i).unwrap() }
/// assert_eq!(yaml("inf").as_f64(), Some(f64::INFINITY));
/// assert_eq!(yaml("-inf").as_f64(), Some(f64::NEG_INFINITY));
/// assert!(yaml("NaN").as_f64().unwrap().is_nan());
/// ```
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.
Ok, cool. So I'm thinking two directions. One
enum N {
PosInt(u64),
/// Always less than zero.
NegInt(i64),
/// Always finite.
Float(f64),
NaN,
InfinityPos,
InfinityNeg
}
but that feels like its mixes up float concerns and non float concerns.
Instead add an enum to wrap the float values like:
enum F {
PosInfinity,
NegInfinity,
Finite(f64),
NaN,
}
so that N
becomes
enum N {
PosInt(u64),
NegInt(i64),
Float(F),
}
Does that jive with you?
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.
What's the reason for requiring the Float variant to be finite? We can always add is_finite
and is_nan
methods to Number
(that return true/false (respectively) for integers).
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.
No requirement really. I'll go your suggested route. I'm mostly just following what the JSON team did.
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.
Given the recent change, I'm not sure it makes sense to have from_f64
return a Result any more. 🤔
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.
True.
src/value/number.rs
Outdated
} | ||
|
||
/// Converts a finite `f64` to a `Number`. Infinite or NaN values are not JSON | ||
/// numbers. |
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.
Infinite or NaN values are not JSON numbers.
Well, they are YAML numbers, so this needs to change.
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.
ha, roger that!
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.
Thanks for keeping working on this! A few more nits but I think we are really close!
src/value/de.rs
Outdated
@@ -41,22 +41,19 @@ impl<'de> Deserialize<'de> for Value { | |||
fn visit_i64<E>(self, i: i64) -> Result<Value, E> | |||
where E: SError, | |||
{ | |||
Ok(Value::I64(i)) | |||
Ok(Value::Number(Number::from(i))) |
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.
FYI, reading this again I can't help but think I'd write Value::Number(i.into())
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.
easy change!
src/value/de.rs
Outdated
} | ||
|
||
fn visit_f64<E>(self, f: f64) -> Result<Value, E> | ||
where E: SError, | ||
{ | ||
Ok(Value::F64(f)) | ||
Ok(Value::Number(Number::from_f64(f).expect("wrapping an existing float"))) |
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.
True.
src/value/number.rs
Outdated
/// assert_eq!(v["c"].as_f64(), Some(-64.0)); | ||
/// # } | ||
/// ``` | ||
/// ```rust |
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.
Please add an empty line above this one
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.
got it
src/value/number.rs
Outdated
/// # | ||
/// assert!(Number::from_f64(256.0).is_some()); | ||
/// | ||
/// //NAN is a number in YAML |
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.
Sorry, I don't think these comments add anything, the code basically says just that.
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.
no worries - removing
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.
Very nice. Looks good to me!
@dtolnay, do you, the original author of the JSON Number abstraction, want to review this as well?
Thank you both! Yes I'll get this in today. |
/// assert!(!Number::from_f64(f64::NEG_INFINITY).is_finite()); | ||
/// ``` | ||
#[inline] | ||
pub fn from_f64(f: f64) -> Number { |
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.
This should just be a normal From<f64>
conversion. JSON is different because not every f64 value can be represented in JSON.
@@ -41,22 +41,19 @@ impl<'de> Deserialize<'de> for Value { | |||
fn visit_i64<E>(self, i: i64) -> Result<Value, E> | |||
where E: SError, | |||
{ | |||
Ok(Value::I64(i)) | |||
Ok(Value::Number(i.into())) |
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.
I would prefer this to be consistent across all three number methods. Here one uses Into::into
, one uses Number::from
, and one uses Number::from_f64
.
@@ -128,8 +125,17 @@ impl<'de> Deserializer<'de> for Value { | |||
match self { | |||
Value::Null => visitor.visit_unit(), | |||
Value::Bool(v) => visitor.visit_bool(v), | |||
Value::I64(i) => visitor.visit_i64(i), | |||
Value::F64(f) => visitor.visit_f64(f), | |||
Value::Number(ref n) => { |
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.
This does not need to use ref
. We own the number.
Value::I64(i) => visitor.visit_i64(i), | ||
Value::F64(f) => visitor.visit_f64(f), | ||
Value::Number(ref n) => { | ||
if let Some(u) = n.as_u64() { |
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.
This should use Number as Deserializer<'de>
like serde_json does. That way you avoid the if / else if / else if / else unreachable mess.
Value::Number(n) => n.deserialize_any(visitor)
@@ -0,0 +1,473 @@ | |||
// Copied from serde_json |
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.
I would prefer to put this file at src/number.rs like how serde_json has it. The Number type is useful independently outside of the context of Value.
PosInt(u64), | ||
/// Always less than zero. | ||
NegInt(i64), | ||
/// Always finite. |
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.
This comment does not match the implementation.
pub fn as_u64(&self) -> Option<u64> { | ||
match self.n { | ||
N::PosInt(n) => Some(n), | ||
N::NegInt(n) => NumCast::from(n), |
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.
This case is impossible - a negative i64 won't convert to u64.
} | ||
} | ||
|
||
/// Checks to see if a number is a number |
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.
What?
match self.n { | ||
N::PosInt(i) => Display::fmt(&i, formatter), | ||
N::NegInt(i) => Display::fmt(&i, formatter), | ||
N::Float(f) => Display::fmt(&f, formatter), |
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.
This should have a special case for inf and nan. YAML has a distinct formatting for them.
@@ -9,8 +9,17 @@ impl Serialize for Value { | |||
match *self { | |||
Value::Null => serializer.serialize_unit(), | |||
Value::Bool(b) => serializer.serialize_bool(b), | |||
Value::I64(i) => serializer.serialize_i64(i), | |||
Value::F64(f) => serializer.serialize_f64(f), | |||
Value::Number(ref n) => { |
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.
This should be implemented as n.serialize(serializer)
to avoid the unreachable case.
@dtolnay and @killercup thank you so much for the mentoring. and 🎉 Serde 1.0 yay |
Copies the
Number
type fromserde_json
and converts this project to use it. Known issue compiling a hash function.Closes: #60