#[derive(jedem::Enum)]: each language gets a real enum - #9
Merged
Conversation
zmaril
force-pushed
the
feat/derive-enum
branch
from
August 18, 2026 16:37
d192c09 to
1d26415
Compare
zmaril
force-pushed
the
feat/derive-enum
branch
from
August 18, 2026 20:44
1d26415 to
d5b429b
Compare
zmaril
force-pushed
the
feat/derive-enum
branch
from
August 18, 2026 20:45
d5b429b to
0aaf54f
Compare
zmaril
force-pushed
the
feat/derive-enum
branch
from
August 18, 2026 20:46
0aaf54f to
c9dd46e
Compare
…tring The last item, and the one whose cost was never really about lines. jawohl had to write a describe() converter turning Validation into a String, so Python received 'irrecoverably_invalid' as a bare string and TypeScript received `string`. The API was degraded to fit the vocabulary. Now a C-like enum crosses as that language's own enum. Python gets a real class via pyclass, registered on the module; TypeScript gets a string-literal union via napi's string_enum. Both reject a value that is not a variant, which is the whole point -- a typo is caught by the language's own tooling rather than surfacing as a value nobody matched. Variants carry an optional boundary spelling, pinned with #[jedem(name = ...)] for values whose wire form is already fixed. Three things the implementation forced, each worth keeping: Wire names must be valid identifiers. A first draft allowed "n/a", which made the pyclass macro fail to expand and cascaded into seven confusing errors in Python only -- the worst place to discover it. A variant has to be NAMEABLE in every target, so the derive now rejects non-identifiers with a message saying why. From<A> for B does not give Option<A> -> Option<B>. Enum conversion is mapped through containers by a shared convert() helper, so Option<Ripeness> and Vec<Ripeness> work rather than failing at the binding's compile step. Enums are collected by walking the ops rather than listed in surface!, because the macro cannot resolve types. That also means an enum reached only through an Option or a Vec is still declared -- otherwise a binding names a type it never defined. An unknown named type in a signature now lowers as an enum, and the curated "jedem cannot lower this" message moves to a diagnostic::on_unimplemented on the EnumType trait, so a genuinely unsupported type still gets the explanation including that there is deliberately no opaque-blob fallback. Adding the Type variant made both backends fail to compile until they handled it, which is the exhaustiveness safety net working as intended. 43 tests, clippy clean, both host round-trips exercising enums as returns, as parameters, and inside Option. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HsDxLrGdx6nPaXkVEWkNvS
zmaril
force-pushed
the
feat/derive-enum
branch
from
August 18, 2026 20:47
c9dd46e to
66af254
Compare
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.
Stacked on #8. The last of the five, and the one whose cost was never really
about lines.
jawohl had to write a
describe()converter turningValidationinto aString, so Python received'irrecoverably_invalid'as a bare string andTypeScript received
string. The API was degraded to fit the vocabulary.Python gets a
pyclassregistered on the module; TypeScript gets napi'sstring_enum. Both reject non-variants — a typo is caught by that language'sown tooling instead of surfacing as a value nobody matched. Variants take an
optional boundary spelling via
#[jedem(name = "...")].Three things the implementation forced
Wire names must be valid identifiers. My first draft allowed
"n/a", whichmade the
pyclassmacro fail to expand and cascaded into seven confusing errorsin Python only — the worst place to find out. A variant has to be nameable
in every target, so the derive rejects non-identifiers with a message saying why.
From<A> for Bdoes not giveOption<A> → Option<B>. Conversion is mappedthrough containers by a shared
convert()helper, soOption<Ripeness>andVec<Ripeness>work rather than failing at the binding's compile step.Enums are collected by walking the ops, not listed in
surface!, because themacro cannot resolve types. That also means an enum reached only through an
Optionis still declared — otherwise a binding names a type it never defined.And a nicer error for everything else
An unknown named type now lowers as an enum, with the curated "jedem cannot lower
this" message moved to a
diagnostic::on_unimplementedon theEnumTypetrait —so a genuinely unsupported type still gets the explanation, including that there
is deliberately no opaque-blob fallback.
Adding the
Typevariant made both backends fail to compile until theyhandled it. That is the exhaustiveness safety net working as designed.
43 tests, clippy clean, both round-trips exercising enums as returns, as
parameters, and inside
Option.🤖 Generated with Claude Code
https://claude.ai/code/session_01HsDxLrGdx6nPaXkVEWkNvS