diff --git a/fuzz/fuzz_targets/bench/lib.rs b/fuzz/fuzz_targets/bench/lib.rs index d8ed9104d..ef10b77b5 100644 --- a/fuzz/fuzz_targets/bench/lib.rs +++ b/fuzz/fuzz_targets/bench/lib.rs @@ -134,6 +134,9 @@ struct ArbitraryPrettyConfig { compact_maps: bool, /// Enable explicit number type suffixes like `1u16` number_suffixes: bool, + /// Use braced struct syntax like `Person { age: 42 }` instead of the + /// parenthesised syntax `Person(age: 42)` or just `(age: 42)` + braced_structs: bool, } fn arbitrary_ron_extensions(u: &mut Unstructured) -> arbitrary::Result { @@ -154,6 +157,7 @@ impl From for PrettyConfig { .compact_structs(arbitrary.compact_structs) .compact_maps(arbitrary.compact_maps) .number_suffixes(arbitrary.number_suffixes) + .braced_structs(arbitrary.braced_structs) } } diff --git a/src/de/mod.rs b/src/de/mod.rs index 6028236b6..a1c4070c2 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -184,6 +184,7 @@ impl<'de> Deserializer<'de> { } else { TupleMode::ImpreciseTupleOrNewtype // Tuple and NewtypeOrTuple match equally }, + ident.is_some(), )?, ident, ) { @@ -201,7 +202,11 @@ impl<'de> Deserializer<'de> { } (StructType::Named, _) => { // giving no name results in worse errors but is necessary here - self.handle_struct_after_name("", visitor) + self.handle_struct_after_name(ident.is_some(), "", visitor) + } + (StructType::BracedNamed, _) => { + // giving no name results in worse errors but is necessary here + self.handle_struct_after_name(true, "", visitor) } (StructType::NewtypeTuple, _) if old_serde_content_newtype => { // deserialize a newtype struct or variant @@ -235,19 +240,29 @@ impl<'de> Deserializer<'de> { /// This method assumes there is no struct name identifier left. fn handle_struct_after_name( &mut self, + struct_name_was_present: bool, name_for_pretty_errors_only: &'static str, visitor: V, ) -> Result where V: Visitor<'de>, { - if self.newtype_variant || self.parser.consume_char('(') { + let open_brace = self.parser.check_char('{'); + + if self.newtype_variant + || self.parser.consume_char('(') + || (struct_name_was_present && self.parser.consume_char('{')) + { let old_newtype_variant = self.newtype_variant; self.newtype_variant = false; let value = guard_recursion! { self => visitor - .visit_map(CommaSeparated::new(Terminator::Struct, self)) + .visit_map(CommaSeparated::new(if open_brace { + Terminator::BracedStruct + } else { + Terminator::Struct + }, self)) .map_err(|err| { struct_error_name( err, @@ -262,7 +277,13 @@ impl<'de> Deserializer<'de> { self.parser.skip_ws()?; - if old_newtype_variant || self.parser.consume_char(')') { + if old_newtype_variant + || (if open_brace { + self.parser.consume_char('}') + } else { + self.parser.consume_char(')') + }) + { Ok(value) } else { Err(Error::ExpectedStructLikeEnd) @@ -289,14 +310,20 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { } #[allow(clippy::wildcard_in_or_patterns)] - match self - .parser - .check_struct_type(NewtypeMode::InsideNewtype, TupleMode::DifferentiateNewtype)? - { + match self.parser.check_struct_type( + NewtypeMode::InsideNewtype, + TupleMode::DifferentiateNewtype, + false, + )? { + StructType::BracedNamed => { + let _ident = self.parser.identifier()?; + self.parser.skip_ws()?; + return self.handle_struct_after_name(true, "", visitor); + } StructType::Named => { // newtype variant wraps a named struct // giving no name results in worse errors but is necessary here - return self.handle_struct_after_name("", visitor); + return self.handle_struct_after_name(false, "", visitor); } StructType::EmptyTuple | StructType::NonNewtypeTuple => { // newtype variant wraps a tuple (struct) @@ -719,13 +746,21 @@ impl<'de, 'a> de::Deserializer<'de> for &'a mut Deserializer<'de> { where V: Visitor<'de>, { - if !self.newtype_variant { - self.parser.consume_struct_name(name)?; - } + let struct_name_was_present = if self.newtype_variant { + if self.parser.check_braced_identifier()?.is_some() { + // braced structs disable newtype variants + self.newtype_variant = false; + self.parser.consume_struct_name(name)? + } else { + false + } + } else { + self.parser.consume_struct_name(name)? + }; self.parser.skip_ws()?; - self.handle_struct_after_name(name, visitor) + self.handle_struct_after_name(struct_name_was_present, name, visitor) } fn deserialize_enum( @@ -778,13 +813,14 @@ enum Terminator { MapAsStruct, Tuple, Struct, + BracedStruct, Seq, } impl Terminator { fn as_char(&self) -> char { match self { - Terminator::Map | Terminator::MapAsStruct => '}', + Terminator::Map | Terminator::MapAsStruct | Terminator::BracedStruct => '}', Terminator::Tuple | Terminator::Struct => ')', Terminator::Seq => ']', } @@ -856,7 +892,7 @@ impl<'de, 'a> de::MapAccess<'de> for CommaSeparated<'a, 'de> { std::any::type_name::() == SERDE_TAG_KEY_CANARY; match self.terminator { - Terminator::Struct => guard_recursion! { self.de => + Terminator::Struct | Terminator::BracedStruct => guard_recursion! { self.de => seed.deserialize(&mut id::Deserializer::new(&mut *self.de, false)).map(Some) }, Terminator::MapAsStruct => guard_recursion! { self.de => @@ -987,7 +1023,7 @@ impl<'de, 'a> de::VariantAccess<'de> for Enum<'a, 'de> { self.de.parser.skip_ws()?; self.de - .handle_struct_after_name("", visitor) + .handle_struct_after_name(true, "", visitor) .map_err(|err| struct_error_name(err, struct_variant)) } } diff --git a/src/parse.rs b/src/parse.rs index b1d4f8831..defeb4c01 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -548,13 +548,20 @@ impl<'a> Parser<'a> { &mut self, newtype: NewtypeMode, tuple: TupleMode, + has_name: bool, ) -> Result { fn check_struct_type_inner( parser: &mut Parser, newtype: NewtypeMode, tuple: TupleMode, + has_name: bool, ) -> Result { - if matches!(newtype, NewtypeMode::NoParensMeanUnit) && !parser.consume_char('(') { + let open_brace = parser.check_char('{'); + + if matches!(newtype, NewtypeMode::NoParensMeanUnit) + && !parser.consume_char('(') + && !(has_name && parser.consume_char('{')) + { return Ok(StructType::Unit); } @@ -567,12 +574,31 @@ impl<'a> Parser<'a> { return Ok(StructType::EmptyTuple); } + // Check for `Ident {}` which is a braced struct + if matches!(newtype, NewtypeMode::NoParensMeanUnit) + && has_name + && open_brace + && parser.check_char('}') + { + return Ok(StructType::BracedNamed); + } + if parser.skip_identifier().is_some() { parser.skip_ws()?; match parser.peek_char() { // Definitely a struct with named fields - Some(':') => return Ok(StructType::Named), + Some(':') => { + return if has_name && open_brace { + Ok(StructType::BracedNamed) + } else { + Ok(StructType::Named) + } + } + // Definitely a braced struct inside a newtype + Some('{') if matches!(newtype, NewtypeMode::InsideNewtype) => { + return Ok(StructType::BracedNamed) + } // Definitely a tuple-like struct with fields Some(',') => { parser.skip_next_char(); @@ -644,7 +670,7 @@ impl<'a> Parser<'a> { // Create a temporary working copy let backup_cursor = self.cursor; - let result = check_struct_type_inner(self, newtype, tuple); + let result = check_struct_type_inner(self, newtype, tuple, has_name); if result.is_ok() { // Revert the parser to before the struct type check @@ -880,6 +906,28 @@ impl<'a> Parser<'a> { None } + pub fn check_braced_identifier(&mut self) -> Result> { + // Create a temporary working copy + let backup_cursor = self.cursor; + + let ident = if let Some(ident) = self.skip_identifier() { + self.skip_ws()?; + + if self.peek_char() == Some('{') { + Some(ident) + } else { + None + } + } else { + None + }; + + // Revert the parser to before the peek + self.set_cursor(backup_cursor); + + Ok(ident) + } + pub fn identifier(&mut self) -> Result<&'a str> { let first = self.peek_char_or_eof()?; if !is_ident_first_char(first) { @@ -1589,6 +1637,7 @@ pub enum StructType { NewtypeTuple, NonNewtypeTuple, Named, + BracedNamed, Unit, } diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 1d3b6c111..e5aaf10a2 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -113,6 +113,9 @@ pub struct PrettyConfig { pub number_suffixes: bool, /// Additional path-based field metadata to serialize pub path_meta: Option, + /// Use braced struct syntax like `Person { age: 42 }` instead of the + /// parenthesised syntax `Person(age: 42)` or just `(age: 42)` + pub braced_structs: bool, } impl PrettyConfig { @@ -341,6 +344,36 @@ impl PrettyConfig { self } + + /// Configures whether structs or struct variants with named fields should + /// be printed using braced syntax (`true`) or parenthesised syntax + /// (`false`). + /// + /// When `false`, the struct `Person { age: 42 }` will serialize to + /// ```ignore + /// Person(age: 42) + /// # ; + /// ``` + /// if printing struct names is turned on, or + /// ```ignore + /// (age: 42) + /// # ; + /// ``` + /// if struct names are turned off. + /// + /// When `true`, the struct `Person { age: 42 }` will serialize to + /// ```ignore + /// Person {age: 42} + /// # ; + /// ``` + /// + /// Default: `false` + #[must_use] + pub fn braced_structs(mut self, braced_structs: bool) -> Self { + self.braced_structs = braced_structs; + + self + } } impl Default for PrettyConfig { @@ -364,6 +397,7 @@ impl Default for PrettyConfig { compact_maps: false, number_suffixes: false, path_meta: None, + braced_structs: false, } } } @@ -475,6 +509,12 @@ impl Serializer { .map_or(false, |(ref config, _)| config.number_suffixes) } + fn braced_structs(&self) -> bool { + self.pretty + .as_ref() + .map_or(false, |(ref config, _)| config.braced_structs) + } + fn extensions(&self) -> Extensions { self.default_extensions | self @@ -956,7 +996,7 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { self.start_indent()?; } - Ok(Compound::new(self, false)) + Ok(Compound::new(self, Some(']'))) } fn serialize_tuple(self, len: usize) -> Result { @@ -964,9 +1004,12 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { self.newtype_variant = false; self.implicit_some_depth = 0; - if !old_newtype_variant { + let closing = if old_newtype_variant { + None + } else { self.output.write_char('(')?; - } + Some(')') + }; if self.separate_tuple_members() { self.is_empty = Some(len == 0); @@ -974,7 +1017,7 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { self.start_indent()?; } - Ok(Compound::new(self, old_newtype_variant)) + Ok(Compound::new(self, closing)) } fn serialize_tuple_struct( @@ -1011,7 +1054,7 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { self.start_indent()?; } - Ok(Compound::new(self, false)) + Ok(Compound::new(self, Some(')'))) } fn serialize_map(self, len: Option) -> Result { @@ -1028,7 +1071,7 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { self.start_indent()?; } - Ok(Compound::new(self, false)) + Ok(Compound::new(self, Some('}'))) } fn serialize_struct(self, name: &'static str, len: usize) -> Result { @@ -1036,8 +1079,13 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { self.newtype_variant = false; self.implicit_some_depth = 0; - if old_newtype_variant { + let closing = if self.braced_structs() { + self.write_identifier(name)?; + self.output.write_str(" {")?; + Some('}') + } else if old_newtype_variant { self.validate_identifier(name)?; + None } else { if self.struct_names() { self.write_identifier(name)?; @@ -1045,14 +1093,15 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { self.validate_identifier(name)?; } self.output.write_char('(')?; - } + Some(')') + }; if !self.compact_structs() { self.is_empty = Some(len == 0); self.start_indent()?; } - Ok(Compound::new(self, old_newtype_variant)) + Ok(Compound::new(self, closing)) } fn serialize_struct_variant( @@ -1067,14 +1116,21 @@ impl<'a, W: fmt::Write> ser::Serializer for &'a mut Serializer { self.validate_identifier(name)?; self.write_identifier(variant)?; - self.output.write_char('(')?; + + let closing = if self.braced_structs() { + self.output.write_str(" {")?; + '}' + } else { + self.output.write_char('(')?; + ')' + }; if !self.compact_structs() { self.is_empty = Some(len == 0); self.start_indent()?; } - Ok(Compound::new(self, false)) + Ok(Compound::new(self, Some(closing))) } } @@ -1087,16 +1143,16 @@ enum State { pub struct Compound<'a, W: fmt::Write> { ser: &'a mut Serializer, state: State, - newtype_variant: bool, + closing: Option, sequence_index: usize, } impl<'a, W: fmt::Write> Compound<'a, W> { - fn new(ser: &'a mut Serializer, newtype_variant: bool) -> Self { + fn new(ser: &'a mut Serializer, closing: Option) -> Self { Compound { ser, state: State::First, - newtype_variant, + closing, sequence_index: 0, } } @@ -1161,8 +1217,10 @@ impl<'a, W: fmt::Write> ser::SerializeSeq for Compound<'a, W> { self.ser.end_indent()?; } - // seq always disables `self.newtype_variant` - self.ser.output.write_char(']')?; + if let Some(closing) = self.closing { + self.ser.output.write_char(closing)?; + } + Ok(()) } } @@ -1210,8 +1268,8 @@ impl<'a, W: fmt::Write> ser::SerializeTuple for Compound<'a, W> { self.ser.end_indent()?; } - if !self.newtype_variant { - self.ser.output.write_char(')')?; + if let Some(closing) = self.closing { + self.ser.output.write_char(closing)?; } Ok(()) @@ -1309,8 +1367,10 @@ impl<'a, W: fmt::Write> ser::SerializeMap for Compound<'a, W> { self.ser.end_indent()?; } - // map always disables `self.newtype_variant` - self.ser.output.write_char('}')?; + if let Some(closing) = self.closing { + self.ser.output.write_char(closing)?; + } + Ok(()) } } @@ -1399,8 +1459,8 @@ impl<'a, W: fmt::Write> ser::SerializeStruct for Compound<'a, W> { self.ser.end_indent()?; } - if !self.newtype_variant { - self.ser.output.write_char(')')?; + if let Some(closing) = self.closing { + self.ser.output.write_char(closing)?; } Ok(()) diff --git a/tests/551_braced_struct.rs b/tests/551_braced_struct.rs new file mode 100644 index 000000000..3a3257a93 --- /dev/null +++ b/tests/551_braced_struct.rs @@ -0,0 +1,9 @@ +#[test] +fn raw_value() { + let _: Vec> = ron::from_str( + r#" +[abc, 922e37, Value, [[]], None, (a: 7), {a: 7}, Person { age: 42 }] +"#, + ) + .unwrap(); +}