Anyone any idea?
# groovy
v
Anyone any idea?
p
Yes, STC should probably capture that. Regarding further tricks, here is an AI read: Short answer: no — there is no plain-syntax trick, and I verified that empirically against the current branch head (master + the 12314 work) with a local package-private-enum fixture. The probe results: ┌─────────────────────────────────────────────┬─────────┬────────────────────┐ │ Form │ Dynamic │ @CompileStatic │ ├─────────────────────────────────────────────┼─────────┼────────────────────┤ │ Outer.PropertyKeys (class ref) │ works │ works │ ├─────────────────────────────────────────────┼─────────┼────────────────────┤ │ Outer.PropertyKeys['localValueSet'] │ works │ works │ ├─────────────────────────────────────────────┼─────────┼────────────────────┤ │ Outer.PropertyKeys.localValueSet │ works │ IllegalAccessError │ ├─────────────────────────────────────────────┼─────────┼────────────────────┤ │ Outer.PropertyKeys.@localValueSet │ works │ IllegalAccessError │ ├─────────────────────────────────────────────┼─────────┼────────────────────┤ │ Outer.PropertyKeys.valueOf('localValueSet') │ works │ IllegalAccessError │ ├─────────────────────────────────────────────┼─────────┼────────────────────┤ │ Outer.PropertyKeys.with { localValueSet } │ works │ IllegalAccessError │ └─────────────────────────────────────────────┴─────────┴────────────────────┘ The reason it's structural, not a missing feature: under @CompileStatic, any form that keeps the identifier requires STC to resolve localValueSet as a member of UIInput$PropertyKeys, and resolution means a direct bytecode reference (GETSTATIC, or INVOKESTATIC for valueOf) — and the JVM refuses to resolve the class itself from outside its package, before it even looks at the member. So .@ buys nothing here (it's an even more direct field access), and even valueOf and a statically-compiled closure fail identically. The two working forms work precisely because they avoid resolution: the bare class reference is emitted via the Class.forName-style fallback classgen uses for inaccessible class literals, and the subscript degrades to the dynamic getAt path — which is why it's String-y. Identifier-preserving and dynamic-free are mutually exclusive by construction. (Worth remembering: javac won't compile any of the three lines — PropertyKeys is deliberately internal jakarta.faces API, which is the real message of GROOVY-12310.) The one recipe that meets the constraints — identifier intact, IDE navigation/refactoring works, no dynamic helper method — is a type-checking extension that marks just that expression for dynamic emission. Verified working on the branch: // InaccessibleEnumExtension.groovy, on the compile classpath import org.codehaus.groovy.ast.CodeVisitorSupport import org.codehaus.groovy.ast.expr.PropertyExpression afterVisitMethod { mn -> mn.code?.visit(new CodeVisitorSupport() { @Override void visitPropertyExpression(PropertyExpression pexp) { if (pexp.propertyAsString == 'localValueSet') { makeDynamic(pexp) } super.visitPropertyExpression(pexp) } }) } with @CompileStatic(extensions='InaccessibleEnumExtension.groovy') on Foo. STC still resolves the reference (so the IDE link to localValueSet survives), but classgen emits an indy property access for that one expression, which the dynamic runtime satisfies reflectively. Whether that's lighter than a @CompileDynamic helper method is a matter of taste — it's more machinery, but it's declarative and scales to a name-list if this pattern recurs.
v
Hm, thanks. So
makeDynamic
does not make the whole method dynamically compiled, just that one statement? I had a look at the
makeDynamic
method in the past and have seen that it does
Copy code
context.getEnclosingMethod().putNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION, Boolean.TRUE);
I interpreted this as the whole method being compiled dynamically.
p
AI read: Good catch: what you saw was right for older versions, but GROOVY-11817 changed it. • 4.0.x and 5.0.0–5.0.3:
makeDynamic
set
DYNAMIC_RESOLUTION
on the enclosing method too, and
StaticTypesWriterController.getCallSiteWriter()
checked that flag. So any marked expression switched the whole method to the dynamic call-site writer. Not quite "the whole method compiled dynamically", but close enough to matter. • 5.0.4+, 5.1.x and 6.0.0 (GROOVY-11817): the writer is now picked per expression, via
getCallSiteWriterFor(expression)
, and only the marked expression goes through the regular (indy) call-site writer. The rest of the method stays statically compiled. In 6.0.0
makeDynamic
no longer marks the method at all. In the 5.x backport the method-level line is still there, but codegen no longer reads it to choose the writer; it only tells codegen to set up the dynamic writer's state at method entry. So on current versions only that
localValueSet
property access is dynamic. On 4.0.x the extension still works, but its effect is roughly the same as a
@CompileDynamic
helper method.
v
Ah, nice. We are still on Groovy 4 where I had hit that, though, but good to know for the future. Shouldn't it then also be possible to have some
doDynamic { ... }
helper that marks its contents as dynamically complied targetedly? Or does something like that already exist in later versions?
p
The current level of granularity is to refactor the insides of your doDynamic call into it's own method, but you are right that now we could be finer grained if we wanted to. I guess it's whether we hold back that implementation detail in case we wanted to change it again in some way without affecting everyone.
v
Yeah, the helper method is what I have, just wondered whether there is a nicer - more groovy - option 🙂