Commit e0a04e7f67

daurnimator <quae@daurnimator.com>
2021-01-31 14:55:22
allow more complex comptime fields in std.json
1 parent f88bb56
Changed files (1)
lib
lib/std/json.zig
@@ -1374,6 +1374,65 @@ test "Value.jsonStringify" {
     }
 }
 
+/// parse tokens from a stream, returning `false` if they do not decode to `value`
+fn parsesTo(comptime T: type, value: T, tokens: *TokenStream, options: ParseOptions) !bool {
+    // TODO: should be able to write this function to not require an allocator
+    const tmp = try parse(T, tokens, options);
+    defer parseFree(T, tmp, options);
+
+    return parsedEqual(tmp, value);
+}
+
+/// Returns if a value returned by `parse` is deep-equal to another value
+fn parsedEqual(a: anytype, b: @TypeOf(a)) bool {
+    switch (@typeInfo(@TypeOf(a))) {
+        .Optional => {
+            if (a == null and b == null) return true;
+            if (a == null or b == null) return false;
+            return parsedEqual(a.?, b.?);
+        },
+        .Union => |unionInfo| {
+            if (info.tag_type) |UnionTag| {
+                const tag_a = std.meta.activeTag(a);
+                const tag_b = std.meta.activeTag(b);
+                if (tag_a != tag_b) return false;
+
+                inline for (info.fields) |field_info| {
+                    if (@field(UnionTag, field_info.name) == tag_a) {
+                        return parsedEqual(@field(a, field_info.name), @field(b, field_info.name));
+                    }
+                }
+                return false;
+            } else {
+                unreachable;
+            }
+        },
+        .Array => {
+            for (a) |e, i|
+                if (!parsedEqual(e, b[i])) return false;
+            return true;
+        },
+        .Struct => |info| {
+            inline for (info.fields) |field_info| {
+                if (!parsedEqual(@field(a, field_info.name), @field(b, field_info.name))) return false;
+            }
+            return true;
+        },
+        .Pointer => |ptrInfo| switch (ptrInfo.size) {
+            .One => return parsedEqual(a.*, b.*),
+            .Slice => {
+                if (a.len != b.len) return false;
+                for (a) |e, i|
+                    if (!parsedEqual(e, b[i])) return false;
+                return true;
+            },
+            .Many, .C => unreachable,
+        },
+        else => return a == b,
+    }
+    unreachable;
+}
+
 pub const ParseOptions = struct {
     allocator: ?*Allocator = null,
 
@@ -1507,9 +1566,7 @@ fn parseInternal(comptime T: type, token: Token, tokens: *TokenStream, options:
                                     }
                                 }
                                 if (field.is_comptime) {
-                                    const value = try parse(field.field_type, tokens, options);
-                                    defer parseFree(field.field_type, value, options);
-                                    if (value != @field(r, field.name)) {
+                                    if (!try parsesTo(field.field_type, field.default_value.?, tokens, options)) {
                                         return error.UnexpectedValue;
                                     }
                                 } else {
@@ -1809,16 +1866,40 @@ test "parseFree descends into tagged union" {
 }
 
 test "parse with comptime field" {
-    const T = struct {
-        comptime a: i32 = 0,
-        b: bool,
-    };
-    testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &TokenStream.init(
-        \\{
-        \\  "a": 0,
-        \\  "b": true
-        \\}
-    ), ParseOptions{}));
+    {
+        const T = struct {
+            comptime a: i32 = 0,
+            b: bool,
+        };
+        testing.expectEqual(T{ .a = 0, .b = true }, try parse(T, &TokenStream.init(
+            \\{
+            \\  "a": 0,
+            \\  "b": true
+            \\}
+        ), ParseOptions{}));
+    }
+
+    { // string comptime values currently require an allocator
+        const T = union(enum) {
+            foo: struct {
+                comptime kind: []const u8 = "boolean",
+                b: bool,
+            },
+            bar: struct {
+                comptime kind: []const u8 = "float",
+                b: f64,
+            },
+        };
+
+        const r = try std.json.parse(T, &std.json.TokenStream.init(
+            \\{
+            \\  "kind": "float",
+            \\  "b": 1.0
+            \\}
+        ), .{
+            .allocator = std.testing.allocator,
+        });
+    }
 }
 
 test "parse into struct with no fields" {