You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2024/04/30 15:55:56 UTC
(groovy) 01/02: GROOVY-11364: STC: propagate receiver generics to candidate return types
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 74f9283223b02cf1d3f330a5ae6856d34d1ad0ec
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Apr 30 10:33:49 2024 -0500
GROOVY-11364: STC: propagate receiver generics to candidate return types
4_0_X backport
---
.../transform/stc/StaticTypeCheckingVisitor.java | 12 +-
src/test/groovy/transform/stc/LambdaTest.groovy | 307 +++++++++++----------
.../transform/stc/MethodReferenceTest.groovy | 23 ++
3 files changed, 197 insertions(+), 145 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index 8d8ab524ec..707b5c054f 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -2509,7 +2509,17 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
int nParameters = candidates.stream().mapToInt(m -> m.getParameters().length).reduce((i,j) -> i == j ? i : -1).getAsInt();
Map<GenericsTypeName, GenericsType> gts = GenericsUtils.extractPlaceholders(receiverType);
stubMissingTypeVariables(receiverType.redirect().getGenericsTypes(), gts); // GROOVY-11241
- candidates.stream().map(candidate -> applyGenericsContext(gts, candidate.getReturnType()))
+ ClassNode ownerType = receiverType;
+ candidates.stream()
+ .map(candidate -> {
+ ClassNode returnType = candidate.getReturnType();
+ if (!candidate.isStatic() && GenericsUtils.hasUnresolvedGenerics(returnType)) {
+ Map<GenericsTypeName, GenericsType> spec = new HashMap<>(); // GROOVY-11364
+ extractGenericsConnections(spec, ownerType, candidate.getDeclaringClass());
+ returnType = applyGenericsContext(spec, returnType);
+ }
+ return returnType;
+ })
.reduce(WideningCategories::lowestUpperBound).ifPresent(returnType -> {
ClassNode closureType = wrapClosureType(returnType);
closureType.putNodeMetaData(CLOSURE_ARGUMENTS, nParameters); // GROOVY-10714
diff --git a/src/test/groovy/transform/stc/LambdaTest.groovy b/src/test/groovy/transform/stc/LambdaTest.groovy
index 974827c2d9..909928391b 100644
--- a/src/test/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/transform/stc/LambdaTest.groovy
@@ -18,6 +18,7 @@
*/
package groovy.transform.stc
+import groovy.test.NotYetImplemented
import org.junit.Test
import static groovy.test.GroovyAssert.assertScript
@@ -38,7 +39,7 @@ final class LambdaTest {
void testFunction() {
assertScript shell, '''
def f() {
- [1, 2, 3].stream().map(e -> e + 1).collect(Collectors.toList())
+ [1, 2, 3].stream().map(i -> i + 1).toList()
}
assert f() == [2, 3, 4]
'''
@@ -48,37 +49,61 @@ final class LambdaTest {
void testFunction2() {
assertScript shell, '''
def f() {
- [1, 2, 3].stream().map(e -> e.plus(1)).collect(Collectors.toList())
+ [1, 2, 3].stream().map(i -> i.plus(1)).toList()
}
assert f() == [2, 3, 4]
'''
}
@Test
- void testFunctionWithTypeArgument() {
+ void testFunction3() {
assertScript shell, '''
def f() {
- [1, 2, 3].stream().<String>map(i -> null).collect(Collectors.toList())
+ [1, 2, 3].stream().<String>map(i -> null).toList()
}
assert f() == [null, null, null]
'''
}
+ // GROOVY-11364
+ @Test
+ void testFunction4() {
+ assertScript shell, '''
+ abstract class A<N extends Number> {
+ protected N process(N n) { n }
+ }
+
+ class C extends A<Integer> {
+ static void consume(Optional<Integer> option) {
+ def result = option.orElse(null)
+ assert result instanceof Integer
+ assert result == 42
+ }
+ void test() {
+ consume(Optional.of(42).map(i -> process(i)))
+ }
+ }
+
+ new C().test()
+ '''
+ }
+
@Test
void testBinaryOperator() {
assertScript shell, '''
def f() {
- [1, 2, 3].stream().reduce(7, (Integer r, Integer e) -> r + e)
+ [1, 2, 3].stream().reduce(7, (Integer r, Integer i) -> r + i)
}
assert f() == 13
'''
}
- @Test // GROOVY-8917
+ // GROOVY-8917
+ @Test
void testBinaryOperatorWithoutExplicitTypes() {
assertScript shell, '''
def f() {
- [1, 2, 3].stream().reduce(7, (r, e) -> r + e)
+ [1, 2, 3].stream().reduce(7, (r, i) -> r + i)
}
assert f() == 13
'''
@@ -88,14 +113,15 @@ final class LambdaTest {
void testBinaryOperatorWithoutExplicitTypes2() {
assertScript shell, '''
def f() {
- BinaryOperator<Integer> accumulator = (r, e) -> r + e
+ BinaryOperator<Integer> accumulator = (r, i) -> r + i
return [1, 2, 3].stream().reduce(7, accumulator)
}
assert f() == 13
'''
}
- @Test // GROOVY-10282
+ // GROOVY-10282
+ @Test
void testBiFunctionAndBinaryOperatorWithSharedTypeParameter() {
assertScript shell, '''
def f() {
@@ -186,7 +212,8 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-10372
+ // GROOVY-10372
+ @Test
void testComparator2() {
def err = shouldFail shell, '''
class T {
@@ -196,7 +223,8 @@ final class LambdaTest {
assert err =~ /Expected type java.lang.Integer for lambda parameter: b/
}
- @Test // GROOVY-9977
+ // GROOVY-9977
+ @Test
void testComparator3() {
assertScript shell, '''
class T {
@@ -214,7 +242,8 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-9997
+ // GROOVY-9997
+ @Test
void testComparator4() {
assertScript '''
@groovy.transform.TypeChecked
@@ -253,7 +282,8 @@ final class LambdaTest {
}
}
- @Test // GROOVY-11304
+ // GROOVY-11304
+ @Test
void testCollectors3() {
assertScript shell, '''
List<String> list = ['a', 'b', 'c']
@@ -617,7 +647,8 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-9347
+ // GROOVY-9347
+ @Test
void testConsumer7() {
assertScript shell, '''
void test() {
@@ -631,7 +662,8 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-9340
+ // GROOVY-9340
+ @Test
void testConsumer8() {
assertScript shell, '''
class Test1 {
@@ -754,7 +786,8 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-9881
+ // GROOVY-9881
+ @Test
void testFunctionalInterface4() {
assertScript shell, '''
class Value<V> {
@@ -778,8 +811,32 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-10372
+ // GROOVY-11121
+ @NotYetImplemented @Test
void testFunctionalInterface5() {
+ String clazz = '''
+ class C<T> {
+ public String which
+ static <T> C<T> of(T value) { new C<>(which: 'T') }
+ static <T> C<T> of(Iterable<T> values) { new C<>(which: 'Iterable<T>') }
+ }
+ '''
+ assertScript shell, clazz + '''
+ assert C.<IntUnaryOperator>of( (int i) -> i + 1 ).which == 'T'
+ '''
+ assertScript shell, clazz + '''
+ @groovy.transform.CompileDynamic
+ void p() {
+ assert C.of( (int i) -> i + 1 ).which == 'T'
+ }
+
+ p()
+ '''
+ }
+
+ // GROOVY-10372
+ @Test
+ void testFunctionalInterface6() {
def err = shouldFail shell, '''
interface I {
def m(List<String> strings)
@@ -790,8 +847,9 @@ final class LambdaTest {
assert err =~ /Expected type java.util.List<java.lang.String> for lambda parameter: list/
}
- @Test // GROOVY-11013
- void testFunctionalInterface6() {
+ // GROOVY-11013
+ @Test
+ void testFunctionalInterface7() {
assertScript shell, '''
interface I<T> {
def m(List<T> list_of_t)
@@ -801,8 +859,9 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-11072
- void testFunctionalInterface7() {
+ // GROOVY-11072
+ @Test
+ void testFunctionalInterface8() {
assertScript shell, '''
class Model {
}
@@ -828,167 +887,107 @@ final class LambdaTest {
'''
}
- @Test
- void testFunctionWithUpdatingLocalVariable() {
+ // GROOVY-11092
+ @NotYetImplemented @Test
+ void testFunctionalInterface9() {
assertScript shell, '''
- class Test1 {
- static main(args) {
- p()
- }
-
- static void p() {
- int i = 1
- assert [2, 4, 7] == [1, 2, 3].stream().map(e -> i += e).collect(Collectors.toList())
- assert 7 == i
- }
+ Function<List<String>,String> f = (one,two) -> { one + two }
+ assert f.apply(['foo','bar']) == 'foobar'
+ '''
+ assertScript shell, '''
+ void setFun(Function<List<String>,String> f) {
+ assert f.apply(['foo','bar']) == 'foobar'
}
+ fun = (one,two) -> { one + two }
+ '''
+ assertScript shell, '''
+ void test(Function<List<String>,String> f) {
+ assert f.apply(['foo','bar']) == 'foobar'
+ }
+ test((one, two) -> { one + two })
'''
}
@Test
- void testFunctionWithUpdatingLocalVariable2() {
- assertScript shell, '''
- class Test1 {
- static main(args) {
- new Test1().p()
- }
-
- void p() {
+ void testFunctionWithUpdatingLocalVariable() {
+ for (mode in ['','static']) {
+ assertScript shell, """
+ $mode void p() {
int i = 1
- assert [2, 4, 7] == [1, 2, 3].stream().map(e -> i += e).collect(Collectors.toList())
- assert 7 == i
+ Object result = [1, 2, 3].stream().map(e -> i += e).collect(Collectors.toList())
+ assert result == [2, 4, 7]
+ assert i == 7
}
- }
- '''
+
+ p()
+ """
+ }
}
@Test
void testFunctionWithVariableDeclaration() {
assertScript shell, '''
- class Test1 {
- static main(args) {
- p()
- }
-
- public static void p() {
- Function<Integer, String> f = (Integer e) -> 'a' + e
- assert ['a1', 'a2', 'a3'] == [1, 2, 3].stream().map(f).collect(Collectors.toList())
- }
- }
+ Function<Integer, String> f = (Integer e) -> 'a' + e
+ assert ['a1', 'a2', 'a3'] == [1, 2, 3].stream().map(f).collect(Collectors.toList())
'''
}
@Test
void testFunctionWithMixingVariableDeclarationAndMethodInvocation() {
assertScript shell, '''
- class Test1 {
- static main(args) {
- p()
- }
+ String x = '#'
+ Integer y = 23
+ assert [1, 2, 3].stream().map(e -> '' + y + x + e).collect(Collectors.toList()) == ['23#1', '23#2', '23#3']
- static void p() {
- String x = '#'
- Integer y = 23
- assert ['23#1', '23#2', '23#3'] == [1, 2, 3].stream().map(e -> '' + y + x + e).collect(Collectors.toList())
-
- Function<Integer, String> f = (Integer e) -> 'a' + e
- assert ['a1', 'a2', 'a3'] == [1, 2, 3].stream().map(f).collect(Collectors.toList())
-
- assert [2, 3, 4] == [1, 2, 3].stream().map(e -> e.plus(1)).collect(Collectors.toList());
- }
- }
+ Function<Integer, String> f = (Integer e) -> 'a' + e
+ assert [1, 2, 3].stream().map(f).collect(Collectors.toList()) == ['a1', 'a2', 'a3']
+ assert [1, 2, 3].stream().map(e -> e.plus(1)).collect(Collectors.toList()) == [2, 3, 4]
'''
}
@Test
void testFunctionWithNestedLambda() {
assertScript shell, '''
- class Test1 {
- static main(args) {
- p()
- }
-
- static void p() {
- [1, 2].stream().forEach(e -> {
- def list = ['a', 'b'].stream().map(f -> f + e).toList()
- if (1 == e) {
- assert ['a1', 'b1'] == list
- } else if (2 == e) {
- assert ['a2', 'b2'] == list
- }
- })
- }
- }
+ [1, 2].stream().forEach(e -> {
+ def list = ['a', 'b'].stream().map(f -> f + e).toList()
+ assert list == (e == 1 ? ['a1', 'b1'] : ['a2', 'b2'])
+ })
'''
}
@Test
void testFunctionWithNestedLambda2() {
assertScript shell, '''
- class Test1 {
- static main(args) {
- p()
- }
+ def list = ['a', 'b'].stream()
+ .map(e -> {
+ [1, 2].stream().map(f -> e + f).toList()
+ }).toList()
- static void p() {
- def list = ['a', 'b'].stream()
- .map(e -> {
- [1, 2].stream().map(f -> e + f).toList()
- }).toList()
-
- assert ['a1', 'a2'] == list[0]
- assert ['b1', 'b2'] == list[1]
- }
- }
+ assert list[0] == ['a1', 'a2']
+ assert list[1] == ['b1', 'b2']
'''
}
@Test
void testFunctionWithNestedLambda3() {
assertScript shell, '''
- class Test1 {
- static main(args) {
- p()
- }
+ def list = ['a', 'b'].stream()
+ .map(e -> {
+ Function<Integer, String> x = (Integer f) -> e + f
+ [1, 2].stream().map(x).toList()
+ }).toList()
- static void p() {
- def list = ['a', 'b'].stream()
- .map(e -> {
- Function<Integer, String> x = (Integer f) -> e + f
- [1, 2].stream().map(x).toList()
- }).toList()
-
- assert ['a1', 'a2'] == list[0]
- assert ['b1', 'b2'] == list[1]
- }
- }
+ assert list[0] == ['a1', 'a2']
+ assert list[1] == ['b1', 'b2']
'''
}
@Test
void testMixingLambdaAndMethodReference() {
assertScript shell, '''
- assert ['1', '2', '3'] == [1, 2, 3].stream().map(Object::toString).collect(Collectors.toList())
- assert [2, 3, 4] == [1, 2, 3].stream().map(e -> e.plus(1)).collect(Collectors.toList())
- assert ['1', '2', '3'] == [1, 2, 3].stream().map(Object::toString).collect(Collectors.toList())
- '''
- }
-
- @Test
- void testInitializeBlocks() {
- assertScript shell, '''
- class Test1 {
- static sl
- def il
- static { sl = [1, 2, 3].stream().map(e -> e + 1).toList() }
-
- {
- il = [1, 2, 3].stream().map(e -> e + 2).toList()
- }
- }
-
- assert [2, 3, 4] == Test1.sl
- assert [3, 4, 5] == new Test1().il
+ assert [1, 2, 3].stream().map(Object::toString).collect(Collectors.toList()) == ['1', '2', '3']
+ assert [1, 2, 3].stream().map( e -> e.plus(1) ).collect(Collectors.toList()) == [2, 3, 4]
+ assert [1, 2, 3].stream().map(Object::toString).collect(Collectors.toList()) == ['1', '2', '3']
'''
}
@@ -1012,7 +1011,26 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-9332
+ @Test
+ void testInitializeBlocks() {
+ assertScript shell, '''
+ class Test1 {
+ static sl
+ def il
+ static { sl = [1, 2, 3].stream().map(e -> e + 1).toList() }
+
+ {
+ il = [1, 2, 3].stream().map(e -> e + 2).toList()
+ }
+ }
+
+ assert [2, 3, 4] == Test1.sl
+ assert [3, 4, 5] == new Test1().il
+ '''
+ }
+
+ // GROOVY-9332
+ @Test
void testStaticInitializeBlocks1() {
assertScript shell, '''
class Test1 {
@@ -1025,7 +1043,8 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-9347
+ // GROOVY-9347
+ @Test
void testStaticInitializeBlocks2() {
assertScript shell, '''
class Test1 {
@@ -1037,7 +1056,8 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-9342
+ // GROOVY-9342
+ @Test
void testStaticInitializeBlocks3() {
assertScript shell, '''
class Test1 {
@@ -1157,7 +1177,6 @@ final class LambdaTest {
test()
'''
-
assert err.message.contains('$Lambda')
}
@@ -1420,7 +1439,6 @@ final class LambdaTest {
}
}
'''
-
assert err.message.contains('tests.lambda.C')
}
@@ -1483,7 +1501,6 @@ final class LambdaTest {
}
}
'''
-
assert err.message.contains('tests.lambda.C')
}
@@ -1761,7 +1778,8 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-9146
+ // GROOVY-9146
+ @Test
void testScriptWithExistingMainCS() {
assertScript shell, '''
static void main(args) {
@@ -1771,7 +1789,8 @@ final class LambdaTest {
'''
}
- @Test // GROOVY-9770
+ // GROOVY-9770
+ @Test
void testLambdaClassIsntSynthetic() {
assertScript shell, '''
class Foo {
diff --git a/src/test/groovy/transform/stc/MethodReferenceTest.groovy b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
index f9c2004357..f189fb6b91 100644
--- a/src/test/groovy/transform/stc/MethodReferenceTest.groovy
+++ b/src/test/groovy/transform/stc/MethodReferenceTest.groovy
@@ -620,6 +620,29 @@ final class MethodReferenceTest {
'''
}
+ @Test // instance::instanceMethod -- GROOVY-11364
+ void testFunctionII5() {
+ assertScript shell, '''
+ abstract class A<N extends Number> {
+ protected N process(N n) { n }
+ }
+
+ @CompileStatic
+ class C extends A<Integer> {
+ static void consume(Optional<Integer> option) {
+ def result = option.orElse(null)
+ assert result instanceof Integer
+ assert result == 42
+ }
+ void test() {
+ consume(Optional.of(42).map(this::process))
+ }
+ }
+
+ new C().test()
+ '''
+ }
+
@Test // instance::instanceMethod -- GROOVY-10057
void testPredicateII() {
assertScript shell, '''