[
https://issues.apache.org/jira/browse/GROOVY-12550?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18134296#comment-18134296
]
ASF GitHub Bot commented on GROOVY-12550:
-----------------------------------------
jdaugherty opened a new pull request, #3079:
URL: https://github.com/apache/groovy/pull/3079
With static compilation, the doCall method of a closure is declared with the
closure's inferred return type. When that type was a parameterized reference to
a class of the current compilation unit, its type arguments were dropped, while
a reference to a class read from bytecode kept them. The same source therefore
compiled to a closure class with a different signature depending on what else
was compiled, which defeats compile avoidance in incremental builds.
Only the class node itself, which carries the declared type parameters, is
replaced by its plain reference. A parameterized reference goes through the
same check for unresolved generics as any other type. The return type of a
packed closure is normalised the same way.
`ClosureReturnTypeSignatureTest` compiles a class once together with the
class of its closures' return type and once against it compiled, and compares
the `doCall` signatures. It fails without the fix. The full core test suite
passes with it.
- Jira: [GROOVY-12550](https://issues.apache.org/jira/browse/GROOVY-12550)
- Reproducer, with `groovyc` and a Gradle incremental build:
https://github.com/jdaugherty/groovy-closure-return-type-signature
- Backport to `GROOVY_5_1_X`:
https://github.com/jdaugherty/groovy/commit/b7a2216499c6230bba22de54c084bf4207fc95a2,
on the branch
[`closure-return-type-signature-5_1_X`](https://github.com/jdaugherty/groovy/tree/closure-return-type-signature-5_1_X).
Packed closures do not exist on 5.1.x, so it changes `createClosureClass`
only. The full 5.1.x core test suite passes with it.
The problem reproduces on 4.0.33 and 5.1.3 too. Could this be backported to
5.x once it is merged?
> STC: a closure's doCall drops the type arguments of a return type whose class
> is in the same compilation unit
> -------------------------------------------------------------------------------------------------------------
>
> Key: GROOVY-12550
> URL: https://issues.apache.org/jira/browse/GROOVY-12550
> Project: Groovy
> Issue Type: Bug
> Components: Static compilation
> Affects Versions: 4.0.33, 5.1.3
> Reporter: James Daugherty
> Priority: Major
>
> With static type checking, the {{doCall}} method of a closure is declared
> with the closure's inferred return type. When that type is a parameterized
> type whose class is in the same compilation unit, its type arguments are
> dropped. When the class is read from the class path, they are kept. The same
> source therefore compiles to a closure class with a different signature
> depending on what else is compiled, which incremental builds and build caches
> see as a change.
> {code:groovy}
> interface Up {}
> class Res<T> { List<T> items = [] }
> @groovy.transform.CompileStatic
> class Svc {
> static <V> V timeIt(Closure<V> body) { body.call() }
> def <U extends Up> Res<U> read(Class<U> type) { new Res<U>() }
> Res<Up> load(Class<Up> type) { timeIt { -> read(type) } }
> }
> {code}
> {noformat}
> Svc compiled together with Res: public Res doCall()
> Svc compiled against a compiled Res: public Res<Up> doCall()
> {noformat}
> Reproduced with 4.0.33, 5.1.3 and master. A reproducer with a groovyc script
> and a Gradle build:
> https://github.com/jdaugherty/groovy-closure-return-type-signature
> Grails applications hit this in incremental builds: {{@Transactional}} and
> similar transformations move a method body into a new method without the
> method's type parameters, so a closure there returns a concrete parameterized
> type.
> *Cause*
> {{ClosureWriter.createClosureClass}} normalises the inferred return type with:
> {code:java}
> else if (returnType.isPrimaryClassNode()) returnType =
> returnType.getPlainNodeReference();
> {code}
> {{isPrimaryClassNode()}} is true for the class node of a class in the
> compilation unit and for every reference to it, including a parameterized one
> such as {{Res<Up>}}, and {{getPlainNodeReference()}} drops the type
> arguments. A reference to a class read from bytecode is not primary, so it
> keeps them unless they are unresolved. On master,
> {{ClosureWriter.inferredClosureReturnType}} does the same for packed closures.
> *Proposed fix*
> Only replace the class node itself, which carries the declared type
> parameters, by its plain reference: {{returnType.isPrimaryClassNode() &&
> !returnType.isRedirectNode()}}. A parameterized reference then goes through
> the same check for unresolved generics as any other type, and both builds
> produce {{Res<Up> doCall()}}, the signature a closure already gets for a
> parameterized library type such as {{List<String>}}.
> Branches with the fix and a test, {{ClosureReturnTypeSignatureTest}}, that
> compiles the example both ways and compares the {{doCall}} signatures. The
> full core test suite passes on both:
> * master:
> https://github.com/jdaugherty/groovy/tree/closure-return-type-signature
> * GROOVY_5_1_X:
> https://github.com/jdaugherty/groovy/tree/closure-return-type-signature-5_1_X
> Related: GROOVY-10932 and GROOVY-12544, other differences between classes of
> the compilation unit and pre-compiled classes that affect incremental builds.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)