Skip to content

Commit 30d504c

Browse files
authored
Merge branch 'main' into register-macros
2 parents a941b5d + 425828c commit 30d504c

22 files changed

+510
-106
lines changed

Diff for: src/element/annotations.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::element::iterators::{AnnotationsIntoIter, SymbolsIterator};
2-
use crate::ion_data::IonOrd;
2+
use crate::ion_data::{IonDataHash, IonDataOrd};
33
use crate::Symbol;
44
use std::cmp::Ordering;
5+
use std::hash::Hasher;
56

67
/// An ordered sequence of symbols that convey additional, application-specific information about
78
/// their associated Ion value.
@@ -141,12 +142,18 @@ impl IntoIterator for Annotations {
141142
}
142143
}
143144

144-
impl IonOrd for Annotations {
145+
impl IonDataOrd for Annotations {
145146
fn ion_cmp(&self, other: &Self) -> Ordering {
146147
self.symbols.ion_cmp(&other.symbols)
147148
}
148149
}
149150

151+
impl IonDataHash for Annotations {
152+
fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
153+
self.symbols.ion_data_hash(state)
154+
}
155+
}
156+
150157
/// Defines conversion into [`Annotations`].
151158
///
152159
/// This trait allows us to have a blanket implementations that can cover many type combinations

Diff for: src/element/mod.rs

+33-3
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ pub use annotations::{Annotations, IntoAnnotations};
2424
pub use sequence::{OwnedSequenceIterator, Sequence};
2525
use std::cmp::Ordering;
2626
use std::fmt::{Display, Formatter};
27+
use std::hash::Hasher;
2728
use std::io;
2829

2930
use crate::{ion_data, Decimal, Int, IonResult, IonType, Str, Symbol, SymbolRef, Timestamp};
3031
use crate::{Blob, Bytes, Clob, List, SExp, Struct};
3132
// Re-export the Value variant types and traits so they can be accessed directly from this module.
3233
use crate::element::builders::{SequenceBuilder, StructBuilder};
3334
use crate::element::reader::ElementReader;
34-
use crate::ion_data::{IonEq, IonOrd};
35+
use crate::ion_data::{IonDataHash, IonDataOrd, IonEq};
3536
use crate::lazy::any_encoding::AnyEncoding;
3637
use crate::lazy::encoding::Encoding;
3738
use crate::lazy::reader::Reader;
@@ -73,7 +74,7 @@ impl IonEq for Value {
7374
}
7475
}
7576

76-
impl IonOrd for Value {
77+
impl IonDataOrd for Value {
7778
fn ion_cmp(&self, other: &Self) -> Ordering {
7879
use Value::*;
7980

@@ -117,6 +118,28 @@ impl IonOrd for Value {
117118
}
118119
}
119120

121+
impl IonDataHash for Value {
122+
fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
123+
use Value::*;
124+
self.ion_type().ion_data_hash(state);
125+
match self {
126+
Null(_) => state.write_u8(0),
127+
Bool(this) => ion_data::ion_data_hash_bool(*this, state),
128+
Int(this) => this.ion_data_hash(state),
129+
Float(this) => ion_data::ion_data_hash_f64(*this, state),
130+
Decimal(this) => this.ion_data_hash(state),
131+
Timestamp(this) => this.ion_data_hash(state),
132+
Symbol(this) => this.ion_data_hash(state),
133+
String(this) => this.ion_data_hash(state),
134+
Clob(this) => this.ion_data_hash(state),
135+
Blob(this) => this.ion_data_hash(state),
136+
List(this) => this.ion_data_hash(state),
137+
SExp(this) => this.ion_data_hash(state),
138+
Struct(this) => this.ion_data_hash(state),
139+
}
140+
}
141+
}
142+
120143
/// Variants for all _values_ within an [`Element`].
121144
#[derive(Debug, Clone, PartialEq)]
122145
pub enum Value {
@@ -336,7 +359,7 @@ impl IonEq for Element {
336359
// 1. Ion type -- It is a logical way to group Ion values, and it is the cheapest comparison
337360
// 2. Annotations -- the vast majority of Ion values have few annotations, so this should usually be cheap
338361
// 3. Value -- compared using IonOrd
339-
impl IonOrd for Element {
362+
impl IonDataOrd for Element {
340363
fn ion_cmp(&self, other: &Self) -> Ordering {
341364
let ord = self.ion_type().ion_cmp(&other.ion_type());
342365
if !ord.is_eq() {
@@ -357,6 +380,13 @@ impl IonOrd for Element {
357380
}
358381
}
359382

383+
impl IonDataHash for Element {
384+
fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
385+
self.annotations.ion_data_hash(state);
386+
self.value.ion_data_hash(state);
387+
}
388+
}
389+
360390
/// An `(annotations, value)` pair representing an Ion value.
361391
#[derive(Clone)]
362392
pub struct Element {

Diff for: src/element/sequence.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use crate::element::builders::SequenceBuilder;
22
use crate::element::iterators::SequenceIterator;
33
use crate::element::Element;
4-
use crate::ion_data::{IonEq, IonOrd};
4+
use crate::ion_data::{IonDataHash, IonDataOrd, IonEq};
55
use crate::lazy::encoding::Encoding;
66
use crate::write_config::WriteConfig;
77
use crate::IonResult;
88
use std::cmp::Ordering;
99
use std::collections::VecDeque;
1010
use std::fmt::{Debug, Formatter};
11+
use std::hash::Hasher;
1112
use std::io;
1213

1314
/// An iterable, addressable series of Ion [`Element`]s.
@@ -188,12 +189,18 @@ impl IonEq for Sequence {
188189
}
189190
}
190191

191-
impl IonOrd for Sequence {
192+
impl IonDataOrd for Sequence {
192193
fn ion_cmp(&self, other: &Self) -> Ordering {
193194
self.elements.ion_cmp(&other.elements)
194195
}
195196
}
196197

198+
impl IonDataHash for Sequence {
199+
fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
200+
self.elements.ion_data_hash(state)
201+
}
202+
}
203+
197204
#[derive(Clone)]
198205
pub struct OwnedSequenceIterator {
199206
elements: VecDeque<Element>,

Diff for: src/ion_data/ion_data_hash.rs

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
use std::hash::Hasher;
2+
use std::ops::Deref;
3+
4+
/// Trait used for delegating [`Hash`] in [`IonData`](crate::IonData).
5+
/// Implementations of [`IonDataHash`] must be consistent with [`IonEq`](crate::ion_data::IonEq).
6+
///
7+
/// This is _not_ the Ion Hash algorithm. Do not write any code that depends on a specific hash
8+
/// being produced by a particular value. Do not expect that the hashes will be stable between
9+
/// two runs of the same application. The only guarantee is that it is consistent with
10+
/// [`IonEq`](crate::ion_data::IonEq)
11+
///
12+
/// This is called `IonDataHash` to avoid ambiguity with the Ion Hash algorithm and its
13+
/// implementation in this crate.
14+
pub(crate) trait IonDataHash {
15+
fn ion_data_hash<H: Hasher>(&self, state: &mut H);
16+
}
17+
18+
impl<R: Deref> IonDataHash for R
19+
where
20+
R::Target: IonDataHash,
21+
{
22+
fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
23+
R::Target::ion_data_hash(self, state)
24+
}
25+
}
26+
27+
impl<T: IonDataHash> IonDataHash for [T] {
28+
fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
29+
state.write_usize(self.len());
30+
for element in self {
31+
T::ion_data_hash(element, state)
32+
}
33+
}
34+
}
35+
36+
/// In a roundabout way, implements IonDataHash for [`f64`].
37+
///
38+
/// We cannot implement [`IonDataHash`] directly on [`f64`]. If [`IonDataHash`] is implemented directly
39+
/// on [`f64`], then _any_ blanket impl of [`IonDataHash`] for a standard library trait will cause
40+
/// `error[E0119]: conflicting implementations of trait` because [`f64`] is an external type (and
41+
/// "upstream crates may add a new impl of trait `std::ops::Deref` for type `f64` in future versions").
42+
pub(crate) fn ion_data_hash_f64<H: Hasher>(this: f64, state: &mut H) {
43+
if this.is_nan() {
44+
// `this` could be positive or negative, signalling or quiet NaN.
45+
// Ensure that all NaNs are treated equivalently, as with IonEq.
46+
state.write_u64(f64::NAN.to_bits())
47+
} else {
48+
state.write_u64(this.to_bits())
49+
}
50+
}
51+
52+
/// In a roundabout way, implements IonDataHash for [`bool`].
53+
///
54+
/// See docs for [`crate::ion_data::ion_data_hash_f64`] for general rationale. Even though the implementation is trivial,
55+
/// this function exists to help convey the intention of using Ion equivalence at the call site.
56+
pub(crate) fn ion_data_hash_bool<H: Hasher>(this: bool, state: &mut H) {
57+
state.write_u8(this as u8)
58+
}

Diff for: src/ion_data/ion_ord.rs

+18-8
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,28 @@ use std::cmp::Ordering;
22
use std::ops::Deref;
33

44
/// Trait used for delegating [Ord] and [PartialOrd] in [IonData](crate::IonData).
5-
/// Implementations of [IonOrd] must be consistent with [IonEq](crate::ion_data::IonEq).
5+
/// Implementations of [IonDataOrd] must be consistent with [IonEq](crate::ion_data::IonEq).
66
/// Since there is no total ordering in the Ion specification, do not write any code that depends on
77
/// a specific order being preserved. Only depend on the fact that a total ordering does exist.
8-
pub(crate) trait IonOrd {
8+
///
9+
/// This trait is named `IonDataOrd` (rather than `IonOrd`) to indicate that it is an implementation
10+
/// detail of `IonData` rather than being part of any Ion specification.
11+
pub(crate) trait IonDataOrd {
912
// Intentionally not public—this trait is exposed via `impl Ord for IonData`.
1013
// Called ion_cmp to avoid shadowing with Ord::cmp
1114
fn ion_cmp(&self, other: &Self) -> Ordering;
1215
}
1316

14-
impl<R: Deref> IonOrd for R
17+
impl<R: Deref> IonDataOrd for R
1518
where
16-
R::Target: IonOrd,
19+
R::Target: IonDataOrd,
1720
{
1821
fn ion_cmp(&self, other: &Self) -> Ordering {
1922
R::Target::ion_cmp(self, other)
2023
}
2124
}
2225

23-
impl<T: IonOrd> IonOrd for [T] {
26+
impl<T: IonDataOrd> IonDataOrd for [T] {
2427
fn ion_cmp(&self, other: &Self) -> Ordering {
2528
let mut i0 = self.iter();
2629
let mut i1 = other.iter();
@@ -42,12 +45,19 @@ impl<T: IonOrd> IonOrd for [T] {
4245

4346
/// Checks Ion ordering for [`f64`].
4447
///
45-
/// We cannot implement [`IonOrd`] directly on [`f64`]. If [`IonOrd`] is implemented directly on
46-
/// [`f64`], then _any_ blanket impl of [`IonOrd`] for a standard library trait will cause
48+
/// We cannot implement [`IonDataOrd`] directly on [`f64`]. If [`IonDataOrd`] is implemented directly on
49+
/// [`f64`], then _any_ blanket impl of [`IonDataOrd`] for a standard library trait will cause
4750
/// `error[E0119]: conflicting implementations of trait` because [`f64`] is an external type (and
4851
/// "upstream crates may add a new impl of trait `std::ops::Deref` for type `f64` in future versions").
4952
pub(crate) fn ion_cmp_f64(this: &f64, that: &f64) -> Ordering {
50-
this.total_cmp(that)
53+
// f64.total_cmp treats various NaN values as non-equivalent. The Ion specification, however,
54+
// treats all NaNs as equivalent, so we need logic to handle that difference.
55+
match (this.is_nan(), that.is_nan()) {
56+
(false, false) => this.total_cmp(that),
57+
(false, true) => Ordering::Less,
58+
(true, false) => Ordering::Greater,
59+
(true, true) => Ordering::Equal,
60+
}
5161
}
5262

5363
/// Checks Ion ordering for [`bool`].

Diff for: src/ion_data/mod.rs

+24-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1+
mod ion_data_hash;
12
mod ion_eq;
23
mod ion_ord;
34

45
use std::cmp::Ordering;
56
use std::fmt::{Display, Formatter};
7+
use std::hash::{Hash, Hasher};
68
use std::io::Write;
79
use std::ops::Deref;
810

911
use crate::{Encoding, IonResult, ValueWriter, WriteAsIon, WriteConfig};
12+
pub(crate) use ion_data_hash::{ion_data_hash_bool, ion_data_hash_f64, IonDataHash};
1013
pub(crate) use ion_eq::{ion_eq_bool, ion_eq_f64, IonEq};
11-
pub(crate) use ion_ord::{ion_cmp_bool, ion_cmp_f64, IonOrd};
14+
pub(crate) use ion_ord::{ion_cmp_bool, ion_cmp_f64, IonDataOrd};
1215

1316
/// A wrapper for lifting Ion compatible data into using Ion-oriented comparisons (versus the Rust
1417
/// value semantics). This enables the default semantics to be what a Rust user expects for native
@@ -37,6 +40,9 @@ pub(crate) use ion_ord::{ion_cmp_bool, ion_cmp_f64, IonOrd};
3740
/// __WARNING__—The Ion specification does _not_ define a total ordering over all Ion values. Do
3841
/// not depend on getting any particular result from [Ord]. Use it only as an opaque total ordering
3942
/// over all [IonData]. _The algorithm used for [Ord] may change between versions._
43+
///
44+
/// __WARNING__—The hashing algorithm is _not_ the same as the
45+
/// [Ion Hash Algorithm](https://amazon-ion.github.io/ion-hash). _The algorithm used for [Hash] may change between versions._
4046
#[derive(Debug, Clone)]
4147
pub struct IonData<T>(T);
4248

@@ -78,15 +84,21 @@ impl<T: Display> Display for IonData<T> {
7884
}
7985
}
8086

81-
impl<T: IonEq + IonOrd> PartialOrd for IonData<T> {
87+
impl<T: IonEq + IonDataOrd> PartialOrd for IonData<T> {
8288
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
8389
Some(self.cmp(other))
8490
}
8591
}
8692

87-
impl<T: IonEq + IonOrd> Ord for IonData<T> {
93+
impl<T: IonEq + IonDataOrd> Ord for IonData<T> {
8894
fn cmp(&self, other: &Self) -> Ordering {
89-
IonOrd::ion_cmp(&self.0, &other.0)
95+
IonDataOrd::ion_cmp(&self.0, &other.0)
96+
}
97+
}
98+
99+
impl<T: IonDataHash> Hash for IonData<T> {
100+
fn hash<H: Hasher>(&self, state: &mut H) {
101+
IonDataHash::ion_data_hash(&self.0, state)
90102
}
91103
}
92104

@@ -116,12 +128,13 @@ impl<T: WriteAsIon> WriteAsIon for IonData<T> {
116128

117129
#[cfg(test)]
118130
mod tests {
119-
use crate::ion_data::{IonEq, IonOrd};
131+
use crate::ion_data::{IonDataHash, IonDataOrd, IonEq};
120132
use crate::lazy::encoding::TextEncoding_1_0;
121133
use crate::{Element, IonData, Symbol, WriteConfig};
122134
use rstest::*;
123135
use std::boxed::Box;
124136
use std::fmt::Debug;
137+
use std::hash::{DefaultHasher, Hash, Hasher};
125138
use std::pin::Pin;
126139
use std::rc::Rc;
127140
use std::sync::Arc;
@@ -137,12 +150,17 @@ mod tests {
137150
#[case::vec_element(|s| Element::read_all(s).unwrap().into() )]
138151
#[case::rc_vec_element(|s| Rc::new(Element::read_all(s).unwrap()).into() )]
139152
#[case::box_pin_rc_vec_box_arc_element(|s| Box::new(Pin::new(Rc::new(vec![Box::new(Arc::new(Element::read_one(s).unwrap()))]))).into() )]
140-
fn can_wrap_data<T: IonEq + IonOrd + Debug>(
153+
fn can_wrap_data<T: IonEq + IonDataOrd + IonDataHash + Debug>(
141154
#[case] the_fn: impl Fn(&'static str) -> IonData<T>,
142155
) {
143156
let id1: IonData<_> = the_fn("nan");
144157
let id2: IonData<_> = the_fn("nan");
145158
assert_eq!(id1, id2);
159+
let mut h1 = DefaultHasher::default();
160+
let mut h2 = DefaultHasher::default();
161+
id1.hash(&mut h1);
162+
id2.hash(&mut h2);
163+
assert_eq!(h1.finish(), h2.finish());
146164

147165
let id1: IonData<_> = the_fn("1.00");
148166
let id2: IonData<_> = the_fn("1.0");

Diff for: src/types/bytes.rs

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
use crate::ion_data::{IonEq, IonOrd};
1+
use crate::ion_data::{IonDataHash, IonDataOrd, IonEq};
22
use std::cmp::Ordering;
3+
use std::hash::{Hash, Hasher};
34

45
/// An owned, immutable byte array.
56
/// ```rust
@@ -35,12 +36,18 @@ impl IonEq for Bytes {
3536
}
3637
}
3738

38-
impl IonOrd for Bytes {
39+
impl IonDataOrd for Bytes {
3940
fn ion_cmp(&self, other: &Self) -> Ordering {
4041
self.cmp(other)
4142
}
4243
}
4344

45+
impl IonDataHash for Bytes {
46+
fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
47+
self.data.hash(state);
48+
}
49+
}
50+
4451
impl PartialEq<[u8]> for Bytes {
4552
fn eq(&self, other: &[u8]) -> bool {
4653
self.as_ref().eq(other)

Diff for: src/types/decimal/coefficient.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use crate::{Int, UInt};
1414
/// When the magnitude is zero, the `Sign` can be used to distinguish between -0 and 0.
1515
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
1616
pub enum Sign {
17-
Negative,
18-
Positive,
17+
Negative = -1,
18+
Positive = 1,
1919
}
2020

2121
/// A signed integer that can be used as the coefficient of a [`Decimal`](crate::Decimal) value.
@@ -293,4 +293,10 @@ mod coefficient_tests {
293293
assert_eq!(Int::try_from(Coefficient::new(0)), Ok(Int::from(0)));
294294
assert!(Int::try_from(Coefficient::negative_zero()).is_err());
295295
}
296+
297+
#[test]
298+
fn test_casting_sign() {
299+
assert_eq!(-1, Sign::Negative as i8);
300+
assert_eq!(1, Sign::Positive as i8);
301+
}
296302
}

0 commit comments

Comments
 (0)