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
Conversation
…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
force-pushed
the
docs/xtend-skill-operator-add-and-exceptions
branch
from
August 10, 2026 22:46
627637c to
0b8d690
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Hardens the
.agents/skills/xtend-to-javaskill with three faithfulness rules and one meta-lesson, learned from an independent blind re-migration ofcheck.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
rules/10§10.4 (+rules/08cross-reference) —operator_add(+=) skips nulls in BOTH overloads. The single most dangerous inferrer trap. Any+=on anEListwithJvmTypesBuilderin scope — single element or collection — binds tooperator_add, which silently drops nulls (both overloads share the same guard, which also no-ops on a null list). Theto<Factory>builders returnnullon a null source element or name (a null type never triggers;toConstructorguards only the source element,toGetter/toSetterthe property name). A bareadd/addAllis not faithful: JVM model containment lists reject null (EObjectEList.canContainNull() == false), so the migrated code throwsIllegalArgumentException("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 orObjects::nonNullfilter (the worked example uses JDK streams, per the skill's own xbase.lib-replacement rules).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 Javacatch (Specific)is runtime-identical and preferred; the literal lowering is needed only when undeclared checked exceptions must escape (and then requires a justified CheckstyleIllegalCatchsuppression). Never narrowcatch (Throwable)tocatch (Exception); never wrap innew RuntimeException(e)/IllegalStateException(e).Exceptions.sneakyThrowis the sanctioned xbase.lib exception (the migrate-off-xbase.lib pitfall targets API-surface types).rules/09§9.11 — charset is a sanctioned deviation. PMDRelianceOnDefaultCharsetflags the implicit default; an explicitCharset.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.known-pitfalls— behavioural equivalence ≠ literal-token equivalence. Don't decide "faithful" by whether a token appears inxtend-gen; prove every divergence against freshxtend-genand 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.inferConstantshad translated the Xtendmembers += allConstants.map[createConstant](null-skippingoperator_add) into a barefor { it.getMembers().add(createConstant(format, c)); }— andcreateConstantreturns 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