Skip to content

docs(emitters): encode every enum member as its JSON kind - #1334

Open
OmarAlJarrah wants to merge 6 commits into
docs/emitters-policy-layersfrom
docs/emitters-enum-value-types
Open

OmarAlJarrah wants to merge 6 commits into
docs/emitters-policy-layersfrom
docs/emitters-enum-value-types

Conversation

@OmarAlJarrah

@OmarAlJarrah OmarAlJarrah commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Summary

An enum whose value type has no Go constant form had no Go lowering. Under docs/emitters/targets/go/types.md a closed enum was typed constants, and Go declares constants of boolean, numeric and string types alone. docs/emitters/contract/type-table.md built an enum value by decoding its wire form through the row's codec, which no constant can be. Sweeping the mechanism ("a target's enum form cannot hold every value type") found a second, wider site the issue ruled out. Kotlin's enum class with @SerialName members writes every member as a string, so a bool or numeric enum goes out as a string its schema does not admit. On the golden spec that is 1,069 bool, 35 integer and 348 number enums (spec @319dc756, morphic @b206dc99), so the Kotlin half is on the golden spec even though the Go half is not.

  • One shared rule (docs/emitters/refine/enums.md#a-member-on-the-wire). A member's wire form is its WireName, else its Value as the JSON kind of the enum's ValueType. Every target decodes by comparing as JSON compares values: numbers by value, so 0.1 and 0.10 are one member, and everything else by text whatever format says, which is what the enum keyword compares.
  • Go. An enum is a named type over its value type's row with a typed constant per member. Where the row has no constant form (date, time, datetime, datetime_offset, duration, uuid, bytes, decimal, and an exactly mapped integer or number), it is a named string over the member's wire text instead. Its codec writes the value type's JSON kind, so a decimal stays a JSON number, and a Value() method decodes the text through the row's codec. That reconciles the type table, which now says so. This is the issue's first alternative, with the number case it left open settled. The second, package-level variables built through the codec, lost for the reasons the issue gave: a var can be assigned, and == is wrong for time.Time and absent for a decimal over math/big. Both are recorded in the document.
  • The open form follows. It was "a named string type", which is wrong for a numeric or boolean open enum and matters more now that docs(emitters): layer runtime policy and read embedded policy #1332 opens every enum a client reads. It is now a named type over the value type's row, or the wire text where the row has no constant.
  • Kotlin. A string enum keeps @SerialName. Every other closed enum gets a generated KSerializer writing its JSON kind, and an entry of a mapped value type holds val value: LocalDate and the like, built through the row's codec.
  • T-36 pins every value type, closed and open, on every target.

Out of scope, as the issue says: which primitive ValueType holds (#1191 settled it; #1325 adds local time and datetime forms, which the rule's list of rows with no Go constant already covers).

Closes #1202

Test plan

  • The Go sample, extracted verbatim with a body for the Value signature, passes go vet.
  • Golden enum value types counted from the IR: bool 1,069, integer 35, number 348, string 6,862.
  • CI: the doc tests over the new anchor and rows.

Stacked on #1332 (then #1331 and #1329), since #1332 rewrote the same enum paragraphs. Merge in order. After each lands, "Update branch" on the next is a merge of main that conflicts nowhere.

An enum whose value type has no Go constant form, a date, a uuid or a
decimal, had no Go lowering, and the type table built an enum value by
decoding its wire form, which no constant can be. Kotlin's enum class
writes every member as its @SerialName string, so a boolean or numeric
enum went out as a string its schema does not admit.

A member is now written as the JSON kind its value type has, and
decoded by comparing as JSON compares values, numbers by value. Go
declares an enum over its value type's row, and over the wire text where
the row has no constant, the codec writing the value type's JSON kind and
a Value method giving the row's type. Kotlin generates a KSerializer for
every enum that is not a string enum, and an entry of a mapped type holds
its value as that type. T-36 pins every value type on every target.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant