Skip to content

ide-template: an authored default is read the same way in every generated language (#7293) - #7336

Merged
delchev merged 1 commit into
masterfrom
issue-7293-java-literals-unquote
Sep 12, 2026
Merged

ide-template: an authored default is read the same way in every generated language (#7293)#7336
delchev merged 1 commit into
masterfrom
issue-7293-java-literals-unquote

Conversation

@delchev

@delchev delchev commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Cause

One authored defaultValue: is written into several generated languages - the repository's Java literal (JavaLiterals) and the item dialog's JavaScript seed (JsLiterals) - and AuthoredDefaults exists to state the invariant that every one of them has to read the authored text the same way, or the value the column holds and the value the form offers stop agreeing.

They did not. JsLiterals.defaultValueExpression unquotes first, so the SQL-quoted shape a working DB DEFAULT needs ('20') reads as the bare one; JavaLiterals.defaultValueExpression unquoted in the String arm only. So defaultValue: "'20'" on an integer column seeded 20 in the item dialog and emitted

entity.Quantity = Integer.valueOf("'20'");

in the repository (Repository.java.template:152) - a NumberFormatException on every create that relied on the default, in the create path every writer passes through.

Change

  • Every arm of JavaLiterals.defaultValueExpression reads the authored text through AuthoredDefaults.unquote, once, up front.

  • The boolean reader does the unquoting itself, in AuthoredDefaults.readsAsTrue, rather than in the Java half: 'true' now reads as true in BOTH languages, where unquoting it on one side only would have reintroduced the very disagreement being fixed.

  • Each numeric arm validates the text by running the factory the generated expression calls (Integer::valueOf, BigDecimal::new, …), so an unparsable numeric default is refused while the author is generating, with a message naming the property, its value and its type - the precedent resolveMultiselect already set in the same class.

    That replaces the previous "fails that one create instead of failing the whole generated build" rationale, which understated the cost: the literal is assigned in the repository's create path, so Integer.valueOf("8.0") failed every create of that entity, not one field - and the .schema carries the same text as the column's DB DEFAULT, which PostgreSQL rejects at DDL time anyway. Every default in the repository's own fixtures and ITs parses, so nothing that generates today stops generating.

Verification

  • ide-template unit suite: 147/147, including the new cases - the '20' case beside the JsLiterals one and the refusal case in JavaLiteralsTest, the quoted-boolean parity case in JsLiteralsTest, and the invariant end to end in ModelParameterProcessorTest, where both literals are asserted together since agreeing is the whole point.
  • ModelGenerationIT green (the generation regression harness).
  • IntentEngineIT 80/80 and IntentEmissionCoverageIT green - the latter compiles the generated client Java, so the emitted literals really compile.
  • mvn formatter:validate green with the formatter cache wiped, and -P release javadoc green on the module.
  • Not verified: no browser IT was run (the change is server-side generation only), and no PostgreSQL leg locally.

Fixes #7293

🤖 Generated with Claude Code

…ated language (#7293)

One authored `defaultValue:` is written into several generated languages - the
repository's Java literal and the item dialog's JavaScript seed - and
`AuthoredDefaults` exists to state that every one of them has to read the
authored text the same way, or the value the column holds and the value the
form offers stop agreeing.

They did not. `JsLiterals` unquotes first, so the SQL-quoted shape a working DB
DEFAULT needs reads as the bare one; `JavaLiterals` unquoted in the `String` arm
only. So `defaultValue: "'20'"` on an integer column seeded 20 in the dialog and
emitted `entity.Quantity = Integer.valueOf("'20'")` in the repository - a
NumberFormatException on every create that relied on the default, in the create
path every writer passes through.

- Every arm of `JavaLiterals.defaultValueExpression` reads the authored text
  through `AuthoredDefaults.unquote`, once, up front.
- The boolean reader does the unquoting itself, in `AuthoredDefaults`, rather
  than in the Java half: `'true'` now reads as `true` in BOTH languages, where
  unquoting it in one would have reintroduced the very disagreement being fixed.
- Each numeric arm validates the text by running the factory the generated
  expression calls (`Integer::valueOf`, `BigDecimal::new`, ...), so an
  unparsable default is refused while the author is generating, naming the
  property, its value and its type - the `resolveMultiselect` precedent in the
  same class. This replaces the old "fails that one create" rationale, which
  understated the cost: the literal is assigned in the repository's create path,
  so `Integer.valueOf("8.0")` failed every create of that entity, not one field.
  Every default in the repository's own fixtures and ITs parses, so nothing that
  generates today stops generating.

Tests: the `'20'` case beside the `JsLiterals` one and the refusal case in
`JavaLiteralsTest`, the quoted-boolean parity case in `JsLiteralsTest`, and the
invariant end to end in `ModelParameterProcessorTest` - both literals asserted
together, since agreeing is the whole point.

Verified: `ide-template` unit suite (147), `ModelGenerationIT`, `IntentEngineIT`
(80), `IntentEmissionCoverageIT` - which compiles the generated client Java -
`formatter:validate` with the cache wiped, and the `-P release` javadoc build.

Fixes #7293

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
case "Short" -> "Short.valueOf(\"" + escape(defaultValue) + "\")";
case "java.math.BigDecimal" -> numericExpression("new java.math.BigDecimal", value, javaClass, property,
java.math.BigDecimal::new);
case "Double" -> numericExpression("Double.valueOf", value, javaClass, property, Double::valueOf);
case "java.math.BigDecimal" -> numericExpression("new java.math.BigDecimal", value, javaClass, property,
java.math.BigDecimal::new);
case "Double" -> numericExpression("Double.valueOf", value, javaClass, property, Double::valueOf);
case "Float" -> numericExpression("Float.valueOf", value, javaClass, property, Float::valueOf);
java.math.BigDecimal::new);
case "Double" -> numericExpression("Double.valueOf", value, javaClass, property, Double::valueOf);
case "Float" -> numericExpression("Float.valueOf", value, javaClass, property, Float::valueOf);
case "Long" -> numericExpression("Long.valueOf", value, javaClass, property, Long::valueOf);
case "Double" -> numericExpression("Double.valueOf", value, javaClass, property, Double::valueOf);
case "Float" -> numericExpression("Float.valueOf", value, javaClass, property, Float::valueOf);
case "Long" -> numericExpression("Long.valueOf", value, javaClass, property, Long::valueOf);
case "Integer" -> numericExpression("Integer.valueOf", value, javaClass, property, Integer::valueOf);
case "Float" -> numericExpression("Float.valueOf", value, javaClass, property, Float::valueOf);
case "Long" -> numericExpression("Long.valueOf", value, javaClass, property, Long::valueOf);
case "Integer" -> numericExpression("Integer.valueOf", value, javaClass, property, Integer::valueOf);
case "Short" -> numericExpression("Short.valueOf", value, javaClass, property, Short::valueOf);
@delchev
delchev merged commit 917332a into master Sep 12, 2026
10 checks passed
@delchev
delchev deleted the issue-7293-java-literals-unquote branch September 12, 2026 05:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants