Skip to content

fix(haskell): don't over-rename enum cases when the enum suffix already disambiguates#2969

Merged
schani merged 7 commits into
masterfrom
agent/fix-issue-2868
Jul 22, 2026
Merged

fix(haskell): don't over-rename enum cases when the enum suffix already disambiguates#2969
schani merged 7 commits into
masterfrom
agent/fix-issue-2868

Conversation

@schani

@schani schani commented Jul 20, 2026

Copy link
Copy Markdown
Member

Bug

For a string enum like ["ok", "error"], the Haskell renderer produced
HealthErrorHealth | OkHealth instead of the expected ErrorHealth | OkHealth.

Root cause

Error is listed as a globally forbidden identifier in
packages/quicktype-core/src/language/Haskell/constants.ts. When
ConvenienceRenderer.makeNameForEnumCase styles the enum case name to
Error, it detects a collision with that forbidden global identifier and
picks an alternative (Health_error -> HealthError) before the
enum-name suffix (Health) is textually appended at emit time in
HaskellRenderer.emitEnumDefinition. Since the suffix is only appended
after name resolution runs, the resolver never sees the final,
already-disambiguated identifier (ErrorHealth) when checking for
forbidden-name collisions, so it picks the uglier alternative unnecessarily.
The generated code was correct and round-tripped fine — this was purely
cosmetic.

Fix

HaskellRenderer now overrides makeNameForEnumCase to fold the enum-name
suffix into the proposed name itself, via a DependencyName (the same
pattern already used elsewhere for names that depend on another name, e.g.
proposeUnionMemberName). This means forbidden-word collision checking now
sees the final, suffixed identifier (ErrorHealth) rather than the bare
case name (Error), so it no longer needlessly renames it.

Emit sites (emitEnumDefinition, the ToJSON/FromJSON instances) now use
a small enumCaseName helper: for the common case (a DependencyName that
already includes the suffix) it's used as-is; for FixedNames (explicit
user-assigned accessor names, which bypass makeNameForEnumCase) it falls
back to the previous behavior of textually appending the enum name, since
those names are user-specified and shouldn't be silently changed.

Test coverage

Added a new JSON Schema fixture:

  • test/inputs/schema/haskell-enum-forbidden.schema — an object with a
    string enum ["ok", "error"], i.e. the schema from the issue.
  • test/inputs/schema/haskell-enum-forbidden.1.json — a matching sample.

Round-tripping alone can't distinguish generated constructor names (both
HealthErrorHealth and ErrorHealth round-trip correctly), so
HaskellLanguage.runCommand in test/languages.ts was extended to also
grep the generated QuickType.hs for the expected ErrorHealth
constructor when running this specific sample, failing the fixture if the
old, over-renamed identifier reappears.

Verification

  • npm run build passes.
  • npm run test:unit passes (163 tests).
  • Confirmed the exact repro from the issue now generates ErrorHealth | OkHealth instead of HealthErrorHealth | OkHealth.
  • Ran the Haskell code generator against several other enum-bearing schema fixtures (enum.schema, enum-with-values.schema, enum-large.schema, optional-enum.schema, keyword-enum.schema, top-level-enum.schema, multi-type-enum.schema, cut-enum.schema) to check for regressions — all generate without error and with sensible names.
  • biome check passes on the changed files.
  • The full FIXTURE=schema-haskell script/test run (which compiles and executes the generated Haskell via stack) was not run locally because the Haskell toolchain (stack/ghc) is not installed in this environment; CI will validate this.

Fixes #2868

🤖 Generated with Claude Code

…the enum suffix already disambiguates (#2868)

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
Comment thread test/languages.ts Outdated
Mark Probst and others added 2 commits July 20, 2026 19:49
)

Co-Authored-By: Claude <noreply@anthropic.com>
…chema (#2868)

The new haskell-enum-forbidden.1.fail.enum.json fail sample runs against
every language with the "enum" feature, including cjson. cJSON's generated
enum getters silently fall back to the first enum member on an unrecognized
string instead of failing, the same pre-existing limitation already
documented and worked around for enum.schema, enum-large.schema,
optional-enum.schema, and const-non-string.schema via the shared
skipsEnumValueValidation list. Add the new schema to that list.

Co-Authored-By: gpt-5.6-sol via pi <noreply@openai.com>
@schani

schani commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

Retriggering CI (no CI run was created for the latest push).

@schani schani closed this Jul 21, 2026
@schani schani reopened this Jul 21, 2026
schani and others added 4 commits July 21, 2026 09:18
No workflow run was created for the previous push (b75e57f) despite
several hours passing and a close/reopen of the PR; pushing an empty
commit to force a fresh synchronize event.
…grep hack

The enum-case naming from issue #2868 was checked by a filename-matching
branch in the Haskell fixture's runCommand that grepped the generated
QuickType.hs for `= ErrorHealth`. That approach was both a special-case hack
and, since haskell-enum-forbidden.schema is skipped for the Haskell fixture
via skipsEnumValueValidation (the driver reports decode failures as "null"
and exit 0, so its fail sample can't be detected), dead code that never ran.

Replace it with a unit test that renders the schema to Haskell and asserts
the generated constructors are `OkHealth`/`ErrorHealth` rather than the
over-renamed `HealthErrorHealth`. A round-trip fixture cannot express this:
the constructor name is invisible to JSON serialization, so correct and
over-renamed output round-trip identically. The test fails against the
pre-fix renderer and passes against the fix.

Co-Authored-By: Claude <noreply@anthropic.com>
@schani
schani merged commit 734529c into master Jul 22, 2026
28 checks passed
@schani
schani deleted the agent/fix-issue-2868 branch July 22, 2026 00:59
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.

[BUG]: Forbidden identifiers too strict for Haskell enums

1 participant