Skip to content

Commit 465f3e0

Browse files
committed
GROOVY-9803, GROOVY-9762 (redux)
1 parent 20f53ff commit 465f3e0

7 files changed

Lines changed: 292 additions & 30 deletions

File tree

base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,29 @@ public void testClosure6b() {
873873
assertType(contents, "array", "java.lang.String[]");
874874
}
875875

876+
@Test // GROOVY-9803
877+
public void testClosure7() {
878+
String contents =
879+
"class C<T> {\n" +
880+
" static <U> C<U> of(U item) {}\n" +
881+
" def <V> C<V> map(F<? super T, ? super V> func) {}\n" +
882+
"}\n" +
883+
"class D {\n" +
884+
" static <W> Set<W> wrap(W o) {}\n" +
885+
"}\n" +
886+
"interface F<X,Y> {\n" +
887+
" Y apply(X x)\n" +
888+
"}\n" +
889+
"@groovy.transform.TypeChecked\n" +
890+
"void test() {\n" +
891+
" def c = C.of(123)\n" +
892+
" def d = c.map(D.&wrap)\n" +
893+
" def e = d.map{x -> x.first()}\n" +
894+
"}\n";
895+
assertType(contents, "e", "C<java.lang.Integer>");
896+
assertType(contents, "x", "java.util.Set<java.lang.Integer>");
897+
}
898+
876899
@Test
877900
public void testArrayDGM() {
878901
String contents =

base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,4 +430,66 @@ public void testTypeChecked9751() {
430430

431431
runNegativeTest(sources, "");
432432
}
433+
434+
@Test
435+
public void testTypeChecked9762() {
436+
//@formatter:off
437+
String[] sources = {
438+
"Main.groovy",
439+
"static <T> List<T> list(T item) {\n" +
440+
" Collections.singletonList(item)\n" +
441+
"}\n" +
442+
"@groovy.transform.TypeChecked\n" +
443+
"void test() {\n" +
444+
" Optional<Integer> opt = Optional.ofNullable(123)\n" +
445+
" List<Integer> result = opt.map(this.&list).get()\n" +
446+
" print result\n" +
447+
"}\n" +
448+
"test()\n",
449+
};
450+
//@formatter:on
451+
452+
runConformTest(sources, "[123]");
453+
}
454+
455+
@Test
456+
public void testTypeChecked9803() {
457+
//@formatter:off
458+
String[] sources = {
459+
"Main.groovy",
460+
"@groovy.transform.TypeChecked\n" +
461+
"void test() {\n" +
462+
" def c = C.of(123)\n" +
463+
" def d = c.map(D.&wrap)\n" +
464+
" def e = d.map{x -> x.first().intValue()}\n" +
465+
" print e.t\n" +
466+
"}\n" +
467+
"test()\n",
468+
469+
"Types.groovy",
470+
"class C<T> {\n" +
471+
" private T t\n" +
472+
" C(T item) {\n" +
473+
" t = item\n" +
474+
" }\n" +
475+
" static <U> C<U> of(U item) {\n" +
476+
" new C<U>(item)\n" +
477+
" }\n" +
478+
" def <V> C<V> map(F<? super T, ? super V> func) {\n" +
479+
" new C<V>(func.apply(t))\n" +
480+
" }\n" +
481+
"}\n" +
482+
"class D {\n" +
483+
" static <W> Set<W> wrap(W o) {\n" +
484+
" Collections.singleton(o)\n" +
485+
" }\n" +
486+
"}\n" +
487+
"interface F<X,Y> {\n" +
488+
" Y apply(X x)\n" +
489+
"}\n",
490+
};
491+
//@formatter:on
492+
493+
runConformTest(sources, "123");
494+
}
433495
}

base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1931,7 +1931,15 @@ static Map<GenericsTypeName, GenericsType> applyGenericsContextToParameterClass(
19311931
GenericsType[] newGTs = applyGenericsContext(spec, gts);
19321932
ClassNode newTarget = parameterUsage.redirect().getPlainNodeReference();
19331933
newTarget.setGenericsTypes(newGTs);
1934+
/* GRECLIPSE edit -- GROOVY-9762, GROOVY-9803
19341935
return GenericsUtils.extractPlaceholders(newTarget);
1936+
*/
1937+
Map<GenericsTypeName, GenericsType> newSpec = GenericsUtils.extractPlaceholders(newTarget);
1938+
newSpec.replaceAll((xx, gt) ->
1939+
Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt)
1940+
);
1941+
return newSpec;
1942+
// GRECLIPSE end
19351943
}
19361944

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

1987-
private static ClassNode[] applyGenericsContext(
1995+
// GRECLIPSE private->package
1996+
static ClassNode[] applyGenericsContext(
19881997
Map<GenericsTypeName, GenericsType> spec, ClassNode[] bounds
19891998
) {
19901999
if (bounds == null) return null;

base/org.codehaus.groovy25/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@
132132
import java.util.Set;
133133
import java.util.concurrent.atomic.AtomicLong;
134134
import java.util.concurrent.atomic.AtomicReference;
135+
import java.util.function.Supplier;
135136

136137
import static org.apache.groovy.ast.tools.ClassNodeUtils.samePackageName;
137138
import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE;
@@ -2634,6 +2635,7 @@ && getType(nameExpr).equals(STRING_TYPE)) {
26342635
.reduce(WideningCategories::lowestUpperBound)
26352636
.filter(returnType -> !returnType.equals(OBJECT_TYPE))
26362637
.ifPresent(returnType -> storeType(expression, wrapClosureType(returnType)));
2638+
expression.setNodeMetaData(MethodNode.class, candidates); // GROOVY-9803
26372639
}
26382640
}
26392641
}
@@ -5519,12 +5521,23 @@ protected ClassNode inferReturnTypeGenerics(
55195521
actualType = actualType.getComponentType();
55205522
}
55215523
if (isUsingGenericsOrIsArrayUsingGenerics(type)) {
5524+
/* GRECLIPSE edit -- GROOVY-9803
55225525
if (implementsInterfaceOrIsSubclassOf(actualType, CLOSURE_TYPE) &&
55235526
isSAMType(type)) {
55245527
// implicit closure coercion in action!
55255528
Map<GenericsTypeName, GenericsType> pholders = applyGenericsContextToParameterClass(resolvedPlaceholders, type);
55265529
actualType = convertClosureTypeToSAMType(expressions.get(i), actualType, type, pholders);
55275530
}
5531+
*/
5532+
if (actualType.isDerivedFrom(CLOSURE_TYPE)) {
5533+
MethodNode sam = findSAM(type);
5534+
if (sam != null) { // implicit closure coercion in action!
5535+
actualType = !type.isUsingGenerics() ? type
5536+
: convertClosureTypeToSAMType(expressions.get(i), actualType, sam, type,
5537+
applyGenericsContextToParameterClass(resolvedPlaceholders, type));
5538+
}
5539+
}
5540+
// GRECLIPSE end
55285541
if (isVargs && lastArg && actualType.isArray()) {
55295542
actualType = actualType.getComponentType();
55305543
}
@@ -5600,6 +5613,48 @@ private static void extractGenericsConnectionsForSuperClassAndInterfaces(final M
56005613
}
56015614
}
56025615

5616+
// GRECLIPSE add -- GROOVY-9803
5617+
private static MethodNode chooseMethod(final MethodPointerExpression source, final Supplier<ClassNode[]> samSignature) {
5618+
List<MethodNode> options = source.getNodeMetaData(MethodNode.class);
5619+
if (options == null || options.isEmpty()) {
5620+
return null;
5621+
}
5622+
5623+
ClassNode[] paramTypes = samSignature.get();
5624+
return options.stream().filter((MethodNode option) -> {
5625+
ClassNode[] types = collateMethodReferenceParameterTypes(source, option);
5626+
if (types.length == paramTypes.length) {
5627+
for (int i = 0, n = types.length; i < n; i += 1) {
5628+
if (!types[i].isGenericsPlaceHolder() && !isAssignableTo(types[i], paramTypes[i])) {
5629+
return false;
5630+
}
5631+
}
5632+
return true;
5633+
}
5634+
return false;
5635+
}).findFirst().orElse(null); // TODO: order matches by param distance
5636+
}
5637+
5638+
private static ClassNode[] collateMethodReferenceParameterTypes(final MethodPointerExpression source, final MethodNode target) {
5639+
Parameter[] params;
5640+
5641+
if (target instanceof ExtensionMethodNode && !((ExtensionMethodNode) target).isStaticExtension()) {
5642+
params = ((ExtensionMethodNode) target).getExtensionMethodNode().getParameters();
5643+
} else if (!target.isStatic() && source.getExpression() instanceof ClassExpression) {
5644+
ClassNode thisType = ((ClassExpression) source.getExpression()).getType();
5645+
// there is an implicit parameter for "String::length"
5646+
int n = target.getParameters().length;
5647+
params = new Parameter[n + 1];
5648+
params[0] = new Parameter(thisType, "");
5649+
System.arraycopy(target.getParameters(), 0, params, 1, n);
5650+
} else {
5651+
params = target.getParameters();
5652+
}
5653+
5654+
return extractTypesFromParameters(params);
5655+
}
5656+
// GRECLIPSE end
5657+
56035658
/**
56045659
* This method will convert a closure type to the appropriate SAM type, which will be used
56055660
* to infer return type generics.
@@ -5608,6 +5663,7 @@ private static void extractGenericsConnectionsForSuperClassAndInterfaces(final M
56085663
* @param samType the type into which the closure is coerced into
56095664
* @return same SAM type, but completed with information from the closure node
56105665
*/
5666+
/* GRECLIPSE edit -- GROOVY-9803
56115667
private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final ClassNode samType, final Map<GenericsTypeName, GenericsType> placeholders) {
56125668
if (!samType.isUsingGenerics()) return samType;
56135669
@@ -5626,16 +5682,44 @@ private static ClassNode convertClosureTypeToSAMType(final Expression expression
56265682
} else if (samReturnType.isGenericsPlaceHolder()) {
56275683
placeholders.put(new GenericsTypeName(samReturnType.getGenericsTypes()[0].getName()), closureType.getGenericsTypes()[0]);
56285684
}
5685+
*/
5686+
private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType, final Map<GenericsTypeName, GenericsType> placeholders) {
5687+
// use the generics information from Closure to further specify the type
5688+
if (closureType.isUsingGenerics()) {
5689+
ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType();
5690+
5691+
Parameter[] parameters = sam.getParameters();
5692+
if (parameters.length > 0 && expression instanceof MethodPointerExpression
5693+
&& isUsingUncheckedGenerics(closureReturnType)) { // needs resolve
5694+
MethodPointerExpression mp = (MethodPointerExpression) expression;
5695+
MethodNode mn = chooseMethod(mp, () ->
5696+
applyGenericsContext(placeholders, extractTypesFromParameters(parameters))
5697+
);
5698+
if (mn != null) {
5699+
ClassNode[] pTypes = collateMethodReferenceParameterTypes(mp, mn);
5700+
Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
5701+
for (int i = 0, n = parameters.length; i < n; i += 1) {
5702+
// SAM parameters should align one-for-one with the referenced method's parameters
5703+
extractGenericsConnections(connections, parameters[i].getOriginType(), pTypes[i]);
5704+
}
5705+
// convert the method reference's generics into the SAM's generics domain
5706+
closureReturnType = applyGenericsContext(connections, closureReturnType);
5707+
// apply known generics connections to the placeholders of the return type
5708+
closureReturnType = applyGenericsContext(placeholders, closureReturnType);
5709+
}
5710+
}
56295711

5630-
// now repeat the same for each parameter given in the ClosureExpression
5631-
if (expression instanceof ClosureExpression && sam.getParameters().length > 0) {
5712+
// the SAM's return type exactly corresponds to the inferred closure return type
5713+
extractGenericsConnections(placeholders, closureReturnType, sam.getReturnType());
5714+
// GRECLIPSE end
5715+
// repeat the same for each parameter given in the ClosureExpression
5716+
if (parameters.length > 0 && expression instanceof ClosureExpression) {
56325717
List<ClassNode[]> genericsToConnect = new LinkedList<ClassNode[]>();
56335718
Parameter[] closureParams = ((ClosureExpression) expression).getParameters();
56345719
ClassNode[] closureParamTypes = extractTypesFromParameters(closureParams);
56355720
if (expression.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS) != null) {
56365721
closureParamTypes = expression.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS);
56375722
}
5638-
final Parameter[] parameters = sam.getParameters();
56395723
for (int i = 0; i < parameters.length; i++) {
56405724
final Parameter parameter = parameters[i];
56415725
if (parameter.getOriginType().isUsingGenerics() && closureParamTypes.length > i) {

base/org.codehaus.groovy30/src/org/codehaus/groovy/ast/tools/GenericsUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -982,6 +982,8 @@ public static boolean hasUnresolvedGenerics(final ClassNode type) {
982982
for (ClassNode upperBound : upperBounds) {
983983
if (hasUnresolvedGenerics(upperBound)) return true;
984984
}
985+
} else {
986+
if (hasUnresolvedGenerics(genericsType.getType())) return true;
985987
}
986988
}
987989
}

base/org.codehaus.groovy30/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,10 +1803,17 @@ static Map<GenericsTypeName, GenericsType> applyGenericsContextToParameterClass(
18031803
GenericsType[] gts = parameterUsage.getGenericsTypes();
18041804
if (gts == null) return Collections.emptyMap();
18051805

1806-
GenericsType[] newGTs = applyGenericsContext(spec, gts);
18071806
ClassNode newTarget = parameterUsage.redirect().getPlainNodeReference();
1808-
newTarget.setGenericsTypes(newGTs);
1807+
newTarget.setGenericsTypes(applyGenericsContext(spec, gts));
1808+
/* GRECLIPSE edit -- GROOVY-9762, GROOVY-9803
18091809
return GenericsUtils.extractPlaceholders(newTarget);
1810+
*/
1811+
Map<GenericsTypeName, GenericsType> newSpec = GenericsUtils.extractPlaceholders(newTarget);
1812+
newSpec.replaceAll((xx, gt) ->
1813+
Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt)
1814+
);
1815+
return newSpec;
1816+
// GRECLIPSE end
18101817
}
18111818

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

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

0 commit comments

Comments
 (0)