|
27 | 27 | import org.codehaus.groovy.ast.AnnotatedNode; |
28 | 28 | import org.codehaus.groovy.ast.AnnotationNode; |
29 | 29 | import org.codehaus.groovy.ast.ClassCodeExpressionTransformer; |
| 30 | +import org.codehaus.groovy.ast.ClassHelper; |
30 | 31 | import org.codehaus.groovy.ast.ClassNode; |
31 | 32 | import org.codehaus.groovy.ast.FieldNode; |
32 | 33 | import org.codehaus.groovy.ast.GenericsType; |
|
36 | 37 | import org.codehaus.groovy.ast.Parameter; |
37 | 38 | import org.codehaus.groovy.ast.PropertyNode; |
38 | 39 | import org.codehaus.groovy.ast.expr.BinaryExpression; |
| 40 | +import org.codehaus.groovy.ast.expr.ConstantExpression; |
39 | 41 | import org.codehaus.groovy.ast.expr.Expression; |
40 | 42 | import org.codehaus.groovy.ast.expr.MethodCallExpression; |
41 | 43 | import org.codehaus.groovy.ast.expr.SpreadExpression; |
|
115 | 117 | @GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) |
116 | 118 | public class TraitASTTransformation extends AbstractASTTransformation implements CompilationUnitAware { |
117 | 119 |
|
| 120 | + /** Marker annotation type for {@code @Anchored} trait static methods. */ |
| 121 | + private static final ClassNode ANCHORED_TYPE = ClassHelper.make(groovy.transform.Anchored.class); |
| 122 | + |
118 | 123 | public static final String DO_DYNAMIC = TraitReceiverTransformer.class + ".doDynamic"; |
119 | 124 | public static final String POST_TYPECHECKING_REPLACEMENT = TraitReceiverTransformer.class + ".replacement"; |
120 | 125 |
|
@@ -246,6 +251,19 @@ private ClassNode createHelperClass(final ClassNode cNode) { |
246 | 251 | processField(field, initializer, staticInitializer, fieldHelper, helper, staticFieldHelper, cNode, fieldNames); |
247 | 252 | } |
248 | 253 |
|
| 254 | + // Reject misapplied @Anchored markers before we waste effort |
| 255 | + // processing them. Errors are registered against the source unit but |
| 256 | + // processing continues so that multiple violations can be reported |
| 257 | + // in a single compilation. |
| 258 | + validateAnchoredAnnotations(cNode); |
| 259 | + |
| 260 | + // Identify @Anchored public statics whose bodies the main loop will |
| 261 | + // emit on the helper. Captured up front so the main loop carries no |
| 262 | + // @Anchored-specific branching and so the interface forwarders can be |
| 263 | + // installed in a single post-processing step after the originals are |
| 264 | + // removed from the trait interface. |
| 265 | + List<MethodNode> anchoredOnInterface = collectAnchoredOnInterface(cNode); |
| 266 | + |
249 | 267 | // add methods |
250 | 268 | List<MethodNode> nonPublicAPIMethods = new ArrayList<>(); |
251 | 269 | List<Statement> staticInitStatements = null; |
@@ -274,6 +292,15 @@ private ClassNode createHelperClass(final ClassNode cNode) { |
274 | 292 | cNode.removeMethod(privateMethod); |
275 | 293 | } |
276 | 294 |
|
| 295 | + // Install a public-static method on the trait interface for each |
| 296 | + // @Anchored callee identified above. The forwarder delegates to the |
| 297 | + // helper so external `Trait.m()` and from-trait `T.m()` calls resolve |
| 298 | + // at the JVM level. Done after the removal step so the original |
| 299 | + // static method is no longer on cNode when the forwarder is added. |
| 300 | + for (MethodNode anchored : anchoredOnInterface) { |
| 301 | + cNode.addMethod(createAnchoredInterfaceForwarder(cNode, helper, anchored)); |
| 302 | + } |
| 303 | + |
277 | 304 | // copy statements from static and instance init blocks |
278 | 305 | if (staticInitStatements != null) { |
279 | 306 | BlockStatement toBlock = getBlockStatement(staticInitializer, staticInitializer.getCode()); |
@@ -617,6 +644,106 @@ private void processField(final FieldNode field, final MethodNode initializer, f |
617 | 644 | fieldHelper.addField(dummyField); |
618 | 645 | } |
619 | 646 |
|
| 647 | + /** |
| 648 | + * Reports a compile error for any {@code @Anchored} annotation that is |
| 649 | + * applied to something other than a public static non-abstract trait |
| 650 | + * method. Without this check the misapplied annotation would be silently |
| 651 | + * ignored, leaving the user with no signal that the marker had no effect. |
| 652 | + */ |
| 653 | + private void validateAnchoredAnnotations(final ClassNode traitClass) { |
| 654 | + for (MethodNode methodNode : traitClass.getMethods()) { |
| 655 | + List<AnnotationNode> annotations = methodNode.getAnnotations(ANCHORED_TYPE); |
| 656 | + if (annotations.isEmpty()) continue; |
| 657 | + String issue; |
| 658 | + if (!methodNode.isStatic()) { |
| 659 | + issue = "is not static"; |
| 660 | + } else if (methodNode.isPrivate()) { |
| 661 | + issue = "is private"; |
| 662 | + } else if (methodNode.isAbstract()) { |
| 663 | + issue = "is abstract"; |
| 664 | + } else { |
| 665 | + continue; // valid |
| 666 | + } |
| 667 | + AnnotationNode anchored = annotations.get(0); |
| 668 | + sourceUnit.addError(new SyntaxException( |
| 669 | + "@Anchored can only be applied to public static trait methods; " |
| 670 | + + traitClass.getName() + "#" + methodNode.getTypeDescriptor() + " " + issue, |
| 671 | + anchored.getLineNumber(), anchored.getColumnNumber())); |
| 672 | + } |
| 673 | + } |
| 674 | + |
| 675 | + /** |
| 676 | + * Returns the public {@code static} trait methods whose {@code @Anchored} |
| 677 | + * marker requests interface promotion (i.e. {@code inInterface=true}, the |
| 678 | + * default). The returned list snapshots the trait's method set so the |
| 679 | + * caller can iterate the methods without being affected by later |
| 680 | + * mutations to {@code traitClass.getMethods()}. |
| 681 | + */ |
| 682 | + private static List<MethodNode> collectAnchoredOnInterface(final ClassNode traitClass) { |
| 683 | + List<MethodNode> result = new ArrayList<>(); |
| 684 | + for (MethodNode methodNode : traitClass.getMethods()) { |
| 685 | + if (methodNode.isStatic() && !methodNode.isPrivate() && !methodNode.isAbstract() |
| 686 | + && isAnchoredOnInterface(methodNode)) { |
| 687 | + result.add(methodNode); |
| 688 | + } |
| 689 | + } |
| 690 | + return result; |
| 691 | + } |
| 692 | + |
| 693 | + /** |
| 694 | + * Returns {@code true} if the method is annotated with {@code @Anchored} |
| 695 | + * and the {@code inInterface} attribute is true (the default). |
| 696 | + */ |
| 697 | + private static boolean isAnchoredOnInterface(final MethodNode methodNode) { |
| 698 | + List<AnnotationNode> anns = methodNode.getAnnotations(ANCHORED_TYPE); |
| 699 | + if (anns.isEmpty()) return false; |
| 700 | + Expression member = anns.get(0).getMember("inInterface"); |
| 701 | + if (member instanceof ConstantExpression |
| 702 | + && Boolean.FALSE.equals(((ConstantExpression) member).getValue())) { |
| 703 | + return false; |
| 704 | + } |
| 705 | + return true; |
| 706 | + } |
| 707 | + |
| 708 | + /** |
| 709 | + * Builds a public-static method on the trait interface that delegates to |
| 710 | + * the corresponding helper method. |
| 711 | + * |
| 712 | + * <p>Emits {@code public static R m(args) { return T$Trait$Helper.m(T.class, args); }}, |
| 713 | + * preserving generics, exceptions and parameter list of the original |
| 714 | + * trait static. The trait class itself is passed as the synthetic |
| 715 | + * {@code $self} receiver expected by the helper, consistent with the |
| 716 | + * declarer-bound dispatch model that {@code @Anchored} selects. |
| 717 | + */ |
| 718 | + private static MethodNode createAnchoredInterfaceForwarder(final ClassNode traitClass, final ClassNode helper, final MethodNode original) { |
| 719 | + Parameter[] params = original.getParameters(); |
| 720 | + Expression[] callArgs = new Expression[params.length + 1]; |
| 721 | + callArgs[0] = classX(traitClass); |
| 722 | + for (int i = 0; i < params.length; i++) { |
| 723 | + callArgs[i + 1] = varX(params[i]); |
| 724 | + } |
| 725 | + MethodCallExpression call = callX(classX(helper), original.getName(), args(callArgs)); |
| 726 | + Statement body = VOID_TYPE.equals(original.getReturnType()) ? stmt(call) : returnS(call); |
| 727 | + MethodNode forwarder = new MethodNode( |
| 728 | + original.getName(), |
| 729 | + ACC_PUBLIC | ACC_STATIC, |
| 730 | + original.getReturnType(), |
| 731 | + params, |
| 732 | + original.getExceptions(), |
| 733 | + body); |
| 734 | + forwarder.setGenericsTypes(original.getGenericsTypes()); |
| 735 | + forwarder.setSynthetic(true); |
| 736 | + forwarder.setSourcePosition(original); |
| 737 | + // Carry over the trait method's RUNTIME/CLASS-retention annotations (e.g. |
| 738 | + // @Deprecated) so the promoted interface static behaves like the original, |
| 739 | + // consistent with the forwarders generated by TraitComposer. The helper |
| 740 | + // filters out SOURCE-retention/transform markers and closure-member ones. |
| 741 | + List<AnnotationNode> copied = new ArrayList<>(), notCopied = new ArrayList<>(); |
| 742 | + GeneralUtils.copyAnnotatedNodeAnnotations(original, copied, notCopied); |
| 743 | + forwarder.addAnnotations(copied); |
| 744 | + return forwarder; |
| 745 | + } |
| 746 | + |
620 | 747 | private MethodNode processMethod(final ClassNode traitClass, final ClassNode traitHelperClass, final MethodNode methodNode, final ClassNode fieldHelper, final Collection<String> knownFields) { |
621 | 748 | boolean isAbstractMethod = methodNode.isAbstract(); |
622 | 749 | boolean isPrivateMethod = methodNode.isPrivate(); |
|
0 commit comments