Paul King created GROOVY-12760:
----------------------------------
Summary: STC: call to a property accessor that also exists as an
interface default method compiles to different bytecode depending on whether
the class is compiled in the same unit
Key: GROOVY-12760
URL: https://issues.apache.org/jira/browse/GROOVY-12760
Project: Groovy
Issue Type: Bug
Components: Static Type Checker
Affects Versions: 6.0.0, 5.1.3, 4.0.33
Reporter: Paul King
When an interface declares *default* accessor methods for a property that an
implementing class declares, {{@CompileStatic}} calls on a subclass receiver
are compiled differently depending on whether the class comes from source in
the same compilation unit or from a .class file:
{code:groovy}
package p
interface Named {
default String getName() { 'named' }
default void setName(String n) { }
}
abstract class Base implements Named { String name }
class Impl extends Base { }
@groovy.transform.CompileStatic
class Svc {
static String rename(Impl base) {
base.name = 'x'
base.setName('y')
base.getName()
}
}
{code}
|| ||compiled together||{{Svc}} compiled against .class files||
|4.0.33|{{invokevirtual Impl.setName}}, {{invokeinterface Named.setName}},
{{invokeinterface Named.getName}}|{{invokevirtual Impl.setName}} ×2,
{{invokevirtual Impl.getName}}|
|5.1.3, 6.0.0|{{invokeinterface Named.setName}} ×2, {{invokeinterface
Named.getName}}|{{invokevirtual Impl.setName}} ×2, {{invokevirtual
Impl.getName}}|
Runtime behaviour is the same, because dispatch reaches {{Base}}'s generated
accessors either way. But the class files differ, which breaks incremental
compilation reproducing a full build (see
[gradle/gradle#39622|https://github.com/gradle/gradle/pull/39622]).
The cause is that while the class is compiled from source, the type checker
sees no accessor in {{Base}}: property accessors are only generated by the
Verifier, after type checking. For abstract interface accessors, {{findMethod}}
adds a synthetic accessor node (GROOVY-10922, GROOVY-12552). But that doesn't
happen when the interface method is a default method: the getter's synthetic is
suppressed because {{getGetterMethod}} finds the default getter (GROOVY-11381),
and the setter's is gated on all candidates being abstract. So the interface
method is chosen. From a .class file, {{Base.getName}}/{{setName}} exist and
are chosen.
Related: GROOVY-12552 (the same disparity for abstract interface accessors),
GROOVY-11381, GROOVY-10922.
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)