Skip to content
Permalink
Browse files
GROOVY-10143: super trait field access
  • Loading branch information
eric-milles committed May 24, 2022
1 parent 899ab95 commit 5176ea3155613deb5398e839856eafdd3788ead9
Showing 12 changed files with 241 additions and 70 deletions.
@@ -64,7 +64,7 @@
*/
public class GenericsUtils {
public static final GenericsType[] EMPTY_GENERICS_ARRAY = GenericsType.EMPTY_ARRAY;
public static final String JAVA_LANG_OBJECT = "java.lang.Object";
public static final String JAVA_LANG_OBJECT = ClassHelper.OBJECT;

/**
* Given a parameterized type and a generic type information, aligns actual type parameters. For example, if a
@@ -18,7 +18,6 @@
*/
package org.codehaus.groovy.transform.trait;

import groovy.lang.MetaProperty;
import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
@@ -40,11 +39,13 @@
import java.util.List;
import java.util.function.Function;

import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getGetterName;
import static org.codehaus.groovy.ast.tools.GeneralUtils.getSetterName;
import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
import static org.codehaus.groovy.ast.ClassHelper.isClassType;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean;
import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
@@ -102,19 +103,13 @@ private Expression transformBinaryExpression(final BinaryExpression exp) {
ClassNode helperType = getHelper(traitType);
// TraitType.super.foo = ... -> TraitType$Trait$Helper.setFoo(this, ...)

String setterName = MetaProperty.getSetterName(leftExpression.getPropertyAsString());
String setterName = getSetterName(leftExpression.getPropertyAsString());
for (MethodNode method : helperType.getMethods(setterName)) {
Parameter[] parameters = method.getParameters();
if (parameters.length == 2 && isSelfType(parameters[0], traitType)) {
MethodCallExpression setterCall = new MethodCallExpression(
new ClassExpression(helperType),
setterName,
new ArgumentListExpression(
isClassType(parameters[0].getType())
? thisPropX(false, "class") : varX("this"),
bin.getRightExpression()
)
);
Expression thisExpr = ClassHelper.isClassType(parameters[0].getType()) ? thisPropX(false, "class") : varX("this");

MethodCallExpression setterCall = callX(classX(helperType), setterName, args(thisExpr, bin.getRightExpression()));
setterCall.getObjectExpression().setSourcePosition(leftExpression.getObjectExpression());
setterCall.getMethod().setSourcePosition(leftExpression.getProperty());
setterCall.setSpreadSafe(leftExpression.isSpreadSafe());
@@ -145,14 +140,9 @@ private Expression transformPropertyExpression(final PropertyExpression exp) {
// TraitType.super.foo -> TraitType$Trait$Helper.getFoo(this)

Function<MethodNode, MethodCallExpression> xform = (methodNode) -> {
MethodCallExpression methodCall = new MethodCallExpression(
new ClassExpression(helperType),
methodNode.getName(),
new ArgumentListExpression(
isClassType(methodNode.getParameters()[0].getType())
? thisPropX(false, "class") : varX("this")
)
);
Expression thisExpr = ClassHelper.isClassType(methodNode.getParameters()[0].getType()) ? thisPropX(false, "class") : varX("this");

MethodCallExpression methodCall = callX(classX(helperType), methodNode.getName(), args(thisExpr));
methodCall.getObjectExpression().setSourcePosition(traitType);
methodCall.getMethod().setSourcePosition(exp.getProperty());
methodCall.setSpreadSafe(exp.isSpreadSafe());
@@ -161,20 +151,18 @@ private Expression transformPropertyExpression(final PropertyExpression exp) {
return methodCall;
};

String getterName = MetaProperty.getGetterName(exp.getPropertyAsString(), null);
String getterName = getGetterName(exp.getPropertyAsString());
for (MethodNode method : helperType.getMethods(getterName)) {
if (method.isStatic() && method.getParameters().length == 1
&& isSelfType(method.getParameters()[0], traitType)
&& !isPrimitiveVoid(method.getReturnType())) {
if (method.isStatic() && !method.isVoidMethod()
&& method.getParameters().length == 1 && isSelfType(method.getParameters()[0], traitType)) {
return xform.apply(method);
}
}

String isserName = "is" + getterName.substring(3);
for (MethodNode method : helperType.getMethods(isserName)) {
if (method.isStatic() && method.getParameters().length == 1
&& isSelfType(method.getParameters()[0], traitType)
&& isPrimitiveBoolean(method.getReturnType())) {
if (method.isStatic() && ClassHelper.isPrimitiveBoolean(method.getReturnType())
&& method.getParameters().length == 1 && isSelfType(method.getParameters()[0], traitType)) {
return xform.apply(method);
}
}
@@ -192,10 +180,9 @@ private Expression transformMethodCallExpression(final MethodCallExpression exp)

List<MethodNode> targets = helperType.getMethods(exp.getMethodAsString());
boolean isStatic = !targets.isEmpty() && targets.stream().map(MethodNode::getParameters)
.allMatch(params -> params.length > 0 && isClassType(params[0].getType()));
.allMatch(params -> params.length > 0 && ClassHelper.isClassType(params[0].getType()));

ArgumentListExpression newArgs = new ArgumentListExpression(
isStatic ? thisPropX(false, "class") : varX("this"));
ArgumentListExpression newArgs = args(isStatic ? thisPropX(false, "class") : varX("this"));
Expression arguments = exp.getArguments();
if (arguments instanceof TupleExpression) {
for (Expression expression : (TupleExpression) arguments) {
@@ -205,12 +192,9 @@ private Expression transformMethodCallExpression(final MethodCallExpression exp)
newArgs.addExpression(transform(arguments));
}

MethodCallExpression newCall = new MethodCallExpression(
new ClassExpression(helperType),
transform(exp.getMethod()),
newArgs
);
MethodCallExpression newCall = callX(classX(helperType), transform(exp.getMethod()), newArgs);
newCall.getObjectExpression().setSourcePosition(traitType);
newCall.setGenericsTypes(exp.getGenericsTypes());
newCall.setSpreadSafe(exp.isSpreadSafe());
newCall.setImplicitThis(false);
return newCall;
@@ -255,7 +239,7 @@ private ClassNode getTraitSuperTarget(final Expression exp) {
private static boolean isSelfType(final Parameter parameter, final ClassNode traitType) {
ClassNode paramType = parameter.getType();
if (paramType.equals(traitType)) return true;
return isClassType(paramType)
return ClassHelper.isClassType(paramType)
&& paramType.getGenericsTypes() != null
&& paramType.getGenericsTypes()[0].getType().equals(traitType);
}
@@ -25,6 +25,7 @@
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.FieldNode;
import org.codehaus.groovy.ast.GenericsType;
import org.codehaus.groovy.ast.GroovyClassVisitor;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.PropertyNode;
@@ -92,30 +93,25 @@ public abstract class TraitComposer {
public static final ClassNode COMPILESTATIC_CLASSNODE = ClassHelper.make(CompileStatic.class);

/**
* Given a class node, if this class node implements a trait, then generate all the appropriate
* code which delegates calls to the trait. It is safe to call this method on a class node which
* does not implement a trait.
* @param cNode a class node
* @param unit the source unit
* Given a class node, if this class node implements a trait, then generate
* all the appropriate code which delegates calls to the trait. It is safe
* to call this method on a class node which does not implement a trait.
*/
public static void doExtendTraits(final ClassNode cNode, final SourceUnit unit, final CompilationUnit cu) {
if (cNode.isInterface()) return;
boolean isItselfTrait = Traits.isTrait(cNode);
SuperCallTraitTransformer superCallTransformer = new SuperCallTraitTransformer(unit);
if (isItselfTrait) {
checkTraitAllowed(cNode, unit);
public static void doExtendTraits(final ClassNode cn, final SourceUnit su, final CompilationUnit cu) {
if (cn.isInterface()) return;
if (Traits.isTrait(cn)) {
checkTraitAllowed(cn, su);
return;
}
if (!cNode.getNameWithoutPackage().endsWith(Traits.TRAIT_HELPER)) {
List<ClassNode> traits = Traits.findTraits(cNode);
for (ClassNode trait : traits) {
TraitHelpersTuple helpers = Traits.findHelpers(trait);
applyTrait(trait, cNode, helpers, unit);
superCallTransformer.visitClass(cNode);
if (unit!=null) {
ASTTransformationCollectorCodeVisitor collector = new ASTTransformationCollectorCodeVisitor(unit, cu.getTransformLoader());
collector.visitClass(cNode);
}
if (!cn.getNameWithoutPackage().endsWith(Traits.TRAIT_HELPER)) {
GroovyClassVisitor visitor = new SuperCallTraitTransformer(su);
for (ClassNode trait : Traits.findTraits(cn)) {
applyTrait(trait, cn, Traits.findHelpers(trait), su);
visitor.visitClass(cn);
}
if (su != null) {
visitor = new ASTTransformationCollectorCodeVisitor(su, cu.getTransformLoader());
visitor.visitClass(cn);
}
}
}
@@ -43,6 +43,7 @@
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;

import java.lang.reflect.Modifier;
import java.util.Collection;
@@ -100,8 +101,7 @@ public Expression transform(final Expression exp) {
return transformBinaryExpression((BinaryExpression) exp, weavedType);
} else if (exp instanceof StaticMethodCallExpression) {
StaticMethodCallExpression call = (StaticMethodCallExpression) exp;
ClassNode ownerType = call.getOwnerType();
if (traitClass.equals(ownerType)) {
if (call.getOwnerType().equals(traitClass)) {
MethodCallExpression mce = callX(
varX(weaved),
call.getMethod(),
@@ -187,24 +187,22 @@ private Expression transformBinaryExpression(final BinaryExpression exp, final C
Expression leftExpression = exp.getLeftExpression();
Expression rightExpression = exp.getRightExpression();
Token operation = exp.getOperation();
if (operation.getText().equals("=")) {
if (operation.getType() == Types.ASSIGN) {
String leftFieldName = null;
// it's an assignment
if (leftExpression instanceof VariableExpression && ((VariableExpression) leftExpression).getAccessedVariable() instanceof FieldNode) {
leftFieldName = ((VariableExpression) leftExpression).getAccessedVariable().getName();
} else if (leftExpression instanceof FieldExpression) {
leftFieldName = ((FieldExpression) leftExpression).getFieldName();
} else if (leftExpression instanceof PropertyExpression
&& (((PropertyExpression) leftExpression).isImplicitThis() || "this".equals(((PropertyExpression) leftExpression).getObjectExpression().getText()))) {
leftFieldName = ((PropertyExpression) leftExpression).getPropertyAsString();
FieldNode fn = tryGetFieldNode(weavedType, leftFieldName);
if (fieldHelper == null || fn == null && !fieldHelper.hasPossibleMethod(Traits.helperSetterName(new FieldNode(leftFieldName, 0, ClassHelper.OBJECT_TYPE, weavedType, null)), rightExpression)) {
return binX(propX(varX(weaved), leftFieldName), operation, transform(rightExpression));
}
}
if (leftFieldName != null) {
FieldNode fn = weavedType.getDeclaredField(leftFieldName);
FieldNode staticField = tryGetFieldNode(weavedType, leftFieldName);
if (fieldHelper == null || staticField == null && !fieldHelper.hasPossibleMethod(Traits.helperSetterName(new FieldNode(leftFieldName, 0, ClassHelper.OBJECT_TYPE, weavedType, null)), rightExpression)) {
return binX(propX(varX(weaved), leftFieldName), operation, transform(rightExpression)); // GROOVY-7342, GROOVY-7456, GROOVY-9739, GROOVY-10143, et al.
}
FieldNode fn = weavedType.getDeclaredField(leftFieldName);
if (fn == null) {
fn = new FieldNode(leftFieldName, 0, ClassHelper.OBJECT_TYPE, weavedType, null);
}
@@ -217,14 +215,15 @@ private Expression transformBinaryExpression(final BinaryExpression exp, final C
MethodCallExpression mce = callX(
receiver,
method,
args(super.transform(rightExpression))
super.transform(rightExpression)
);
mce.setImplicitThis(false);
mce.setSourcePosition(exp);
markDynamicCall(mce, staticField, isStatic);
return mce;
}
}

Expression leftTransform = transform(leftExpression);
Expression rightTransform = transform(rightExpression);
Expression ret = exp instanceof DeclarationExpression ?
@@ -0,0 +1,22 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.bugs.groovyA143

abstract class A {
}
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.bugs.groovyA143

@groovy.transform.SelfType(A)
trait B {
abstract boolean booleanMethod1()
abstract boolean booleanMethod2()
abstract boolean booleanMethod3()
Object getManager() {
if (this.managerObject == null) {
this.managerObject = new C(this as B)
}
this.managerObject
}
C managerObject
}
@@ -0,0 +1,26 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.bugs.groovyA143

class C {
final B base
C(B base) {
this.base = base
}
}
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package groovy.bugs.groovyA143

@groovy.transform.CompileStatic
@groovy.transform.SelfType([A])
trait D extends B {
boolean booleanMethod1() {
true
}
boolean booleanMethod2() {
true
}
boolean booleanMethod3() {
true
}
@Override
Object getManager() {
if (managerObject == null) {
managerObject = new E(this as B)
}
managerObject
}
}

0 comments on commit 5176ea3

Please sign in to comment.