116116@ GroovyASTTransformation (phase = CompilePhase .SEMANTIC_ANALYSIS )
117117public class TraitASTTransformation extends AbstractASTTransformation implements CompilationUnitAware {
118118
119- /** Marker annotation type for {@code @Anchored } trait static methods. */
120- private static final ClassNode ANCHORED_TYPE = ClassHelper .make (groovy .transform .Anchored .class );
119+ /** Marker annotation type for {@code @Virtual } trait static methods. */
120+ private static final ClassNode VIRTUAL_TYPE = ClassHelper .make (groovy .transform .Virtual .class );
121121
122122 /**
123123 * Metadata key that marks trait-generated calls requiring dynamic dispatch.
@@ -268,18 +268,17 @@ private void createHelperClasses(final ClassNode cNode) {
268268 processField (field , initializer , staticInitializer , fieldHelper , helper , staticFieldHelper , cNode , fieldNames );
269269 }
270270
271- // Reject misapplied @Anchored markers before we waste effort
271+ // Reject misapplied @Virtual markers before we waste effort
272272 // processing them. Errors are registered against the source unit but
273273 // processing continues so that multiple violations can be reported
274274 // in a single compilation.
275- validateAnchoredAnnotations (cNode );
275+ validateVirtualAnnotations (cNode );
276276
277- // Identify @Anchored public statics whose bodies the main loop will
278- // emit on the helper. Captured up front so the main loop carries no
279- // @Anchored-specific branching and so the interface forwarders can be
280- // installed in a single post-processing step after the originals are
281- // removed from the trait interface.
282- List <MethodNode > anchoredOnInterface = collectAnchoredOnInterface (cNode );
277+ // Identify public static trait methods whose bodies the main loop
278+ // will emit on the helper; each gets a JVM-native interface static
279+ // method promoted onto the trait interface in a post-processing
280+ // step after the originals are removed from the trait interface.
281+ List <MethodNode > interfaceStatics = collectInterfaceStatics (cNode );
283282
284283 // add methods
285284 List <MethodNode > nonPublicAPIMethods = new ArrayList <>();
@@ -310,12 +309,13 @@ private void createHelperClasses(final ClassNode cNode) {
310309 }
311310
312311 // Install a public-static method on the trait interface for each
313- // @Anchored callee identified above. The forwarder delegates to the
314- // helper so external `Trait.m()` and from-trait `T.m()` calls resolve
315- // at the JVM level. Done after the removal step so the original
316- // static method is no longer on cNode when the forwarder is added.
317- for (MethodNode anchored : anchoredOnInterface ) {
318- cNode .addMethod (createAnchoredInterfaceForwarder (cNode , helper , anchored ));
312+ // public static trait method identified above. The forwarder
313+ // delegates to the helper so external `Trait.m()` and from-trait
314+ // `T.m()` calls resolve at the JVM level. Done after the removal
315+ // step so the original static method is no longer on cNode when
316+ // the forwarder is added.
317+ for (MethodNode m : interfaceStatics ) {
318+ cNode .addMethod (createInterfaceStaticForwarder (cNode , helper , m ));
319319 }
320320
321321 // copy statements from static and instance init blocks
@@ -651,15 +651,15 @@ private void processField(final FieldNode field, final MethodNode initializer, f
651651 }
652652
653653 /**
654- * Reports a compile error for any {@code @Anchored } annotation that is
654+ * Reports a compile error for any {@code @Virtual } annotation that is
655655 * applied to something other than a public static non-abstract trait
656656 * method. Without this check the misapplied annotation would be silently
657657 * ignored, leaving the user with no signal that the marker had no effect.
658658 */
659- private void validateAnchoredAnnotations (final ClassNode traitClass ) {
659+ private void validateVirtualAnnotations (final ClassNode traitClass ) {
660660 for (MethodNode methodNode : traitClass .getMethods ()) {
661- List <AnnotationNode > annotations = methodNode .getAnnotations (ANCHORED_TYPE );
662- if (annotations .isEmpty ()) continue ;
661+ List <AnnotationNode > virtualAnns = methodNode .getAnnotations (VIRTUAL_TYPE );
662+ if (virtualAnns .isEmpty ()) continue ;
663663 String issue ;
664664 if (!methodNode .isStatic ()) {
665665 issue = "is not static" ;
@@ -670,47 +670,38 @@ private void validateAnchoredAnnotations(final ClassNode traitClass) {
670670 } else {
671671 continue ; // valid
672672 }
673- AnnotationNode anchored = annotations .get (0 );
673+ AnnotationNode at = virtualAnns .get (0 );
674674 sourceUnit .addError (new SyntaxException (
675- "@Anchored can only be applied to public static trait methods; "
675+ "@Virtual can only be applied to public static trait methods; "
676676 + traitClass .getName () + "#" + methodNode .getTypeDescriptor () + " " + issue ,
677- anchored .getLineNumber (), anchored .getColumnNumber ()));
677+ at .getLineNumber (), at .getColumnNumber ()));
678678 }
679679 }
680680
681681 /**
682- * Returns the public {@code static} trait methods whose {@code @Anchored}
683- * marker requests interface promotion (i.e. {@code inInterface=true}, the
684- * default). The returned list snapshots the trait's method set so the
685- * caller can iterate the methods without being affected by later
686- * mutations to {@code traitClass.getMethods()}.
682+ * Returns the public {@code static} non-abstract trait methods that
683+ * should be promoted onto the generated trait interface as JVM-native
684+ * interface static methods. Excludes {@code @Virtual} methods, whose
685+ * dispatch path requires the helper-based dynamic-dispatch mechanism;
686+ * promoting them onto the interface as direct static methods would
687+ * make {@code @CompileStatic} callers bind to the trait's copy
688+ * statically and bypass the virtual-dispatch path entirely.
689+ *
690+ * <p>The returned list snapshots the trait's method set so the caller
691+ * can iterate without being affected by later mutations to
692+ * {@code traitClass.getMethods()}.
687693 */
688- private static List <MethodNode > collectAnchoredOnInterface (final ClassNode traitClass ) {
694+ private static List <MethodNode > collectInterfaceStatics (final ClassNode traitClass ) {
689695 List <MethodNode > result = new ArrayList <>();
690696 for (MethodNode methodNode : traitClass .getMethods ()) {
691697 if (methodNode .isStatic () && !methodNode .isPrivate () && !methodNode .isAbstract ()
692- && isAnchoredOnInterface ( methodNode )) {
698+ && methodNode . getAnnotations ( VIRTUAL_TYPE ). isEmpty ( )) {
693699 result .add (methodNode );
694700 }
695701 }
696702 return result ;
697703 }
698704
699- /**
700- * Returns {@code true} if the method is annotated with {@code @Anchored}
701- * and the {@code inInterface} attribute is true (the default).
702- */
703- private static boolean isAnchoredOnInterface (final MethodNode methodNode ) {
704- List <AnnotationNode > anns = methodNode .getAnnotations (ANCHORED_TYPE );
705- if (anns .isEmpty ()) return false ;
706- Expression member = anns .get (0 ).getMember ("inInterface" );
707- if (member instanceof ConstantExpression
708- && Boolean .FALSE .equals (((ConstantExpression ) member ).getValue ())) {
709- return false ;
710- }
711- return true ;
712- }
713-
714705 /**
715706 * Builds a public-static method on the trait interface that delegates to
716707 * the corresponding helper method.
@@ -719,9 +710,9 @@ private static boolean isAnchoredOnInterface(final MethodNode methodNode) {
719710 * preserving generics, exceptions and parameter list of the original
720711 * trait static. The trait class itself is passed as the synthetic
721712 * {@code $self} receiver expected by the helper, consistent with the
722- * declarer-bound dispatch model that {@code @Anchored} selects .
713+ * declarer-bound dispatch model of trait static methods .
723714 */
724- private static MethodNode createAnchoredInterfaceForwarder (final ClassNode traitClass , final ClassNode helper , final MethodNode original ) {
715+ private static MethodNode createInterfaceStaticForwarder (final ClassNode traitClass , final ClassNode helper , final MethodNode original ) {
725716 Parameter [] params = original .getParameters ();
726717 Expression [] callArgs = new Expression [params .length + 1 ];
727718 callArgs [0 ] = classX (traitClass );
0 commit comments