fix(cplusplus): forward-declare recursive unions instead of self-referential aliases#3046
Open
schani wants to merge 5 commits into
Open
fix(cplusplus): forward-declare recursive unions instead of self-referential aliases#3046schani wants to merge 5 commits into
schani wants to merge 5 commits into
Conversation
…rential aliases (#273) Recursive unions reachable through a container (e.g. a union that contains itself via std::vector) were emitted as a plain `using X = std::variant<..., X, ...>;` alias, which g++ rejects because X is not in scope on its own right-hand side. Such unions are now emitted as a forward-declared struct that inherits from the variant, matching how recursive classes are already handled. Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Generated-output differences5 files differ — 4 modified, 1 new, 0 deleted |
Member
Author
|
This is odd. It includes |
Generated-output differences5 files differ — 4 modified, 1 new, 0 deleted |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Issue #273 tracks "Detect and properly handle recursive types" across
quicktype's backends. The concrete, still-reproducing report (2021 comment,
confirmed on current master) is in the C++ backend: a recursive union that
refers back to itself through a container (e.g.
std::vector) was emittedas a plain type alias:
using RecursiveThing = std::variant<std::vector<RecursiveThing>, std::string>;This does not compile with
g++ -std=c++17:because a
usingalias's name is not in scope on its own right-hand side —unlike a
struct/class, whose name is visible immediately after itsopening brace.
Root cause
CPlusPlusRenderer.isImplicitCycleBreakertreatedarray/mapcontainertypes as unconditional cycle breakers, so a union reachable only through a
container was never flagged as needing forward declaration/heap indirection
the way recursive classes already are.
emitUnionTypedefsthen alwaysemitted named unions as a bare
usingalias, which is only valid when theunion's own name doesn't appear inside its own definition.
The fix
array/map) is now only treated as an implicit cyclebreaker when none of its children are (or contain) a union — since
breaking the cycle for a union alias doesn't actually work.
isRecursiveUnionto detect a named union that reaches itselfthrough its members.
emitUnionTypedefsnow emits such recursive unions as a forward-declaredstruct X : std::variant<...>wrapper (with inherited constructors/assignment) instead of an invalid self-referential
usingalias, mirroringhow recursive classes are already handled.
from_json/to_jsonfree-function forwarding for the wrapper structso (de)serialization still goes through the underlying variant's
adl_serializer.canBreakCyclesnow also recognizesUnionType(previously onlyClassType) as able to break declaration cycles.CPlusPlusRenderer.emitIncludes's<optional>inclusion to alsocover cases (
haveNamedUnions && !boost) that only arise via wrapperstructs.
Test coverage
test/inputs/schema/recursive-union-flattening.schemaalready exercisedthis exact bug shape and was listed in
CPlusPlusLanguage.skipSchemaintest/languages.tswith the comment "Recursive top-level unions producealiases that can refer to later aliases." That schema is now un-skipped and
serves as the regression test — it was verified to fail (bad/non-compiling
output) before the fix and to pass after.
Verification performed locally
npm run buildpasses.(
--lang c++ --code-format with-struct --namespace schema --src-lang schema --no-boost) against a minimal recursive schema; confirmed the invalidusingalias before the fix, and a compiling forward-declaredstructwrapper after. Compiled the generated header with
g++ -std=c++17andadditionally wrote a small round-trip driver confirming
parse→serialize produces the original JSON.
QUICKTEST=true FIXTURE=schema-cplusplus script/test: all 70/70 samplespass, including the newly un-skipped
recursive-union-flattening.schema.QUICKTEST=true FIXTURE=cplusplus script/test: all samples pass excepttwo pre-existing, unrelated failures caused by Boost headers not being
installed in this sandbox (
boost: truerenderer-option cases), which arenot touched by this change.
npm run test:unit: 170/170 tests pass.matrix.
Fixes #273
🤖 Generated with Claude Code