Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PrettyConfig::braced_structs #551

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions fuzz/fuzz_targets/bench/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Extensions> {
Expand All @@ -154,6 +157,7 @@ impl From<ArbitraryPrettyConfig> for PrettyConfig {
.compact_structs(arbitrary.compact_structs)
.compact_maps(arbitrary.compact_maps)
.number_suffixes(arbitrary.number_suffixes)
.braced_structs(arbitrary.braced_structs)
}
}

Expand Down
68 changes: 52 additions & 16 deletions src/de/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ impl<'de> Deserializer<'de> {
} else {
TupleMode::ImpreciseTupleOrNewtype // Tuple and NewtypeOrTuple match equally
},
ident.is_some(),
)?,
ident,
) {
Expand All @@ -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
Expand Down Expand Up @@ -235,19 +240,29 @@ impl<'de> Deserializer<'de> {
/// This method assumes there is no struct name identifier left.
fn handle_struct_after_name<V>(
&mut self,
struct_name_was_present: bool,
name_for_pretty_errors_only: &'static str,
visitor: V,
) -> Result<V::Value>
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,
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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<V>(
Expand Down Expand Up @@ -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 => ']',
}
Expand Down Expand Up @@ -856,7 +892,7 @@ impl<'de, 'a> de::MapAccess<'de> for CommaSeparated<'a, 'de> {
std::any::type_name::<K::Value>() == 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 =>
Expand Down Expand Up @@ -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))
}
}
Expand Down
55 changes: 52 additions & 3 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,13 +548,20 @@ impl<'a> Parser<'a> {
&mut self,
newtype: NewtypeMode,
tuple: TupleMode,
has_name: bool,
) -> Result<StructType> {
fn check_struct_type_inner(
parser: &mut Parser,
newtype: NewtypeMode,
tuple: TupleMode,
has_name: bool,
) -> Result<StructType> {
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);
}

Expand All @@ -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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -880,6 +906,28 @@ impl<'a> Parser<'a> {
None
}

pub fn check_braced_identifier(&mut self) -> Result<Option<&'a str>> {
// 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) {
Expand Down Expand Up @@ -1589,6 +1637,7 @@ pub enum StructType {
NewtypeTuple,
NonNewtypeTuple,
Named,
BracedNamed,
Unit,
}

Expand Down
Loading
Loading