Skip to content

Commit

Permalink
Pre-allocate capacity for maps in json! macro.
Browse files Browse the repository at this point in the history
Attempt to address issue serde-rs#810.

This implementation works by expanding the contents of the map twice,
first to produce a capacity value, then actually inserting the elements.

Because it expands the contents of the map twice, when encountering invalid syntax,
it may print error messages twice, which may be unwanted.
  • Loading branch information
zachs18 committed Apr 5, 2022
1 parent 829175e commit ab5f90f
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 1 deletion.
109 changes: 108 additions & 1 deletion src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,112 @@ macro_rules! json_internal {
json_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
};


//////////////////////////////////////////////////////////////////////////
// TT muncher for counting the elements inside of an object {...}.
// Each entry is replaced with `1 + ` (or `1` if it is the last element).
// 0 is inserted if the object has a trailing comma.
//
// Must be invoked as: json_internal!(@object_capacity () ($($tt)*) ($($tt)*))
//
// We require two copies of the input tokens so that we can match on one
// copy and trigger errors on the other copy.
//////////////////////////////////////////////////////////////////////////

// Done.
(@object_capacity () () ()) => {0};

// Current entry followed by trailing comma.
(@object_capacity entry , $($rest:tt)*) => {
1 + json_internal!(@object_capacity () ($($rest)*) ($($rest)*))
};

// Current entry followed by unexpected token.
(@object_capacity entry $unexpected:tt $($rest:tt)*) => {
json_unexpected!($unexpected)
};

// Insert the last entry without trailing comma.
(@object_capacity entry) => {
1
};

// Next value is `null`.
(@object_capacity ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
json_internal!(@object_capacity entry $($rest)*)
};

// Next value is `true`.
(@object_capacity ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
json_internal!(@object_capacity entry $($rest)*)
};

// Next value is `false`.
(@object_capacity ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
json_internal!(@object_capacity entry $($rest)*)
};

// Next value is an array.
(@object_capacity ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
json_internal!(@object_capacity entry $($rest)*)
};

// Next value is a map.
(@object_capacity ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
json_internal!(@object_capacity entry $($rest)*)
};

// Next value is an expression followed by comma.
(@object_capacity ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
json_internal!(@object_capacity entry , $($rest)*)
};

// Last value is an expression with no trailing comma.
(@object_capacity ($($key:tt)+) (: $value:expr) $copy:tt) => {
json_internal!(@object_capacity entry)
};

// Missing value for last entry. Trigger a reasonable error message.
(@object_capacity ($($key:tt)+) (:) $copy:tt) => {
// "unexpected end of macro invocation"
json_internal!()
};

// Missing colon and value for last entry. Trigger a reasonable error
// message.
(@object_capacity ($($key:tt)+) () $copy:tt) => {
// "unexpected end of macro invocation"
json_internal!()
};

// Misplaced colon. Trigger a reasonable error message.
(@object_capacity () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `:`".
json_unexpected!($colon)
};

// Found a comma inside a key. Trigger a reasonable error message.
(@object_capacity ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
// Takes no arguments so "no rules expected the token `,`".
json_unexpected!($comma)
};

// Key is fully parenthesized. This is not necessary for counting capacity
// since we don't evaluate $key anyway, so just use the munching below.
// (@object_capacity () (($key:expr) : $($rest:tt)*) $copy:tt) => {
// json_internal!(@object_capacity ($key) (: $($rest)*) (: $($rest)*))
// };

// Refuse to absorb colon token into key expression.
(@object_capacity ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
json_expect_expr_comma!($($unexpected)+)
};

// Munch a token into the current key.
(@object_capacity ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
json_internal!(@object_capacity ($($key)* $tt) ($($rest)*) ($($rest)*))
};

//////////////////////////////////////////////////////////////////////////
// The main implementation.
//
Expand Down Expand Up @@ -266,7 +372,8 @@ macro_rules! json_internal {

({ $($tt:tt)+ }) => {
$crate::Value::Object({
let mut object = $crate::Map::new();
let capacity = json_internal!(@object_capacity () ($($tt)+) ($($tt)+));
let mut object = $crate::Map::with_capacity(capacity);
json_internal!(@object object () ($($tt)+) ($($tt)+));
object
})
Expand Down
8 changes: 8 additions & 0 deletions tests/ui/missing_colon.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ error: unexpected end of macro invocation
| ^^^^^^^^^^^^^^ missing tokens in macro arguments
|
= note: this error originates in the macro `json_internal` (in Nightly builds, run with -Z macro-backtrace for more info)

error: unexpected end of macro invocation
--> tests/ui/missing_colon.rs:4:5
|
4 | json!({ "a" });
| ^^^^^^^^^^^^^^ missing tokens in macro arguments
|
= note: this error originates in the macro `json_internal` (in Nightly builds, run with -Z macro-backtrace for more info)
8 changes: 8 additions & 0 deletions tests/ui/missing_value.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ error: unexpected end of macro invocation
| ^^^^^^^^^^^^^^^^ missing tokens in macro arguments
|
= note: this error originates in the macro `json_internal` (in Nightly builds, run with -Z macro-backtrace for more info)

error: unexpected end of macro invocation
--> tests/ui/missing_value.rs:4:5
|
4 | json!({ "a" : });
| ^^^^^^^^^^^^^^^^ missing tokens in macro arguments
|
= note: this error originates in the macro `json_internal` (in Nightly builds, run with -Z macro-backtrace for more info)

0 comments on commit ab5f90f

Please sign in to comment.