Repository navigation
Conversation
CasingConfiguration.computeName() built Java identifiers via toCamelCase(), which uses a word-splitting regex that never matches underscores. An all-underscore input like "_" therefore collapsed to an empty string before the reserved-keyword check ever ran, so JavaPoet rejected the resulting empty method name for a union discriminant of "_". Short-circuit all-underscore inputs before word-splitting so they keep their literal underscores, and add "_" to JAVA_RESERVED_KEYWORDS so it escapes to "__" like any other reserved identifier, while the serialized discriminator value stays "_". Fixes fern-api#17431
There was a problem hiding this comment.
AI Review Summary
Narrow, targeted fix: short-circuits all-underscore names before word-splitting and adds _ to the Java keyword set. Logic looks correct for the reported case, but the underlying failure mode (casing collapsing input to "") is broader than underscores, and the new tests only assert the camel variants of a 9-positional-arg NameParts.
- 🔵 3 suggestion(s)
| if (ALL_UNDERSCORES.matcher(name).matches()) { | ||
| String safeName = sanitizeName(name); | ||
| return new NameParts(inputName, name, safeName, name, safeName, name, safeName, name, safeName); | ||
| } |
There was a problem hiding this comment.
🔵 suggestion
This fixes underscores specifically, but any input whose characters don't match SPLIT_WORDS_PATTERN collapses to "" and will blow up JavaPoet the same way (e.g. discriminator values "-", "@", "$$", "-_-"). Consider a general fallback after casing: if the computed camel/pascal name is empty, fall back to the preprocessed input (or a sanitized placeholder) rather than special-casing ^_+$. Same class of bug, one guard.
There was a problem hiding this comment.
Fixed — you were right, this was too narrow. Generalized it to strip illegal-for-Java-identifier characters and keep whatever's left, falling back to _ (escaped to __) only if nothing legal remains.
Verifying that end-to-end (union with two wordless variants, - and @, as siblings) actually surfaced a worse follow-on bug your suggestion's exact approach would've hit too: both fell back to the same placeholder, so generation reported success but silently emitted non-compiling Java (duplicate is_() method, duplicate __Value nested class). Fixed by encoding the stripped characters' code points instead (- -> __2d, @ -> __40) so distinct inputs can't collide. Details in the updated PR description.
|
|
||
| if (ALL_UNDERSCORES.matcher(name).matches()) { | ||
| String safeName = sanitizeName(name); | ||
| return new NameParts(inputName, name, safeName, name, safeName, name, safeName, name, safeName); |
There was a problem hiding this comment.
🔵 suggestion
Nine positional args alternating name/safeName is easy to get wrong (or to silently mis-order if the record's field order ever changes) and the new tests only assert camelUnsafe/camelSafe, so a swap would go unnoticed. Worth asserting pascal/snake/screamingSnake variants in the tests too.
There was a problem hiding this comment.
Fixed — added assertions on pascalSafe/snakeSafe/screamingSnakeSafe (not just camelSafe) in the new WordlessNameTests, so a mis-ordered NameParts arg would now get caught.
| rejected the resulting empty method name. All-underscore inputs are now kept | ||
| intact, and `_` is escaped to `__` (matching the existing convention used for | ||
| other reserved words), while the serialized discriminator value is unchanged. | ||
| type: fix |
There was a problem hiding this comment.
🔵 suggestion
The fix lives in generator-utils, which the model (and other Java generators) also consume. If those generators ship their own changelogs, consider adding matching entries so consumers of fern-java-model etc. see the fix too.
There was a problem hiding this comment.
Checked — model doesn't have the changes/unreleased/ changelog automation that sdk does (no directory, no template), so there's nowhere equivalent to add an entry there right now. Scoping the changelog to sdk to match what's actually been verified end-to-end.
The previous commit only special-cased all-underscore input. Any
discriminator value with no letters or digits (e.g. "-", "@", "-_-")
hits the same splitWords()-collapses-to-empty-string bug and crashes
JavaPoet the same way, since word-splitting never recognizes those
characters as words either.
Generalize the fallback: when casing produces an empty name, strip
characters that aren't legal in a Java identifier and keep whatever's
left ("$$" stays "$$", "-_-" becomes "_"), falling back to "_" (then
escaped to "__") only if nothing legal remains.
The wordless-name fallback added in the previous commit collapsed any
input with no legal Java-identifier characters left (e.g. "-", "@") to
the same placeholder ("_"). Two such discriminants as siblings in one
union therefore generated identically-named methods and a duplicate
nested class - generation reported success but emitted non-compiling
Java.
Encode the stripped characters' code points instead of collapsing to a
shared placeholder, so distinct inputs always produce distinct
identifiers (e.g. "-" -> "__2d", "@" -> "__40").
|
This PR is stale because it has been open 25 days with no activity. Remove stale label or comment or this will be closed in 5 days. |
|
@iamnamananand996 Related to #17430, this fixes a separate casing failure for wordless discriminator values such as |
|
This PR is stale because it has been open 25 days with no activity. Remove stale label or comment or this will be closed in 5 days. |
Description
Closes #17431
CasingConfiguration.computeName()builds Java identifiers viatoCamelCase(), whose word-splitting regex only recognizes letter/digit runs. Any discriminator value with no letters or digits at all (e.g."_","-","@","-_-") therefore collapsed to""before the reserved-keyword check ever ran, so JavaPoet rejected the resulting empty method name (IllegalArgumentException: not a valid name:), aborting generation.This is a different root cause from #17430 (missing
null/true/falseentries in the same keyword set) — here the keyword list is never even reached, since the input is discarded earlier in casing conversion.The fix went through two iterations, both included in this PR:
_when nothing legal remains. Verifying this end-to-end (a union with two wordless variants,"-"and"@", as siblings) surfaced a follow-on bug: distinct wordless values all fell back to the same identifier, so generation "succeeded" but silently emitted non-compiling Java (duplicate method signatures, a duplicate nested class name). Fixed by encoding the stripped characters' code points instead of collapsing to a shared placeholder, so distinct inputs never collide.Changes Made
CasingConfiguration.computeNameInternal(): when casing produces an empty name, fall back to legal Java-identifier characters stripped from the input, or an encoding of the input's code points (e.g.-→__2d,@→__40) if none remain, so distinct wordless discriminants never collide"_"toJAVA_RESERVED_KEYWORDSso a preserved"_"gets escaped to"__", consistent with how other reserved words are handled_/__/___(escaped/already-valid), ordinary underscore-separated names (unaffected), wordless symbols (-,@,-_-,$$), and a non-collision assertion between distinct wordless inputsgenerators/java/sdk/changes/unreleased/Testing
generator-utils/model/sdk, all passing;spotlessCheckpassing)fernapi/fern-java-sdkDocker image from this branch's source at each iteration and ranfern generate --localagainst:"_"repro from Java SDK generation fails on discriminator value _ #17431 → succeeds,public static Filter __(UnderscoreFilter value),@JsonTypeName("_")"-"-only repro → succeeds,public static Filter __2d(DashFilter value),@JsonTypeName("-")"-"and"@"variants → succeeds with distinct identifiers (__2d/__40,is__2d()/is__40(),__2dValue/__40Value), confirming the collision is fixednull/true/false) against a combined build to confirm the two PRs don't interact