Scott Murphy Heiberg created GROOVY-12743:
---------------------------------------------

             Summary: An annotation with a closure member on a trait method 
fails with UnsupportedOperationException in TraitComposer (seen with 
groovy-contracts @ThrowsIf and @Decreases)
                 Key: GROOVY-12743
                 URL: https://issues.apache.org/jira/browse/GROOVY-12743
             Project: Groovy
          Issue Type: Bug
    Affects Versions: 6.0.0
            Reporter: Scott Murphy Heiberg


h3. Problem

Any annotation whose member is given a closure, placed on a trait method with a 
body, crashes the compiler when a class implements the trait:

{code:groovy}
import java.lang.annotation.*

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Check { Class value() }

trait T {
    @Check({ it > 0 }) int m() { 1 }
}
class C implements T {}
{code}

{noformat}
General error during semantic analysis: java.lang.UnsupportedOperationException
        at java.base/java.util.AbstractList.add(AbstractList.java:155)
        at 
org.codehaus.groovy.ast.tools.GeneralUtils.copyAnnotatedNodeAnnotations(GeneralUtils.java:1867)
        at 
org.codehaus.groovy.transform.trait.TraitComposer.createForwarderMethod(TraitComposer.java:358)
        at 
org.codehaus.groovy.transform.trait.TraitComposer.applyTrait(TraitComposer.java:167)
{noformat}

The groovy-contracts annotations {{@ThrowsIf}} and {{@Decreases}} on a trait 
method with a body fail the same way. Reproduced on Groovy 6.0.0 and on master 
(df1fe66ed1).

h3. Cause

{{TraitComposer.createForwarderMethod}} passes an unmodifiable empty list as 
the "not copied" annotations to {{GeneralUtils.copyAnnotatedNodeAnnotations}}, 
with a comment that it should always stay empty. The closure of an annotation 
member is still a {{ClosureExpression}} in the canonicalization phase (it 
becomes a class only at class generation), so the helper adds the annotation to 
that list and {{AbstractList.add}} throws. 
{{TraitASTTransformation.createInterfaceStaticForwarder}} already uses a real 
list.

h3. Fix

{{TraitComposer}} uses a real list; the annotation stays on the trait method 
and the helper method and is, as before for closure members, not copied to the 
forwarder. Whether {{@ThrowsIf}} and {{@Decreases}} can then be enforced on a 
trait method is a groovy-contracts question (their transforms run after the 
trait transform has rewritten the body, so a condition that refers to trait 
state has no receiver in the helper class); the contracts issue on trait 
methods reports them as unsupported. Pull request to follow.




--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to