Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
da9168c
Add new error relating to ranges
Ztry8 Apr 21, 2026
362900a
Add basic deserialization for ranges
Ztry8 Apr 25, 2026
df87102
Add basic serialization for ranges
Ztry8 Apr 25, 2026
1892f20
Add support for inclusive ranges serialization
Ztry8 Apr 25, 2026
930f462
Make difference for inclusives in deserialization
Ztry8 Apr 25, 2026
da5b656
Add support for `core::range`
Ztry8 Apr 25, 2026
fc89a9a
Fix serialization for `core::range` support
Ztry8 Apr 25, 2026
b1e9404
Allow byte literals
Ztry8 Apr 26, 2026
d278210
Add test for integer bases, binary, octal, hex, and byte literals
Ztry8 Apr 26, 2026
7f41a78
Add unclosed ranges & new test for them
Ztry8 Apr 26, 2026
348d834
Add pretty config for ranges & new test for them
Ztry8 Apr 26, 2026
873c42f
Use pattern matching for field in `deserialize_struct`
Ztry8 Apr 26, 2026
481377c
`RangeMapAccess`: change magic numbers to enum
Ztry8 Apr 26, 2026
91de9b5
Move `NumberDeserializer` into `value` module
Ztry8 Apr 26, 2026
66c891f
Add additional name check in `deserialize_struct`
Ztry8 Apr 26, 2026
d423eef
Add ranges to `grammar.md` & `README.md`
Ztry8 Apr 26, 2026
51a1ad0
Fix punctuation
Ztry8 Apr 28, 2026
b2b6d41
Fix `docs/grammar.md`: restrict range bounds to numbers
Ztry8 Apr 28, 2026
1400780
Update `docs/grammar.md`: add number type
Ztry8 Apr 28, 2026
bb80be8
Add additional check for field name in ranges deserialization
Ztry8 Apr 28, 2026
8a62f7b
Remove visitor inspection (no longer needed)
Ztry8 Apr 28, 2026
3c04f28
Remove code duplication in deserialization
Ztry8 Apr 28, 2026
c5272a5
Add RangeFull & new tests for unclosed ranges
Ztry8 Apr 28, 2026
9beb51b
Guard against excess value reads in Range{From,To}MapAccess
Ztry8 May 2, 2026
4b2765e
Validate numeric bounds before emitting compact range syntax
Ztry8 May 2, 2026
30e425e
Split RangeTo and RangeToInclusive into separate deserialize branches
Ztry8 May 2, 2026
6e7a558
Fix formatting deserialization error
Ztry8 May 2, 2026
93fb3a1
Fix formatting deserialization error
Ztry8 May 2, 2026
a528961
Add check for structure name in range deserealization
Ztry8 May 2, 2026
c2be8f8
Refactor deserialization: add helper function
Ztry8 May 2, 2026
4788a8a
Add new tests for unclosed ranges of byte literals
Ztry8 May 2, 2026
9e94138
Handle inf/NaN as range bounds in deserialization
Ztry8 May 2, 2026
b7ea12d
Fix error formatting in range deserialization
Ztry8 May 2, 2026
270f27b
Improve untagged test
Ztry8 May 2, 2026
76c4422
Expand ranges description in `README.md`
Ztry8 May 4, 2026
fbb1068
Move helper function into `Parser` `impl` & remove old comment
Ztry8 May 8, 2026
bf844d0
Refactor deserialization: factor repeated code to common function
Ztry8 May 8, 2026
50175a6
Refactor deserialization: change condition
Ztry8 May 8, 2026
c6fada3
Refactor parsing & deserialization of NaN/inf float ranges
Ztry8 May 8, 2026
9b8f2b0
Replace bool flags with enum in serialization
Ztry8 May 8, 2026
b5c8e16
Replace `BTreeMap` with fields in ranges serialization
Ztry8 May 8, 2026
7e32f30
Use buffered fields in ranges serialization fallback
Ztry8 May 8, 2026
eee1757
Replace `String` with `Number` in range serialization
Ztry8 May 8, 2026
b7d06f1
Remove unused import
Ztry8 May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 116 additions & 2 deletions src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,19 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
'(' => self.handle_any_struct(visitor, None),
'[' => self.deserialize_seq(visitor),
'{' => self.deserialize_map(visitor),
'0'..='9' | '+' | '-' | '.' => self.parser.any_number()?.visit(visitor),
'0'..='9' | '+' | '-' | '.' => {
let start = self.parser.any_number()?;

if self.parser.consume_str("..=") {
Comment thread
Ztry8 marked this conversation as resolved.
let end = self.parser.any_number()?;
return visitor.visit_map(RangeMapAccess::new(start, end, "end"));
} else if self.parser.consume_str("..") {
let end = self.parser.any_number()?;
return visitor.visit_map(RangeMapAccess::new(start, end, "end"));
}

start.visit(visitor)
}
'"' | 'r' => self.deserialize_string(visitor),
'b' if self.parser.src().starts_with("b'") => self.parser.any_number()?.visit(visitor),
Comment thread
Ztry8 marked this conversation as resolved.
Outdated
'b' => self.deserialize_byte_buf(visitor),
Expand Down Expand Up @@ -715,12 +727,43 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> {
fn deserialize_struct<V>(
self,
name: &'static str,
_fields: &'static [&'static str],
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value>
where
V: Visitor<'de>,
{
if fields == ["start", "end"] || fields == ["start", "last"] {
Comment thread
Ztry8 marked this conversation as resolved.
Outdated
if let Some(c) = self.parser.peek_char() {
if matches!(c, '0'..='9' | '+' | '-' | '.') {
Comment thread
Ztry8 marked this conversation as resolved.
Outdated
let start = self.parser.any_number()?;

let inclusive = if self.parser.consume_str("..=") {
true
} else if self.parser.consume_str("..") {
false
} else {
return Err(Error::ExpectedRangeSyntax);
};

if inclusive && name == "Range" {
Comment thread
Ztry8 marked this conversation as resolved.
Outdated
return Err(Error::Message(String::from(
"expected `..` for Range, found `..=`",
Comment thread
Ztry8 marked this conversation as resolved.
Outdated
)));
}

if !inclusive && name == "RangeInclusive" {
return Err(Error::Message(String::from(
"expected `..=` for RangeInclusive, found `..`",
Comment thread
Ztry8 marked this conversation as resolved.
Outdated
)));
}

let end = self.parser.any_number()?;
return visitor.visit_map(RangeMapAccess::new(start, end, fields[1]));
}
}
}

if !self.newtype_variant {
self.parser.consume_struct_name(name)?;
}
Expand Down Expand Up @@ -827,6 +870,77 @@ impl<'a, 'de> CommaSeparated<'a, 'de> {
}
}

struct RangeMapAccess {
start: crate::value::Number,
end: crate::value::Number,
state: u8,
Comment thread
Ztry8 marked this conversation as resolved.
Outdated
end_key: &'static str,
}

impl RangeMapAccess {
fn new(start: crate::value::Number, end: crate::value::Number, end_key: &'static str) -> Self {
RangeMapAccess {
start,
end,
state: 0,
end_key,
}
}
}

impl<'de> de::MapAccess<'de> for RangeMapAccess {
type Error = Error;

fn next_key_seed<K: de::DeserializeSeed<'de>>(&mut self, seed: K) -> Result<Option<K::Value>> {
match self.state {
0 => {
self.state = 1;
seed.deserialize(de::value::StrDeserializer::<Error>::new("start"))
.map(Some)
}
2 => {
self.state = 3;
seed.deserialize(de::value::StrDeserializer::<Error>::new(self.end_key))
.map(Some)
}
_ => Ok(None),
}
}

fn next_value_seed<V: de::DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value> {
match self.state {
1 => {
self.state = 2;
seed.deserialize(NumberDeserializer(self.start))
}
3 => {
self.state = 4;
seed.deserialize(NumberDeserializer(self.end))
}
_ => Err(Error::ExpectedDifferentLength {
expected: String::from("map of length 2"),
found: 3,
}),
}
}
}

struct NumberDeserializer(crate::value::Number);
Comment thread
Ztry8 marked this conversation as resolved.
Outdated

impl<'de> de::Deserializer<'de> for NumberDeserializer {
type Error = Error;

fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value> {
self.0.visit(visitor)
}

serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64
char str string bytes byte_buf option unit unit_struct
newtype_struct seq tuple tuple_struct map struct enum identifier ignored_any
}
}

impl<'de, 'a> de::SeqAccess<'de> for CommaSeparated<'a, 'de> {
type Error = Error;

Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pub enum Error {
ExpectedRawValue,
ExceededRecursionLimit,
ExpectedStructName(String),
ExpectedRangeSyntax,
}

impl fmt::Display for SpannedError {
Expand Down Expand Up @@ -286,6 +287,7 @@ impl fmt::Display for Error {
"Expected the explicit struct name {}, but none was found",
Identifier(name)
),
Error::ExpectedRangeSyntax => f.write_str("Expected `..` or `..=` for range syntax"),
}
}
}
Expand Down
11 changes: 9 additions & 2 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,9 @@ impl<'a> Parser<'a> {
}
}

let num_bytes = self.next_chars_while_len(is_float_char);
let raw_bytes = self.next_chars_while_len(is_float_char);
let src = &self.src()[..raw_bytes];
let num_bytes = src.find("..").unwrap_or(raw_bytes);

if num_bytes == 0 {
return Err(Error::ExpectedFloat);
Expand Down Expand Up @@ -1019,7 +1021,12 @@ impl<'a> Parser<'a> {
'+' | '-' => 1,
_ => 0,
};
let valid_float_len = self.next_chars_while_from_len(skip, is_float_char);
let raw_float_len = self.next_chars_while_from_len(skip, is_float_char);
// Trim at ".." to avoid treating range operators as float chars
let valid_float_len = self.src()[skip..]
.find("..")
.map(|i| i.min(raw_float_len))
.unwrap_or(raw_float_len);
let valid_int_len = self.next_chars_while_from_len(skip, is_int_char);
valid_float_len > valid_int_len
} else {
Expand Down
33 changes: 33 additions & 0 deletions src/ser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,12 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer<W> {

fn serialize_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
let old_newtype_variant = self.newtype_variant;
if len == 2 && (name == "Range" || name == "RangeInclusive") {
Comment thread
Ztry8 marked this conversation as resolved.
Outdated
self.newtype_variant = false;
self.implicit_some_depth = 0;
return Ok(Compound::new_range(self, name == "RangeInclusive"));
}

self.newtype_variant = false;
self.implicit_some_depth = 0;

Expand Down Expand Up @@ -1106,6 +1112,7 @@ pub struct Compound<'a, W: fmt::Write> {
state: State,
newtype_variant: bool,
sequence_index: usize,
range_inclusive: Option<bool>,
}

impl<'a, W: fmt::Write> Compound<'a, W> {
Expand All @@ -1115,6 +1122,17 @@ impl<'a, W: fmt::Write> Compound<'a, W> {
state: State::First,
newtype_variant,
sequence_index: 0,
range_inclusive: None,
}
}

fn new_range(ser: &'a mut Serializer<W>, inclusive: bool) -> Self {
Compound {
ser,
state: State::First,
newtype_variant: false,
sequence_index: 0,
range_inclusive: Some(inclusive),
}
}
}
Expand Down Expand Up @@ -1340,6 +1358,17 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> {
where
T: ?Sized + Serialize,
{
if let Some(inclusive) = self.range_inclusive {
if key == "end" || key == "last" {
self.ser
.output
.write_str(if inclusive { "..=" } else { ".." })?;
}

guard_recursion! { self.ser => value.serialize(&mut *self.ser)? };
return Ok(());
}

let mut restore_field = self.ser.pretty.as_mut().and_then(|(config, _)| {
config.path_meta.take().map(|mut field| {
if let Some(fields) = field.fields_mut() {
Expand Down Expand Up @@ -1403,6 +1432,10 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> {
}

fn end(self) -> Result<()> {
if self.range_inclusive.is_some() {
return Ok(());
}

if let State::Rest = self.state {
if let Some((ref config, ref pretty)) = self.ser.pretty {
if pretty.indent <= config.depth_limit && !config.compact_structs {
Expand Down
26 changes: 26 additions & 0 deletions tests/601_support_for_number_ranges.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use ron;
use serde::{Deserialize, Serialize};

#[derive(PartialEq, Deserialize, Serialize, Debug)]
struct RangeTest {
a: std::ops::Range<i32>,
b: std::ops::RangeInclusive<i32>,
c: std::ops::Range<f32>,
d: std::ops::RangeInclusive<f32>,
}

Comment thread
Ztry8 marked this conversation as resolved.
#[test]
fn test_ranges() {
let ranges = RangeTest {
a: 0..5,
b: 1..=3,
c: 0.6..4.3,
d: 0.3..=5.7,
};

let ser = ron::to_string(&ranges).unwrap();
assert_eq!(ser, "(a:0..5,b:1..=3,c:0.6..4.3,d:0.3..=5.7)");
Comment thread
Ztry8 marked this conversation as resolved.
Outdated

let de: RangeTest = ron::from_str(&ser).unwrap();
assert_eq!(de, ranges);
}
Loading