[ 
https://issues.apache.org/jira/browse/GROOVY-12293?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18107616#comment-18107616
 ] 

Paul King commented on GROOVY-12293:
------------------------------------

AI thoughts:
{quote}Thanks for raising this. The error-consistency gaps on the Groovy side 
are being handled in the linked GROOVY-12294; some thoughts on the remaining 
parts below.
h5. The Spock {{@Use}} case

That one can't be made consistent from the Groovy side, I'm afraid. Spock's 
{{@Use}} is a runtime extension ({{{}UseExtension{}}} wraps the feature 
execution in {{GroovyCategorySupport.use}} via an interceptor), so the compiler 
never sees a lexical {{use()}} call — there is simply no marker STC could key 
off, which is why you get the plain "cannot find matching method" error. If 
your STC transform pre-expanded {{@Use}} annotations into lexical \{{use(...) { 
}}} blocks before the type checker runs, you'd at least get the category error 
consistently — and it would automatically pick up any future STC support for 
categories.
h5. Why lifting the limitation isn't just method resolution

The category methods could indeed be resolved statically — {{@Category}} output 
is exactly extension-method shape ({{{}static m(TargetType $this, ...){}}}), 
which the static compiler already knows how to call directly. The hard part is 
that it would be a semantic change, not just a resolution change:
 * {{use()}} is _dynamically_ (thread) scoped, not lexically scoped: while the 
block is open, any dynamic dispatch on that thread — including inside methods 
called from the block, and in third-party code — sees the category methods. A 
compile-time version can only ever cover the lexically visible calls.
 * Dynamic {{use()}} is late-bound: method existence and overload selection are 
decided per call against whatever version of the category class is on the 
_runtime_ classpath. Freezing at compile time pins selection (Java-linkage-like 
semantics; version skew surfaces as {{NoSuchMethodError}} instead of silently 
different dispatch). That's the same trade extension modules already made, so 
arguably acceptable — but it is a behavioral difference from the dynamic 
construct being mimicked.
 * Precedence inverts: at runtime, category methods _shadow_ existing instance 
methods; under STC, extension methods _lose_ to declared instance methods (and 
the runtime tie rules are already murky, cf. GROOVY-8121). Faithful freezing 
needs a category-shadows-instance rule the type checker doesn't currently have.
 * It could only work for statically recognizable forms (class literals + 
closure literal) — \{{use(someVar) { }}} would remain an error.

h5. What works today

A {{@Category}} class is already a valid extension class — the same class can 
be registered as an extension module with a one-line 
{{META-INF/groovy/org.codehaus.groovy.runtime.ExtensionModule}} descriptor, and 
then works under {{{}@TypeChecked{}}}/{{{}@CompileStatic{}}} (globally rather 
than scoped, resolved to direct static calls). For mixed codebases there's also 
the type-checking-extension route ({{{}makeDynamic{}}}) with the {{use()}} call 
kept in dynamic code.
h5. Longer term

There's real, long-standing demand for scoped, static-compilation-friendly 
extension methods — which is essentially what a statically-resolved {{use()}} 
would be:
 * GROOVY-2521 ({{{}use{}}} with import-like locality, 2008)
 * GROOVY-11143 (extension methods with static-import locality, 2023)
 * the {{@Use}} annotation asks (GROOVY-3387, GROOVY-8417)
 * JRuby-like parity: Ruby's refinements do exactly this (lexically scoped 
augmentation), though dynamic-only — Groovy could do it 
static-compilation-friendly
 * this issue

With Groovy 6 close to feature freeze, my current thinking is that this belongs 
in a proper design discussion for Groovy 7 rather than special-casing {{use()}} 
blocks under STC now. A declarative mechanism would likely also serve the Spock 
case better than block recognition, since {{@Use}} could map onto it directly.

To make that concrete, a potential shape to support something like what your 
are after is an annotation on a class or script, naming one or more extension 
classes — anything in category/extension-module shape, i.e. public static 
methods whose first parameter is the receiver type, which is exactly what 
{{@Category}} already generates:
{code:groovy}
@UseExtensions(StringExtras)
class MyService {
    def greet(String name) { name.titleCase() }  // resolves to 
StringExtras.titleCase(String)
}
{code}
Within the annotated scope (and only there), the named classes' methods would 
participate in method resolution exactly as extension-module methods do today — 
the type checker sees them, and {{@CompileStatic}} compiles them to direct 
static calls, so there's no runtime registration, no metaclass mutation, and no 
thread-scoped {{use()}} machinery. Effectively it's the existing 
extension-module mechanism with the global registration replaced by a lexical 
opt-in: same method shape, same resolution rules, same codegen — just scoped to 
the annotated unit instead of every compilation unit sharing the classpath. 
Because resolution can work from the {{{}ClassNode{}}}, it would also work for 
extension classes compiled in the same compilation unit, which the 
descriptor-based mechanism can't do.

The open design questions are mostly about dynamic code rather than static: 
what a _dynamic_ method call inside the annotated scope should see (nothing 
extra? a per-call fallback through the category machinery? — the former is 
simplest and still covers the static-compilation use case that motivates the 
feature), and precedence when an extension method collides with a real instance 
method (runtime categories shadow instance methods; extension methods today 
defer to them — the deferring rule is probably the right one for a compile-time 
feature). One cautionary tale worth keeping in mind for the dynamic answer: 
whatever we pick shouldn't reintroduce what -{{@Mixin}}- gave us — that 
transform just injected a {{metaClass.mixin(...)}} call, i.e. unscoped runtime 
metaclass mutation leaking well beyond the annotated code, which is a big part 
of why it was deprecated in favour of traits. The scoping must stay lexical 
even on the dynamic path, or be absent there. Ruby's refinements answer the 
scoping question with lexical-only (not inherited, not transitive), which is 
likely the right default here too. None of this needs new syntax — it's 
resolver and type-checker work only — which is what makes an incubating 
annotation plausible as a first step, with first-class {{{}import 
extension{}}}-style syntax as a possible later refinement if the semantics 
prove out.
{quote}

> STC with category classes or at least consistent error
> ------------------------------------------------------
>
>                 Key: GROOVY-12293
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12293
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Björn Kautler
>            Priority: Major
>
> If you use a category like `use(...) { ... }` the STC throws a compile error, 
> that usage of categories is not possible with STC due to their dynamic nature.
> It would be nice if this limitation could be lifted. Why can't the category 
> method not be resolved like other methods and under static compilation used 
> directly?
> I probably miss something why this is not possible, but maybe then at least a 
> consistent error could somehow be issued. If I have a Spock specification 
> under STC (there is a PR to make this possible and I ported it to an AST 
> transform that also makes it possible) and there have a `@Use(...)` 
> annotation, then the compile error just says that the method is not found 
> while expectation was that it was resolved from the category.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to