Skip to content

Commit

Permalink
GROOVY-9803, GROOVY-9762 (redux)
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Nov 12, 2020
1 parent 20f53ff commit 465f3e0
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,29 @@ public void testClosure6b() {
assertType(contents, "array", "java.lang.String[]");
}

@Test // GROOVY-9803
public void testClosure7() {
String contents =
"class C<T> {\n" +
" static <U> C<U> of(U item) {}\n" +
" def <V> C<V> map(F<? super T, ? super V> func) {}\n" +
"}\n" +
"class D {\n" +
" static <W> Set<W> wrap(W o) {}\n" +
"}\n" +
"interface F<X,Y> {\n" +
" Y apply(X x)\n" +
"}\n" +
"@groovy.transform.TypeChecked\n" +
"void test() {\n" +
" def c = C.of(123)\n" +
" def d = c.map(D.&wrap)\n" +
" def e = d.map{x -> x.first()}\n" +
"}\n";
assertType(contents, "e", "C<java.lang.Integer>");
assertType(contents, "x", "java.util.Set<java.lang.Integer>");
}

@Test
public void testArrayDGM() {
String contents =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,4 +430,66 @@ public void testTypeChecked9751() {

runNegativeTest(sources, "");
}

@Test
public void testTypeChecked9762() {
//@formatter:off
String[] sources = {
"Main.groovy",
"static <T> List<T> list(T item) {\n" +
" Collections.singletonList(item)\n" +
"}\n" +
"@groovy.transform.TypeChecked\n" +
"void test() {\n" +
" Optional<Integer> opt = Optional.ofNullable(123)\n" +
" List<Integer> result = opt.map(this.&list).get()\n" +
" print result\n" +
"}\n" +
"test()\n",
};
//@formatter:on

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

@Test
public void testTypeChecked9803() {
//@formatter:off
String[] sources = {
"Main.groovy",
"@groovy.transform.TypeChecked\n" +
"void test() {\n" +
" def c = C.of(123)\n" +
" def d = c.map(D.&wrap)\n" +
" def e = d.map{x -> x.first().intValue()}\n" +
" print e.t\n" +
"}\n" +
"test()\n",

"Types.groovy",
"class C<T> {\n" +
" private T t\n" +
" C(T item) {\n" +
" t = item\n" +
" }\n" +
" static <U> C<U> of(U item) {\n" +
" new C<U>(item)\n" +
" }\n" +
" def <V> C<V> map(F<? super T, ? super V> func) {\n" +
" new C<V>(func.apply(t))\n" +
" }\n" +
"}\n" +
"class D {\n" +
" static <W> Set<W> wrap(W o) {\n" +
" Collections.singleton(o)\n" +
" }\n" +
"}\n" +
"interface F<X,Y> {\n" +
" Y apply(X x)\n" +
"}\n",
};
//@formatter:on

runConformTest(sources, "123");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1931,7 +1931,15 @@ static Map<GenericsTypeName, GenericsType> applyGenericsContextToParameterClass(
GenericsType[] newGTs = applyGenericsContext(spec, gts);
ClassNode newTarget = parameterUsage.redirect().getPlainNodeReference();
newTarget.setGenericsTypes(newGTs);
/* GRECLIPSE edit -- GROOVY-9762, GROOVY-9803
return GenericsUtils.extractPlaceholders(newTarget);
*/
Map<GenericsTypeName, GenericsType> newSpec = GenericsUtils.extractPlaceholders(newTarget);
newSpec.replaceAll((xx, gt) ->
Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt)
);
return newSpec;
// GRECLIPSE end
}

private static GenericsType[] applyGenericsContext(
Expand Down Expand Up @@ -1984,7 +1992,8 @@ private static boolean hasNonTrivialBounds(GenericsType gt) {
|| !OBJECT_TYPE.equals(upperBounds[0])));
}

private static ClassNode[] applyGenericsContext(
// GRECLIPSE private->package
static ClassNode[] applyGenericsContext(
Map<GenericsTypeName, GenericsType> spec, ClassNode[] bounds
) {
if (bounds == null) return null;
Expand Down
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.Supplier;

import static org.apache.groovy.ast.tools.ClassNodeUtils.samePackageName;
import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE;
Expand Down Expand Up @@ -2634,6 +2635,7 @@ && getType(nameExpr).equals(STRING_TYPE)) {
.reduce(WideningCategories::lowestUpperBound)
.filter(returnType -> !returnType.equals(OBJECT_TYPE))
.ifPresent(returnType -> storeType(expression, wrapClosureType(returnType)));
expression.setNodeMetaData(MethodNode.class, candidates); // GROOVY-9803
}
}
}
Expand Down Expand Up @@ -5519,12 +5521,23 @@ protected ClassNode inferReturnTypeGenerics(
actualType = actualType.getComponentType();
}
if (isUsingGenericsOrIsArrayUsingGenerics(type)) {
/* GRECLIPSE edit -- GROOVY-9803
if (implementsInterfaceOrIsSubclassOf(actualType, CLOSURE_TYPE) &&
isSAMType(type)) {
// implicit closure coercion in action!
Map<GenericsTypeName, GenericsType> pholders = applyGenericsContextToParameterClass(resolvedPlaceholders, type);
actualType = convertClosureTypeToSAMType(expressions.get(i), actualType, type, pholders);
}
*/
if (actualType.isDerivedFrom(CLOSURE_TYPE)) {
MethodNode sam = findSAM(type);
if (sam != null) { // implicit closure coercion in action!
actualType = !type.isUsingGenerics() ? type
: convertClosureTypeToSAMType(expressions.get(i), actualType, sam, type,
applyGenericsContextToParameterClass(resolvedPlaceholders, type));
}
}
// GRECLIPSE end
if (isVargs && lastArg && actualType.isArray()) {
actualType = actualType.getComponentType();
}
Expand Down Expand Up @@ -5600,6 +5613,48 @@ private static void extractGenericsConnectionsForSuperClassAndInterfaces(final M
}
}

// GRECLIPSE add -- GROOVY-9803
private static MethodNode chooseMethod(final MethodPointerExpression source, final Supplier<ClassNode[]> samSignature) {
List<MethodNode> options = source.getNodeMetaData(MethodNode.class);
if (options == null || options.isEmpty()) {
return null;
}

ClassNode[] paramTypes = samSignature.get();
return options.stream().filter((MethodNode option) -> {
ClassNode[] types = collateMethodReferenceParameterTypes(source, option);
if (types.length == paramTypes.length) {
for (int i = 0, n = types.length; i < n; i += 1) {
if (!types[i].isGenericsPlaceHolder() && !isAssignableTo(types[i], paramTypes[i])) {
return false;
}
}
return true;
}
return false;
}).findFirst().orElse(null); // TODO: order matches by param distance
}

private static ClassNode[] collateMethodReferenceParameterTypes(final MethodPointerExpression source, final MethodNode target) {
Parameter[] params;

if (target instanceof ExtensionMethodNode && !((ExtensionMethodNode) target).isStaticExtension()) {
params = ((ExtensionMethodNode) target).getExtensionMethodNode().getParameters();
} else if (!target.isStatic() && source.getExpression() instanceof ClassExpression) {
ClassNode thisType = ((ClassExpression) source.getExpression()).getType();
// there is an implicit parameter for "String::length"
int n = target.getParameters().length;
params = new Parameter[n + 1];
params[0] = new Parameter(thisType, "");
System.arraycopy(target.getParameters(), 0, params, 1, n);
} else {
params = target.getParameters();
}

return extractTypesFromParameters(params);
}
// GRECLIPSE end

/**
* This method will convert a closure type to the appropriate SAM type, which will be used
* to infer return type generics.
Expand All @@ -5608,6 +5663,7 @@ private static void extractGenericsConnectionsForSuperClassAndInterfaces(final M
* @param samType the type into which the closure is coerced into
* @return same SAM type, but completed with information from the closure node
*/
/* GRECLIPSE edit -- GROOVY-9803
private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final ClassNode samType, final Map<GenericsTypeName, GenericsType> placeholders) {
if (!samType.isUsingGenerics()) return samType;
Expand All @@ -5626,16 +5682,44 @@ private static ClassNode convertClosureTypeToSAMType(final Expression expression
} else if (samReturnType.isGenericsPlaceHolder()) {
placeholders.put(new GenericsTypeName(samReturnType.getGenericsTypes()[0].getName()), closureType.getGenericsTypes()[0]);
}
*/
private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType, final Map<GenericsTypeName, GenericsType> placeholders) {
// use the generics information from Closure to further specify the type
if (closureType.isUsingGenerics()) {
ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType();

Parameter[] parameters = sam.getParameters();
if (parameters.length > 0 && expression instanceof MethodPointerExpression
&& isUsingUncheckedGenerics(closureReturnType)) { // needs resolve
MethodPointerExpression mp = (MethodPointerExpression) expression;
MethodNode mn = chooseMethod(mp, () ->
applyGenericsContext(placeholders, extractTypesFromParameters(parameters))
);
if (mn != null) {
ClassNode[] pTypes = collateMethodReferenceParameterTypes(mp, mn);
Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
for (int i = 0, n = parameters.length; i < n; i += 1) {
// SAM parameters should align one-for-one with the referenced method's parameters
extractGenericsConnections(connections, parameters[i].getOriginType(), pTypes[i]);
}
// convert the method reference's generics into the SAM's generics domain
closureReturnType = applyGenericsContext(connections, closureReturnType);
// apply known generics connections to the placeholders of the return type
closureReturnType = applyGenericsContext(placeholders, closureReturnType);
}
}

// now repeat the same for each parameter given in the ClosureExpression
if (expression instanceof ClosureExpression && sam.getParameters().length > 0) {
// the SAM's return type exactly corresponds to the inferred closure return type
extractGenericsConnections(placeholders, closureReturnType, sam.getReturnType());
// GRECLIPSE end
// repeat the same for each parameter given in the ClosureExpression
if (parameters.length > 0 && expression instanceof ClosureExpression) {
List<ClassNode[]> genericsToConnect = new LinkedList<ClassNode[]>();
Parameter[] closureParams = ((ClosureExpression) expression).getParameters();
ClassNode[] closureParamTypes = extractTypesFromParameters(closureParams);
if (expression.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS) != null) {
closureParamTypes = expression.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS);
}
final Parameter[] parameters = sam.getParameters();
for (int i = 0; i < parameters.length; i++) {
final Parameter parameter = parameters[i];
if (parameter.getOriginType().isUsingGenerics() && closureParamTypes.length > i) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,8 @@ public static boolean hasUnresolvedGenerics(final ClassNode type) {
for (ClassNode upperBound : upperBounds) {
if (hasUnresolvedGenerics(upperBound)) return true;
}
} else {
if (hasUnresolvedGenerics(genericsType.getType())) return true;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1803,10 +1803,17 @@ static Map<GenericsTypeName, GenericsType> applyGenericsContextToParameterClass(
GenericsType[] gts = parameterUsage.getGenericsTypes();
if (gts == null) return Collections.emptyMap();

GenericsType[] newGTs = applyGenericsContext(spec, gts);
ClassNode newTarget = parameterUsage.redirect().getPlainNodeReference();
newTarget.setGenericsTypes(newGTs);
newTarget.setGenericsTypes(applyGenericsContext(spec, gts));
/* GRECLIPSE edit -- GROOVY-9762, GROOVY-9803
return GenericsUtils.extractPlaceholders(newTarget);
*/
Map<GenericsTypeName, GenericsType> newSpec = GenericsUtils.extractPlaceholders(newTarget);
newSpec.replaceAll((xx, gt) ->
Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt)
);
return newSpec;
// GRECLIPSE end
}

private static GenericsType[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final GenericsType[] gts) {
Expand Down Expand Up @@ -1864,7 +1871,8 @@ private static boolean hasNonTrivialBounds(final GenericsType gt) {
return false;
}

private static ClassNode[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final ClassNode[] bounds) {
// GRECLIPSE private->package
static ClassNode[] applyGenericsContext(final Map<GenericsTypeName, GenericsType> spec, final ClassNode[] bounds) {
if (bounds == null) return null;
ClassNode[] newBounds = new ClassNode[bounds.length];
for (int i = 0, n = bounds.length; i < n; i += 1) {
Expand Down
Loading

0 comments on commit 465f3e0

Please sign in to comment.