Skip to content

Commit

Permalink
GROOVY-8917, GROOVY-9347, GROOVY-10049
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Sep 16, 2021
1 parent eadbd7b commit c1c4fef
Show file tree
Hide file tree
Showing 4 changed files with 292 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,35 @@ public void testTypeChecked15() {

@Test
public void testTypeChecked16() {
//@formatter:off
String[] sources = {
"Main.groovy",
"@groovy.transform.TypeChecked\n" +
"void test() {\n" +
" new Order<Pogo, Comparable>({Pogo p -> p.s})\n" + // No such property s for class Object
"}\n" +
"test()\n",

"Order.groovy",
"class Order<T, U extends Comparable<? super U>> {\n" +
" Order(java.util.function.Function<? super T, ? extends U> keyExtractor) {\n" +
" }\n" +
"}\n",

"Pogo.groovy",
"@groovy.transform.Canonical\n" +
"class Pogo {\n" +
" Number n\n" +
" String s\n" +
"}\n",
};
//@formatter:on

runConformTest(sources);
}

@Test
public void testTypeChecked17() {
//@formatter:off
String[] sources = {
"Main.groovy",
Expand Down Expand Up @@ -1079,6 +1108,44 @@ public void testTypeChecked8909a() {
"----------\n");
}

@Test
public void testTypeChecked8917() {
if (Float.parseFloat(System.getProperty("java.specification.version")) > 8)
vmArguments = new String[] {"--add-opens", "java.base/java.util.stream=ALL-UNNAMED"};

//@formatter:off
String[] sources = {
"Main.groovy",
"@groovy.transform.TypeChecked\n" +
"def test() {\n" +
" [1, 2, 3].stream().reduce(7) { r, e -> r + e }\n" +
"}\n" +
"print test()\n",
};
//@formatter:on

runConformTest(sources, "13");
}

@Test
public void testTypeChecked8917a() {
if (Float.parseFloat(System.getProperty("java.specification.version")) > 8)
vmArguments = new String[] {"--add-opens", "java.base/java.util.stream=ALL-UNNAMED"};

//@formatter:off
String[] sources = {
"Main.groovy",
"@groovy.transform.TypeChecked\n" +
"def test() {\n" +
" [1, 2, 3].stream().<String>map{i -> null}.limit(1).toList()\n" +
"}\n" +
"print test()\n",
};
//@formatter:on

runConformTest(sources, "[null]");
}

@Test
public void testTypeChecked8974() {
//@formatter:off
Expand Down Expand Up @@ -2643,12 +2710,9 @@ public void testTypeChecked10049() {
//@formatter:off
String[] sources = {
"Main.groovy",
"def <X /*extends Number*/> Set<X> generateNumbers(Class<X> type) {\n" +
" return Collections.singleton(type.newInstance(42))\n" +
"}\n" +
"@groovy.transform.TypeChecked\n" +
"def <Y extends Number> void printNumbers(Class<Y> numberType) {\n" +
" generateNumbers(numberType).stream()\n" +
"def <T extends Number> void printNumbers(Class<T> numberType) {\n" +
" Collections.singleton(numberType.newInstance(42)).stream()\n" +
" .filter { n -> n.intValue() > 0 }\n" +
" .forEach { n -> print n }\n" +
"}\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findSetters;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findTargetVariable;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolve;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolveType;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCombinedBoundType;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getGenericsWithoutArray;
Expand Down Expand Up @@ -3433,6 +3434,7 @@ private void processNamedParam(AnnotationConstantExpression value, Map<Object, E
* @param selectedMethod the method accepting a closure
*/
protected void inferClosureParameterTypes(final ClassNode receiver, final Expression arguments, final ClosureExpression expression, final Parameter param, final MethodNode selectedMethod) {
MethodNode abstractMethod;
List<AnnotationNode> annotations = param.getAnnotations(CLOSUREPARAMS_CLASSNODE);
if (annotations != null && !annotations.isEmpty()) {
for (AnnotationNode annotation : annotations) {
Expand All @@ -3443,12 +3445,85 @@ protected void inferClosureParameterTypes(final ClassNode receiver, final Expres
doInferClosureParameterTypes(receiver, arguments, expression, selectedMethod, hintClass, resolverClass, options);
}
}
/* GRECLIPSE edit -- GROOVY-8917, GROOVY-9347, GROOVY-10049
} else if (isSAMType(param.getOriginType())) {
// SAM coercion
inferSAMType(param, receiver, selectedMethod, InvocationWriter.makeArgumentList(arguments), expression);
}
*/
} else if ((abstractMethod = findSAM(param.getOriginType())) != null) {
Map<GenericsTypeName, GenericsType> context = selectedMethod.isStatic() ? new HashMap<>() : extractPlaceHolders(null, receiver, getDeclaringClass(selectedMethod, arguments));
GenericsType[] typeParameters = selectedMethod instanceof ConstructorNode ? selectedMethod.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, selectedMethod.getGenericsTypes());

if (typeParameters != null) {
boolean typeParametersResolved = false;
// first check for explicit type arguments
Expression emc = typeCheckingContext.getEnclosingMethodCall();
if (emc instanceof MethodCallExpression) {
MethodCallExpression mce = (MethodCallExpression) emc;
if (mce.getArguments() == arguments) {
GenericsType[] typeArguments = mce.getGenericsTypes();
if (typeArguments != null) {
int n = typeParameters.length;
if (n == typeArguments.length) {
typeParametersResolved = true;
for (int i = 0; i < n; i += 1) {
context.put(new GenericsTypeName(typeParameters[i].getName()), typeArguments[i]);
}
}
}
}
}
if (!typeParametersResolved) {
// check for implicit type arguments
int i = -1; Parameter[] p = selectedMethod.getParameters();
for (Expression argument : (ArgumentListExpression) arguments) { i += 1;
if (argument instanceof ClosureExpression || isNullConstant(argument)) continue;

ClassNode pType = p[Math.min(i, p.length - 1)].getType();
Map<GenericsTypeName, GenericsType> gc = new HashMap<>();
extractGenericsConnections(gc, wrapTypeIfNecessary(getType(argument)), pType);

gc.forEach((key, gt) -> {
for (GenericsType tp : typeParameters) {
if (tp.getName().equals(key.getName())) {
context.putIfAbsent(key, gt); // TODO: merge
break;
}
}
});
}

for (GenericsType tp : typeParameters) {
context.computeIfAbsent(new GenericsTypeName(tp.getName()), x -> fullyResolve(tp, context));
}
}
}

Map<GenericsType, GenericsType> samTypeGenerics = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(abstractMethod.getDeclaringClass(), applyGenericsContext(context, param.getType()));
ClassNode[] samParamTypes = Arrays.stream(abstractMethod.getParameters()).map(Parameter::getType).map(t -> t.isGenericsPlaceHolder() ? GenericsUtils.findActualTypeByGenericsPlaceholderName(t.getUnresolvedName(), samTypeGenerics) : t).toArray(ClassNode[]::new);

ClassNode[] paramTypes = expression.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS);
if (paramTypes == null) {
int n; Parameter[] p = expression.getParameters();
if (p == null) {
// zero parameters
paramTypes = ClassNode.EMPTY_ARRAY;
} else if ((n = p.length) == 0) {
// implicit parameter(s)
paramTypes = samParamTypes;
} else {
paramTypes = new ClassNode[n];
for (int i = 0; i < n; i += 1) {
paramTypes[i] = i < samParamTypes.length ? samParamTypes[i] : null;
}
}
expression.putNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS, paramTypes);
}
}
// GRECLIPSE end
}

/* GRECLIPSE edit
private void inferSAMType(Parameter param, ClassNode receiver, MethodNode methodWithSAMParameter, ArgumentListExpression originalMethodCallArguments, ClosureExpression openBlock) {
// In a method call with SAM coercion the inference is to be
// understood as a two phase process. We have the normal method call
Expand All @@ -3464,14 +3539,6 @@ private void inferSAMType(Parameter param, ClassNode receiver, MethodNode method
// First we try to get as much information about the declaration
// class through the receiver
Map<GenericsTypeName, GenericsType> targetMethodDeclarationClassConnections = new HashMap<GenericsTypeName, GenericsType>();
// GRECLIPSE add -- GROOVY-9347, GROOVY-10049
for (ClassNode face : receiver.getAllInterfaces()) {
if (face != receiver) {
ClassNode type = StaticTypeCheckingSupport.getCorrectedClassNode(receiver, face, true);
extractGenericsConnections(targetMethodDeclarationClassConnections, type, face.redirect());
}
}
// GRECLIPSE end
extractGenericsConnections(targetMethodDeclarationClassConnections, receiver, receiver.redirect());
// then we use the method with the SAM parameter to get more information about the declaration
Parameter[] parametersOfMethodContainingSAM = methodWithSAMParameter.getParameters();
Expand Down Expand Up @@ -3537,6 +3604,7 @@ private void inferSAMType(Parameter param, ClassNode receiver, MethodNode method
private ClassNode typeOrNull(ClassNode[] parameterTypesForSAM, int i) {
return i < parameterTypesForSAM.length ? parameterTypesForSAM[i] : null;
}
*/

private List<ClassNode[]> getSignaturesFromHint(final ClosureExpression expression, final MethodNode selectedMethod, final Expression hintClass, final Expression options) {
// initialize hints
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,9 @@
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findSetters;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findTargetVariable;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolve;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.fullyResolveType;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCombinedBoundType;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getCorrectedClassNode;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getGenericsWithoutArray;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.getOperationName;
import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf;
Expand Down Expand Up @@ -3101,8 +3101,77 @@ protected void inferClosureParameterTypes(final ClassNode receiver, final Expres
}
}
} else if (isSAMType(param.getOriginType())) {
// SAM coercion
/* GRECLIPSE edit -- GROOVY-8917, GROOVY-10049
inferSAMType(param, receiver, selectedMethod, InvocationWriter.makeArgumentList(arguments), expression);
*/
Map<GenericsTypeName, GenericsType> context = selectedMethod.isStatic() ? new HashMap<>() : extractPlaceHolders(null, receiver, getDeclaringClass(selectedMethod, arguments));
GenericsType[] typeParameters = selectedMethod instanceof ConstructorNode ? selectedMethod.getDeclaringClass().getGenericsTypes() : applyGenericsContext(context, selectedMethod.getGenericsTypes());

if (typeParameters != null) {
boolean typeParametersResolved = false;
// first check for explicit type arguments
Expression emc = typeCheckingContext.getEnclosingMethodCall();
if (emc instanceof MethodCallExpression) {
MethodCallExpression mce = (MethodCallExpression) emc;
if (mce.getArguments() == arguments) {
GenericsType[] typeArguments = mce.getGenericsTypes();
if (typeArguments != null) {
int n = typeParameters.length;
if (n == typeArguments.length) {
typeParametersResolved = true;
for (int i = 0; i < n; i += 1) {
context.put(new GenericsTypeName(typeParameters[i].getName()), typeArguments[i]);
}
}
}
}
}
if (!typeParametersResolved) {
// check for implicit type arguments
int i = -1; Parameter[] p = selectedMethod.getParameters();
for (Expression argument : (ArgumentListExpression) arguments) { i += 1;
if (argument instanceof ClosureExpression || isNullConstant(argument)) continue;

ClassNode pType = p[Math.min(i, p.length - 1)].getType();
Map<GenericsTypeName, GenericsType> gc = new HashMap<>();
extractGenericsConnections(gc, wrapTypeIfNecessary(getType(argument)), pType);

gc.forEach((key, gt) -> {
for (GenericsType tp : typeParameters) {
if (tp.getName().equals(key.getName())) {
context.putIfAbsent(key, gt); // TODO: merge
break;
}
}
});
}

for (GenericsType tp : typeParameters) {
context.computeIfAbsent(new GenericsTypeName(tp.getName()), x -> fullyResolve(tp, context));
}
}
}

ClassNode[] samParamTypes = GenericsUtils.parameterizeSAM(applyGenericsContext(context, param.getType())).getV1();

ClassNode[] paramTypes = expression.getNodeMetaData(CLOSURE_ARGUMENTS);
if (paramTypes == null) {
int n; Parameter[] p = expression.getParameters();
if (p == null) {
// zero parameters
paramTypes = ClassNode.EMPTY_ARRAY;
} else if ((n = p.length) == 0) {
// implicit parameter(s)
paramTypes = samParamTypes;
} else {
paramTypes = new ClassNode[n];
for (int i = 0; i < n; i += 1) {
paramTypes[i] = i < samParamTypes.length ? samParamTypes[i] : null;
}
}
expression.putNodeMetaData(CLOSURE_ARGUMENTS, paramTypes);
}
// GRECLIPSE end
}
}

Expand All @@ -3116,25 +3185,16 @@ protected void inferClosureParameterTypes(final ClassNode receiver, final Expres
* the same time the SAM class is used in the target method parameter,
* providing a connection from the SAM type and the target method's class.
*/
/* GRECLIPSE edit
private void inferSAMType(final Parameter param, final ClassNode receiver, final MethodNode methodWithSAMParameter, final ArgumentListExpression originalMethodCallArguments, final ClosureExpression openBlock) {
// first we try to get as much information about the declaration class through the receiver
Map<GenericsTypeName, GenericsType> targetMethodConnections = new HashMap<>();
/* GRECLIPSE edit -- GROOVY-10049
for (ClassNode face : receiver.getAllInterfaces()) {
extractGenericsConnections(targetMethodConnections, getCorrectedClassNode(receiver, face, true), face.redirect());
}
if (!receiver.isInterface()) {
extractGenericsConnections(targetMethodConnections, receiver, receiver.redirect());
}
*/
for (ClassNode face : receiver.getAllInterfaces()) {
if (face != receiver) {
ClassNode type = getCorrectedClassNode(receiver, face, true);
extractGenericsConnections(targetMethodConnections, type, face.redirect());
}
}
extractGenericsConnections(targetMethodConnections, receiver, receiver.redirect());
// GRECLIPSE end
// then we use the method with the SAM-type parameter to get more information about the declaration
Parameter[] parametersOfMethodContainingSAM = methodWithSAMParameter.getParameters();
Expand Down Expand Up @@ -3229,6 +3289,7 @@ private void tryToInferUnresolvedBlockParameterType(final ClassNode paramTypeWit
private static ClassNode typeOrNull(final ClassNode[] parameterTypesForSAM, final int i) {
return i < parameterTypesForSAM.length ? parameterTypesForSAM[i] : null;
}
*/

private List<ClassNode[]> getSignaturesFromHint(final ClosureExpression expression, final MethodNode selectedMethod, final Expression hintClass, final Expression options) {
// initialize hints
Expand Down
Loading

0 comments on commit c1c4fef

Please sign in to comment.