Skip to content

Commit

Permalink
GROOVY-8272, GROOVY-10312, GROOVY-11302: trait method invocation
Browse files Browse the repository at this point in the history
4_0_X backport
  • Loading branch information
eric-milles committed Jan 30, 2024
1 parent 98b2d29 commit 57524c6
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 206 deletions.
45 changes: 22 additions & 23 deletions src/main/java/org/codehaus/groovy/control/StaticImportVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,12 @@
import static org.apache.groovy.ast.tools.ClassNodeUtils.isInnerClass;
import static org.apache.groovy.ast.tools.ClassNodeUtils.isValidAccessorName;
import static org.apache.groovy.ast.tools.ExpressionUtils.isSuperExpression;
import static org.apache.groovy.ast.tools.ExpressionUtils.isThisOrSuper;
import static org.apache.groovy.ast.tools.ExpressionUtils.transformInlineConstants;
import static org.apache.groovy.util.BeanUtils.capitalize;
import static org.codehaus.groovy.ast.tools.ClosureUtils.getParametersSafe;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
import static org.codehaus.groovy.transform.trait.Traits.isTrait;

/**
* Visitor to resolve constants and method calls from static imports.
Expand Down Expand Up @@ -267,43 +267,42 @@ protected Expression transformMethodCallExpression(MethodCallExpression mce) {
return result;
}

if (method instanceof ConstantExpression && ((ConstantExpression) method).getValue() instanceof String && (mce.isImplicitThis() || isThisOrSuper(object))) {
String methodName = (String) ((ConstantExpression) method).getValue();
if (method instanceof ConstantExpression && ((ConstantExpression) method).getValue() instanceof String
&& mce.isImplicitThis() && !isTrait(currentClass)) { // GROOVY-7191, GROOVY-8272, GROOVY-10312
String name = mce.getMethodAsString();

boolean foundInstanceMethod = !staticWrtCurrent && currentClass.hasPossibleMethod(methodName, args);
boolean foundInstanceMethod = !staticWrtCurrent && currentClass.hasPossibleMethod(name, args);

Predicate<ClassNode> hasPossibleStaticMember = cn -> {
if (hasPossibleStaticMethod(cn, methodName, args, true)) {
if (hasPossibleStaticMethod(cn, name, args, true)) {
return true;
}
// GROOVY-9587: don't check for property for non-empty call args
// GROOVY-9587: skip property check for non-empty call arguments
if (args instanceof TupleExpression && ((TupleExpression) args).getExpressions().isEmpty()
&& hasPossibleStaticProperty(cn, methodName)) {
&& hasPossibleStaticProperty(cn, name)) {
return true;
}
return false;
};

if (mce.isImplicitThis()) {
if (isInnerClass(currentClass)) {
if (inSpecialConstructorCall && !foundInstanceMethod) {
// check for reference to outer class method in this(...) or super(...)
if (currentClass.getOuterClass().hasPossibleMethod(methodName, args)) {
object = new PropertyExpression(new ClassExpression(currentClass.getOuterClass()), new ConstantExpression("this"));
} else if (hasPossibleStaticMember.test(currentClass.getOuterClass())) {
Expression result = new StaticMethodCallExpression(currentClass.getOuterClass(), methodName, args);
result.setSourcePosition(mce);
return result;
}
}
} else if (inSpecialConstructorCall || (!inClosure && !foundInstanceMethod && !methodName.equals("call"))) {
// check for reference to static method in this(...) or super(...) or when call not resolved
if (hasPossibleStaticMember.test(currentClass)) {
Expression result = new StaticMethodCallExpression(currentClass, methodName, args);
if (isInnerClass(currentClass)) {
if (inSpecialConstructorCall && !foundInstanceMethod) {
// check for reference to outer class method in this(...) or super(...)
if (currentClass.getOuterClass().hasPossibleMethod(name, args)) {
object = new PropertyExpression(new ClassExpression(currentClass.getOuterClass()), new ConstantExpression("this"));
} else if (hasPossibleStaticMember.test(currentClass.getOuterClass())) {
Expression result = new StaticMethodCallExpression(currentClass.getOuterClass(), name, args);
result.setSourcePosition(mce);
return result;
}
}
} else if (inSpecialConstructorCall || (!inClosure && !foundInstanceMethod && !name.equals("call"))) {
// check for reference to static method in this(...) or super(...) or when call not resolved
if (hasPossibleStaticMember.test(currentClass)) {
Expression result = new StaticMethodCallExpression(currentClass, name, args);
result.setSourcePosition(mce);
return result;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import java.util.Collections;
import java.util.List;

import static org.codehaus.groovy.ast.tools.GenericsUtils.makeClassSafe0;
import static org.codehaus.groovy.ast.tools.ParameterUtils.parametersCompatible;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType;

/**
Expand All @@ -44,6 +46,17 @@ public TraitTypeCheckingExtension(final StaticTypeCheckingVisitor typeCheckingVi
super(typeCheckingVisitor);
}

private static Parameter[] asParameters(final ClassNode traitClass, final ClassNode[] argumentTypes) {
ClassNode classType = makeClassSafe0(ClassHelper.CLASS_Type, traitClass.asGenericsType());

Parameter[] parameters = new Parameter[1 + argumentTypes.length];
parameters[0] = new Parameter(classType,"self");
for (int i = 1; i < parameters.length; i += 1) {
parameters[i] = new Parameter(argumentTypes[i - 1], "p" + i);
}
return parameters;
}

@Override
public List<MethodNode> handleMissingMethod(final ClassNode receiver, final String name, final ArgumentListExpression argumentList, final ClassNode[] argumentTypes, final MethodCall call) {
String[] decomposed = Traits.decomposeSuperCallName(name);
Expand Down Expand Up @@ -72,20 +85,19 @@ public List<MethodNode> handleMissingMethod(final ClassNode receiver, final Stri

if (call instanceof MethodCallExpression) {
MethodCallExpression mce = (MethodCallExpression) call;
ClassNode dynamic = mce.getNodeMetaData(TraitASTTransformation.DO_DYNAMIC);
if (dynamic != null) return Collections.singletonList(makeDynamic(call, dynamic));
ClassNode returnType = mce.getNodeMetaData(TraitASTTransformation.DO_DYNAMIC);
if (returnType != null) return Collections.singletonList(makeDynamic(call, returnType));

// GROOVY-7322, GROOVY-8272, GROOVY-8587, GROOVY-8854: trait: this.m($static$self)
// GROOVY-7191, GROOVY-7322, GROOVY-8272, GROOVY-8587, GROOVY-10106, GROOVY-10312: STC: (this or $self or $static$self).m()
ClassNode targetClass = isClassClassNodeWrappingConcreteType(receiver)? receiver.getGenericsTypes()[0].getType(): receiver;
if (Traits.isTrait(targetClass.getOuterClass()) && argumentTypes.length > 0 && ClassHelper.isClassType(argumentTypes[0])) {
Parameter[] signature = java.util.Arrays.stream(argumentTypes).map(t -> new Parameter(t,"")).toArray(Parameter[]::new);
List<ClassNode> traits = Traits.findTraits(targetClass.getOuterClass());
traits.remove(targetClass.getOuterClass());

for (ClassNode trait : traits) { // check super trait for static method
MethodNode method = Traits.findHelper(trait).getDeclaredMethod(name, signature);
if (method != null && method.isStatic()) {
return Collections.singletonList(makeDynamic(call, method.getReturnType()));
if (targetClass.getName().endsWith("$Trait$Helper")) targetClass = targetClass.getOuterClass();
if (Traits.isTrait(targetClass)) {
for (ClassNode trait : Traits.findTraits(targetClass)) {
for (MethodNode method : Traits.findHelper(trait).getDeclaredMethods(name)) {
if (method.isPublic() && method.isStatic() && parametersCompatible(
asParameters(trait, argumentTypes), method.getParameters())) {
return Collections.singletonList(makeDynamic(call, method.getReturnType()));
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@
import static org.codehaus.groovy.ast.tools.GeneralUtils.binX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.castX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.isInstanceOfX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ternaryX;
Expand Down Expand Up @@ -102,25 +101,18 @@ public Expression transform(final Expression exp) {
} else if (exp instanceof MethodCallExpression) {
MethodCallExpression mce = (MethodCallExpression) exp;
String obj = mce.getObjectExpression().getText();
if (mce.isImplicitThis() || "this".equals(obj)) {
return transformMethodCallOnThis(mce); // this.m(p) --> this.m($self, p)
} else if ("super".equals(obj)) {
return transformSuperMethodCall(mce); // super.m(p) --> $self.Ttrait$super$m(p)
if ("super".equals(obj)) {
return transformSuperMethodCall(mce); // super.m(x) --> $self.Ttrait$super$m(x)
} else if ("this".equals(obj)) {
return transformMethodCallOnThis(mce); // this.m(x) --> $self.m(x) or this.m($self, x)
}
} else if (exp instanceof StaticMethodCallExpression) {
StaticMethodCallExpression call = (StaticMethodCallExpression) exp;
if (call.getOwnerType().equals(traitClass)) {
// GROOVY-7191, GROOVY-8272, GROOVY-8854, GROOVY-10312: T.m(p) --> this.m($static$self, p)
Expression staticSelf = varX(weaved);
if (!ClassHelper.isClassType(weavedType)) {
staticSelf = callX(staticSelf, "getClass");
((MethodCallExpression) staticSelf).setImplicitThis(false);
staticSelf = castX(ClassHelper.CLASS_Type.getPlainNodeReference(), staticSelf);
}
if (call.getOwnerType().equals(traitClass)) { // T.m(x) --> ( $self or $static$self ).m(x)
MethodCallExpression mce = callX(
varX("this"),
varX(weaved),
call.getMethod(),
createArgumentList(staticSelf, call.getArguments())
transform(call.getArguments())
);
mce.setSafe(false);
mce.setSpreadSafe(false);
Expand Down Expand Up @@ -304,35 +296,30 @@ private Expression transformSuperMethodCall(final MethodCallExpression call) {
}

private Expression transformMethodCallOnThis(final MethodCallExpression call) {
Expression method = call.getMethod();
Expression method = call.getMethod();
Expression arguments = call.getArguments();
Expression thisExpr = call.getObjectExpression();
Expression thisExpr = call.getObjectExpression();

if (method instanceof ConstantExpression) {
String methodName = call.getMethodAsString();
for (MethodNode methodNode : traitClass.getMethods(methodName)) {
if (methodName.equals(methodNode.getName()) && (methodNode.isStatic() || methodNode.isPrivate())) {
MethodCallExpression newCall;
if (!inClosure && methodNode.isStatic()) { // GROOVY-10312: $self or $static$self.staticMethod(...)
newCall = callX(varX(weaved), methodName, transform(arguments));
newCall.setImplicitThis(false);
newCall.setSafe(false);
} else {
ArgumentListExpression newArgs = createArgumentList(methodNode.isStatic() ? asClass(thisExpr) : weaved, arguments);
newCall = callX(inClosure ? classX(traitHelperClass) : thisExpr, methodName, newArgs);
newCall.setImplicitThis(true);
newCall.setSafe(call.isSafe());
}
// GROOVY-7191, GROOVY-7213, GROOVY-7214, GROOVY-8282, GROOVY-8854, GROOVY-8859, et al.
for (MethodNode methodNode : traitClass.getDeclaredMethods(call.getMethodAsString())) {
if (methodNode.isPrivate()) {
// this.m(x) --> this.m($self or $static$self or (Class) $self.getClass(), x)
Expression selfClassOrObject = methodNode.isStatic() && !ClassHelper.isClassType(weaved.getOriginType()) ? castX(ClassHelper.CLASS_Type.getPlainNodeReference(), callX(weaved, "getClass")) : weaved;
MethodCallExpression newCall = callX(thisExpr, method, createArgumentList(selfClassOrObject, arguments));
newCall.setGenericsTypes(call.getGenericsTypes());
newCall.setImplicitThis(call.isImplicitThis());
newCall.setSpreadSafe(call.isSpreadSafe());
newCall.setSourcePosition(call);
return newCall;
}
}
}

// this.m(x) --> (this or $self or $static$self).m(x)
MethodCallExpression newCall = callX(inClosure ? thisExpr : weaved, method, transform(arguments));
newCall.setGenericsTypes(call.getGenericsTypes()); // GROOVY-11302: this.<T>m(x)
newCall.setImplicitThis(inClosure ? call.isImplicitThis() : false);
newCall.setSafe(inClosure ? call.isSafe() : false);
newCall.setSpreadSafe(call.isSpreadSafe());
newCall.setSourcePosition(call);
return newCall;
Expand Down
76 changes: 0 additions & 76 deletions src/test/groovy/bugs/Groovy7909Bug.groovy

This file was deleted.

42 changes: 0 additions & 42 deletions src/test/org/codehaus/groovy/transform/traitx/Groovy7214Bug.groovy

This file was deleted.

Loading

0 comments on commit 57524c6

Please sign in to comment.