From c822128a02046b4c99933362bbc341d5bf973f93 Mon Sep 17 00:00:00 2001 From: daubaris Date: Fri, 22 Nov 2019 18:28:29 +0200 Subject: [PATCH] Mixed type arrays (#358) * Added support of mixed-type arrays * Add tests cases * Replaced &'static str type for type_ and created a new enum instead * Restored ArrayMixedType --- src/de.rs | 19 ------ src/ser.rs | 68 ++++++++++--------- test-suite/tests/invalid.rs | 15 ---- test-suite/tests/valid.rs | 15 ++++ .../array-mixed-types-arrays-and-ints.json | 11 +++ .../array-mixed-types-arrays-and-ints.toml | 0 .../array-mixed-types-ints-and-floats.json | 9 +++ .../array-mixed-types-ints-and-floats.toml | 0 .../array-mixed-types-strings-and-ints.json | 9 +++ .../array-mixed-types-strings-and-ints.toml | 0 test-suite/tests/valid/example-v0.4.0.toml | 3 +- 11 files changed, 81 insertions(+), 68 deletions(-) create mode 100644 test-suite/tests/valid/array-mixed-types-arrays-and-ints.json rename test-suite/tests/{invalid => valid}/array-mixed-types-arrays-and-ints.toml (100%) create mode 100644 test-suite/tests/valid/array-mixed-types-ints-and-floats.json rename test-suite/tests/{invalid => valid}/array-mixed-types-ints-and-floats.toml (100%) create mode 100644 test-suite/tests/valid/array-mixed-types-strings-and-ints.json rename test-suite/tests/{invalid => valid}/array-mixed-types-strings-and-ints.toml (100%) diff --git a/src/de.rs b/src/de.rs index ae44f20..5a0d0c7 100644 --- a/src/de.rs +++ b/src/de.rs @@ -11,7 +11,6 @@ use std::f64; use std::fmt; use std::iter; use std::marker::PhantomData; -use std::mem::discriminant; use std::str; use std::vec; @@ -147,10 +146,6 @@ enum ErrorKind { found: &'static str, }, - /// An array was decoded but the types inside of it were mixed, which is - /// disallowed by TOML. - MixedArrayType, - /// A duplicate table definition was found. DuplicateTable(String), @@ -1827,13 +1822,7 @@ impl<'a> Deserializer<'a> { if let Some(span) = self.eat_spanned(Token::RightBracket)? { return Ok((span, ret)); } - let at = self.tokens.current(); let value = self.value()?; - if let Some(last) = ret.last() { - if !value.same_type(last) { - return Err(self.error(at, ErrorKind::MixedArrayType)); - } - } ret.push(value); intermediate(self)?; if !self.eat(Token::Comma)? { @@ -2118,7 +2107,6 @@ impl fmt::Display for Error { } ErrorKind::NumberInvalid => "invalid number".fmt(f)?, ErrorKind::DateInvalid => "invalid date".fmt(f)?, - ErrorKind::MixedArrayType => "mixed types in an array".fmt(f)?, ErrorKind::DuplicateTable(ref s) => { write!(f, "redefinition of table `{}`", s)?; } @@ -2180,7 +2168,6 @@ impl error::Error for Error { ErrorKind::Wanted { .. } => "expected a token but found another", ErrorKind::NumberInvalid => "invalid number", ErrorKind::DateInvalid => "invalid date", - ErrorKind::MixedArrayType => "mixed types in an array", ErrorKind::DuplicateTable(_) => "duplicate table", ErrorKind::RedefineAsArray => "table redefined as array", ErrorKind::EmptyTableKey => "empty table key found", @@ -2283,9 +2270,3 @@ impl<'a> E<'a> { } } } - -impl<'a> Value<'a> { - fn same_type(&self, other: &Value<'a>) -> bool { - discriminant(&self.e) == discriminant(&other.e) - } -} diff --git a/src/ser.rs b/src/ser.rs index 0734803..d81f75e 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -124,8 +124,8 @@ pub enum Error { #[doc(hidden)] KeyNewline, - /// Arrays in TOML must have a homogenous type, but a heterogeneous array - /// was emitted. + /// An array had to be homogenous, but now it is allowed to be heterogenous. + #[doc(hidden)] ArrayMixedType, /// All values in a TOML table must be emitted before further tables are @@ -201,6 +201,12 @@ pub struct Serializer<'a> { settings: Rc, } +#[derive(Debug, Copy, Clone)] +enum ArrayState { + Started, + StartedAsATable, +} + #[derive(Debug, Clone)] enum State<'a> { Table { @@ -212,7 +218,7 @@ enum State<'a> { Array { parent: &'a State<'a>, first: &'a Cell, - type_: &'a Cell>, + type_: &'a Cell>, len: Option, }, End, @@ -222,7 +228,7 @@ enum State<'a> { pub struct SerializeSeq<'a, 'b> { ser: &'b mut Serializer<'a>, first: Cell, - type_: Cell>, + type_: Cell>, len: Option, } @@ -417,7 +423,7 @@ impl<'a> Serializer<'a> { self } - fn display(&mut self, t: T, type_: &'static str) -> Result<(), Error> { + fn display(&mut self, t: T, type_: ArrayState) -> Result<(), Error> { self.emit_key(type_)?; write!(self.dst, "{}", t).map_err(ser::Error::custom)?; if let State::Table { .. } = self.state { @@ -426,7 +432,7 @@ impl<'a> Serializer<'a> { Ok(()) } - fn emit_key(&mut self, type_: &'static str) -> Result<(), Error> { + fn emit_key(&mut self, type_: ArrayState) -> Result<(), Error> { self.array_type(type_)?; let state = self.state.clone(); self._emit_key(&state) @@ -491,16 +497,12 @@ impl<'a> Serializer<'a> { Ok(()) } - fn array_type(&mut self, type_: &'static str) -> Result<(), Error> { + fn array_type(&mut self, type_: ArrayState) -> Result<(), Error> { let prev = match self.state { State::Array { type_, .. } => type_, _ => return Ok(()), }; - if let Some(prev) = prev.get() { - if prev != type_ { - return Err(Error::ArrayMixedType); - } - } else { + if let None = prev.get() { prev.set(Some(type_)); } Ok(()) @@ -747,7 +749,7 @@ impl<'a> Serializer<'a> { macro_rules! serialize_float { ($this:expr, $v:expr) => {{ - $this.emit_key("float")?; + $this.emit_key(ArrayState::Started)?; if ($v.is_nan() || $v == 0.0) && $v.is_sign_negative() { write!($this.dst, "-").map_err(ser::Error::custom)?; } @@ -778,39 +780,39 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { type SerializeStructVariant = ser::Impossible<(), Error>; fn serialize_bool(self, v: bool) -> Result<(), Self::Error> { - self.display(v, "bool") + self.display(v, ArrayState::Started) } fn serialize_i8(self, v: i8) -> Result<(), Self::Error> { - self.display(v, "integer") + self.display(v, ArrayState::Started) } fn serialize_i16(self, v: i16) -> Result<(), Self::Error> { - self.display(v, "integer") + self.display(v, ArrayState::Started) } fn serialize_i32(self, v: i32) -> Result<(), Self::Error> { - self.display(v, "integer") + self.display(v, ArrayState::Started) } fn serialize_i64(self, v: i64) -> Result<(), Self::Error> { - self.display(v, "integer") + self.display(v, ArrayState::Started) } fn serialize_u8(self, v: u8) -> Result<(), Self::Error> { - self.display(v, "integer") + self.display(v, ArrayState::Started) } fn serialize_u16(self, v: u16) -> Result<(), Self::Error> { - self.display(v, "integer") + self.display(v, ArrayState::Started) } fn serialize_u32(self, v: u32) -> Result<(), Self::Error> { - self.display(v, "integer") + self.display(v, ArrayState::Started) } fn serialize_u64(self, v: u64) -> Result<(), Self::Error> { - self.display(v, "integer") + self.display(v, ArrayState::Started) } fn serialize_f32(self, v: f32) -> Result<(), Self::Error> { @@ -827,7 +829,7 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { } fn serialize_str(self, value: &str) -> Result<(), Self::Error> { - self.emit_key("string")?; + self.emit_key(ArrayState::Started)?; self.emit_str(value, false)?; if let State::Table { .. } = self.state { self.dst.push_str("\n"); @@ -893,7 +895,7 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { } fn serialize_seq(self, len: Option) -> Result { - self.array_type("array")?; + self.array_type(ArrayState::Started)?; Ok(SerializeSeq { ser: self, first: Cell::new(true), @@ -925,7 +927,7 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { } fn serialize_map(self, _len: Option) -> Result { - self.array_type("table")?; + self.array_type(ArrayState::StartedAsATable)?; Ok(SerializeTable::Table { ser: self, key: String::new(), @@ -940,10 +942,10 @@ impl<'a, 'b> ser::Serializer for &'b mut Serializer<'a> { _len: usize, ) -> Result { if name == datetime::NAME { - self.array_type("datetime")?; + self.array_type(ArrayState::Started)?; Ok(SerializeTable::Datetime(self)) } else { - self.array_type("table")?; + self.array_type(ArrayState::StartedAsATable)?; Ok(SerializeTable::Table { ser: self, key: String::new(), @@ -988,8 +990,8 @@ impl<'a, 'b> ser::SerializeSeq for SerializeSeq<'a, 'b> { fn end(self) -> Result<(), Error> { match self.type_.get() { - Some("table") => return Ok(()), - Some(_) => match (self.len, &self.ser.settings.array) { + Some(ArrayState::StartedAsATable) => return Ok(()), + Some(ArrayState::Started) => match (self.len, &self.ser.settings.array) { (Some(0..=1), _) | (_, &None) => { self.ser.dst.push_str("]"); } @@ -1002,7 +1004,7 @@ impl<'a, 'b> ser::SerializeSeq for SerializeSeq<'a, 'b> { }, None => { assert!(self.first.get()); - self.ser.emit_key("array")?; + self.ser.emit_key(ArrayState::Started)?; self.ser.dst.push_str("[]") } } @@ -1244,7 +1246,7 @@ impl<'a, 'b> ser::Serializer for DateStrEmitter<'a, 'b> { } fn serialize_str(self, value: &str) -> Result<(), Self::Error> { - self.0.display(value, "datetime")?; + self.0.display(value, ArrayState::Started)?; Ok(()) } @@ -1528,13 +1530,13 @@ impl fmt::Display for Error { match *self { Error::UnsupportedType => "unsupported Rust type".fmt(f), Error::KeyNotString => "map key was not a string".fmt(f), - Error::ArrayMixedType => "arrays cannot have mixed types".fmt(f), Error::ValueAfterTable => "values must be emitted before tables".fmt(f), Error::DateInvalid => "a serialized date was invalid".fmt(f), Error::NumberInvalid => "a serialized number was invalid".fmt(f), Error::UnsupportedNone => "unsupported None value".fmt(f), Error::Custom(ref s) => s.fmt(f), Error::KeyNewline => unreachable!(), + Error::ArrayMixedType => unreachable!(), Error::__Nonexhaustive => panic!(), } } @@ -1545,13 +1547,13 @@ impl error::Error for Error { match *self { Error::UnsupportedType => "unsupported Rust type", Error::KeyNotString => "map key was not a string", - Error::ArrayMixedType => "arrays cannot have mixed types", Error::ValueAfterTable => "values must be emitted before tables", Error::DateInvalid => "a serialized date was invalid", Error::NumberInvalid => "a serialized number was invalid", Error::UnsupportedNone => "unsupported None value", Error::Custom(_) => "custom error", Error::KeyNewline => unreachable!(), + Error::ArrayMixedType => unreachable!(), Error::__Nonexhaustive => panic!(), } } diff --git a/test-suite/tests/invalid.rs b/test-suite/tests/invalid.rs index 3312629..ccc9338 100644 --- a/test-suite/tests/invalid.rs +++ b/test-suite/tests/invalid.rs @@ -14,21 +14,6 @@ macro_rules! test( ($name:ident, $s:expr, $msg:expr) => ( fn $name() { bad!($s, $msg); } ) ); -test!( - array_mixed_types_arrays_and_ints, - include_str!("invalid/array-mixed-types-arrays-and-ints.toml"), - "mixed types in an array at line 1 column 24" -); -test!( - array_mixed_types_ints_and_floats, - include_str!("invalid/array-mixed-types-ints-and-floats.toml"), - "mixed types in an array at line 1 column 23" -); -test!( - array_mixed_types_strings_and_ints, - include_str!("invalid/array-mixed-types-strings-and-ints.toml"), - "mixed types in an array at line 1 column 27" -); test!( datetime_malformed_no_leads, include_str!("invalid/datetime-malformed-no-leads.toml"), diff --git a/test-suite/tests/valid.rs b/test-suite/tests/valid.rs index e080958..0bb7caa 100644 --- a/test-suite/tests/valid.rs +++ b/test-suite/tests/valid.rs @@ -146,6 +146,21 @@ test!( include_str!("valid/arrays-nested.toml"), include_str!("valid/arrays-nested.json") ); +test!( + array_mixed_types_ints_and_floats, + include_str!("valid/array-mixed-types-ints-and-floats.toml"), + include_str!("valid/array-mixed-types-ints-and-floats.json") +); +test!( + array_mixed_types_arrays_and_ints, + include_str!("valid/array-mixed-types-arrays-and-ints.toml"), + include_str!("valid/array-mixed-types-arrays-and-ints.json") +); +test!( + array_mixed_types_strings_and_ints, + include_str!("valid/array-mixed-types-strings-and-ints.toml"), + include_str!("valid/array-mixed-types-strings-and-ints.json") +); test!( empty, include_str!("valid/empty.toml"), diff --git a/test-suite/tests/valid/array-mixed-types-arrays-and-ints.json b/test-suite/tests/valid/array-mixed-types-arrays-and-ints.json new file mode 100644 index 0000000..10074ec --- /dev/null +++ b/test-suite/tests/valid/array-mixed-types-arrays-and-ints.json @@ -0,0 +1,11 @@ +{ + "arrays-and-ints": { + "type": "array", + "value": [ + {"type": "integer", "value": "1"}, + {"type": "array", "value": [ + { "type": "string", "value":"Arrays are not integers."} + ]} + ] + } +} diff --git a/test-suite/tests/invalid/array-mixed-types-arrays-and-ints.toml b/test-suite/tests/valid/array-mixed-types-arrays-and-ints.toml similarity index 100% rename from test-suite/tests/invalid/array-mixed-types-arrays-and-ints.toml rename to test-suite/tests/valid/array-mixed-types-arrays-and-ints.toml diff --git a/test-suite/tests/valid/array-mixed-types-ints-and-floats.json b/test-suite/tests/valid/array-mixed-types-ints-and-floats.json new file mode 100644 index 0000000..c90665e --- /dev/null +++ b/test-suite/tests/valid/array-mixed-types-ints-and-floats.json @@ -0,0 +1,9 @@ +{ + "ints-and-floats": { + "type": "array", + "value": [ + {"type": "integer", "value": "1"}, + {"type": "float", "value": "1.1"} + ] + } +} diff --git a/test-suite/tests/invalid/array-mixed-types-ints-and-floats.toml b/test-suite/tests/valid/array-mixed-types-ints-and-floats.toml similarity index 100% rename from test-suite/tests/invalid/array-mixed-types-ints-and-floats.toml rename to test-suite/tests/valid/array-mixed-types-ints-and-floats.toml diff --git a/test-suite/tests/valid/array-mixed-types-strings-and-ints.json b/test-suite/tests/valid/array-mixed-types-strings-and-ints.json new file mode 100644 index 0000000..8ae322e --- /dev/null +++ b/test-suite/tests/valid/array-mixed-types-strings-and-ints.json @@ -0,0 +1,9 @@ +{ + "strings-and-ints": { + "type": "array", + "value": [ + {"type": "string", "value": "hi"}, + {"type": "integer", "value": "42"} + ] + } +} diff --git a/test-suite/tests/invalid/array-mixed-types-strings-and-ints.toml b/test-suite/tests/valid/array-mixed-types-strings-and-ints.toml similarity index 100% rename from test-suite/tests/invalid/array-mixed-types-strings-and-ints.toml rename to test-suite/tests/valid/array-mixed-types-strings-and-ints.toml diff --git a/test-suite/tests/valid/example-v0.4.0.toml b/test-suite/tests/valid/example-v0.4.0.toml index ffbcce0..69f1c1b 100644 --- a/test-suite/tests/valid/example-v0.4.0.toml +++ b/test-suite/tests/valid/example-v0.4.0.toml @@ -170,7 +170,8 @@ False = false ## Array # Arrays are square brackets with other primitives inside. Whitespace is -# ignored. Elements are separated by commas. Data types may not be mixed. +# ignored. Elements are separated by commas. Since 2019-11-06 data types can be +# mixed. [array]