Skip to content

Commit

Permalink
GROOVY-9977
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Mar 12, 2021
1 parent a8c2423 commit b454948
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1262,4 +1262,25 @@ public void testTypeChecked9974() {

runConformTest(sources, "true");
}

@Test
public void testTypeChecked9977() {
//@formatter:off
String[] sources = {
"Main.groovy",
"@groovy.transform.TypeChecked\n" +
"class C {\n" +
" public final Comparator<Integer> f = { a, b -> Integer.compare(a, b) }\n" +
" \n" +
" final Comparator<Integer> p = { a, b -> Integer.compare(a, b) }\n" +
" def m() {\n" +
" Comparator<Integer> v = { a, b -> Integer.compare(a, b) }\n" +
" }\n" +
"}\n" +
"print new C().getP().compare(0, 1)\n",
};
//@formatter:on

runConformTest(sources, "-1");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Supplier;

import static org.apache.groovy.ast.tools.ClassNodeUtils.samePackageName;
Expand Down Expand Up @@ -186,7 +187,6 @@
import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.thisPropX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
import static org.codehaus.groovy.ast.tools.GenericsUtils.extractPlaceholders;
import static org.codehaus.groovy.ast.tools.GenericsUtils.toGenericTypesString;
import static org.codehaus.groovy.ast.tools.WideningCategories.isBigDecCategory;
import static org.codehaus.groovy.ast.tools.WideningCategories.isBigIntCategory;
Expand Down Expand Up @@ -858,7 +858,8 @@ public void visitBinaryExpression(final BinaryExpression expression) {
try {
final Expression leftExpression = expression.getLeftExpression();
final Expression rightExpression = expression.getRightExpression();
leftExpression.visit(this);

leftExpression.visit(this); ClassNode lType;
SetterInfo setterInfo = removeSetterInfo(leftExpression);
if (setterInfo != null) {
if (ensureValidSetter(expression, leftExpression, rightExpression, setterInfo)) {
Expand All @@ -867,12 +868,18 @@ public void visitBinaryExpression(final BinaryExpression expression) {
// GRECLIPSE end
return;
}

lType = getType(leftExpression);
} else {
// GRECLIPSE add -- GROOVY-9977
lType = getType(leftExpression);
if (op == ASSIGN && isFunctionalInterface(lType)) {
processFunctionalInterfaceAssignment(lType, rightExpression);
}
// GRECLIPSE end
rightExpression.visit(this);
}
ClassNode lType = getType(leftExpression);
/* GRECLIPSE edit -- GROOVY-9953
ClassNode lType = getType(leftExpression);
ClassNode rType = getType(rightExpression);
if (isNullConstant(rightExpression)) {
if (!isPrimitiveType(lType))
Expand Down Expand Up @@ -1033,6 +1040,37 @@ && isAssignment(enclosingBinaryExpression.getOperation().getType())) {
}
}

// GRECLIPSE add
private static boolean isFunctionalInterface(final ClassNode type) {
return type.isInterface() && isSAMType(type);
}

private void processFunctionalInterfaceAssignment(final ClassNode lhsType, final Expression rhsExpression) {
if (rhsExpression instanceof ClosureExpression) {
MethodNode abstractMethod = ClassHelper.findSAM(lhsType);
Map<GenericsType, GenericsType> mappings = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(abstractMethod.getDeclaringClass(), lhsType);
Function<ClassNode, ClassNode> resolver = t -> t.isGenericsPlaceHolder() ? GenericsUtils.findActualTypeByGenericsPlaceholderName(t.getUnresolvedName(), mappings) : t;

ClassNode[] samParameterTypes = Arrays.stream(abstractMethod.getParameters()).map(Parameter::getType).map(resolver).toArray(ClassNode[]::new);
Parameter[] closureParameters = getParametersSafe((ClosureExpression) rhsExpression);
int n = closureParameters.length;
if (n == samParameterTypes.length) {
for (int i = 0; i < n; i += 1) {
Parameter parameter = closureParameters[i];
if (parameter.isDynamicTyped()) {
parameter.setType(samParameterTypes[i]);
parameter.setOriginType(samParameterTypes[i]);
}
}
} else {
addStaticTypeError("Wrong number of parameters: ", rhsExpression);
}

storeInferredReturnType(rhsExpression, resolver.apply(abstractMethod.getReturnType()));
}
}
// GRECLIPSE end

/**
* Given a binary expression corresponding to an assignment, will check that the type of the RHS matches one
* of the possible setters and if not, throw a type checking error.
Expand Down Expand Up @@ -1995,7 +2033,13 @@ public void visitProperty(PropertyNode node) {
try {
typeCheckingContext.isInStaticContext = node.isInStaticContext();
currentProperty = node;
/* GRECLIPSE edit -- GROOVY-9977
super.visitProperty(node);
*/
visitAnnotations(node);
visitClassCodeContainer(node.getGetterBlock());
visitClassCodeContainer(node.getSetterBlock());
// GRECLIPSE end
} finally {
currentProperty = null;
typeCheckingContext.isInStaticContext = osc;
Expand All @@ -2008,9 +2052,24 @@ public void visitField(final FieldNode node) {
try {
typeCheckingContext.isInStaticContext = node.isInStaticContext();
currentField = node;
/* GRECLIPSE edit -- GROOVY-9977
super.visitField(node);
*/
visitAnnotations(node);
// GRECLIPSE end
Expression init = node.getInitialExpression();
if (init != null) {
// GRECLIPSE add -- GROOVY-9977
ClassNode lType = getType(node);
if (isFunctionalInterface(lType)) {
processFunctionalInterfaceAssignment(lType, init);
}
init.visit(this);
ClassNode rType = getType(init);
if (init instanceof ConstructorCallExpression) {
inferDiamondType((ConstructorCallExpression) init, lType);
}
// GRECLIPSE end
FieldExpression left = new FieldExpression(node);
BinaryExpression bexp = binX(
left,
Expand All @@ -2020,13 +2079,13 @@ public void visitField(final FieldNode node) {
bexp.setSourcePosition(init);
/* GRECLIPSE edit -- GROOVY-9882
typeCheckAssignment(bexp, left, node.getOriginType(), init, getType(init));
*/
ClassNode lType = node.getOriginType(), rType = getType(init);
typeCheckAssignment(bexp, left, lType, init, getResultType(lType, ASSIGN, rType, bexp));
// GRECLIPSE end
if (init instanceof ConstructorCallExpression) {
inferDiamondType((ConstructorCallExpression) init, node.getOriginType());
}
*/
typeCheckAssignment(bexp, left, lType, init, getResultType(lType, ASSIGN, rType, bexp));
// GRECLIPSE end
}
} finally {
currentField = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ public void visitBinaryExpression(final BinaryExpression expression) {
}
} else {
lType = getType(leftExpression);
/* GRECLIPSE edit -- GROOVY-9977
boolean isFunctionalInterface = isFunctionalInterface(lType);
if (isFunctionalInterface && rightExpression instanceof MethodReferenceExpression) {
LambdaExpression lambdaExpression = constructLambdaExpressionForMethodReference(lType);
Expand All @@ -778,7 +779,11 @@ public void visitBinaryExpression(final BinaryExpression expression) {
} else if (op == ASSIGN && isFunctionalInterface && rightExpression instanceof ClosureExpression) {
inferParameterAndReturnTypesOfClosureOnRHS(lType, (ClosureExpression) rightExpression);
}

*/
if (op == ASSIGN && isFunctionalInterface(lType)) {
processFunctionalInterfaceAssignment(lType, rightExpression);
}
// GRECLIPSE end
rightExpression.visit(this);
}

Expand Down Expand Up @@ -943,6 +948,20 @@ private void validateResourceInARM(final BinaryExpression expression, final Clas
}
}

// GRECLIPSE add
private void processFunctionalInterfaceAssignment(final ClassNode lhsType, final Expression rhsExpression) {
if (rhsExpression instanceof ClosureExpression) {
inferParameterAndReturnTypesOfClosureOnRHS(lhsType, (ClosureExpression) rhsExpression);
} else if (rhsExpression instanceof MethodReferenceExpression) {
LambdaExpression lambdaExpression = constructLambdaExpressionForMethodReference(lhsType);

inferParameterAndReturnTypesOfClosureOnRHS(lhsType, lambdaExpression);
rhsExpression.putNodeMetaData(CONSTRUCTED_LAMBDA_EXPRESSION, lambdaExpression);
rhsExpression.putNodeMetaData(CLOSURE_ARGUMENTS, Arrays.stream(lambdaExpression.getParameters()).map(Parameter::getType).toArray(ClassNode[]::new));
}
}
// GRECLIPSE end

private void inferParameterAndReturnTypesOfClosureOnRHS(final ClassNode lhsType, final ClosureExpression rhsExpression) {
Tuple2<ClassNode[], ClassNode> typeInfo = GenericsUtils.parameterizeSAM(lhsType);
Parameter[] closureParameters = getParametersSafe(rhsExpression);
Expand Down Expand Up @@ -1886,7 +1905,13 @@ public void visitProperty(final PropertyNode node) {
try {
typeCheckingContext.isInStaticContext = node.isInStaticContext();
currentProperty = node;
/* GRECLIPSE edit -- GROOVY-9977
super.visitProperty(node);
*/
visitAnnotations(node);
visitClassCodeContainer(node.getGetterBlock());
visitClassCodeContainer(node.getSetterBlock());
// GRECLIPSE end
} finally {
currentProperty = null;
typeCheckingContext.isInStaticContext = osc;
Expand All @@ -1899,9 +1924,24 @@ public void visitField(final FieldNode node) {
try {
typeCheckingContext.isInStaticContext = node.isInStaticContext();
currentField = node;
/* GRECLIPSE edit -- GROOVY-9977
super.visitField(node);
*/
visitAnnotations(node);
// GRECLIPSE end
Expression init = node.getInitialExpression();
if (init != null) {
// GRECLIPSE add -- GROOVY-9977
ClassNode lType = getType(node);
if (isFunctionalInterface(lType)) {
processFunctionalInterfaceAssignment(lType, init);
}
init.visit(this);
ClassNode rType = getType(init);
if (init instanceof ConstructorCallExpression) {
inferDiamondType((ConstructorCallExpression) init, lType);
}
// GRECLIPSE end
FieldExpression left = new FieldExpression(node);
BinaryExpression bexp = binX(
left,
Expand All @@ -1911,17 +1951,12 @@ public void visitField(final FieldNode node) {
bexp.setSourcePosition(init);
/* GRECLIPSE edit -- GROOVY-9882
typeCheckAssignment(bexp, left, node.getOriginType(), init, getType(init));
*/
ClassNode lType = node.getOriginType(), rType = getType(init);
typeCheckAssignment(bexp, left, lType, init, getResultType(lType, ASSIGN, rType, bexp));
// GRECLIPSE end
if (init instanceof ConstructorCallExpression) {
inferDiamondType((ConstructorCallExpression) init, node.getOriginType());
}
// GRECLIPSE add
else if (init instanceof ClosureExpression && isFunctionalInterface(lType)) {
inferParameterAndReturnTypesOfClosureOnRHS(lType, (ClosureExpression) init);
}
*/
typeCheckAssignment(bexp, left, lType, init, getResultType(lType, ASSIGN, rType, bexp));
// GRECLIPSE end
}
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ public void visitBinaryExpression(final BinaryExpression expression) {
}
} else {
lType = getType(leftExpression);
/* GRECLIPSE edit -- GROOVY-9977
boolean isFunctionalInterface = isFunctionalInterface(lType);
if (isFunctionalInterface && rightExpression instanceof MethodReferenceExpression) {
LambdaExpression lambdaExpression = constructLambdaExpressionForMethodReference(lType);
Expand All @@ -778,7 +779,11 @@ public void visitBinaryExpression(final BinaryExpression expression) {
} else if (op == ASSIGN && isFunctionalInterface && rightExpression instanceof ClosureExpression) {
inferParameterAndReturnTypesOfClosureOnRHS(lType, (ClosureExpression) rightExpression);
}

*/
if (op == ASSIGN && isFunctionalInterface(lType)) {
processFunctionalInterfaceAssignment(lType, rightExpression);
}
// GRECLIPSE end
rightExpression.visit(this);
}

Expand Down Expand Up @@ -941,6 +946,20 @@ private void validateResourceInARM(final BinaryExpression expression, final Clas
}
}

// GRECLIPSE add
private void processFunctionalInterfaceAssignment(final ClassNode lhsType, final Expression rhsExpression) {
if (rhsExpression instanceof ClosureExpression) {
inferParameterAndReturnTypesOfClosureOnRHS(lhsType, (ClosureExpression) rhsExpression);
} else if (rhsExpression instanceof MethodReferenceExpression) {
LambdaExpression lambdaExpression = constructLambdaExpressionForMethodReference(lhsType);

inferParameterAndReturnTypesOfClosureOnRHS(lhsType, lambdaExpression);
rhsExpression.putNodeMetaData(CONSTRUCTED_LAMBDA_EXPRESSION, lambdaExpression);
rhsExpression.putNodeMetaData(CLOSURE_ARGUMENTS, Arrays.stream(lambdaExpression.getParameters()).map(Parameter::getType).toArray(ClassNode[]::new));
}
}
// GRECLIPSE end

private void inferParameterAndReturnTypesOfClosureOnRHS(final ClassNode lhsType, final ClosureExpression rhsExpression) {
Tuple2<ClassNode[], ClassNode> typeInfo = GenericsUtils.parameterizeSAM(lhsType);
Parameter[] closureParameters = getParametersSafe(rhsExpression);
Expand Down Expand Up @@ -1873,7 +1892,13 @@ public void visitProperty(final PropertyNode node) {
try {
typeCheckingContext.isInStaticContext = node.isInStaticContext();
currentProperty = node;
/* GRECLIPSE edit -- GROOVY-9977
super.visitProperty(node);
*/
visitAnnotations(node);
visitClassCodeContainer(node.getGetterBlock());
visitClassCodeContainer(node.getSetterBlock());
// GRECLIPSE end
} finally {
currentProperty = null;
typeCheckingContext.isInStaticContext = osc;
Expand All @@ -1886,9 +1911,24 @@ public void visitField(final FieldNode node) {
try {
typeCheckingContext.isInStaticContext = node.isInStaticContext();
currentField = node;
/* GRECLIPSE edit -- GROOVY-9977
super.visitField(node);
*/
visitAnnotations(node);
// GRECLIPSE end
Expression init = node.getInitialExpression();
if (init != null) {
// GRECLIPSE add -- GROOVY-9977
ClassNode lType = getType(node);
if (isFunctionalInterface(lType)) {
processFunctionalInterfaceAssignment(lType, init);
}
init.visit(this);
ClassNode rType = getType(init);
if (init instanceof ConstructorCallExpression) {
inferDiamondType((ConstructorCallExpression) init, lType);
}
// GRECLIPSE end
FieldExpression left = new FieldExpression(node);
BinaryExpression bexp = binX(
left,
Expand All @@ -1898,17 +1938,12 @@ public void visitField(final FieldNode node) {
bexp.setSourcePosition(init);
/* GRECLIPSE edit -- GROOVY-9882
typeCheckAssignment(bexp, left, node.getOriginType(), init, getType(init));
*/
ClassNode lType = node.getOriginType(), rType = getType(init);
typeCheckAssignment(bexp, left, lType, init, getResultType(lType, ASSIGN, rType, bexp));
// GRECLIPSE end
if (init instanceof ConstructorCallExpression) {
inferDiamondType((ConstructorCallExpression) init, node.getOriginType());
}
// GRECLIPSE add
else if (init instanceof ClosureExpression && isFunctionalInterface(lType)) {
inferParameterAndReturnTypesOfClosureOnRHS(lType, (ClosureExpression) init);
}
*/
typeCheckAssignment(bexp, left, lType, init, getResultType(lType, ASSIGN, rType, bexp));
// GRECLIPSE end
}
} finally {
Expand Down

0 comments on commit b454948

Please sign in to comment.