Repository navigation
fix(compilers/openapi)!: lower allOf by what its conjuncts say - #1269
Merged
Merged
Conversation
A use site read its annotations from two schemas, the one written there and the schema its $ref chain ends on, each reader taking the pair apart on its own. Readers now take a chain of schemas, nearest first, so a position can name more than one source without each reader growing an argument. Site gains Voices, inline schemas read at their own pointers beside the schema written at the position. Nothing yet supplies one, so no output changes.
An allOf always lowered to a Model, and an inline branch contributed only
its properties and required list. Two consequences reached consumers. An
allOf admitting no object, `{allOf: [{type: integer}, {type: integer,
format: int32}]}`, became an empty model, so the IR asserted an object the
source rules out. And an OpenAPI 3.0 document refining a reference, the
only way 3.0 can annotate one, lost its annotations: the branch of
`{allOf: [{$ref: Owner}, {nullable: true, readOnly: true}]}` went to
Unmodeled verbatim, leaving a field typed as writable and non-nullable.
An allOf no conjunct can make an object is now a refinement: one use of
the conjunct deciding it, reached through a reading of what each conjunct
admits, across $ref, allOf, oneOf, anyOf, type, enum and const. It reads
the source and never the registry, so no lowering order decides it, and a
position's many readers share one reading a run. A cycle, a spent budget,
a false branch or a union on a branch stays the composition it was, which
loses nothing.
What an inline branch writes for a position now folds there, in a
refinement and in a composition, each keyword landing where the same
keyword beside the allOf lands. A branch disagreeing with any conjunct on
one folds nothing and stays whole, in every order, so no precedence
between branches exists. The use reads through what it refines, so a
field naming a component refinement keeps a readOnly on its second hop.
A $ref a value set narrows stays verbatim and still speaks for the field,
its default only where the set takes it. The IR's shape does not change.
This was referenced Nov 1, 2026
Three keywords a refinement's conjuncts wrote reached nothing, with no
report, where the plain spelling of the same schema keeps them.
A `nullable: true` on a branch was consumed as folding, but it widened
only the allOf: a `type` beside the allOf forbids null, and the conjunction
of the two left the position non-nullable while the branch kept nothing.
It now widens the whole schema, as the keyword beside the allOf does.
A default written with a value set was judged by that set, which applies
only past the narrowing: `{allOf: [{$ref: Mode}, {enum: [a], default: x}]}`
lost it, and through a $ref to such a component the target's default won
over the nearer one. The filter now starts at the $ref the set narrows.
A parameter's readOnly and writeOnly were read off its schema and its
voices, so a principal owning a node, which holds neither, dropped them.
The rule said a `$ref` whose target admits an object makes an allOf a
composition, but `{allOf: [{$ref: T}, {type: string}]}` over a T that may be
either refines, as a Model there would be false. It also said any composition
saying more than its `$ref` lends a carrier nothing, though a bound beside the
`$ref` lends as the 3.1 spelling does, and listed the ways the two spellings
part without the third: a `$ref` naming them reads each differently.
models.md still gave `allOf: [{$ref: SomeScalar}]` as a Base over a Scalar,
which is the shape this rule replaces. Rewrap what ran past the column.
The report on a branch with residue said only its properties and required
list compose, which stopped being true once what a branch writes for the
position folds: `{description: d, additionalProperties: false, ...}` folds
the description and names `additionalProperties`, under words claiming
neither happened. It now says nothing reads the rest of the branch.
The reading of what a schema admits compared values as rendered text, so
`{allOf: [{$ref: Level}, {enum: [1.0]}]}` over an enum of 1 and 2 read as
sharing nothing and stayed a model, a default of 1.0 was refused by an enum
of 1, and which of two lists narrowed the other turned on their spelling.
JSON Schema holds 1 and 1.0 to be one value. The enum's own entry key already
says so, numbers by magnitude and objects whatever their member order, so the
reading now compares by it.
32 tasks done
The emitter tables said a Model over a non-object is only a Base that may be
an object or conjuncts no value satisfies, but a conjunct writing what only a
Model holds composes too: `{allOf: [{$ref: Str}], required: [a]}` is a Model
over a string. models.md said every conjunct stays reconstructible, which a
restated `type` is not: it is consumed, saying only what the position does.
A member was read twice, as a value for its key and as text for its kind, each check masking the other, and its kind came from the first byte of the text. A member is now one reading of its value, whose kind the value states.
This was referenced Nov 1, 2026
…ines-a-reference # Conflicts: # docs/compilers/openapi.md
This was referenced Nov 1, 2026
Closed
What an allOf's branches say for the position holding them was read again at every reader and every use, and three of those reads grew with the square of the input: - readVoices rendered an entry's first writer once for each later conjunct comparing with it, so one allOf of 20,001 branches, the first writing a 400 KB description, cost 559 G instructions against main's 10.4 G. - annotation.Read overlaid everything read so far on each voice's Unmodeled, so a site paid for every voice all those before it. - Each $ref to a composition walked the composition's folding branches, and 400, 800 and 1,600 uses of a decorator of as many branches cost 2.6, 9.1 and 35.0 G, where main is linear. A schema's voicing, what it lends a use and its rendered text are now read once a run through lowering.Remembered, each a function of the source and the bindings. A first writer is rendered once, a voice adds only the entries the site lacks (underlayUnmodeled), and a use reads only the branches lending an entry that the schema and the branches before them do not write (lendingBranches): every writer of an entry agrees, so a reader taking the nearest takes the same value from any. Storing a Use in the memo makes the binaries' reachability reach annotation.Kind.String through Use.Site, so its apiRoots entry goes.
widenByBranch widened a position by every inline branch that speaks
for it and writes `nullable`, whatever its value, once any branch of
the allOf wrote `true`. Its premise was that a branch folding `false`
then disagrees with that `true`, but a `true` beside a $ref branch is
no fold, so nothing compares it, and the `false` branch speaks:
{type: string, allOf: [{$ref: Str, nullable: true},
{nullable: false, description: d}]}
lowered to a nullable reference, where the same allOf without the
`false` branch did not.
nullableVoices now returns the branches whose `nullable: true` folds,
read once a run, since every use of a schema asks, where widensAny
walked the whole flattened allOf at each use. A read across a $ref
stops at the first such branch, as it reports nothing.
The merge of #1217 taught voicesAmong and restates to pass over a key written as null, which reads as the keyword left out: a branch writing `description: null` had disagreed with one writing `description: d`, keeping d verbatim, and items restating a $ref's but for a null keyword had kept their branch. Neither reader had a test of its own, and TestCompile_ANullKeywordLowersAsTheKeywordLeftOut writes one branch alone, where no disagreement can arise. These pin both, in both orders for the first.
Brings in #1302 and #1304. - #1302 took IRVersion 0.26.0, so this branch's bump moves to 0.27.0, its row keeping 0.26.0's shape. - #1302 reads a collection bound into what a position lowered to a reference owns, through CarrierConstraintsOf and referenceConstraintsOf. A refinement lowers to a reference, so each takes the voices ConstraintsOf takes here, and a bound beside an allOf refining a $ref is typed on the alias or the carrier, which TestCollectionBound_BesideARefinementReachesWhatThePositionOwns pins. A bound on its branch still stays with the branch (#1312), so referenceConstraintsOf's comment and the constraints rule's status, which said such an allOf lowers to a Model, say that instead.
Two conjuncts writing one value bound differently disagreed, so each
branch stayed whole under Unmodeled and the position held neither:
{allOf: [{$ref: Str}, {maxLength: 10}, {maxLength: 5, description: d}]}
kept both branches, the description with them. #1307 made a
redeclared field's bounds meet, each taking what the declarations say
together, and merge.Constraints is the meet ConstraintsOf folds voices
with, so a position's conjuncts now agree the same way: the example
holds maxLength 5 and its description, and multipleOf 4 beside 6 holds
12, in either order.
The agreement is a set's answer, read once per schema: a bound agrees
where every writer's reading of it states a value and the values meet
(merge.Meets). Two patterns have no one value, and a 3.0 exclusive
modifier with no bound beside it fills no field, so each still
disagrees with a writer spelling it otherwise. The allof-inline-residue
rule says so, and its status names the one case left, a collection
bound on a branch (#1312), in place of #10.
A refinement's inline principal that lowers as a type array dropped what none of its variants holds, as a type array written alone did, and the allof-refinement rule and keepsAtOutcome named that as a limit (#852). #1217 closed it: the census asks the variants and keeps such a keyword on the union, and a principal reaches the same census. So `{allOf: [{type: [string, integer], minLength: 1}]}` keeps minLength on its union, as `{type: [string, integer], minLength: 1}` does. The limit leaves the rule's status and the comment, and TestRefinement_ATypeArrayPrincipalKeepsWhatNoVariantHolds holds both spellings to it.
Three statements claimed more than the code does: - models.md said an allOf none of whose conjuncts can make an object is a use of one of them, and the Go and Kotlin lowering rows named three cases that keep such an allOf a Model. allof-refinement keeps composing for more: two $refs, a union on a branch, a capped or false branch, more conjuncts than the election compares. Each now says short of those cases. - ref-use-site said a sole $ref composed with nothing only a Model holds lends a carrier what it lends written alone. A use reads through no allOf that points 2 to 4 of allof-refinement keep composing either (throughRef), and the sentence now says so. - allof-refinement said an enum or a const beside an allOf wins first, and nothing of what that allOf keeps: it stays verbatim, folding nothing and lowering no $ref it writes, which its status now tracks as #1309. The new type array test joins the rule's tests.
7 tasks done
This was referenced Nov 2, 2026
ref-use-site said "A composition of a sole $ref, a decorator or a bound
beside it, lends a carrier what the $ref would lend written alone",
which reads as three kinds of composition where it meant one, with the
decorator and the bound as examples. It also named only a conjunct
writing what only a Model holds and points 2 to 4 of allof-refinement
as what makes such a composition lend nothing, where a $dynamicRef on
a branch does too: throughRef reads past no branch refinableBranches
refuses. Compiled at the branch head,
W1: {allOf: [{$ref: Str}, {$dynamicRef: '#meta'}]}
W2: {allOf: [{$ref: Str}, {maxLength: 3}]}
over Str: {type: string, description: from-str, readOnly: true}, a
property naming W1 carries no description and no visibility, and one
naming W2 carries both from Str.
The two sentences now say which compositions hold Base and Mixins and
lend nothing, then that any other lends.
Brings in #1229 and #1325. - Both bump IRVersion, to 0.27.0 and 0.28.0, so this branch's bump moves to 0.29.0. Its row takes main's new shape and keeps this branch's names and meaning; the version history entry moves with it. - #1229 gives ir.Parameter a Secret, so a parameter's redaction has a home and xml is the one keyword left with none. fillParamSchema keeps xml for each voice and the schema, and the wrapper that also kept format: password goes. The secret-format golden is regenerated: it differs from main's by this branch's cases, and from this branch's by the parameter's Secret. - #1229 reads a target's mapped keys onto a carrier at the end of a $ref only. A refinement is a use of what it names, so {allOf: [{$ref: T}], description: d} read T's deprecated keyword but not its x-stability, where {$ref: T, description: d} read both. KeysLentTo now reads the keys along the chain the keywords are read along (Use.Referents), nearest first: the nearest link stating a deprecation, by a keyword or a key, decides it and lends its members, and any link asking for redaction asks it of the use. A position owning a node lends its carrier none, as it lends no keyword. Promoter.Referent keys what it remembers by the schema's identity, as an inline link has no binding position. TestPromotion_ARefinementLendsWhatItRefines pins it. - TestPromotion_ADecoratorMatchesTheRefItStandsFor assumed an allOf of one $ref over a string is a decorator. It is a refinement here, so the test now holds its carriers to the $ref spelling's, and keeps the fold comparison over object targets, where it is still a decorator. - #1258's example, deprecated: false on such an allOf over a string, now reads as beside the $ref, so its status names the object case that still parts, and extension-promotion's #1220 entry names what is left of it.
Electing a refinement's principal asks narrowsAll of every conjunct,
the $ref's among them, and for the $ref narrowsAll compared its value
set with the conjunct's, which is the same set: a walk of every member
of the target that answers nothing. A conjunct was compared with
itself too, and valuesWithin walked a larger set looking for a member
a smaller one lacks, which it must. So each position narrowing an enum
walked the whole enum, a cost of positions times members.
narrowsAll skips the $ref and the conjunct it judges, and valuesWithin
answers a set larger than the other at once, so electing a narrowing
walks the narrowing's own members. No answer changes: a sweep of
testdata, the reference corpus and the golden spec, 2,183 specs, finds
no difference in exit code, IR or diagnostics.
Instructions retired compiling 500 fields narrowing one enum, one
thread with the collector off, 3 runs alternating:
4,000 members 3.03 G [3.02-3.07] -> 2.58 G [2.57-2.61]
1,000 members 1.37 G [1.37-1.37] -> 1.25 G [1.25-1.26]
4,000 models extending a base, no value set, stay at 7.73 G.
TestElect_WalksOnlyTheSetItNarrows counts the members the election
looks up, through a function field on admission a compile leaves nil.
Deciding whether an allOf over a $ref refines it reads what the $ref admits across its chain, up to maxAdmissionReads schemas, and each position reads afresh. A chain of decorators, each an allOf naming the next, so pays up to that bound at every link: 3,000 links cost 7.38 G instructions against main's 6.14 G. A reading is not remembered, because its answer turns on the budget left when it starts, so a remembered one would have to carry what it spent. The limit is stated where the bound is.
#1229, merged in fd37a88, gives ir.Parameter a Secret and holds a parameter's format: password there rather than under openapi:format. TestParameter_ABranchSpeaksAsItsSchemaDoes still expected the verbatim entry for a branch writing it, so it failed on the merge. It now holds the branch's redaction to Secret and the entry's absence, beside the readOnly and xml the parameter still keeps verbatim at the branch.
KeysLentTo lends a carrier no key where its position lowered to a node
of its own, as the carrier reads no keyword there: a decorator over an
object holds its target's Deprecation on its node, for the fold to
read. Nothing held that, so removing the guard put the target's
promoted deprecation on every carrier of
{allOf: [{$ref: SpentObject}], description: d} and no test failed.
TestPromotion_ARefinementLendsWhatItRefines now has the row.
Brings in #1327. - #1327 took IRVersion 0.30.0, with no change of shape, so this branch's bump moves to 0.31.0. Its row keeps the shape both share and this branch's names and meaning, and its version history entry moves with it, pinned against 0.30.0. - #1327 and this branch each added a test at one place in preserve_internal_test.go; both are kept.
A $ref that loses the type to a value set narrowing it, or that an
enum beside its allOf passes over, still speaks on the node the
position lowers to (ref-use-site): nodeUse lists it among what that
node falls back to, so its `deprecated` reaches the node. A
declaration's promotion took no referent, so a key stating the same
deprecation did not:
Narrowed: {allOf: [{$ref: Old}, {enum: [x]}]}
held Old's deprecation where Old wrote `deprecated: true`, and none
where it wrote `x-stability: deprecated`. The $ref is no Base of the
node, so nothing else reaches it.
The fold KeysLentTo ran for a carrier is now Use.lentKeys, and
attachDeclaredAnnotations runs it over the nodeUse its keywords come
from. A node with no lost $ref falls back to nothing, so it promotes as
before; a boolean schema's position, with no schema, writes no
`deprecated`. A sweep of testdata, the reference corpus and the golden
spec, 2,183 specs, finds no difference, as none narrows a target
deprecated by a key. TestPromotion_ALostRefLendsTheNodeItSpeaksOn pins
both spellings, and a declaration given no referent again fails it.
Brings in #1227, which resolves a $ref against the schema identifiers each document declares and expands a $dynamicRef through the binding table. The merge is clean, and #1227 takes no IRVersion, so this branch's stays 0.31.0. Every reader this branch adds reaches a target through the bindings (the admission read, lentBy, Use.lentKeys), so each follows #1227's resolution with the lowering: a refinement over a $ref an $id resolves reads that target as its $ref spelling does. Swept over testdata, the reference corpus and the golden spec, #1227 changes one spec on main and the same one here, its own identifiers_in_another_document, with the same IR and diagnostics on both. A $dynamicRef still ends a refinement's read, which keeps such an allOf the composition it was.
The two Status lines tracking #1312 scoped the limit to an allOf over a $ref, but foldEntry folds no minItems, maxItems or uniqueItems on any inline branch. So {type: array, items: {type: string}, allOf: [{maxItems: 2}]} keeps its branch verbatim beside a List whose Constraints has the field, where the same bound beside the allOf, or written flat, reaches Constraints. State the limit for every principal in both rules, and at foldEntry, the code that decides it.
8 tasks done
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
An
allOfalways lowered to aModel, and an inline branch contributed only itspropertiesandrequired. Two common shapes came out wrong.allOfthat admits no object.{allOf: [{type: integer}, {type: integer, format: int32}]}was an empty model, so a consumer got an object with no fields for a schema meaningint32.{allOf: [{$ref: Port}], type: integer}was a model over an integer. AModelasserts an object, and the source rules one out.$ref, so a document annotates a use by wrapping it:{allOf: [{$ref: Owner}, {nullable: true, readOnly: true, description: The owner}]}. The branch went toUnmodeledwhole, leavingownerwritable, non-nullable and undocumented. Spelled beside theallOf, the same keywords reached all three.The change has three parts, specified in
docs/compilers/openapi.md.allof-refinement). AnallOfno conjunct can make an object is one use of the conjunct that decides it, its principal, with noModel. What each conjunct admits is read across$ref,allOf,oneOf,anyOf,type,enumandconst, from the source and the bindings and never the registry, so no lowering order decides it. A narrowingenumorconstwins, else the$ref, else the conjunct naming a kind that lies within the most others. Nothing is minted: each node stands at a pointer naming its schema.allof-inline-residue). What an inline branch writes for a position lands where the same keyword beside theallOflands, in a refinement and in a composition. Value bounds meet as a redeclared field's do (fix(compilers/openapi): intersect a redeclared field's bounds #1307):maxLength: 3besidemaxLength: 5folds to3,multipleOf: 4beside6to12. Any other keyword two conjuncts write differently, apatternamong them, folds nothing and stays whole, in every order, so no precedence between branches exists.ref-use-site). A carrier holding a refinement reads its target, and a$refto a refinement reads through it, so a field naming{allOf: [{$ref: Id}], description: …}keeps thereadOnlythat onlyIdwrites. The vendor keys feat(ir)!: type the secrecy and stability a document states #1229 promotes follow the same chain (extension-promotion): the nearest link stating a deprecation, by a keyword or a key, decides it, and any link asking for redaction asks it of the use. A$refa value set narrows stays verbatim underopenapi:allOf/<n>, still lowers what it names, and still speaks for the field's docs, deprecation, visibility and redaction, what is written beside it first. Adefaulta$reflends across a conjunct admitting less than it does counts only where the position takes it, one written with the set stands as a plainenum's does, and the$ref's examples stay on its target's node, as any target's do.What a position decides, what a schema's branches say for it, what it lends a use, its rendered text and an enum's members are each read once a run, through
lowering.Remembered, a per-run memo in the wayTypePositionkeeps its answers. Each is a function of the source and the bindings. Several readers ask for each position and every use asks of its target, and a first writer is rendered once, not at each comparison: before that, oneallOfof 20,001 branches cost 559 G instructions wheremaincost 11 G (Cost below).Breaking
IRVersionmoves to 0.31.0, with no change of shape:allOfno conjunct can make an object is noModel, short of the casesallof-refinementkeeps composing: a consumer finds aScalar, anEnumor a reference where it found aModel.allOfbranch takes the hint of the schema holding theallOf, where it took a union variant's,variant_N, so the symbols an emitter generates from it are renamed.TestIRVersion_PinsTheNamesholds this:composed/allOf/1intestdata/golden/openapi/hint_positions.yamlmoves fromvariant_1tocomposed.docs/ir/version-history.mdrecords both under 0.31.0, after #1229, #1325, #1333 and #1327 took 0.27.0 to 0.30.0. #1361 and the PRs stacked on it take 0.31.0 to 0.36.0 too, so whichever lands after another takes the next.Decisions a reviewer should weigh
{allOf: [{$ref: Status}], description}a target's ergonomics and so a refine step. That still holds over an object, which stays a composition and a decorator. Over a non-object, aModelis a false claim about the wire that every consumer readingKindsees, a position with no$refhas nothing to collapse into, and a narrowed enum has no decorator form.docs/ir/models.md#an-allof-no-object-satisfiesrecords this with the designs rejected.$refinto it, the one casehint_positions.yamlshows, takes the composition's hint as well, so two nodes there sharecomposed, which an emitter tells apart as it tells any two hints apart.$refonly, so merged as it was,{allOf: [{$ref: T}], description: d}readT'sdeprecatedbut not itsx-stability, where{$ref: T, description: d}read both. The merge reads the keys along the chain the keywords are read along, which keeps the two spellings one answer, on a carrier and on a node a narrowed$refstill speaks on, where nothing else reaches that$ref. The promoter now remembers each reading by the schema rather than its binding position, as a link inside anallOfhas none.Limits, each stated at the code and in its rule
oneOforanyOfon a branch or beside theallOfstays a composition, until openapi: distribute a union's inline branches when properties sit beside it #661 lowers those branches. On the golden spec these are all 8 remaining warnings.$refconjuncts stay a composition, since keeping one reference typed would drop the other.allOfanenumor aconstbeside it passes over stays verbatim, folding nothing and lowering no$refit writes (openapi: an allOf an enum beside it passes over folds nothing and lowers no target #1309).allOfreachesConstraints(openapi: a collection bound on an inline allOf branch beside a $ref is kept verbatim #1312):{type: array, items: …, allOf: [{maxItems: 2}]}keepsopenapi:allOf/0beside aListthat has the field, as one over a$refdoes, wheremainmade either aModel.emailanduuidsay, the first takes the position and the other is kept with an info, so order decides there. A field redeclared the same ways does the same. A$refelement is not compared, soitems: {$ref: Str}againstitems: {type: string, format: uuid}is such a tie.5and5.0, keep the first spelling, as a redeclared field's do.formatoritemswith notypenames no kind, so it decides nothing:{allOf: [{type: integer}, {format: int32}]}lowers tointegerwith the branch kept, in both orders. Across the 5,935allOfpositions of golden,testdataand the corpus at2af95505, none is spelled that way.schema nesting exceeds 256, where plain arrays reach it at 256.maincompiled such a schema only by leaving its branches unlowered. Nothing in golden or the corpus comes near: the deepestallOflowers at a depth of 4.allOfover a$refrefines it reads that$ref's chain afresh at each position, up to 256 schemas, so a chain of decorators pays up to that bound at every link: 20% overmainon 3,000 links (Cost).{allOf: [{$ref: T}], K}matches 3.1's{$ref: T, K}whereKis annotations, mapped vendor keys and value bounds, at every position. The two part on a structural keyword, which a$refsite still keeps verbatim (openapi: a structural keyword beside a 3.1 $ref is kept verbatim, not composed #1067), ontype: [T, "null"], which conjoins here and widens there, and at a$refnaming either, which readsKhere and past it there, as a$refchain is read to its end (openapi: a use site takes its $ref chain's last annotations, skipping an alias's own #1029).Other issues touched
wrappedandwrappedFormatover a non-object now lower likebesideandbesideFormat, and a key on an inline branch sits where the promotion reads it. It stays open over an object: a key beside theallOfor on its branch lands on the decorator node, where beside a$refit lands on the carrier, which the fold of a decorator reads alike.deprecated: falseon anallOfover a string, now reads as it does beside the$ref, since thatallOfis a use of its target. It stays open over an object, where theallOfis still a decorator.2af95505. Its field-level half is untouched.$refand over an inlinetype: arrayalike.mainscans an enum for a null member at every$refto it, the cost both builds share in the enum row below.Other open PRs
Not stacked. Trial-merged at
11856156, each also intomain. These conflict here alone:allof.go,recursion_test.go, the rules document and theallof-inline-residuegolden. It reads what each conjunct admits on its own, so whichever lands second folds that reading intoadmission, and two readers of one question do not drift.apiRootsmap inreachability_test.go, which this branch re-aligned by dropping its longest key. Keeping both sides' entries and runninggofmtresolves either.generationstable,apiRoots, andattachDeclaredAnnotations, where feat(ir)!: say what unions, literals and catch-alls admit #1361 rewords the comment beside the two promotion calls this branch gives a referent. Whichever lands second keeps this branch'sfrom := use.lentKeys(c, s)and feat(ir)!: say what unions, literals and catch-alls admit #1361's comment.#1278 and #1371 conflict with
mainas well. The rest merge clean. #1199 widens two style checks, andinternal/archtestpasses on its merge at957cd415.Golden spec
cloudflare/api-schemas@319dc756,main@117d29e7, this branch @5cfd7105, whose counts below11856156reproduces exactly.On
main:On this branch:
The last two lines are the
openapi:allOf/<n>entries and theno home for typereports on amodel. The 8 warnings left all sit beside ananyOfon a branch. Both builds exit 1 on the same 9 security requirements naming schemes the spec does not declare. 227 properties list theextension-promotionmarker here, against 211 onmain: each of the 16 is a refinement whose deprecation message a mapped key states, beside it or on its target, now on the field as its$refspelling's would be, wheremainleft it on a decorator node.email-security_AllowPolicy.last_modifiedis one. Compiling the spec costs 84.34 G instructions here and 83.47 G onmain, +1.0% with overlapping ranges (Cost).The hardest sites, read in the IR:
access_target_criteria_base.portnamesaccess_port, with no node and no diagnostic. Onmainit was a model holdingopenapi:type.access_end_user_props.typeis a one-memberEnum, with "The application type." fromaccess_typeon the field and that$refunderopenapi:allOf/0.pages_project.canonical_deploymentstays a model decorator, now nullable withnullable-widening, read-only and described.waf-product-api-bundle_custom-scan.idis read-only, from areadOnlytwo hops away on a component refinement.Cost
Instructions retired by the compile stage alone,
openapi.New().Compilein a benchmark run once a process under/usr/bin/time -lwithGOMAXPROCS=1 GOGC=off, the median and range of 3 runs alternating,main@117d29e7against this branch @5cfd7105. The collector's work varies with machine load, which had widened runs of the CLI to a 9% range, so it is left out.mainallOfof 20,001 branches, the first describing 400 KBitemscarry a 300 KB descriptionEach range but the golden spec's is within 0.02 G of its median. Before the memo, at
cb1af3ae, the first three shapes cost 559.5 G, 63.6 G and 472.8 G through the CLI and the uses 2.6, 9.1 and 35.0 G, growing with their square. Those runs were the CLI's on a loaded machine, so only the size of the gap reads off them.The chain is the one shape more than 10% above
main, for the reason its limit above states; the rest are within 9%. The enum row cost 17.8% more through the CLI atfbebf329, until63fd362e: electing the narrowing compared the target's value set with itself at each position, a walk of all its members. What remains of its cost on both builds is #1369. Through the CLI, the 150 KB row costs 10.37 G here and 2.02 G onmain(atfbebf329and7f849127, 5 runs alternating): that is the output, as each of 3,000 fields now holds the description, 452 MB of IR, whichmainwrites too for the same description beside a plaintype.Test plan
On
18dc7539unless named.11856156, the flat spelling againstmain: no comparison wheremainmatches it and this branch does not, over the 748 pairs below on 3.0 and 3.1, and over 118 one- and two-conjunct cases at a query parameter, a request body, a response header, a map value and a union variant on each. Each difference left at those positions is an inlineenumorarrayprincipal keeping its node while a sibling's keyword lands on the carrier or a decorator alias, or a 3.0nullablewith notypebeside it, read in under the widening policy and marked inferred. The 984 specs passmorphic-harnessbut two 3.0 ones writingreadOnlywithwriteOnly, which the validator refuses.fb0fa6a7, two oracles against the flat spelling. One compiles{allOf: [K]}againstKfor 48 keyword sets at a property, anitemsand a component, on 3.0 and 3.1; the other{allOf: [K1, K2]}and{K1, allOf: [K2]}against{K1, K2}for the 748 ordered pairs of 28 keyword sets sharing no keyword, at a property and anitems. Each difference is one a rule states: aformatoritemswith notypedeciding nothing, an inline principal keeping its own node, where a sibling branch's keywords land on the carrier or an alias and a component is a named alias over it, anenumbeside theallOfpassed over (openapi: an allOf an enum beside it passes over folds nothing and lowers no target #1309), conjuncts sharing no value (openapi: an allOf whose conjuncts share no value draws no report #1215), and a collection bound on a branch, whose stated limit11856156widens from a$refto every principal. On 3.1,maindiffers on 47 of the 48 one-branch sets. All 844 specs passmorphic-harnessbut a 3.0type: null, which the validator refuses. 38 defects planted across the admission read, the election, the voices, a use's chain, the annotation reader and the parameter readers each fail a test.11856156, which holdsmain@bc76c006: both jobs pass, run 41477233669.main@bc76c006(feat(compilers/openapi): read each document's schema identifiers #1227), which resolves a$refagainst each document's schema identifiers: clean, with no version of its own.sweep.pyovertestdata, the reference corpus and the golden spec shows feat(compilers/openapi): read each document's schema identifiers #1227 changing one spec onmainand the same one here, its ownidentifiers_in_another_document, with the same IR and diagnostics on both. A refinement over a$refan$idresolves reads that target as its$refspelling does, and the keyword-against-key and redaction grids below find no difference on the merge either.deprecated: trueand byx-stability: deprecated, and again forformat: passwordagainstx-sensitive: true: no deprecation,SensitiveorSecretdiffers on any type, property, parameter or header, here or onmain, where the build before2df2b53bdiffers in all 120. Their 480 specs passmorphic-harness.2df2b53b: a node a$refstill speaks on, narrowed by a value set or passed over by an enum beside itsallOf, takes that$ref's promoted keys as it takes its keyword.TestPromotion_ALostRefLendsTheNodeItSpeaksOnpins both spellings, and a declaration given no referent again fails it, against a control run that passes. A sweep of 2,183 specs from8e600020finds no difference, as none narrows a target a key deprecates.main@02c972b6(feat(ir)!: type the secrecy and stability a document states #1229, fix!: type the local time formats and keep time's offset #1325), @117d29e7(fix(compilers/openapi): keep unmounted path-item docs and params #1322, feat!: follow references into OpenAPI 3.2 components/mediaTypes #1333) and @6996ce45(fix(compilers/openapi)!: scope and build path item entries once #1327):TestIRVersion_PinsTheEncodedShapeandTestIRVersion_PinsTheNamesat 0.31.0, the conformance goldens,TestLoweringSpec, the census and the promotion tests.secret-formatwas regenerated, and differs frommain's by this branch's cases and from this branch's by the parameter'sSecret. Promotion through a refinement is pinned byTestPromotion_ARefinementLendsWhatItRefinesand byTestPromotion_ADecoratorMatchesTheRefItStandsFor, which now holds a refinement's carriers to the$refspelling's and keeps the fold comparison over an object.TestParameter_ABranchSpeaksAsItsSchemaDoesnow holds a branch'sformat: passwordto the parameter'sSecret, which feat(ir)!: type the secrecy and stability a document states #1229 gave it a home in. Six planted defects inKeysLentToeach fail the row written for them, against a control run that passes: reading the first link only, a keyword not deciding, a branch's keyword ignored, redaction read off the deciding link alone, the farthest link deciding, and a position owning a node lending its carrier.63fd362e:sweep.pyfrom the merge before it overtestdata, the reference corpus and the golden spec, 2,183 specs, finds no difference in exit code, IR or diagnostics.TestElect_WalksOnlyTheSetItNarrowsfails on each of 3 planted defects: the$refcompared with itself, a conjunct with itself, and a larger set walked for a member a smaller one lacks.fbebf329against7f849127: none grows faster than its size on either build, but for the enum term63fd362eremoves and the chains within the read limit.ownernullable, read-only and described, identically to the same keywords beside theallOf;zone_idkeeps its description andmaxLength;Ta namedint32with no diagnostic; disjoint branches still warn at each branch.maingained since2af95505met this branch's readers in three places, each settled in the merges and pinned by a test. Atype: null, which reads as the keyword left out (fix(compilers/openapi): keep a keyword at every schema outcome #1217), had admitted nothing and kept{allOf: [{type: null}]}aModel, whichTestCompile_ANullKeywordLowersAsTheKeywordLeftOutreddened on; adescription: nullon one branch disagreed with another branch's description, nowTestVoices_ANullKeywordWritesNothing; and fix(compilers/openapi)!: type a collection bound beside a $ref #1302's collection bound beside a reference reaches a refinement's alias or carrier, whichTestCollectionBound_BesideARefinementReachesWhatThePositionOwnspins.nullable: falsebranch widened a position whose onlytruesat beside a$refbranch, which no fold compares:{type: string, allOf: [{$ref: Str, nullable: true}, {nullable: false, description: d}]}was a nullable reference. Fixed inc917e4e6, with a test against the sameallOfwithout thefalsebranch.sweep.pyovertestdataand the reference corpus, with each commit since the merges against its parent: the cost commit38f35b54againstcb1af3ae, 2,179 specs, 0 differ, IR bytes equal; the bound meet against74054539, 2,180 specs, 4 differ, each a pair of bounds folding to the tighter value with its two branches no longer kept. The golden spec's IR is byte-identical across both and acrossc917e4e6.TestLendingBranches_LendsEachEntryOncenow covers it. The byte-count tests the cost commit adds pass 20 runs under-race -covermode=atomic.main: 3.0's boolean and 3.1's numeric exclusive bounds, alone, together and against a bound beside theallOf, each lowering to the conjunction; a lone 3.0exclusiveMinimum: trueon a branch, reported there and leaving theminimumbeside theallOfinclusive; a refinement's branches meeting each other and a bound beside theallOfon the field, while the target keeps its own on its node; andmultipleOf: 7e10000000beside3, which meets to210000e9999996at once, as a redeclared field's does onmain.909ddee0, an ordered-pair grid of 31 conjunct spellings on 3.0 and 30 on 3.1: each pair as anallOfin both branch orders, with a use of it, compared with branch indices mapped to their content. Of 900 pairs, the one that differs is two$refs, whoseMixinskeep source order; withagreementplanted to let the first writer win, 7 more differ on each version. Multipart parts and parameters written as refinements come out as their plain spellings do, wheremainkept them as models, and a refinement in a second document reads that document's$refs, with its reports there. The 29 specs behind these probes passmorphic-harness.2af95505, beforemain's fix(compilers/openapi): keep a keyword at every schema outcome #1217, feat(ir)!: settle doc references, promotion records and names #1219, fix!: keep a security requirement naming no scheme #1226, fix(compilers/openapi)!: type a collection bound beside a $ref #1302, feat(engine): add an opt-in content-addressed IR cache #1304 and fix(compilers/openapi): intersect a redeclared field's bounds #1307 were merged in: an ordered-pair grid of 50 conjunct spellings on 3.0 and 60 on 3.1 in both orders with--verify, whose ties are each reported in both orders; 810 cases of 18 annotations and value bounds over 5 targets at every position against their 3.1$refspelling; 54,912 keyword-presence pairs; a per-carrier scan of 137,467 and 61,173 properties and parameters with a planted control, none lost; and 153 planted defects, 148 killed and 5 equivalent.TestRefinement_DecidedByTheSetInEveryOrder,TestNullableWidening_ABranchWidensTheAllOfHoldingItand the tests their rules name hold what those taught.Closes #660
Closes #1043