Repository navigation
feat(ir)!: give parameters and enum members stable IDs - #811
Open
fuad-daoud wants to merge 3 commits into
Open
fuad-daoud wants to merge 3 commits into
fuad-daoud wants to merge 3 commits into
Conversation
A parameter was named by its name, which one operation can repeat: id in the path and id in the query are two parameters, yet a binding, a pagination input and an idempotency token each pointed at "id". Parameter gains ID, `param/<space>/<operation mount>/parameters/<name>/<in>`, built on the ID framework: the name is one escaped RFC 6901 token, the mount scopes it per operation, and no list index is used. A path-item parameter copied into two operations, or a path item mounted from two paths, gets an ID per mount. pass.Validate now joins bindings to Operation.Params by ID and rejects a binding naming another operation's parameter. Breaking: HTTPParamBinding.Param, ParamPath.Param and Idempotency.TokenParam change from a parameter name to a ParamID, and Parameter gains a required "id". IRVersion moves to 0.8.0; 0.7.0 documents are no longer read. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A binding is checked against the parameters of the operation it sits on, but Pagination.InputCursor, Pagination.InputLimit and Idempotency.TokenParam only had to name some parameter in the document. Now that parameters have IDs, another operation's parameter is a well-formed reference that nothing rejected, and an emitter would read a cursor off the wrong operation. pass.Validate reports such a reference as pass/param-binding-mismatch. An empty ID and an ID no operation declares are left to the checks that already own them, so each defect is reported once. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An enum member was a name and a value. Two members can render from the same words (a-b and a_b), and reordering the list moved every position, so a consumer had nothing stable to key a member by and an emitter resolving a naming collision had to key it by position. EnumMember gains ID, `e/<enum's namespace>/<enum pointer>/<key>`. The namespace is the one the enum's own TypeID lives in, so a member sits under the type that declares it. The key is the value, tagged by kind and escaped as one RFC 6901 token, never the name or the position; a value held twice keeps every member, and the repeats take #2, #3 and are reported as openapi/duplicate-enum-value. irverify holds a member under its enum's namespace and path (ir/member-id-scope) and pass.Validate rejects a repeat within one enum. Breaking: EnumMember gains a required "id", and the "e" kind joins IDSpaces. This extends the 0.8.0 generation, so IRVersion is unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Oct 10, 2026
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
Closes #596. Part of #255: item 1, enum-member identity. Items 2–4 have no code yet; their decisions are recorded on #255.
Replaces #807. That PR conflicted with #801, which landed operation-group identity and declared ID namespaces while #807 was in review. This PR is the part #801 does not cover, rebuilt on #801's framework.
Two named things in the IR were referenced only by their name, and names are presentation (invariant 3). This gives each a synthetic ID, moves every reference onto it, and registers both new kinds in
Document.IDSpaces.Parameters:
ParamID(#596)OpenAPI identifies a parameter by
(name, in), soidin the query andidin the path are both legal on one operation. The IR referenced parameters by name, so their two bindings could not say which one they bound.Parameter.IDisparam/openapi/<operation mount pointer>/parameters/<name>/<in>.ids.Ptrescaping feat(ir)!: identify operation groups and declare ID namespaces #801 uses.$ref'd path item can be mounted at several paths.HTTPParamBinding.Param,Pagination.InputCursor/InputLimit(ParamPath.Param) andIdempotency.TokenParamnow hold aParamID.pass/validaterejects any of them naming a parameter its own operation does not declare (pass/param-binding-mismatch). An ID nobody declares is left to the dangling-reference check, so it is reported once.irverify:paramis in its class→prefix table, socheckDeclaredIDsholds a parameter ID to the grammar.Enum members:
EnumMemberID(#255, item 1)emitter-design§4.12 resolves naming collisions in a table keyed by IR ID. The commonest collision, enum members canonicalizing to the same words, had no ID to key.EnumMember.IDise/<enum's namespace>/<enum's path>/<key>.s:string,y:symbol,n:canonical number,b:bool,x:bytes), so1and"1"differ.openapifor a component,anonfor an inline enum. It is derived from the enum's pointer the wayids.ForPointeris, so nocompile.Spaceis built from data (feat(ir)!: identify operation groups and declare ID namespaces #801's archtest rule).#2suffix, and the compiler warnsopenapi/duplicate-enum-value.pass/duplicate-enum-member-idreports a repeated ID within one enum.irverifyreports a member ID outside its enum's namespace or path (ir/member-id-scope).EnumMember.IDas the collision key.Deliberately not changed
defaulthint beside a same-named tag, per feat(ir)!: identify operation groups and declare ID namespaces #801's recorded decision (openapi: a synthesized operation group can share its name with a declared tag #810, closed). Reporting such collisions is pass: report names that collide once canonicalized #647.irverifycheck that a parameter ID sits under its own operation's ID, and no grammar forProperty.Argsparameter IDs, which no compiler mints yet. Both are stated inir-design.md.Breaking
IRVersionmoves from0.7.0to0.8.0. §2.1 bumps once where a change lands onmain, and 0.7.0 already landed with feat(ir)!: identify operation groups and declare ID namespaces #801.CompatibleVersionis exact, so a stored 0.7.0 document is refused.HTTPParamBinding.Param,ParamPath.ParamandIdempotency.TokenParamchange from a parameter name to aParamID.ParameterandEnumMembergain a requiredid.Document.IDSpacesgains theparamandekinds.Test plan
make gatepasses on this branch (basee0c9762), including 100% statement coverage, golangci-lint v2.14.0, fuzz and bench smoke.Every new test was checked by planting the defect it guards against, watching it go red, then reverting. Highlights:
TestParams_PathItemParamIsScopedPerOperation,param-identity,component-reuseindropped from the IDTestParams_SameNameInTwoLocationsGetsDistinctIDsTestParams_IDsFollowNameNotPositionparam-identityTestValidate_ParamReferences_OtherOperationsParamIsRejectedparamoremissing fromIDSpacesir/id-space-undeclared)TestEnumMemberKey_IsInjectiveAcrossKindsopenapiTestVerify_MemberIDOutsideItsEnumIsAViolationNew conformance fixtures:
param-identity:idin query and path; a path-item parameter on GET and POST; a$ref'd path item mounted at two paths, declared in the order that would expose a declaration-pointer ID.enum-member-identity:[a-b, a_b, "A B", "a/b", "a#b", a-b],[-1, 1, 0]and an inline enum.The goldens are not a no-op regeneration. Deleting the query
idor the membera_bfrom its fixture reddens the suite without-update.The harness reports
okon both new fixtures.Probes with
morphic compile/validate:idquery/path pair, each binding naming its own;getanddelete;Both documents validate with exit 0.
A duplicate
(name, in)on one operation is already an error diagnostic (validation-operation-parameters), so two parameters can never share one ID.🤖 Generated with Claude Code
https://claude.ai/code/session_016MQFAkYBVaqgSxGwe9ZJSn