GROOVY-12115: Reify a receiver's type argument as a Class parameter u… - #2645
GROOVY-12115: Reify a receiver's type argument as a Class parameter u…#2645paulk-asert wants to merge 3 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2645 +/- ##
==================================================
+ Coverage 69.8702% 69.8875% +0.0173%
- Complexity 35139 35228 +89
==================================================
Files 1554 1555 +1
Lines 130731 130939 +208
Branches 23916 23990 +74
==================================================
+ Hits 91342 91510 +168
- Misses 31144 31156 +12
- Partials 8245 8273 +28
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR implements GROOVY-12115 by introducing @ClassTag, enabling the static type checker to synthesize missing Class<T> token arguments from a receiver’s generic type arguments under @TypeChecked/@CompileStatic. It also adds a compiler-configuration allowlist to control when this token synthesis may preempt an existing token-less overload (defaulting to Groovy’s withDefault).
Changes:
- Add
groovy.transform.stc.@ClassTagand integrate token-synthesis + retry selection intoStaticTypeCheckingVisitor. - Annotate relevant GDK APIs (
asChecked, checkedwithDefaultoverloads) so tokens can be synthesized under static checking. - Add
CompilerConfiguration.classTagPreemptionTargets(with tests) and introduce new STC tests covering additive injection, preemption, rollback behavior, and config gating.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
src/main/java/groovy/transform/stc/ClassTag.java |
New parameter annotation defining the @ClassTag contract for compiler-supplied Class tokens under static checking. |
src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java |
Implements the token injection/preemption logic, retry selection, rollback behavior, and validates @ClassTag("...") overrides. |
src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java |
Marks asChecked and checked withDefault overload token parameters with @ClassTag and updates documentation accordingly. |
src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java |
Adds configurable allowlist for preemptive @ClassTag overload selection and preserves immutability of CompilerConfiguration.DEFAULT. |
src/test/groovy/org/codehaus/groovy/control/CompilerConfigurationTest.java |
Tests defaults, copy semantics, null-handling, and immutability for classTagPreemptionTargets. |
src/test/groovy/groovy/transform/stc/ClassTagStaticTest.groovy |
New STC regression tests validating token synthesis, preemption behavior, rollback, and configuration gating under @CompileStatic. |
Comments suppressed due to low confidence (1)
src/main/java/groovy/transform/stc/ClassTag.java:58
- Minor grammar issue in the Javadoc: “a wildcard-only type do not” should be “does not”.
* <li><em>The type variable must be statically known.</em> {@code List<String>} works;
* {@code def} / raw {@code List} / a wildcard-only type do not — no token is synthesised.</li>
Request changes before merge:1. Method selection: inject → re-select → rollbackIn Suggestion: Treat 2. Preemption policy via method-name config is the wrong layer
Suggestion: Co-locate preemption with the API (e.g. an attribute on 3. ClassTag logic should not grow
|
…nder @CompileStatic (@ClassTag)
…comments @ClassTag gains preempt() (default false) — intent. Same-owner containment (new, in ClassTagSupport.matchPreemption) — the safety invariant. classTagPreemptionTargets → classTagPreemptionDisabled — consent, inverted to a veto in exactly the groovy.extension.disable selector format Tests — 32 in ClassTagStaticTest
|
Thanks @daniellansun and @blackdrag for the thorough reviews — nearly everything has been actioned, and the preemption design has been reworked substantially in response. Details below; the branch is updated. Preemption redesign: intent + containment + consentThe
For Jochen specifically: Daniel's review items
Jochen's test questions
Status / open items
Two known follow-ups: (a) the preemption check now runs without the old O(1) name pre-gate — it's bounded (cached DGM lookup filtered by owner / receiver-hierarchy scan), but I'd like a compile-time benchmark on a large Comments on the revised model welcome — particularly whether |
Follow-up (a) in more detail: preemption-check cost — benchmark plan + proposed gateExpanding on the compile-time concern noted above, since it's the one open item that could block merge. The cost, preciselyThe earlier allowlist design had an accidental virtue:
Almost always the outcome is "no preempt-intent overload exists here" — we pay enumeration cost to discover a negative that is knowable in advance. That's the target. Proposed gate: a per-classloader "preemptive names" set in
|
…comments Performance tweaks
Follow-up (a): done — preemption-check cost measured, gate landedResolving the compile-time question I flagged above. Short version: the concern was real, the gate fixes it, and it's now measured rather than asserted. BenchmarkAdded The regression, confirmedWithout the gate, preemption matching runs on every resolved call once a token-less overload has matched: (avgt ms/op, 60 classes × 80 calls, 2 forks × 5+5 iterations.) The The gate
Post-gate
Diff and verificationThree files: the benchmark (new), One documented residual
|
✅ All tests passed ✅🏷️ Commit: 862c01e Learn more about TestLens at testlens.app. |
…nder @CompileStatic (@ClassTag)