fix(compilers/openapi): keep bounds beside a preserved union - #363
Open
OmarAlJarrah wants to merge 1 commit into
Open
fix(compilers/openapi): keep bounds beside a preserved union#363OmarAlJarrah wants to merge 1 commit into
OmarAlJarrah wants to merge 1 commit into
Conversation
A schema that co-declares oneOf/anyOf with structural keywords lowers its
structural body and keeps the union verbatim. When that body reduces to a
shared primitive, the position hoists an alias so the preserved union has
a node of its own to attach to — and that alias was interned with nil
constraints, so every value constraint written at the position was
dropped in silence:
A: {type: string, minLength: 3, oneOf: [{minLength: 1}, {minLength: 2}]}
lowered to a scalar with no constraints at all, and no diagnostic named
minLength.
Ownership is what made it terminal. hoistDeclarationHome resolves to
whatever node the pointer already owns and returns early, so the fallback
that carries constraints for every other position never ran. A position
that also wrote an unhomed keyword happened to be rescued, because
preserveUnhomedKeywords interns its alias through the constraint-reading
path first — which is why this went unnoticed.
Read the position's constraints and hand them to internAlias, as
lowerComponentSchema, hoistDeclarationHome, hoistSubSchema and
preserveUnhomedKeywords all already do. Their diagnostics are routed too,
so the co-declared-bound reconciliation now reports at these positions,
where before nothing read the bounds to report on.
The corpus had the same blind spot as the code: no committed spec put a
constraint beside a union kept verbatim, so nothing moved when the drop
was introduced. allof-oneof-cooccurrence gains that shape beside the
model-body case it already carried.
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.
Summary
A schema that co-declares
oneOf/anyOfwith structural keywords lowers its structuralbody and keeps the union verbatim beside it. When the body reduces to a shared primitive,
the position hoists an alias so the preserved union has a node of its own to attach to —
and that alias was interned with
nilconstraints, so every value constraint written atthe position was dropped in silence.
Ownership is what made it terminal:
hoistDeclarationHomeresolves to whatever node thepointer already owns and returns early, so the fallback that carries constraints for every
other position never ran. A position that also wrote an unhomed keyword happened to be
rescued, because
preserveUnhomedKeywordsinterns its alias through the constraint-readingpath first — which is why this went unnoticed.
The fix reads the position's constraints and hands them to
internAlias, as every otheralias hoist here does. Their diagnostics are routed too, so the co-declared-bound
reconciliation now reports at these positions, where before nothing read the bounds to
report on.
Every
internAliascall siteFive call sites, one of which passed
nil:lowerComponentSchema(schema.go)schemaConstraintshoistDeclarationHome(schema.go)schemaConstraintslowerBesideUnmodeledUnion(schema.go)nilpreserveUnhomedKeywords(schema.go)schemaConstraintshoistSubSchema(resolve.go)schemaConstraintsThe wider mechanism — a node that hoists for its own reasons, and so must carry the
position's constraints itself because owning the pointer stops the declaration-home
fallback — was swept over the
internNodehoisters too:lowerModel,lowerArray(collection bounds via
listConstraints),hoistByteScalar,hoistFormatScalarandhoistContentScalarall read them already;falseSchemainterns for a schema that writesno keywords at all.
What the sweep also turned up is a different question, and #268 already frames it: a
keyword the elected lowering never reads.
lowerAllOfbuilds anir.Modeland neverfills its
Constraints, so{allOf: [...], minProperties: 2}dropsminProperties; andlowerEnum/hoistLiteral/lowerOneOfAnyOfown their pointer whileir.Enum,ir.Literalandir.Unioncarry noConstraintsfield at all, so{enum: [...], minLength: 3}has nowhere to put the bound. Deciding those needs the per-winner rule #268is about — whether the answer is an IR home or an
Unmodeledentry differs between thetwo — so they are left standing here and recorded on that issue rather than settled as a
side effect of this fix.
Test plan
Compiling the issue's reproducer through the CLI, before and after.
Before:
After:
The numeric variant
{type: string, minimum: 10, exclusiveMinimum: 0, oneOf: [...]}behavesthe same way, and now also reports the bound it had to reconcile — a diagnostic that could
not fire while nothing read the bounds:
TestOneOf_CoDeclaredKeepsTheBoundsWrittenBesideItcovers both routes to the verbatimlowering plus the reconciliation case. Restoring the
nilreddens it:Dropping only the diagnostic routing (
cons, _ := schemaConstraints(...)) reddens the thirdcase on its own, so both statements of the fix are held by an assertion:
No golden moved when the fix landed, which is the corpus sharing the code's blind spot: no
committed spec put a constraint beside a union kept verbatim.
allof-oneof-cooccurrencegains that shape (
BoundedKinds) beside the model-body case it already carried; its goldenis regenerated with
-updateand now records the alias holding both the union andminLength: 3. Restoring thenilreddensTestConformance/allof-oneof-cooccurrencetoo.Gate:
gofmt,go vet ./...,golangci-lint run(0 issues),go build ./...,./scripts/check-coverage.sh(100% of 4944 statements) all pass.Closes #343