Skip to content

Commit ab5f90f

Browse files
committed
Pre-allocate capacity for maps in json! macro.
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.
1 parent 829175e commit ab5f90f

File tree

3 files changed

+124
-1
lines changed

3 files changed

+124
-1
lines changed

src/macros.rs

+108-1
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,112 @@ macro_rules! json_internal {
234234
json_internal!(@object $object ($($key)* $tt) ($($rest)*) ($($rest)*));
235235
};
236236

237+
238+
//////////////////////////////////////////////////////////////////////////
239+
// TT muncher for counting the elements inside of an object {...}.
240+
// Each entry is replaced with `1 + ` (or `1` if it is the last element).
241+
// 0 is inserted if the object has a trailing comma.
242+
//
243+
// Must be invoked as: json_internal!(@object_capacity () ($($tt)*) ($($tt)*))
244+
//
245+
// We require two copies of the input tokens so that we can match on one
246+
// copy and trigger errors on the other copy.
247+
//////////////////////////////////////////////////////////////////////////
248+
249+
// Done.
250+
(@object_capacity () () ()) => {0};
251+
252+
// Current entry followed by trailing comma.
253+
(@object_capacity entry , $($rest:tt)*) => {
254+
1 + json_internal!(@object_capacity () ($($rest)*) ($($rest)*))
255+
};
256+
257+
// Current entry followed by unexpected token.
258+
(@object_capacity entry $unexpected:tt $($rest:tt)*) => {
259+
json_unexpected!($unexpected)
260+
};
261+
262+
// Insert the last entry without trailing comma.
263+
(@object_capacity entry) => {
264+
1
265+
};
266+
267+
// Next value is `null`.
268+
(@object_capacity ($($key:tt)+) (: null $($rest:tt)*) $copy:tt) => {
269+
json_internal!(@object_capacity entry $($rest)*)
270+
};
271+
272+
// Next value is `true`.
273+
(@object_capacity ($($key:tt)+) (: true $($rest:tt)*) $copy:tt) => {
274+
json_internal!(@object_capacity entry $($rest)*)
275+
};
276+
277+
// Next value is `false`.
278+
(@object_capacity ($($key:tt)+) (: false $($rest:tt)*) $copy:tt) => {
279+
json_internal!(@object_capacity entry $($rest)*)
280+
};
281+
282+
// Next value is an array.
283+
(@object_capacity ($($key:tt)+) (: [$($array:tt)*] $($rest:tt)*) $copy:tt) => {
284+
json_internal!(@object_capacity entry $($rest)*)
285+
};
286+
287+
// Next value is a map.
288+
(@object_capacity ($($key:tt)+) (: {$($map:tt)*} $($rest:tt)*) $copy:tt) => {
289+
json_internal!(@object_capacity entry $($rest)*)
290+
};
291+
292+
// Next value is an expression followed by comma.
293+
(@object_capacity ($($key:tt)+) (: $value:expr , $($rest:tt)*) $copy:tt) => {
294+
json_internal!(@object_capacity entry , $($rest)*)
295+
};
296+
297+
// Last value is an expression with no trailing comma.
298+
(@object_capacity ($($key:tt)+) (: $value:expr) $copy:tt) => {
299+
json_internal!(@object_capacity entry)
300+
};
301+
302+
// Missing value for last entry. Trigger a reasonable error message.
303+
(@object_capacity ($($key:tt)+) (:) $copy:tt) => {
304+
// "unexpected end of macro invocation"
305+
json_internal!()
306+
};
307+
308+
// Missing colon and value for last entry. Trigger a reasonable error
309+
// message.
310+
(@object_capacity ($($key:tt)+) () $copy:tt) => {
311+
// "unexpected end of macro invocation"
312+
json_internal!()
313+
};
314+
315+
// Misplaced colon. Trigger a reasonable error message.
316+
(@object_capacity () (: $($rest:tt)*) ($colon:tt $($copy:tt)*)) => {
317+
// Takes no arguments so "no rules expected the token `:`".
318+
json_unexpected!($colon)
319+
};
320+
321+
// Found a comma inside a key. Trigger a reasonable error message.
322+
(@object_capacity ($($key:tt)*) (, $($rest:tt)*) ($comma:tt $($copy:tt)*)) => {
323+
// Takes no arguments so "no rules expected the token `,`".
324+
json_unexpected!($comma)
325+
};
326+
327+
// Key is fully parenthesized. This is not necessary for counting capacity
328+
// since we don't evaluate $key anyway, so just use the munching below.
329+
// (@object_capacity () (($key:expr) : $($rest:tt)*) $copy:tt) => {
330+
// json_internal!(@object_capacity ($key) (: $($rest)*) (: $($rest)*))
331+
// };
332+
333+
// Refuse to absorb colon token into key expression.
334+
(@object_capacity ($($key:tt)*) (: $($unexpected:tt)+) $copy:tt) => {
335+
json_expect_expr_comma!($($unexpected)+)
336+
};
337+
338+
// Munch a token into the current key.
339+
(@object_capacity ($($key:tt)*) ($tt:tt $($rest:tt)*) $copy:tt) => {
340+
json_internal!(@object_capacity ($($key)* $tt) ($($rest)*) ($($rest)*))
341+
};
342+
237343
//////////////////////////////////////////////////////////////////////////
238344
// The main implementation.
239345
//
@@ -266,7 +372,8 @@ macro_rules! json_internal {
266372

267373
({ $($tt:tt)+ }) => {
268374
$crate::Value::Object({
269-
let mut object = $crate::Map::new();
375+
let capacity = json_internal!(@object_capacity () ($($tt)+) ($($tt)+));
376+
let mut object = $crate::Map::with_capacity(capacity);
270377
json_internal!(@object object () ($($tt)+) ($($tt)+));
271378
object
272379
})

tests/ui/missing_colon.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,11 @@ error: unexpected end of macro invocation
55
| ^^^^^^^^^^^^^^ missing tokens in macro arguments
66
|
77
= note: this error originates in the macro `json_internal` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
error: unexpected end of macro invocation
10+
--> tests/ui/missing_colon.rs:4:5
11+
|
12+
4 | json!({ "a" });
13+
| ^^^^^^^^^^^^^^ missing tokens in macro arguments
14+
|
15+
= note: this error originates in the macro `json_internal` (in Nightly builds, run with -Z macro-backtrace for more info)

tests/ui/missing_value.stderr

+8
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,11 @@ error: unexpected end of macro invocation
55
| ^^^^^^^^^^^^^^^^ missing tokens in macro arguments
66
|
77
= note: this error originates in the macro `json_internal` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
error: unexpected end of macro invocation
10+
--> tests/ui/missing_value.rs:4:5
11+
|
12+
4 | json!({ "a" : });
13+
| ^^^^^^^^^^^^^^^^ missing tokens in macro arguments
14+
|
15+
= note: this error originates in the macro `json_internal` (in Nightly builds, run with -Z macro-backtrace for more info)

0 commit comments

Comments
 (0)