Skip to content

docs: encode operator_add null-skip, exception lowering, and charset rules in xtend-to-java skill - #1473

Draft
joaodinissf wants to merge 1 commit into
dsldevkit:masterfrom
joaodinissf:docs/xtend-skill-operator-add-and-exceptions
Draft

docs: encode operator_add null-skip, exception lowering, and charset rules in xtend-to-java skill#1473
joaodinissf wants to merge 1 commit into
dsldevkit:masterfrom
joaodinissf:docs/xtend-skill-operator-add-and-exceptions

Conversation

@joaodinissf

@joaodinissf joaodinissf commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

What

Hardens the .agents/skills/xtend-to-java skill with three faithfulness rules and one meta-lesson, learned from an independent blind re-migration of check.core, an archaeology audit of the already-merged migrations, and a three-way adversarial review (2 blind Claude agents + codex) of this change itself.

The rules

  1. rules/10 §10.4 (+ rules/08 cross-reference) — operator_add (+=) skips nulls in BOTH overloads. The single most dangerous inferrer trap. Any += on an EList with JvmTypesBuilder in scope — single element or collection — binds to operator_add, which silently drops nulls (both overloads share the same guard, which also no-ops on a null list). The to<Factory> builders return null on a null source element or name (a null type never triggers; toConstructor guards only the source element, toGetter/toSetter the property name). A bare add/addAll is not faithful: JVM model containment lists reject null (EObjectEList.canContainNull() == false), so the migrated code throws IllegalArgumentException("The 'no null' constraint is violated") at the add site the first time a null flows — where the Xtend original silently skipped. Loud in production, invisible to PMD/Checkstyle/SpotBugs and to every test that doesn't feed a null. Faithful Java needs a null guard or Objects::nonNull filter (the worked example uses JDK streams, per the skill's own xbase.lib-replacement rules).
  2. rules/05 §5.5 — Xtend exception lowering. catch (Specific)catch (Throwable) + instanceof + sneakyThrow; an uncaught checked-exception body → sneakyThrow(raw) with no declared checked exceptions. A plain Java catch (Specific) is runtime-identical and preferred; the literal lowering is needed only when undeclared checked exceptions must escape (and then requires a justified Checkstyle IllegalCatch suppression). Never narrow catch (Throwable) to catch (Exception); never wrap in new RuntimeException(e)/IllegalStateException(e). Exceptions.sneakyThrow is the sanctioned xbase.lib exception (the migrate-off-xbase.lib pitfall targets API-surface types).
  3. rules/09 §9.11 — charset is a sanctioned deviation. PMD RelianceOnDefaultCharset flags the implicit default; an explicit Charset.defaultCharset() would pass the gate while keeping the platform-dependence bug. Honour the data contract's encoding where one exists (e.g. IFile.getCharset()), StandardCharsets.UTF_8 (the project source encoding) otherwise. The repo's two legacy NOPMD suppressions are grandfathered, not precedent.
  4. known-pitfalls — behavioural equivalence ≠ literal-token equivalence. Don't decide "faithful" by whether a token appears in xtend-gen; prove every divergence against fresh xtend-gen and cover it with a test — gates and existing tests only catch what they already exercise.

Why now — this caught a real shipped bug

Rule 1 isn't hypothetical. Applying it to the merged migrations found that FormatJvmModelInferrer.inferConstants had translated the Xtend members += allConstants.map[createConstant] (null-skipping operator_add) into a bare for { it.getMembers().add(createConstant(format, c)); } — and createConstant returns null for a value-less constant, so the migrated code throws where the original skipped. No gate or test caught it because none declared a value-less constant. That fix ships separately (#1474); this PR encodes the rule that prevents the next one.

Skill-only change (markdown under .agents/skills/); no build impact.

🤖 Generated with Claude Code

…rules in xtend-to-java skill

Harden the skill with three faithfulness rules and one meta-lesson learned from an
independent blind re-migration of check.core, an archaeology audit of the merged
migrations, and a three-way adversarial review of this change itself.

- rules/10 §10.4 (+ rules/08 cross-reference): any `+=` on an EList with
  JvmTypesBuilder in scope binds to operator_add, which SKIPS nulls in BOTH
  overloads — single element and collection (each delegates through the same
  null guard, which also no-ops on a null list). The to<Factory> builders
  return null on a null source element or name (not on a null type);
  toConstructor guards only the source element, toGetter/toSetter the
  property name. A bare add/addAll is never faithful for a null-capable
  producer: JVM model containment lists reject null (EObjectEList
  canContainNull() == false), so the migrated code throws
  IllegalArgumentException("The 'no null' constraint is violated") at the
  add site the first time a null flows — where the Xtend original silently
  skipped. Invisible to gates and to any test that never feeds a null; this
  shipped once (FormatJvmModelInferrer.inferConstants), cited as the worked
  example. The RIGHT example uses JDK streams (Objects::nonNull), not
  xbase.lib helpers, per the skill's own replacement rules.
- rules/05 §5.5: how Xtend lowers try/catch (catch(Specific) ->
  catch(Throwable)+instanceof+sneakyThrow; uncaught checked body ->
  sneakyThrow(raw), no declared checked exceptions). A plain Java
  catch(Specific) is runtime-identical and preferred; the literal lowering
  is needed only when undeclared checked exceptions must escape, and then
  trips Checkstyle IllegalCatch (justified suppression, §9.11 adjudication).
  Never narrow catch(Throwable) to catch(Exception); never wrap in new
  RuntimeException/IllegalStateException. Exceptions.sneakyThrow is the
  sanctioned xbase.lib exception (pins the Require-Bundle; the pitfall
  targets API-surface types like Pair).
- rules/09 §9.11: charset is a sanctioned deviation — PMD
  RelianceOnDefaultCharset flags the implicit default; the fix is honouring
  the data contract's encoding (e.g. IFile.getCharset()) where one exists,
  UTF_8 (the project source encoding) otherwise — not an explicit
  defaultCharset() that would pass the gate while keeping the bug. The two
  legacy NOPMD suppressions are grandfathered, not precedent.
- known-pitfalls: behavioural equivalence is not literal-token equivalence —
  prove every divergence against fresh xtend-gen and cover it with a test;
  gates and existing tests only catch what they already exercise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joaodinissf
joaodinissf force-pushed the docs/xtend-skill-operator-add-and-exceptions branch from 627637c to 0b8d690 Compare August 10, 2026 22:46
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