Skip to content

fix: hold aliases and string lists to their rules, quote messages - #740

Merged
OmarAlJarrah merged 11 commits into
mainfrom
fix/irverify-naming
Oct 13, 2026
Merged

OmarAlJarrah merged 11 commits into
mainfrom
fix/irverify-naming

Conversation

@fuad-daoud

@fuad-daoud fuad-daoud commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Three gaps in what checks an IR document's names and string lists, each settled where the rule belongs.

An alias another entity also answers to (#398). A property alias that a sibling property of the same model answers to, by its wire name or by an alias of its own, leaves a reader resolving that field with two candidates. A source can write one: Avro accepts any string as an alias, and no lowering can drop either claim without choosing between them. So this is a problem in the spec, not a compiler bug. pass/validate now reports it as pass/ambiguous-alias, beside pass/duplicate-wire-name, which already holds that scope.

Across the type registry nothing reports a collision, because nothing in the IR can decide one:

  • A relative Avro alias is qualified by its own type's namespace, so one string on two types in two namespaces names two different types.
  • One document can hold several independent schemas, such as the message payloads of an AsyncAPI document, and each may legally declare the same type with the same alias.

A collision between named types is the compiler's to report, within the schema it parsed. ir-design §3.2 records both halves. TestVerify_AliasClaimedByAnotherEntityIsClean pins that irverify stays out of them, and it still catches a per-list seen map hoisted across Namings.

Every []string field gets a decision (#399). irverify keeps a stringLists table keyed Type.Field, the shape idPositions already uses for ID positions. TestStringLists_AreAllClassified fails when the ir package declares a []string field the table does not classify, or when the table names one that is gone. Two rules come out of it:

  • Paths. TypeCommon.Namespace, Service.Namespace and GraphQLBinding.FieldPath never hold a blank segment (ir/path-segment-blank). A segment may repeat: com.acme.acme has three levels, and every namespace grammar allows that.
  • Lifecycle sets. Visibility.Only, Operation.ParameterVisibility and Operation.ReturnTypeVisibility hold no blank entry and no repeat (ir/set-entry-blank, ir/set-entry-duplicate). ir.Lifecycle is an alias of string, so a text search for []string misses these three. The type-checked test is what found them.

The rest carry no list rule:

Messages quote document text (#400). Every Violation.Message that interpolates text from the document passes it through strconv.Quote, so a message is valid UTF-8 whatever the document holds. Violation.Message now states that contract. The member-ID scope message was the one site still spelling raw bytes. TestVerify_MessagesQuoteWhatTheDocumentSpells pins the exact message at every quoting site, with an ill-formed byte in the quoted text wherever the check still reaches it. Violation.Path is the other half of #400, and #739 holds it.

Alternatives considered

  • Holding cross-entity alias collisions in irverify. irverify reports compiler bugs only. A collision a source wrote is carried by a correct compiler, as two fields with one wire name are, so irverify would report a defect the compiler did not make. At registry scope it would also fire on both legal shapes above.
  • A repeat rule for namespace segments. It would reject a legal namespace such as com.acme.acme, which is three levels deep.
  • A generic list helper with per-list flags. Only two acting rules exist. A table of decisions keeps the verdict for every field in one place a new field cannot skip.

Limitations

  • The blank rules share isBlankName, so irverify: the blank-alias rule misreads Unicode's default-ignorable set #832 (its misreading of Unicode's default-ignorable set) applies to path segments and set entries too.
  • pass/ambiguous-alias compares a model's own properties, the scope pass/duplicate-wire-name holds. Inherited properties and other property lists, such as headers and resource identifiers, are not compared, matching that rule.
  • checkStringLists is one more reflective walk, as every irverify check is. Against main, Verify took 6–9% longer on the three largest reference specs (github.json at 11,968 types: 2.59 s → 2.82 s). Validate was unchanged within noise. ir: build WalkValues paths only when a visitor reads them #813 tracks walk cost in general.

Test plan

  • go test ./ir/irverify ./pass ./internal/archtest -count=1
  • Mutation sweep. Each strconv.Quote site in ir/irverify, unquoted one at a time, reddens a test. Each of the following, planted one at a time, reddens a named test:
    • in stringlists: the blank rule, the repeat rule (off, and on for paths), the ill-formed skip, the path/set selection, the first-index bookkeeping, three of the classifications, an unlisted field let through rather than held as a path, and a held list without a test fixture or with a fixture expecting the other rule;
    • in the validate check: the wire-name pre-claim, the first-claimant guard, the own-name skip, the empty-alias skip, the reported node, and the per-model scope;
    • and a per-list seen map hoisted across Namings.
  • Real-world sweep: about 2,000 OpenAPI 3 specs (the reference corpora plus testdata), compiled through main and this branch, give identical irverify violations, validate diagnostics and error diagnostics on every spec.
  • make gate, with the pinned golangci-lint v2.14.0, on the branch before main's cycle check (fix(ir): report a document that contains itself #737) was merged in. After that merge: build, vet, lint and the affected packages' tests. CI runs the full gate on the head.

Closes #398
Closes #399
Closes #400

- #398: a new walk check rejects an alias that two type-registry Namings both
  claim. It is reported at the later alias, as ir/naming-alias-shared, and is
  scoped to TypeCommon.Name: an alias is a schema-resolution name and a source
  that writes one scopes it to its own record, so a document-wide compare would
  false-positive on two models stating the same short alias.
- #399: the per-list alias switch becomes appendListViolations, and a TypeCommon
  or Service Namespace is held to its blank and repeat rules, reported as
  ir/namespace-blank and ir/namespace-duplicate. The verdicts for Tags, Scopes,
  content types, Server Enum, versions and FieldPath are recorded in the helper's
  doc comment rather than implemented.
- #400 (message half): every document-derived fragment in a Violation.Message is
  quoted with strconv.Quote, so no message carries raw bytes. Violation.Path is
  out of scope.

Closes #398, #399
Refs #400
@fuad-daoud fuad-daoud self-assigned this Sep 30, 2026
fuad-daoud and others added 9 commits October 7, 2026 08:33
Resolve the conflicts with main, keeping the generalized list rules and
main's condensed wording, and bring the doc comments this branch adds
under the 100-word cap main now enforces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GCzyGPq9DQ7RLfagZm2JB5
Plans and agent working notes are not part of the repository; what a
reviewer needs is in the PR body, the commits and the linked issues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The naming tests build documents without idSpaces, which main's id-spaces-absent rule now reports; they run through verifyDeclared, and namespaceDoc declares its spaces.
The one textual conflict is irverify.go's imports, where main's sort by
slices and cmp meets this branch's strconv. Main also moved the tests'
source reads onto type-checked declarations, so the namespace-owner test
reads the TypeKind constants through irDecls rather than the parsed-source
helper it was written against.
An alias that another property of the same model answers to, by its wire
name or by an alias of its own, leaves a reader resolving that field with
two candidates. A source can write one, since Avro accepts any string as
an alias, so it is a problem in the spec rather than a compiler bug:
pass/validate reports it as pass/ambiguous-alias, beside the duplicate
wire names that same scope already holds.

The registry-wide alias check in irverify is removed. Nothing in the IR
decides a collision between types: a relative Avro alias is qualified by
its own type's namespace, so one string on two types can be two names,
and one document can hold independent schemas that each declare the same
type and alias. ir-design §3.2 records both halves, and a pin test keeps
irverify out of them.
Every []string field the ir package declares now has a decision in
stringLists, keyed Type.Field as idPositions keys ID positions, and
TestStringLists_AreAllClassified fails when a field is added without one.

A path (TypeCommon.Namespace, Service.Namespace, GraphQLBinding.FieldPath)
holds no blank segment, reported as ir/path-segment-blank. A repeated
segment is legal, since com.acme.acme has three levels, so the namespace
repeat rule is removed. A lifecycle set (Visibility.Only and an
operation's ParameterVisibility and ReturnTypeVisibility) holds no blank
entry and no repeat, reported as ir/set-entry-blank and
ir/set-entry-duplicate. ir.Lifecycle aliases string, which is why a text
search for []string fields never listed these three.

Tags, scopes, media types, encodings and server variable values are the
source's own entries, which only the source format can judge; #738 holds
OpenAPI's tags and scopes. Version labels answer to Document.Versions as
a whole, which #860 tracks.
Violation.Message now states its contract: text taken from the document
is strconv.Quote'd, so a message is valid UTF-8 whatever the document
holds. The member-id scope message was the one site still interpolating
raw bytes.

TestVerify_MessagesQuoteWhatTheDocumentSpells reaches every message that
interpolates document text and pins its exact spelling, with an ill-formed
byte in the quoted text wherever the check still reaches it. Unquoting any
site reddens its own case; before it, most sites could lose their quote
with every test green. Violation.Path is #739.
Main added the cycle check, whose export beside ValueCarriers met this
branch's HeldLists in export_test.go; both are kept.
The cyclic-structure message interpolates the path a reference was
entered at, and that path spells map keys from the document. Its case
builds the cycle under a type ID holding an ill-formed byte, so the table
reaches every message that quotes document text.
@OmarAlJarrah OmarAlJarrah changed the title fix(irverify): tighten naming rules and quote messages fix: hold aliases and string lists to their rules, quote messages Oct 13, 2026
@OmarAlJarrah
OmarAlJarrah merged commit 322271e into main Oct 13, 2026
1 check passed
@OmarAlJarrah
OmarAlJarrah deleted the fix/irverify-naming branch October 13, 2026 18:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants