A node hoisted at an inline structural position — items, additionalProperties, a
patternProperties entry, a prefixItems slot — takes its Naming.Hint from whichever lowering
interns it first. The structural lowering composes the enclosing node's hint with the position's
role (compile.SubHint(hint, "item")); hoistSubSchema, reached through an outside $ref naming
that same pointer, falls through subSchemaHint to the pointer's last segment. Both are valid
hints and nothing compares them, so the same components compiled in two declaration orders produce
two different documents — silently, with no diagnostic on either side.
openapi: 3.1.0
info: {title: O, version: "1.0.0"}
paths: {}
components:
schemas:
A: {type: array, items: {type: object, properties: {n: {type: string}}}}
Outsider: {$ref: '#/components/schemas/A/items'}
Compiled with A declared first, t/anon/components/schemas/A/items is hinted a_item; with
Outsider first, items. cmp.Diff over the two documents shows only that field. The four
positions and the two spellings each takes:
| pointer |
structural lowering |
outside $ref |
…/A/items |
a_item |
items |
…/A/additionalProperties |
a_value |
additional_properties |
…/A/patternProperties/^x |
a_pattern |
x |
…/A/prefixItems/0 |
a_0 |
0 |
Why this is filed on its own
This is #181's mechanism at the positions #181 and #281 did not sweep. Both of those closed the
composition-branch family, where the pointer walk can answer what the composition would
(branchPointerHint) because a branch's hint is positional and needs nothing from the enclosing
node. These four cannot be answered that way: the structural spelling is built out of the
enclosing node's hint, which a bare pointer walk does not have, so closing this means either
deriving the whole enclosing chain from the pointer or deciding that the weaker spelling names the
node. That is a naming decision, not an agreement fix.
Deriving the chain from the pointer is also not total: a position under /paths/… takes its hint
from an operationId or a media type key, neither of which the pointer records.
The divergence is why orderInvariantIR() (compilers/openapi/internal/schema/schema_test.go)
excludes ir.Naming.Hint. TestInlinePosition_OutsideRefDoesNotMoveTheHome already permutes all
four positions, so the exclusion is exactly what stands between that test and this bug; closing
this removes the exclusion and turns that test into the regression.
Method
Compiled both orders through the CLI with -skip-validate and diffed the two documents with
sources dropped.
A node hoisted at an inline structural position —
items,additionalProperties, apatternPropertiesentry, aprefixItemsslot — takes itsNaming.Hintfrom whichever loweringinterns it first. The structural lowering composes the enclosing node's hint with the position's
role (
compile.SubHint(hint, "item"));hoistSubSchema, reached through an outside$refnamingthat same pointer, falls through
subSchemaHintto the pointer's last segment. Both are validhints and nothing compares them, so the same components compiled in two declaration orders produce
two different documents — silently, with no diagnostic on either side.
Compiled with
Adeclared first,t/anon/components/schemas/A/itemsis hinteda_item; withOutsiderfirst,items.cmp.Diffover the two documents shows only that field. The fourpositions and the two spellings each takes:
$ref…/A/itemsa_itemitems…/A/additionalPropertiesa_valueadditional_properties…/A/patternProperties/^xa_patternx…/A/prefixItems/0a_00Why this is filed on its own
This is #181's mechanism at the positions #181 and #281 did not sweep. Both of those closed the
composition-branch family, where the pointer walk can answer what the composition would
(
branchPointerHint) because a branch's hint is positional and needs nothing from the enclosingnode. These four cannot be answered that way: the structural spelling is built out of the
enclosing node's hint, which a bare pointer walk does not have, so closing this means either
deriving the whole enclosing chain from the pointer or deciding that the weaker spelling names the
node. That is a naming decision, not an agreement fix.
Deriving the chain from the pointer is also not total: a position under
/paths/…takes its hintfrom an operationId or a media type key, neither of which the pointer records.
The divergence is why
orderInvariantIR()(compilers/openapi/internal/schema/schema_test.go)excludes
ir.Naming.Hint.TestInlinePosition_OutsideRefDoesNotMoveTheHomealready permutes allfour positions, so the exclusion is exactly what stands between that test and this bug; closing
this removes the exclusion and turns that test into the regression.
Method
Compiled both orders through the CLI with
-skip-validateand diffed the two documents withsourcesdropped.