[
https://issues.apache.org/jira/browse/GROOVY-12611?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18135623#comment-18135623
]
ASF GitHub Bot commented on GROOVY-12611:
-----------------------------------------
codeconsole opened a new pull request, #3138:
URL: https://github.com/apache/groovy/pull/3138
In trait code, an implicit-this call from a closure to a private or static
trait method always called the trait method, even when the closure's delegate
had a matching method:
```groovy
trait T {
private int size() { 1 } // or: static int size() { 1 }
def m() { [5, 6].with { size() } }
}
class C implements T {}
assert new C().m() == 2 // was 1
```
The same code in a class returns 2 in dynamic, `@TypeChecked` and
`@CompileStatic` code. 4.0.33, 5.0.8, 6.0.0 and master return 1 for private
methods, and 5.0.8 onwards for static ones too. `TraitReceiverTransformer`
rewrote the call to `T$Trait$Helper.size($self)` before the closure's resolve
strategy could apply. The implementing class has no private trait methods, so
the call cannot simply be left for the closure to resolve against its owner.
`TraitReceiverTransformer` still produces the helper call, and now attaches
the plain implicit-this call to it as a post-type-checking replacement. Then:
- **Type checking.** `TraitTypeCheckingExtension.afterMethodCall` walks the
receivers that a class's implicit-this call would get. If a delegate that comes
before the owner has the method, it type-checks the plain call against that
delegate and stores its result type.
- **Static compilation.** Statically compiled code compiles that delegate
call. Otherwise it keeps the direct helper call, so the common case still
compiles to a static call.
- **Dynamic and `@TypeChecked` code.** These keep the plain call.
`PostTypeCheckingExpressionReplacer` now visits closure bodies to apply the
replacement.
- **Runtime fallback.** `ClosureMetaClass` recognises a closure whose
enclosing class is a trait helper. Where a class would call the owner's method,
it calls the helper method for a private or static trait method, passing the
owner or the owner's class. The extra lookup only runs for closures nested in a
`$Trait$Helper`.
Explicit `this.size()`, owner-first closures, lambdas, and static methods
inherited from a super trait keep calling the trait method. As before, the
trait method wins over a same-named method of the implementing class.
#3125 (GROOVY-12598) adds an `inLambda` flag to `TraitReceiverTransformer`
with slightly different semantics (it stays true for closures nested in a
lambda); whichever lands second needs a small merge.
## Tests
`TraitASTTransformationTest.testPrivateTraitMethodInDelegateFirstClosure`,
`testStaticTraitMethodInDelegateFirstClosure` and
`testPrivateTraitMethodInClosureWithDelegateSetAtRuntime` each run in dynamic,
`@TypeChecked` and `@CompileStatic` mode. All fail without the change except
the `@CompileStatic` mode of the last, which checks that statically compiled
code still decides at compile time. `org.codehaus.groovy.transform.traitx.*`,
`groovy.transform.stc.*`, `org.codehaus.groovy.classgen.asm.sc.*`, `gls.*`,
`groovy.ThisAndSuperTest`, `groovy.Closure*`, `groovy.lang.Closure*` and
`org.codehaus.groovy.runtime.metaclass.*` pass. The Checkstyle and CodeNarc
gates pass.
> A private or static trait method called from a closure ignores the closure's
> delegate
> -------------------------------------------------------------------------------------
>
> Key: GROOVY-12611
> URL: https://issues.apache.org/jira/browse/GROOVY-12611
> Project: Groovy
> Issue Type: Bug
> Affects Versions: 4.0.33, 5.0.8, 6.0.0
> Reporter: Scott Murphy Heiberg
> Priority: Major
>
> {code:groovy}
> trait T {
> private int size() { 1 } // or: static int size() { 1 }
> def m() { [5, 6].with { size() } }
> }
> class C implements T {}
> assert new C().m() == 2
> {code}
> Actual: 1, in dynamic, @TypeChecked and @CompileStatic code. The same happens
> for a static trait method called from a static trait method.
> Expected: 2, as for the same code in a class. The delegate's method is used
> when the resolve strategy tries the delegate first and the delegate has the
> method. Otherwise the trait method is called. A public instance trait method
> already behaves this way.
> Cause: TraitReceiverTransformer rewrites an implicit-this call to a private
> or static trait method inside a closure into a direct call on the trait
> helper class, so the closure's delegate is never consulted.
> Seen in 4.0.33, 5.0.8 and 6.0.0 (4.0.33 is right for public static methods).
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)