Skip to content

feat: lower result<T, E> whose E is not a string - #242

Open
robfischer1 wants to merge 2 commits into
arcjet:mainfrom
robfischer1:typed-result-errors
Open

robfischer1 wants to merge 2 commits into
arcjet:mainfrom
robfischer1:typed-result-errors

Conversation

@robfischer1

Copy link
Copy Markdown

Stacked on #236. Until that merges this PR's diff includes its commits; only the last commit is new here. Once #236 lands I'll rebase onto main.

Any err type other than string panicked:

TODO(#4): implement remaining result conversion

The Go shape stays (T, error). The err case comes back as *ResultError[E], a generic error type that is emitted only when needed, and errors.As recovers the E. A trap or a host failure is never a ResultError.

A host function returns &ResultError[E]{Value: e} for the err case. Any other error has no WIT representation, so it traps the guest, as a panic in a host function already does.

Each enum also gains a String() that returns the WIT case name, so ResultError.Error() reads as WIT spelled it.

Tests:

  • exports::tests::test_export_result_with_enum_err
  • imports::tests::test_import_result_with_record_err
  • imports::tests::test_enum_string_answers_the_wit_case
  • factory::tests::test_result_error_only_when_needed
  • The result-errors example: enum, record and variant E, with and without ok, from guest and from host, plus the trap

🤖 Generated with Claude Code

https://claude.ai/code/session_01K99bnsYaj3w4MMWSF5hTMu

robfischer1 and others added 2 commits October 7, 2026 10:23
WIT scopes a case name to its enum; gravity emitted every case as an
unqualified package constant. Two enums sharing a case name, or a case
sharing a type's name, then redeclared an identifier and the bindings
failed go vet:

    enum_collisions.go:41:2: PeerSvid redeclared in this block

The identity lift had to spell its `via` cases `via-peer-svid`, ... to
dodge `credential-kind`'s `peer-svid`. Every case constant is now the
enum's Go name followed by the case's (ViaPeerSvid,
CredentialKindPeerSvid, ShapePoint beside the record Point), as Go
spells an enumerated constant (http.MethodGet). The enum's qualified
name is already unique (arcjet#197), so its constants are too.

BREAKING: every enum constant is renamed (Red becomes ColorRed). The
upstream examples' tests are updated, and the regressions WIT drops the
comment that called constant qualification out of scope. Qualifying
only colliding names, as arcjet#197 does for types, was rejected: a new enum
would then rename an existing enum's constants.

Tests: imports::tests::test_enum_case_constants_are_named_for_their_enum
(fails without the fix) and the new enum-collisions example
(TestSharedCaseNamesStayDistinct, TestCaseNamedLikeAType; without the
fix go vet fails with the error above).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gravity lowered only result<T, string> and result<_, string>; any other
err type panicked at generation:

    not yet implemented: TODO(arcjet#4): implement remaining result conversion

so the identity lift had to return a `decision { ok: option<resolved>,
refused: option<refusal> }` record instead of result<resolved, refusal>.

The Go shape stays (T, error) / error. The err case comes back as a
*ResultError[E], a generic error type emitted once per file when any
function needs it; errors.As recovers the E, and a trap, a host failure
or any other error is never one. On the host side a function returns
&ResultError[E]{Value: e} for the err case; an error that is not a
*ResultError[E] has no WIT representation and traps the guest, as a
panic in a host function does. Enums, records and variants (including a
record dispatched directly as a variant case) work as E, with or
without an ok type.

ResultError.Error formats its value with %v, so each enum's private
type gains a String that answers the WIT case name ("untyped-peer"), and
the error text reads as WIT wrote it.

Tests: exports::tests::test_export_result_with_enum_err,
imports::tests::test_import_result_with_record_err (both fail without
the fix with the panic above),
imports::tests::test_enum_string_answers_the_wit_case,
factory::tests::test_result_error_only_when_needed, and the new
result-errors example: TestEnumErr, TestRecordErr, TestVariantErr,
TestErrWithoutOk, TestHostReturnsTypedErrs, TestHostPlainErrorTraps
(without the fix the bindings do not generate).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@robfischer1
robfischer1 requested a review from a team as a code owner October 7, 2026 14:26
@arcjet-review arcjet-review Bot added the needs review Awaiting human review label Oct 7, 2026
@arcjet-review arcjet-review Bot removed the needs review Awaiting human review label Oct 7, 2026
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