Skip to content

Commit 09c0fd9

Browse files
committed
Move number/string dispatch onto IValue; keep only representations under value
A JSON number/string is not a representation — each is a type that spans two representations (inline decimal vs heap scalar; inline short string vs interned string). So the misnamed value::number / value::string modules are gone; their cross-representation dispatch now lives directly on IValue as new_*/number_*/string_* methods, which pick a representation as early as possible and defer to it: - new_string asks inline::string::try_encode (new) whether the string fits inline, and only interns when it does not; new_i64/new_u64/new_f64 likewise try inline::number first, then fall back to a heap scalar. - number_*/string_* accessors dispatch on the tag and immediately defer to the owning representation (inline::number/scalar, inline::string/ interned), with the small amount of genuinely cross-representation logic (numeric NumVal comparison) kept as private helpers. value/ now contains only representations: inline, scalar, interned, array, object. INumber/IString are unchanged thin facades that delegate to the new IValue methods. No public API or behavior change; validated with cargo test, clippy, fmt, and Miri on x86-64, s390x (BE-64), powerpc (BE-32) and i686 — no UB on any target.
1 parent 934ae4d commit 09c0fd9

6 files changed

Lines changed: 347 additions & 363 deletions

File tree

src/number.rs

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,17 @@
22
//!
33
//! [`INumber`] is the public *type* for JSON numbers. It is a thin, transparent
44
//! wrapper around an [`IValue`] that is known to be a number; all of the actual
5-
//! logic (construction, conversions, comparison, hashing) lives in the
6-
//! [`crate::value::number`] module and is shared with `IValue` itself. The
7-
//! number can be stored either inline or as a heap scalar, but that choice is
8-
//! entirely hidden behind this type.
5+
//! logic (construction, conversions, comparison, hashing) lives on `IValue` as
6+
//! its `new_*`/`number_*` methods and is shared with `IValue` itself. The number
7+
//! can be stored either inline or as a heap scalar, but that choice is entirely
8+
//! hidden behind this type.
99
#![allow(clippy::float_cmp)]
1010

1111
use std::cmp::Ordering;
1212
use std::convert::TryFrom;
1313
use std::fmt::{self, Debug, Formatter};
1414
use std::hash::Hash;
1515

16-
use crate::value::number as num;
1716
use crate::value::IValue;
1817

1918
/// The `INumber` type represents a JSON number. It is decoupled from any specific
@@ -43,136 +42,136 @@ impl INumber {
4342
/// Returns the number zero (without a decimal point). Does not allocate.
4443
#[must_use]
4544
pub fn zero() -> Self {
46-
INumber(num::new_i64(0))
45+
INumber(IValue::new_i64(0))
4746
}
4847
/// Returns the number one (without a decimal point). Does not allocate.
4948
#[must_use]
5049
pub fn one() -> Self {
51-
INumber(num::new_i64(1))
50+
INumber(IValue::new_i64(1))
5251
}
5352

5453
/// Converts this number to an i64 if it can be represented exactly.
5554
#[must_use]
5655
pub fn to_i64(&self) -> Option<i64> {
57-
num::to_i64(&self.0)
56+
self.0.number_to_i64()
5857
}
5958
/// Converts this number to a u64 if it can be represented exactly.
6059
#[must_use]
6160
pub fn to_u64(&self) -> Option<u64> {
62-
num::to_u64(&self.0)
61+
self.0.number_to_u64()
6362
}
6463
/// Converts this number to an f64 if it can be represented exactly.
6564
#[must_use]
6665
pub fn to_f64(&self) -> Option<f64> {
67-
num::to_f64(&self.0)
66+
self.0.number_to_f64()
6867
}
6968
/// Converts this number to an f32 if it can be represented exactly.
7069
#[must_use]
7170
pub fn to_f32(&self) -> Option<f32> {
72-
num::to_f32(&self.0)
71+
self.0.number_to_f32()
7372
}
7473
/// Converts this number to an i32 if it can be represented exactly.
7574
#[must_use]
7675
pub fn to_i32(&self) -> Option<i32> {
77-
num::to_i32(&self.0)
76+
self.0.number_to_i32()
7877
}
7978
/// Converts this number to a u32 if it can be represented exactly.
8079
#[must_use]
8180
pub fn to_u32(&self) -> Option<u32> {
82-
num::to_u32(&self.0)
81+
self.0.number_to_u32()
8382
}
8483
/// Converts this number to an isize if it can be represented exactly.
8584
#[must_use]
8685
pub fn to_isize(&self) -> Option<isize> {
87-
num::to_isize(&self.0)
86+
self.0.number_to_isize()
8887
}
8988
/// Converts this number to a usize if it can be represented exactly.
9089
#[must_use]
9190
pub fn to_usize(&self) -> Option<usize> {
92-
num::to_usize(&self.0)
91+
self.0.number_to_usize()
9392
}
9493
/// Converts this number to an f64, potentially losing precision in the process.
9594
#[must_use]
9695
pub fn to_f64_lossy(&self) -> f64 {
97-
num::to_f64_lossy(&self.0)
96+
self.0.number_to_f64_lossy()
9897
}
9998
/// Converts this number to an f32, potentially losing precision in the process.
10099
#[must_use]
101100
pub fn to_f32_lossy(&self) -> f32 {
102-
num::to_f32_lossy(&self.0)
101+
self.0.number_to_f32_lossy()
103102
}
104103

105104
/// This allows distinguishing between `1.0` and `1` in the original JSON.
106105
/// Numeric operations will otherwise treat these two values as equivalent.
107106
#[must_use]
108107
pub fn has_decimal_point(&self) -> bool {
109-
num::has_decimal_point(&self.0)
108+
self.0.number_has_decimal_point()
110109
}
111110
}
112111

113112
impl Hash for INumber {
114113
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
115-
num::hash(&self.0, state);
114+
self.0.number_hash(state);
116115
}
117116
}
118117

119118
impl From<u64> for INumber {
120119
fn from(v: u64) -> Self {
121-
INumber(num::new_u64(v))
120+
INumber(IValue::new_u64(v))
122121
}
123122
}
124123
impl From<u32> for INumber {
125124
fn from(v: u32) -> Self {
126-
INumber(num::new_u64(u64::from(v)))
125+
INumber(IValue::new_u64(u64::from(v)))
127126
}
128127
}
129128
impl From<u16> for INumber {
130129
fn from(v: u16) -> Self {
131-
INumber(num::new_u64(u64::from(v)))
130+
INumber(IValue::new_u64(u64::from(v)))
132131
}
133132
}
134133
impl From<u8> for INumber {
135134
fn from(v: u8) -> Self {
136-
INumber(num::new_u64(u64::from(v)))
135+
INumber(IValue::new_u64(u64::from(v)))
137136
}
138137
}
139138
impl From<usize> for INumber {
140139
fn from(v: usize) -> Self {
141-
INumber(num::new_u64(v as u64))
140+
INumber(IValue::new_u64(v as u64))
142141
}
143142
}
144143

145144
impl From<i64> for INumber {
146145
fn from(v: i64) -> Self {
147-
INumber(num::new_i64(v))
146+
INumber(IValue::new_i64(v))
148147
}
149148
}
150149
impl From<i32> for INumber {
151150
fn from(v: i32) -> Self {
152-
INumber(num::new_i64(i64::from(v)))
151+
INumber(IValue::new_i64(i64::from(v)))
153152
}
154153
}
155154
impl From<i16> for INumber {
156155
fn from(v: i16) -> Self {
157-
INumber(num::new_i64(i64::from(v)))
156+
INumber(IValue::new_i64(i64::from(v)))
158157
}
159158
}
160159
impl From<i8> for INumber {
161160
fn from(v: i8) -> Self {
162-
INumber(num::new_i64(i64::from(v)))
161+
INumber(IValue::new_i64(i64::from(v)))
163162
}
164163
}
165164
impl From<isize> for INumber {
166165
fn from(v: isize) -> Self {
167-
INumber(num::new_i64(v as i64))
166+
INumber(IValue::new_i64(v as i64))
168167
}
169168
}
170169

171170
impl TryFrom<f64> for INumber {
172171
type Error = ();
173172
fn try_from(v: f64) -> Result<Self, ()> {
174173
if v.is_finite() {
175-
Ok(INumber(num::new_f64(v)))
174+
Ok(INumber(IValue::new_f64(v)))
176175
} else {
177176
Err(())
178177
}
@@ -183,7 +182,7 @@ impl TryFrom<f32> for INumber {
183182
type Error = ();
184183
fn try_from(v: f32) -> Result<Self, ()> {
185184
if v.is_finite() {
186-
Ok(INumber(num::new_f64(f64::from(v))))
185+
Ok(INumber(IValue::new_f64(f64::from(v))))
187186
} else {
188187
Err(())
189188
}
@@ -245,7 +244,7 @@ impl PartialEq for INumber {
245244
impl Eq for INumber {}
246245
impl Ord for INumber {
247246
fn cmp(&self, other: &Self) -> Ordering {
248-
num::cmp(&self.0, &other.0)
247+
self.0.number_cmp(&other.0)
249248
}
250249
}
251250
impl PartialOrd for INumber {
@@ -256,7 +255,7 @@ impl PartialOrd for INumber {
256255

257256
impl Debug for INumber {
258257
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
259-
num::debug(&self.0, f)
258+
self.0.number_debug(f)
260259
}
261260
}
262261

src/string.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,21 @@
22
//!
33
//! [`IString`] is the public *type* for JSON strings. It is a thin, transparent
44
//! wrapper around an [`IValue`] that is known to be a string; the actual logic
5-
//! (construction, byte/str access, comparison, formatting) lives in the
6-
//! [`crate::value::string`] module and is shared with `IValue` itself. A string
7-
//! can be stored either inline or as a heap interned string, but that choice is
8-
//! entirely hidden behind this type.
5+
//! (construction, byte/str access, comparison, formatting) lives on `IValue` as
6+
//! its `new_string`/`string_*` methods and is shared with `IValue` itself. A
7+
//! string can be stored either inline or as a heap interned string, but that
8+
//! choice is entirely hidden behind this type.
99
1010
use std::cmp::Ordering;
1111
use std::fmt::{self, Debug, Display, Formatter};
1212
use std::hash::Hash;
1313
use std::ops::Deref;
1414

15-
use crate::value::string as str_repr;
1615
use crate::value::IValue;
1716

1817
#[doc(hidden)]
1918
pub fn init_cache() {
20-
str_repr::init_cache();
19+
crate::value::interned::init_cache();
2120
}
2221

2322
/// The `IString` type is an interned, immutable string, and is where this crate
@@ -53,13 +52,13 @@ impl IString {
5352
/// global cache. Longer strings are interned in the global string cache.
5453
#[must_use]
5554
pub fn intern(s: &str) -> Self {
56-
IString(str_repr::new(s))
55+
IString(IValue::new_string(s))
5756
}
5857

5958
/// Returns the length (in bytes) of this string.
6059
#[must_use]
6160
pub fn len(&self) -> usize {
62-
str_repr::len(&self.0)
61+
self.0.string_len()
6362
}
6463

6564
/// Returns `true` if this is the empty string "".
@@ -71,13 +70,13 @@ impl IString {
7170
/// Obtains a `&str` from this `IString`. This is a cheap operation.
7271
#[must_use]
7372
pub fn as_str(&self) -> &str {
74-
str_repr::as_str(&self.0)
73+
self.0.string_as_str()
7574
}
7675

7776
/// Obtains a byte slice from this `IString`. This is a cheap operation.
7877
#[must_use]
7978
pub fn as_bytes(&self) -> &[u8] {
80-
str_repr::bytes(&self.0)
79+
self.0.string_bytes()
8180
}
8281

8382
/// Returns the empty string.

src/value/inline/string.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ const CONTROL_OFFSET: usize = std::mem::size_of::<usize>() - 1;
3333
#[cfg(target_endian = "big")]
3434
const CHAR_OFFSET: usize = 0;
3535

36+
/// The inline bits for `s` if it fits inline (at most [`CAPACITY`] bytes), or
37+
/// `None` if it is too long and must be stored some other way. This is how the
38+
/// value layer asks the inline representation whether it can hold a string.
39+
pub(crate) fn try_encode(s: &str) -> Option<usize> {
40+
(s.len() <= CAPACITY).then(|| encode(s))
41+
}
42+
3643
/// The inline bits for a string of at most [`CAPACITY`] bytes.
3744
pub(crate) fn encode(s: &str) -> usize {
3845
debug_assert!(s.len() <= CAPACITY);

0 commit comments

Comments
 (0)