schemagen: fix nullable root types (#13)
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 52s
CI / pre-commit (pull_request) Successful in 54s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 47s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m35s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m27s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-arm64, ubuntu-latest-arm64, true) (pull_request) Successful in 52s
CI / pre-commit (pull_request) Successful in 54s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-arm64, ubuntu-latest-arm64, false) (pull_request) Successful in 47s
CI / build (-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++, clang-amd64, ubuntu-latest-amd64, true) (pull_request) Successful in 1m35s
CI / build (-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++, gcc-amd64, ubuntu-latest-amd64, false) (pull_request) Successful in 1m27s
The C++ code generator now handles schemas where the top-level type is
nullable ("type": ["object", "null"], ["string", "null"], or
["array", "null"]).
Changes to weaseljson_schemagen.py:
- Rename the inner object struct when the root is a nullable object, so
the `using Root = std::optional<...>` alias no longer conflicts with
`struct Root`.
- Treat nullable root objects and arrays as container roots, emplacing
the inner value before pushing the root frame and pointing the frame at
the contained value.
- For nullable root scalars/enums, engage() now returns a pointer to the
value inside the optional rather than to the optional wrapper itself.
- cbNull() now safely accepts a top-level null when the root is nullable
and rejects it otherwise.
Regression tests added:
- nullable_object.schema.json + test_nullable_root.cpp
- nullable_string.schema.json
- nullable_array.schema.json
Closes #13
This commit is contained in:
@@ -493,6 +493,18 @@ class Emitter:
|
||||
self.root_ty, self.root_nullable = Builder._unpack(
|
||||
self.b.build_type(self.b.root_schema, "Root")
|
||||
)
|
||||
|
||||
# A nullable root object would otherwise produce
|
||||
# using Root = std::optional<Root>;
|
||||
# which conflicts with the struct named Root. Rename the inner struct.
|
||||
if isinstance(self.root_ty, TObj) and self.root_nullable:
|
||||
old_name = self.root_ty.name
|
||||
new_name = self.b.unique_name("RootInner")
|
||||
obj = self.b.objects.pop(old_name)
|
||||
obj.name = new_name
|
||||
self.b.objects[new_name] = obj
|
||||
self.root_ty = TObj(new_name)
|
||||
|
||||
break_cycles(self.b.objects)
|
||||
|
||||
# register all array kinds (walk every field + root)
|
||||
@@ -582,9 +594,7 @@ namespace {ns} {{"""
|
||||
def _kind_enum(self):
|
||||
kinds = list(self.b.objects.keys())
|
||||
kinds += [n for n, _ in self.arr_types]
|
||||
root_is_container = (
|
||||
isinstance(self.root_ty, (TObj, TArr)) and not self.root_nullable
|
||||
)
|
||||
root_is_container = isinstance(self.root_ty, (TObj, TArr))
|
||||
if not root_is_container:
|
||||
kinds.append("RootScalar")
|
||||
kinds.append("Skip")
|
||||
@@ -593,9 +603,9 @@ namespace {ns} {{"""
|
||||
|
||||
def _root_info(self):
|
||||
"""Return (root_cat, root_container_kind_or_None)."""
|
||||
if isinstance(self.root_ty, TObj) and not self.root_nullable:
|
||||
if isinstance(self.root_ty, TObj):
|
||||
return ("Obj", f"Kind::{self.root_ty.name}")
|
||||
if isinstance(self.root_ty, TArr) and not self.root_nullable:
|
||||
if isinstance(self.root_ty, TArr):
|
||||
return ("Arr", f"Kind::{self.arr_kind(self.root_ty)}")
|
||||
return (self.cat(self.root_ty), None)
|
||||
|
||||
@@ -665,7 +675,12 @@ namespace {ns} {{"""
|
||||
lines.append(" }")
|
||||
root_cat, root_kind = self._root_info()
|
||||
if root_kind is None:
|
||||
lines.append(" case Kind::RootScalar: return &result_;")
|
||||
if self.root_nullable:
|
||||
lines.append(
|
||||
" case Kind::RootScalar: { if (!result_) result_.emplace(); return &*result_; }"
|
||||
)
|
||||
else:
|
||||
lines.append(" case Kind::RootScalar: return &result_;")
|
||||
lines.append(" default: return nullptr;")
|
||||
lines.append(" }")
|
||||
lines.append(" }")
|
||||
@@ -807,7 +822,12 @@ namespace {ns} {{"""
|
||||
lines = [" if (error_) return;"]
|
||||
if root_kind is not None and root_cat == event_cat:
|
||||
lines.append(" if (stack_.empty()) {")
|
||||
lines.append(f" stack_.push_back(Frame{{{root_kind}, &result_}});")
|
||||
if self.root_nullable:
|
||||
lines.append(" result_.emplace();")
|
||||
dest = "&*result_"
|
||||
else:
|
||||
dest = "&result_"
|
||||
lines.append(f" stack_.push_back(Frame{{{root_kind}, {dest}}});")
|
||||
if event_cat == "Obj":
|
||||
lines.append(" {")
|
||||
lines.append(f" int n = fieldCount({root_kind});")
|
||||
@@ -839,6 +859,9 @@ namespace {ns} {{"""
|
||||
root_cat, root_kind = self._root_info()
|
||||
begin_obj = self._begin_container("Obj", root_kind)
|
||||
begin_arr = self._begin_container("Arr", root_kind)
|
||||
null_at_root = (
|
||||
"done_ = true; return;" if self.root_nullable else "reject(); return;"
|
||||
)
|
||||
|
||||
return f"""
|
||||
class RootBuilder {{
|
||||
@@ -1065,6 +1088,9 @@ private:
|
||||
|
||||
void cbNull() {{
|
||||
if (error_) return;
|
||||
if (stack_.empty()) {{
|
||||
{null_at_root}
|
||||
}}
|
||||
Frame &f = stack_.back();
|
||||
if (f.kind == Kind::Skip) return;
|
||||
SlotInfo si = slotInfoG(f);
|
||||
|
||||
Reference in New Issue
Block a user