Skip to content

Commit 64dfe08

Browse files
committed
GROOVY-9803, GROOVY-10974, GROOVY-10975, GROOVY-11241, GROOVY-11259
1 parent dca052d commit 64dfe08

File tree

6 files changed

+211
-24
lines changed

6 files changed

+211
-24
lines changed

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

+3-6
Original file line numberDiff line numberDiff line change
@@ -964,12 +964,9 @@ public void testClosure11() {
964964
"interface F<X,Y> {\n" +
965965
" Y apply(X x)\n" +
966966
"}\n" +
967-
"@groovy.transform.TypeChecked\n" +
968-
"void test() {\n" +
969-
" def c = C.of(123)\n" +
970-
" def d = c.map(" + toSet + ")\n" +
971-
" def e = d.map{x -> x.first()}\n" +
972-
"}\n";
967+
"def c = C.of(123)\n" +
968+
"def d = c.map(" + toSet + ")\n" +
969+
"def e = d.map{x -> x.first()}\n";
973970
assertType(contents, "d", "C<java.util.Set<java.lang.Integer>>");
974971
assertType(contents, "x", "java.util.Set<java.lang.Integer>");
975972
assertType(contents, "e", "C<java.lang.Integer>");

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -7417,9 +7417,14 @@ public void testCompileStatic10047b() {
74177417
runNegativeTest(sources,
74187418
"----------\n" +
74197419
"1. ERROR in Main.groovy (at line 5)\n" +
7420-
"\tprint(['a','bc','def'].stream().collect(toMap(Function.<String>identity(), List::size)))\n" +
7420+
"\tprint(['a','bc','def'].stream().collect(toMap(Function.<String>identity(), List::size)))\n" + (!isAtLeastGroovy(40)
7421+
?
74217422
"\t ^^^^^^^^^^\n" +
7422-
"Groovy:Failed to find class method 'size(java.lang.String)' or instance method 'size()' for the type: java.util.List\n" +
7423+
"Groovy:Failed to find class method 'size(java.lang.String)' or instance method 'size()' for the type: java.util.List\n"
7424+
:
7425+
"\t ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" +
7426+
"Groovy:[Static type checking] - Cannot call <R,A> java.util.stream.Stream#collect(java.util.stream.Collector<? super java.lang.String, A, R>) with arguments [java.util.stream.Collector<java.util.List, ?, java.util.Map<java.lang.String, java.lang.Integer>>]\n"
7427+
) +
74237428
"----------\n");
74247429
}
74257430

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

+127-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2023 the original author or authors.
2+
* Copyright 2009-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -2896,6 +2896,41 @@ public void testTypeChecked9769() {
28962896
runConformTest(sources);
28972897
}
28982898

2899+
@Test
2900+
public void testTypeChecked9803() {
2901+
//@formatter:off
2902+
String[] sources = {
2903+
"Main.groovy",
2904+
"def <E> Set<E> asSet(E element) {Collections.singleton(element)}\n" +
2905+
"@groovy.transform.TypeChecked\n" +
2906+
"Try<String> test() {\n" +
2907+
" def try_of_str = Try.success('WORKS')\n" +
2908+
" def try_of_opt = try_of_str.map(this.&asSet)\n" +
2909+
" try_of_str = try_of_opt.map{it.first().toLowerCase()}\n" +
2910+
"}\n" +
2911+
"print(test().x)\n",
2912+
2913+
"Try.groovy",
2914+
"import java.util.function.Function\n" +
2915+
"class Try<X> { X x\n" +
2916+
" static <Y> Try<Y> success(Y y) {\n" +
2917+
" new Try<Y>(x: y)\n" +
2918+
" }\n" +
2919+
" def <Z> Try<Z> map(Function<? super X, ? extends Z> f) {\n" +
2920+
" new Try<Z>(x: f.apply(x))\n" +
2921+
" }\n" +
2922+
"}\n",
2923+
};
2924+
//@formatter:on
2925+
2926+
runConformTest(sources, "works");
2927+
if (isParrotParser()) {
2928+
sources[1] = sources[1]
2929+
.replace(".&", "::");
2930+
runConformTest(sources, "works");
2931+
}
2932+
}
2933+
28992934
@Test
29002935
public void testTypeChecked9821() {
29012936
//@formatter:off
@@ -7095,23 +7130,52 @@ public void testTypeChecked10897() {
70957130
}
70967131

70977132
@Test
7098-
public void testTypeChecked10975() {
7099-
assumeTrue(isParrotParser());
7133+
public void testTypeChecked10974() {
7134+
//@formatter:off
7135+
String[] sources = {
7136+
"Main.groovy",
7137+
"import java.util.stream.*\n" +
7138+
"import java.util.function.*\n" +
7139+
"@groovy.transform.TypeChecked\n" +
7140+
"def test(DoubleStream x, ObjDoubleConsumer<Boolean> y, BiConsumer<Boolean, Boolean> z) {\n" +
7141+
" def b = x.collect({ -> true}, y.&accept, z.&accept) // b should infer as Boolean\n" +
7142+
" // <R> R collect(Supplier<R>,ObjDoubleConsumer<R>,BiConsumer<R,R>)\n" +
7143+
" Spliterator.OfDouble s_of_d = Arrays.spliterator(new double[0])\n" +
7144+
" StreamSupport.doubleStream(s_of_d, /*parallel:*/b)\n" +
7145+
"}\n" +
7146+
"test(DoubleStream.of(0d), {Boolean b, double d -> }, {Boolean e, Boolean f -> })\n",
7147+
};
7148+
//@formatter:on
71007149

7150+
runConformTest(sources);
7151+
if (isParrotParser()) {
7152+
sources[1] = sources[1]
7153+
.replace(".&", "::");
7154+
runConformTest(sources);
7155+
}
7156+
}
7157+
7158+
@Test
7159+
public void testTypeChecked10975() {
71017160
//@formatter:off
71027161
String[] sources = {
71037162
"Main.groovy",
71047163
"@groovy.transform.TypeChecked\n" +
71057164
"void test() {\n" +
71067165
" Collection<Integer> c = [1]\n" +
71077166
" Map<Integer,Integer> m = [1:1]\n" +
7108-
" new Hashtable(Collections.min(c, m::put))\n" + // Cannot find matching constructor Hashtable(Object)
7167+
" new Hashtable(Collections.min(c, m.&put))\n" + // Cannot find matching constructor Hashtable(Object)
71097168
"}\n" +
71107169
"test()\n",
71117170
};
71127171
//@formatter:on
71137172

71147173
runConformTest(sources);
7174+
if (isParrotParser()) {
7175+
sources[1] = sources[1]
7176+
.replace(".&", "::");
7177+
runConformTest(sources);
7178+
}
71157179
}
71167180

71177181
@Test
@@ -7238,4 +7302,63 @@ public void testTypeChecked11168() {
72387302

72397303
runConformTest(sources, "works");
72407304
}
7305+
7306+
@Test
7307+
public void testTypeChecked11241() {
7308+
//@formatter:off
7309+
String[] sources = {
7310+
"Main.groovy",
7311+
"@groovy.transform.TypeChecked\n" +
7312+
"Try<String> test() {\n" +
7313+
" def try_of = Try.of{Optional.of('works')}\n" +
7314+
" def result = try_of.mapTry(Optional.&get)\n" +
7315+
" return result\n" +
7316+
"}\n" +
7317+
"print(test().x)\n",
7318+
7319+
"Try.groovy",
7320+
"import java.util.function.*\n" +
7321+
"class Try<X> { X x\n" +
7322+
" static <Y> Try<Y> of(Supplier<? extends Y> s) {\n" +
7323+
" new Try<Y>(x: s.get())\n" +
7324+
" }\n" +
7325+
" def <Z> Try<Z> mapTry(Function<? super X, ? extends Z> f) {\n" +
7326+
" new Try<Z>(x: f.apply(x))\n" +
7327+
" }\n" +
7328+
"}\n",
7329+
};
7330+
//@formatter:on
7331+
7332+
runConformTest(sources, "works");
7333+
if (isParrotParser()) {
7334+
sources[1] = sources[1]
7335+
.replace(".&", "::");
7336+
runConformTest(sources, "works");
7337+
}
7338+
}
7339+
7340+
@Test
7341+
public void testTypeChecked11259() {
7342+
//@formatter:off
7343+
String[] sources = {
7344+
"Main.groovy",
7345+
"void consume(Set<String> keys){print(keys)}\n" +
7346+
"@groovy.transform.TypeChecked\n" +
7347+
"void test(Map<String, String> map) {\n" +
7348+
" def keys = map.entrySet().stream()\n" +
7349+
" .map(Map.Entry.&getKey)\n" +
7350+
" .toSet()\n" +
7351+
" consume(keys)\n" +
7352+
"}\n" +
7353+
"test(foo:'bar', fizz:'buzz')\n",
7354+
};
7355+
//@formatter:on
7356+
7357+
runConformTest(sources, "[foo, fizz]");
7358+
if (isParrotParser()) {
7359+
sources[1] = sources[1]
7360+
.replace(".&", "::");
7361+
runConformTest(sources, "[foo, fizz]");
7362+
}
7363+
}
72417364
}

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

+38-6
Original file line numberDiff line numberDiff line change
@@ -5599,9 +5599,15 @@ protected ClassNode inferReturnTypeGenerics(final ClassNode receiver, final Meth
55995599
// 1) resolve type parameters of method
56005600

56015601
if (methodGenericTypes != null) {
5602+
Parameter[] parameters = method.getParameters();
5603+
// GRECLIPSE add -- GROOVY-11241, GROOVY-11259, et al.
5604+
if (asBoolean(context)) parameters = Arrays.stream(parameters).map(p ->
5605+
new Parameter(applyGenericsContext(context, p.getType()), p.getName())
5606+
).toArray(Parameter[]::new);
5607+
// GRECLIPSE end
56025608
Map<GenericsTypeName, GenericsType> resolvedPlaceholders = new HashMap<>();
56035609
for (GenericsType gt : methodGenericTypes) resolvedPlaceholders.put(new GenericsTypeName(gt.getName()), gt);
5604-
applyGenericsConnections(extractGenericsConnectionsFromArguments(methodGenericTypes, method.getParameters(), arguments, explicitTypeHints), resolvedPlaceholders);
5610+
applyGenericsConnections(extractGenericsConnectionsFromArguments(methodGenericTypes, parameters, arguments, explicitTypeHints), resolvedPlaceholders);
56055611

56065612
returnType = applyGenericsContext(resolvedPlaceholders, returnType);
56075613
}
@@ -5825,6 +5831,17 @@ private static ClassNode[] collateMethodReferenceParameterTypes(final MethodPoin
58255831
System.arraycopy(target.getParameters(), 0, params, 1, n);
58265832
} else {
58275833
params = target.getParameters();
5834+
// GRECLIPSE add -- GROOVY-10974
5835+
if (!target.isStatic() && target.getDeclaringClass().getGenericsTypes() != null) {
5836+
ClassNode objectExprType = source.getExpression().getNodeMetaData(INFERRED_TYPE);
5837+
if (objectExprType == null && source.getExpression() instanceof VariableExpression) {
5838+
Variable variable = ((VariableExpression) source.getExpression()).getAccessedVariable();
5839+
if (variable instanceof ASTNode) objectExprType = ((ASTNode) variable).getNodeMetaData(INFERRED_TYPE);
5840+
}
5841+
Map<GenericsTypeName,GenericsType> spec = extractPlaceHolders(objectExprType, target.getDeclaringClass());
5842+
return applyGenericsContext(spec, extractTypesFromParameters(params));
5843+
}
5844+
// GRECLIPSE end
58285845
}
58295846

58305847
return extractTypesFromParameters(params);
@@ -5856,16 +5873,29 @@ private static ClassNode convertClosureTypeToSAMType(Expression expression, fina
58565873
applyGenericsContext(placeholders, extractTypesFromParameters(parameters))
58575874
);
58585875
if (mn != null) {
5876+
// GRECLIPSE add -- GROOVY-10067, GROOVY-11259
5877+
if (GenericsUtils.hasUnresolvedGenerics(closureReturnType))
5878+
closureReturnType = mn.getReturnType(); // reset any #T
5879+
// GRECLIPSE end
58595880
ClassNode[] pTypes = collateMethodReferenceParameterTypes(mp, mn);
58605881
Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
5882+
// GRECLIPSE add -- GROOVY-11241
5883+
if (!mn.isStatic() && mp.getExpression() instanceof ClassExpression) {
5884+
ClassNode objectType = parameters[0].getType();
5885+
objectType = applyGenericsContext(placeholders, objectType);
5886+
extractGenericsConnections(connections, objectType, pTypes[0].redirect());
5887+
}
5888+
// GRECLIPSE end
58615889
for (int i = 0, n = parameters.length; i < n; i += 1) {
58625890
// SAM parameters should align one-for-one with the referenced method's parameters
58635891
extractGenericsConnections(connections, parameters[i].getOriginType(), pTypes[i]);
58645892
}
58655893
// convert the method reference's generics into the SAM's generics domain
58665894
closureReturnType = applyGenericsContext(connections, closureReturnType);
5895+
pTypes = applyGenericsContext(connections, pTypes);
58675896
// apply known generics connections to the placeholders of the return type
58685897
closureReturnType = applyGenericsContext(placeholders, closureReturnType);
5898+
pTypes = applyGenericsContext(placeholders, pTypes); // and the parameters
58695899

58705900
expression = new ClosureExpression(Arrays.stream(pTypes).map(t -> new Parameter(t,"")).toArray(Parameter[]::new), null);
58715901
}
@@ -5876,11 +5906,13 @@ private static ClassNode convertClosureTypeToSAMType(Expression expression, fina
58765906

58775907
// repeat the same for each parameter given in the ClosureExpression
58785908
if (parameters.length > 0 && expression instanceof ClosureExpression) {
5879-
ClassNode[] paramTypes = applyGenericsContext(placeholders, extractTypesFromParameters(parameters));
58805909
int i = 0;
5881-
// GROOVY-10054, GROOVY-10699, GROOVY-10749, et al.
5882-
for (Parameter p : getParametersSafe((ClosureExpression) expression))
5883-
if (!p.isDynamicTyped()) extractGenericsConnections(placeholders, p.getType(), paramTypes[i++]);
5910+
// GROOVY-10054, GROOVY-10699, GROOVY-10749, GROOVY-10974, GROOVY-11241
5911+
for (Parameter p : getParametersSafe((ClosureExpression) expression)) {
5912+
if (!p.isDynamicTyped())
5913+
extractGenericsConnections(placeholders, p.getType(), parameters[i].getType());
5914+
i += 1;
5915+
}
58845916
}
58855917
}
58865918

@@ -6357,4 +6389,4 @@ public void visitVariableExpression(final VariableExpression expression) {
63576389
super.visitVariableExpression(expression);
63586390
}
63596391
}
6360-
}
6392+
}

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

+18-2
Original file line numberDiff line numberDiff line change
@@ -5484,15 +5484,20 @@ private Map<GenericsTypeName, GenericsType> extractGenericsConnectionsFromArgume
54845484
List<MethodNode> candidates = argument.getNodeMetaData(MethodNode.class);
54855485
if (candidates != null && !candidates.isEmpty()) {
54865486
MethodPointerExpression methodPointer = (MethodPointerExpression) argument;
5487-
p = methodPointer.getNodeMetaData(CLOSURE_ARGUMENTS); // GROOVY-10974, GROOVY-10975
5488-
if (p == null) p = collateMethodReferenceParameterTypes(methodPointer, candidates.get(0));
5487+
p = collateMethodReferenceParameterTypes(methodPointer, candidates.get(0));
54895488
if (p.length > 0 && GenericsUtils.hasUnresolvedGenerics(returnType)) {
5489+
// GRECLIPSE add -- GROOVY-11241, GROOVY-11259
5490+
returnType = candidates.get(0).getReturnType();
5491+
if (!candidates.get(0).isStatic() && methodPointer.getExpression() instanceof ClassExpression)
5492+
extractGenericsConnections(connections, q[0], p[0].redirect());
5493+
// GRECLIPSE end
54905494
for (int j = 0; j < q.length; j += 1) {
54915495
// SAM parameters are like arguments in this case
54925496
extractGenericsConnections(connections, q[j], p[j]);
54935497
}
54945498
// convert the method's generics into the SAM's generics
54955499
returnType = applyGenericsContext(connections, returnType);
5500+
p = applyGenericsContext(connections, p );
54965501

54975502
connections.clear();
54985503
}
@@ -5647,6 +5652,17 @@ private static ClassNode[] collateMethodReferenceParameterTypes(final MethodPoin
56475652
System.arraycopy(target.getParameters(), 0, params, 1, n);
56485653
} else {
56495654
params = target.getParameters();
5655+
// GRECLIPSE add -- GROOVY-10974, GROOVY-10975
5656+
if (!target.isStatic() && target.getDeclaringClass().getGenericsTypes() != null) {
5657+
ClassNode objectExprType = source.getExpression().getNodeMetaData(INFERRED_TYPE);
5658+
if (objectExprType == null && source.getExpression() instanceof VariableExpression) {
5659+
Variable variable = ((VariableExpression) source.getExpression()).getAccessedVariable();
5660+
if (variable instanceof ASTNode) objectExprType = ((ASTNode) variable).getNodeMetaData(INFERRED_TYPE);
5661+
}
5662+
Map<GenericsTypeName,GenericsType> spec = extractPlaceHolders(objectExprType, target.getDeclaringClass());
5663+
return applyGenericsContext(spec, extractTypesFromParameters(params));
5664+
}
5665+
// GRECLIPSE end
56505666
}
56515667

56525668
return extractTypesFromParameters(params);

0 commit comments

Comments
 (0)