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 2023/06/07 14:30:46 UTC
[groovy] branch GROOVY_3_0_X updated: GROOVY-11085: STC: propagate inferred closure param type
This is an automated email from the ASF dual-hosted git repository.
emilles pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
new 588d20ab49 GROOVY-11085: STC: propagate inferred closure param type
588d20ab49 is described below
commit 588d20ab494712d8260002e6d935dc5b26562a4c
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Wed Jun 7 09:13:40 2023 -0500
GROOVY-11085: STC: propagate inferred closure param type
---
.../transform/stc/StaticTypeCheckingVisitor.java | 23 +-
.../groovy/transform/stc/ClosuresSTCTest.groovy | 610 +++++++++------------
.../groovy/transform/stc/CoercionSTCTest.groovy | 381 ++++++++++++-
.../groovy/transform/stc/GenericsSTCTest.groovy | 45 ++
src/test/groovy/transform/stc/LambdaTest.groovy | 154 ++----
5 files changed, 743 insertions(+), 470 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 313f165435..ac3e4cdfce 100644
--- a/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -3105,6 +3105,9 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
paramTypes = samParamTypes;
} else { // TODO: error for length mismatch
paramTypes = Arrays.copyOf(samParamTypes, n);
+ for (int i = 0, j = Math.min(n, samParamTypes.length); i < j; i += 1) {
+ if (p[i].isDynamicTyped()) p[i].setType(samParamTypes[i]); // GROOVY-11085
+ }
}
expression.putNodeMetaData(CLOSURE_ARGUMENTS, paramTypes);
}
@@ -4712,28 +4715,22 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
}
private ClassNode inferSAMTypeGenericsInAssignment(final ClassNode samType, final MethodNode abstractMethod, final ClassNode closureType, final ClosureExpression closureExpression) {
- // if the sam type or closure type do not provide generics information,
- // we cannot infer anything, thus we simply return the provided samUsage
- GenericsType[] samTypeGenerics = samType.getGenericsTypes();
- GenericsType[] closureGenerics = closureType.getGenericsTypes();
- if (samTypeGenerics == null || closureGenerics == null) return samType;
-
- // extract the generics from the return type
Map<GenericsTypeName, GenericsType> connections = new HashMap<>();
+
+ // extract generics from the closure return type
extractGenericsConnections(connections, wrapTypeIfNecessary(getInferredReturnType(closureExpression)), abstractMethod.getReturnType());
- // next we get the block parameter types and set the generics
- // information just like before
- // TODO: add vargs handling
+ // extract generics from the closure parameters
if (closureExpression.isParameterSpecified()) {
Parameter[] closureParams = closureExpression.getParameters();
- Parameter[] methodParams = abstractMethod.getParameters();
+ Parameter[] methodParams = abstractMethod.getParameters();
for (int i = 0, n = Math.min(closureParams.length, methodParams.length); i < n; i += 1) {
ClassNode closureParamType = closureParams[i].getType();
- ClassNode methodParamType = methodParams[i].getType();
- extractGenericsConnections(connections, closureParamType, methodParamType);
+ ClassNode methodParamType = methodParams[i].getType();
+ extractGenericsConnections(connections, wrapTypeIfNecessary(closureParamType), methodParamType);
}
}
+
return applyGenericsContext(connections, samType.redirect());
}
diff --git a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
index d6bca6f472..9de00b7e75 100644
--- a/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
+++ b/src/test/groovy/transform/stc/ClosuresSTCTest.groovy
@@ -18,64 +18,42 @@
*/
package groovy.transform.stc
+import groovy.test.NotYetImplemented
+
/**
* Unit tests for static type checking : closures.
*/
class ClosuresSTCTest extends StaticTypeCheckingTestCase {
- void testClosureWithoutArguments1() {
+ void testCallClosure1() {
assertScript '''
def c = { return 'foo' }
assert c() == 'foo'
'''
}
- void testClosureWithoutArguments2() {
+ void testCallClosure2() {
assertScript '''
def c = { -> return 'foo' }
assert c() == 'foo'
'''
}
- // GROOVY-9079
- void testClosureWithoutArguments3() {
- assertScript '''
- java.util.concurrent.Callable<String> c = { -> return 'foo' }
- assert c() == 'foo'
- '''
- }
-
- // GROOVY-10071
- void testClosureWithoutArguments4() {
- assertScript '''
- def c = { ... zeroOrMore -> return 'foo' + zeroOrMore }
- assert c('bar', 'baz') == 'foo[bar, baz]'
- assert c('bar') == 'foo[bar]'
- assert c() == 'foo[]'
+ void testCallClosure3() {
+ shouldFail MissingMethodException, '''
+ def c = { -> }
+ c('')
'''
}
- // GROOVY-10072, GROOVY-11023
- void testClosureWithoutArguments5() {
- assertScript '''
- def c = { p = 'foo' -> return p }
- assert c('bar') == 'bar'
- assert c() == 'foo'
- '''
-
- assertScript '''
- def c = { p, q = p.toString() -> '' + p + q }
- assert c('foo', 'bar') == 'foobar'
- assert c('foo') == 'foofoo'
- '''
- }
-
- void testClosureWithArguments1() {
+ void testCallClosure4() {
assertScript '''
def c = { int a, int b -> a + b }
assert c(5, 7) == 12
'''
+ }
+ void testCallClosure5() {
shouldFailWithMessages '''
def c = { int a, int b -> a + b }
c('5', '7')
@@ -83,12 +61,14 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
'Cannot call closure that accepts [int, int] with [java.lang.String, java.lang.String]'
}
- void testClosureWithArguments2() {
+ void testCallClosure6() {
assertScript '''
def result = { int a, int b -> a + b }(5, 7)
assert result == 12
'''
+ }
+ void testCallClosure7() {
shouldFailWithMessages '''
{ int a, int b -> a + b }('5', 7)
''',
@@ -96,13 +76,59 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
}
// GROOVY-6365
- void testClosureWithArguments3() {
+ void testCallClosure8() {
assertScript '''
def c = { Object[] args -> args.length }
assert c('one', 'two') == 2
'''
}
+ // GROOVY-10071
+ void testCallClosure9() {
+ assertScript '''
+ def c = { ... zeroOrMore -> return 'foo' + zeroOrMore }
+ assert c('bar', 'baz') == 'foo[bar, baz]'
+ assert c('bar') == 'foo[bar]'
+ assert c() == 'foo[]'
+ '''
+ }
+
+ // GROOVY-10072
+ void testCallClosure10() {
+ assertScript '''
+ def c = { p = 'foo' -> return p }
+ assert c('bar') == 'bar'
+ assert c() == 'foo'
+ '''
+ }
+
+ // GROOVY-10072
+ void testCallClosure11() {
+ assertScript '''
+ def c = { Number n, Number total = 41 -> total += n }
+ assert c(1) == 42
+ '''
+ }
+
+ // GROOVY-10072
+ void testCallClosure12() {
+ shouldFailWithMessages '''
+ def c = { Number n, Number total = new Date() -> total += n }
+ ''',
+ 'Cannot assign value of type java.util.Date to variable of type java.lang.Number'
+ }
+
+ // GROOVY-11023
+ void testCallClosure13() {
+ assertScript '''
+ def c = { p, q = p.toString() -> '' + p + q }
+ assert c('foo', 'bar') == 'foobar'
+ assert c('foo') == 'foofoo'
+ '''
+ }
+
+ //
+
void testClosureReturnTypeInference1() {
assertScript '''
def c = { int a, int b -> return a + b }
@@ -121,9 +147,9 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
shouldFailWithMessages '''
def c = { int x ->
if (x == 0) {
- 1L // long
+ 1L
} else {
- x // int
+ x
}
}
byte res = c(0)
@@ -148,79 +174,101 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-9971
void testClosureReturnTypeInference5() {
assertScript '''
- import java.util.function.Consumer
-
- class C {
- static <T> void m(T a, Consumer<T> c) {
- c.accept(a)
- }
- static void main(args) {
- def c = { ->
- int x = 0
- m('') {
- print 'void return'
- }
- }
- c.call()
- }
+ def m(Closure<String> c) {
+ c.call()
}
+ final x = 123
+ Closure<String> c = { -> "x=$x" }
+ String type = c.call().class.name
+ assert type == 'java.lang.String'
+ type = (m { -> "x=$x" }).class.name
+ assert type == 'java.lang.String' // not GStringImpl
'''
}
- // GROOVY-8427
+ // GROOVY-10082, GROOVY-10277
+ @NotYetImplemented
void testClosureReturnTypeInference6() {
assertScript '''
- import java.util.function.Consumer
+ class A {}
+ class B extends A {}
+ Closure<A> c = { -> new B() }
- class C {
- static <T> void m(T a, Consumer<T> c) {
- c.accept(a)
- }
- static void main(args) {
- def c = { ->
- int x = 0
- m('') {
- print 'void return'
- }
- }
- c.call()
- }
- }
+ def result = c()
+ assert result instanceof A
+ assert result instanceof B
'''
+ shouldFailWithMessages '''
+ Closure<String> c = { -> 42 }
+ ''',
+ 'Cannot return value of type int for closure expecting java.lang.String'
}
- // GROOVY-8202
+ // GROOVY-10091, GROOVY-10277
+ @NotYetImplemented
void testClosureReturnTypeInference7() {
+ shouldFailWithMessages '''
+ class A<T> {}
+ class B extends A<Number> {}
+ class X extends A<String> {}
+ class Y<Z> extends A<Number> {}
+
+ Closure<A<Number>> c
+ c = { -> return new B() }
+ c = { -> return new X() }
+ c = { -> return new Y<String>() }
+ ''',
+ 'Cannot return value of type X for closure expecting A<java.lang.Number>'
+ }
+
+ // GROOVY-10792
+ @NotYetImplemented
+ void testClosureReturnTypeInference8() {
+ shouldFailWithMessages '''
+ void proc(Closure<Boolean> c) {
+ boolean result = c().booleanValue()
+ assert !result
+ }
+ def list = []
+ proc {
+ list
+ }
+ ''',
+ 'Cannot return value of type java.util.List<java.lang.Object> for closure expecting java.lang.Boolean'
+ }
+
+ // GROOVY-8202
+ void testClosureReturnTypeInference9() {
assertScript '''
void proc() {
}
String test0(flag) {
- if (flag) {
- 'foo'
- } else {
- proc()
- }
- }
- String test1(flag) {
- Closure<String> c = { ->
if (flag) {
- 'bar'
+ 'foo'
} else {
- proc()
- null
+ proc()
}
- }
- c.call()
+ }
+ String test1(flag) {
+ Closure<String> c = { ->
+ if (flag) {
+ 'bar'
+ } else {
+ proc()
+ null
+ }
+ }
+ c.call()
}
String test2(flag) {
- Closure<String> c = { -> // Cannot assign Closure<Object> to Closure<String>
- if (flag) {
- 'baz'
- } else {
- proc()
+ Closure<String> c = { -> // Cannot assign Closure<Object> to Closure<String>
+ if (flag) {
+ 'baz'
+ } else {
+ proc()
+ }
}
- }
- c.call()
+ c.call()
}
assert test0(true) == 'foo'
@@ -242,21 +290,6 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
'''
}
- // GROOVY-9971
- void testClosureReturnTypeInference8() {
- assertScript '''
- def m(Closure<String> c) {
- c.call()
- }
- final x = 123
- Closure<String> c = { -> "x=$x" }
- String type = c.call().class.name
- assert type == 'java.lang.String'
- type = (m { -> "x=$x" }).class.name
- assert type == 'java.lang.String' // not GStringImpl
- '''
- }
-
// GROOVY-5145
void testCollect() {
assertScript '''
@@ -335,11 +368,27 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
def c = x
c.m()
}
- ''', 'Cannot find matching method A#m()'
+ ''',
+ 'Cannot find matching method A#m()'
}
- // GROOVY-10052
+ // GROOVY-10356
+ @NotYetImplemented
void testClosureSharedVariable4() {
+ assertScript '''
+ interface A {
+ void m()
+ }
+ def a = (A) null
+ def x = { ->
+ a = null
+ }
+ a?.m()
+ '''
+ }
+
+ // GROOVY-10052
+ void testClosureSharedVariable5() {
assertScript '''
String x
def f = { ->
@@ -351,7 +400,7 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
}
// GROOVY-10052
- void testClosureSharedVariable5() {
+ void testClosureSharedVariable6() {
assertScript '''
def x
def f = { ->
@@ -375,24 +424,10 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
'''
}
- void testClosureCallAsAMethod() {
- assertScript '''
- Closure cl = { 'foo' }
- assert cl() == 'foo'
- '''
- }
-
- void testClosureCallWithOneArgAsAMethod() {
- assertScript '''
- Closure cl = { int x -> "foo$x" }
- assert cl(1) == 'foo1'
- '''
- }
-
void testRecurseClosureCallAsAMethod() {
assertScript '''
Closure<Integer> cl
- cl = { int x-> x==0?x:1+cl(x-1) }
+ cl = { int x -> x == 0 ? x : 1+cl(x-1) }
'''
}
@@ -423,7 +458,8 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
shouldFailWithMessages '''
Closure fib
fib = { int n -> n<2?n:fib(n-1)+fib(n-2) }
- ''', 'Cannot find matching method java.lang.Object#plus(java.lang.Object)'
+ ''',
+ 'Cannot find matching method java.lang.Object#plus(java.lang.Object)'
}
void testClosureRecursionWithDef() {
@@ -431,9 +467,9 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
def fib
fib = { int n -> n<2?n:fib(n-1)+fib(n-2) }
''',
- 'Cannot find matching method java.lang.Object#plus(java.lang.Object)',
- 'Cannot find matching method java.lang.Object#call(int)',
- 'Cannot find matching method java.lang.Object#call(int)'
+ 'Cannot find matching method java.lang.Object#plus(java.lang.Object)',
+ 'Cannot find matching method java.lang.Object#call(int)',
+ 'Cannot find matching method java.lang.Object#call(int)'
}
void testClosureRecursionWithClosureTypeArgument() {
@@ -455,17 +491,17 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-5639
void testShouldNotThrowClosureSharedVariableError() {
assertScript '''
- Closure<Void> c = {
- List<String> list = new ArrayList<String>()
- String s = "foo"
- 10.times {
- list.add(s)
+ Closure<Void> c = {
+ List<String> list = new ArrayList<String>()
+ String s = "foo"
+ 10.times {
+ list.add(s)
+ }
}
- }
'''
}
- // a case in Grails
+ // from Grails
void testShouldNotThrowClosureSharedVariableError2() {
assertScript '''
class AntPathMatcher {
@@ -488,12 +524,10 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
// GROOVY-5693
void testClosureArgumentCheckWithFlowTyping() {
assertScript '''
- Closure a = {
- int i ->
+ Closure a = { int i ->
println "First closure "+ i
}
- Closure b = {
- String s ->
+ Closure b = { String s ->
println "Second closure "+ s
}
a(5)
@@ -509,15 +543,25 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
'''
}
+ // GROOVY-10221
+ void testClosureArgumentCheckWithFlowTyping2() {
+ assertScript '''
+ class C<T1, T2 extends T1> {
+ void test() {
+ def one = { T2 x -> "" }
+ Closure<T2> two = { T2 x -> x }
+ one(two((T2) null))
+ }
+ }
+ new C<Number,Integer>().test()
+ '''
+ }
+
// GROOVY-5705
void testNPEWhenCallingClosureFromAField() {
assertScript '''
- import groovy.transform.*
-
class Test {
Closure c = { it }
-
- @TypeChecked
void test() {
c("123")
}
@@ -528,7 +572,7 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
}
// GROOVY-6219
- void testShouldFailBecauseClosureReturnTypeDoesnMatchMethodSignature() {
+ void testClosureReturnDoesNotMatchTarget() {
shouldFailWithMessages '''
void printMessage(Closure<String> messageProvider) {
println "Received message : ${messageProvider()}"
@@ -537,220 +581,106 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
void testMessage() {
printMessage { int x, int y -> x+y }
}
- ''', 'Cannot find matching method'
+ ''',
+ 'Cannot find matching method'
}
- // GROOVY-6189
- void testSAMsInMethodSelection() {
- // simple direct case
- assertScript """
+ void testSAMsInMethodSelection1() {
+ assertScript '''
interface MySAM {
def someMethod()
}
- def foo(MySAM sam) {sam.someMethod()}
- assert foo {1} == 1
- """
+ def foo(MySAM sam) {
+ sam.someMethod()
+ }
+ assert foo { -> 1 } == 1
+ assert foo(() -> 1) == 1
+ '''
+ }
+ // GROOVY-6189
+ void testSAMsInMethodSelection2() {
// overloads with classes implemented by Closure
- ["java.util.concurrent.Callable", "Object", "Closure", "GroovyObjectSupport", "Cloneable", "Runnable", "GroovyCallable", "Serializable", "GroovyObject"].each {
- className ->
+ for (type in [
+ 'groovy.lang.Closure',
+ 'groovy.lang.GroovyCallable',
+ 'groovy.lang.GroovyObject',
+ 'groovy.lang.GroovyObjectSupport',
+
+ 'java.lang.Object',
+ 'java.lang.Runnable',
+ 'java.lang.Cloneable',
+ 'java.io.Serializable',
+ 'java.util.concurrent.Callable'
+ ]) {
assertScript """
interface MySAM {
def someMethod()
}
- def foo(MySAM sam) {sam.someMethod()}
- def foo($className x) {2}
- assert foo {1} == 2
+ def foo($type ref) { 2 }
+ def foo(MySAM sam) { sam.someMethod() }
+ assert foo { -> 1 } == 2
+ assert foo(() -> 1) == 2
"""
}
}
- void testSAMVariable() {
- assertScript """
- interface SAM { def foo(); }
-
- @ASTTest(phase=INSTRUCTION_SELECTION, value={
- assert node.getNodeMetaData(INFERRED_TYPE).name == 'SAM'
- })
- SAM s = {1}
- assert s.foo() == 1
- def t = (SAM) {2}
- assert t.foo() == 2
- """
- }
-
- // GROOVY-7927
- void testSAMGenericsInAssignment() {
- assertScript """
- interface SAM<T,R> { R accept(T t); }
- SAM<Integer,Integer> s = { Integer n -> -n }
- assert s.accept(1) == -1
- """
- }
-
- void testSAMProperty() {
- assertScript """
- interface SAM { def foo(); }
- class X {
- SAM s
- }
- def x = new X(s:{1})
- assert x.s.foo() == 1
- """
- }
-
- // GROOVY-7003
- void testSAMProperty2() {
- assertScript '''
- import java.beans.*
-
- class C {
- static PropertyChangeListener listener = { PropertyChangeEvent event ->
- result = "${event.oldValue} -> ${event.newValue}"
- }
- public static result
- }
-
- def event = new PropertyChangeEvent(new Object(), 'foo', 'bar', 'baz')
- C.getListener().propertyChange(event)
- assert C.result == 'bar -> baz'
- '''
- }
-
- void testSAMAttribute() {
- assertScript """
- interface SAM { def foo(); }
- class X {
- public SAM s
- }
- def x = new X()
- x.s = {1}
- assert x.s.foo() == 1
- x = new X()
- x.@s = {2}
- assert x.s.foo() == 2
- """
- }
-
- // GROOVY-10254
- void testSAMReturnType() {
- assertScript '''
- interface SAM<T> { T get() }
- SAM<Integer> foo() {
- return { -> 42 }
- }
-
- def result = foo().get()
- assert result == 42
- '''
- }
-
- void testMultipleSAMSignature() {
- assertScript '''
- interface SAM { def foo() }
- def method(SAM a, SAM b) {
- a.foo()
- b.foo()
- }
- method({println 'a'}, {println 'b'})
- '''
- }
-
- void testMultipleSAMSignature2() {
+ // GROOVY-9881
+ void testSAMsInMethodSelection3() {
+ // Closure implements both and Runnable is "closer"
assertScript '''
- interface SAM { def foo() }
- def method(Object o, SAM a, SAM b) {
- a.foo()
- b.foo()
+ import java.util.concurrent.Callable
+ def foo(Callable c) { 'call' }
+ def foo(Runnable r) { 'run' }
+ def which = foo {
+ print 'bar'
}
- method(new Object(), {println 'a'}, {println 'b'})
+ assert which == 'run'
'''
}
- void testMultipleSAMMethodWithClosure() {
- assertScript '''
- interface SAM { def foo() }
- def method(SAM a, SAM b) {
- a.foo()
- b.foo()
- }
- def method(Closure a, SAM b) {
- b.foo()
- }
- def called = false
- method({called = true;println 'a'}, {println 'b'})
- assert !called
- '''
- }
-
- void testMultipleSAMMethodWithClosureInverted() {
- assertScript '''
- interface SAM { def foo() }
- def method(SAM a, SAM b) {
- a.foo()
- b.foo()
- }
- def method(SAM a, Closure b) {
- a.foo()
+ void testSAMsInMethodSelection4() {
+ shouldFailWithMessages '''
+ interface One { void m() }
+ interface Two { void m() }
+ def foo(One one) { one.m() }
+ def foo(Two two) { two.m() }
+ foo {
+ print 'bar'
}
- def called = false
- method({println 'a'}, {called=true;println 'b'})
- assert !called
- '''
+ ''',
+ 'Reference to method is ambiguous. Cannot choose between'
+ }
+
+ void testSAMsInMethodSelection5() {
+ for (spec in ['','x,y ->']) {
+ shouldFailWithMessages """
+ import java.util.function.Function
+ import java.util.function.Supplier
+ def foo(Function f) { f.apply(0) }
+ def foo(Supplier s) { s.get() }
+ foo { $spec
+ 'bar'
+ }
+ """,
+ 'Reference to method is ambiguous. Cannot choose between'
+ }
}
- void testAmbiguousSAMOverload() {
- shouldFailWithMessages '''
- interface Sammy { def sammy() }
- interface Sam { def sam() }
- def method(Sam sam) { sam.sam() }
- def method(Sammy sammy) { sammy.sammy() }
- method {
- println 'foo'
- }
- ''', 'Reference to method is ambiguous. Cannot choose between'
- }
-
- void testSAMType() {
- assertScript """
- interface Foo {int foo()}
- Foo f = {1}
- assert f.foo() == 1
- abstract class Bar implements Foo {}
- Bar b = {2}
- assert b.foo() == 2
- """
- shouldFailWithMessages """
- interface Foo2 {
- String toString()
- }
- Foo2 f2 = {int i->"hi"}
- """, "Cannot assign"
- shouldFailWithMessages """
- interface Foo2 {
- String toString()
- }
- abstract class Bar2 implements Foo2 {}
- Bar2 b2 = {"there"}
- """, "Cannot assign"
- assertScript """
- interface Foo3 {
- boolean equals(Object)
- int f()
- }
- Foo3 f3 = {1}
- assert f3.f() == 1
- """
- shouldFailWithMessages """
- interface Foo3 {
- boolean equals(Object)
- int f()
- }
- abstract class Bar3 implements Foo3 {
- int f(){2}
- }
- Bar3 b3 = {2}
- """, "Cannot assign"
+ @NotYetImplemented
+ void testSAMsInMethodSelection6() {
+ for (spec in ['->','x ->']) {
+ assertScript """
+ import java.util.function.Function
+ import java.util.function.Supplier
+ def foo(Function f) { f.apply(0) }
+ def foo(Supplier s) { s.get() }
+ def ret = foo { $spec
+ 'bar'
+ }
+ assert ret == 'bar'
+ """
+ }
}
// GROOVY-6238
@@ -827,20 +757,6 @@ class ClosuresSTCTest extends StaticTypeCheckingTestCase {
'''
}
- void testParameterlessClosureToSAMTypeArgumentCoercion() {
- assertScript '''
- interface SamType {
- int sam()
- }
-
- int foo(SamType samt) {
- samt.sam()
- }
-
- assert foo { -> 1 } == 1
- '''
- }
-
// GROOVY-9558
void testPutAtClosureDelegateProperty() {
assertScript '''
diff --git a/src/test/groovy/transform/stc/CoercionSTCTest.groovy b/src/test/groovy/transform/stc/CoercionSTCTest.groovy
index a8fef776f7..b2608e872b 100644
--- a/src/test/groovy/transform/stc/CoercionSTCTest.groovy
+++ b/src/test/groovy/transform/stc/CoercionSTCTest.groovy
@@ -18,11 +18,21 @@
*/
package groovy.transform.stc
+import groovy.test.NotYetImplemented
+import org.codehaus.groovy.control.customizers.ImportCustomizer
+
/**
* Unit tests for static type checking : coercions.
*/
class CoercionSTCTest extends StaticTypeCheckingTestCase {
+ @Override
+ void configure() {
+ config.addCompilationCustomizers(
+ new ImportCustomizer().addStarImports('java.util.function')
+ )
+ }
+
void testCoerceToArray() {
assertScript '''
try {
@@ -30,8 +40,7 @@ class CoercionSTCTest extends StaticTypeCheckingTestCase {
} catch (Throwable t) {
def newTrace = []
def clean = newTrace.toArray(newTrace as StackTraceElement[])
- // doing twice, because bug showed that the more you call the array coercion, the more the error
- // gets stupid :
+ // doing twice, because bug showed that the more you call the array coercion, the more the error gets stupid:
// Cannot call java.util.List#toArray([Ljava.lang.Object;) with arguments [[Ljava.lang.StackTraceElement; -> [Ljava.lang.StackTraceElement;]
// Cannot call java.util.List#toArray([[Ljava.lang.Object;) with arguments [[Ljava.lang.StackTraceElement; -> [Ljava.lang.StackTraceElement;]
// Cannot call java.util.List#toArray([[[Ljava.lang.Object;) with arguments [[Ljava.lang.StackTraceElement; -> [Ljava.lang.StackTraceElement;]
@@ -117,11 +126,377 @@ class CoercionSTCTest extends StaticTypeCheckingTestCase {
'''
assertScript '''
String s = [:]
- assert s == '{}' || s == '[:]'
+ assert s == '[:]' || s == '{}'
'''
assertScript '''
String s = []
assert s == '[]'
'''
}
+
+ void testCoerceToFunctionalInterface1() {
+ String sam = '@FunctionalInterface interface SAM { def foo() }'
+
+ assertScript sam + '''
+ def test(SAM a, SAM b) {
+ '' + a.foo() + b.foo()
+ }
+ String result = test({'a'}, {'b'})
+ assert result == 'ab'
+ '''
+
+ assertScript sam + '''
+ def test(Object o, SAM a, SAM b) {
+ '' + a.foo() + b.foo()
+ }
+ String result = test(new Object(), {'a'}, {'b'})
+ assert result == 'ab'
+ '''
+
+ assertScript sam + '''
+ def test(SAM a, SAM b) {
+ '' + a.foo() + b.foo()
+ }
+ def test(Closure a, SAM b) {
+ b.foo()
+ }
+ String result = test({'a'}, {'b'})
+ assert result == 'b'
+ '''
+
+ assertScript sam + '''
+ def test(SAM a, SAM b) {
+ '' + a.foo() + b.foo()
+ }
+ def test(SAM a, Closure b) {
+ a.foo()
+ }
+ String result = test({'a'}, {'b'})
+ assert result == 'a'
+ '''
+ }
+
+ // GROOVY-10254
+ void testCoerceToFunctionalInterface2() {
+ assertScript '''
+ @FunctionalInterface
+ interface SAM<T> { T get() }
+
+ SAM<Integer> foo() {
+ return { -> 42 }
+ }
+
+ def result = foo().get()
+ assert result == 42
+ '''
+ }
+
+ // GROOVY-10277
+ @NotYetImplemented
+ void testCoerceToFunctionalInterface3() {
+ assertScript '''
+ Consumer<Number> c = { n -> }
+ Supplier<Number> s = { -> 42 }
+ Predicate<Number> p = { n -> 42 }
+ '''
+
+ assertScript '''
+ def c = (Consumer<Number>) { n -> }
+ def s = (Supplier<Number>) { -> 42 }
+ def p = (Predicate<Number>) { n -> 42 }
+ '''
+
+ assertScript '''
+ def c = { n -> } as Consumer<Number>
+ def s = { -> 42 } as Supplier<Number>
+ def p = { n -> 42 } as Predicate<Number>
+ '''
+
+ shouldFailWithMessages '''
+ def s = (Supplier<Number>) { -> false }
+ ''',
+ 'Cannot return value of type boolean for closure expecting java.lang.Number'
+
+ shouldFailWithMessages '''
+ def s = { -> false } as Supplier<Number>
+ ''',
+ 'Cannot return value of type boolean for closure expecting java.lang.Number'
+
+ shouldFailWithMessages '''
+ def s = (() -> ['']) as Supplier<Number>
+ ''',
+ 'Cannot return value of type java.util.List<java.lang.String> for lambda expecting java.lang.Number'
+ }
+
+ void testCoerceToFunctionalInterface4() {
+ assertScript '''
+ interface I { int m() }
+
+ @ASTTest(phase=INSTRUCTION_SELECTION, value={
+ assert node.getNodeMetaData(INFERRED_TYPE).name == 'I'
+ })
+ I i = { 1 }
+ assert i.m() == 1
+ def x = (I) { 2 }
+ assert x.m() == 2
+ '''
+
+ assertScript '''
+ interface I { int m() }
+ abstract class A implements I { }
+
+ I i = { 1 }
+ assert i.m() == 1
+ A a = { 2 }
+ assert a.m() == 2
+ '''
+
+ assertScript '''
+ interface I { // non-functional, but every instance extends Object
+ boolean equals(Object)
+ int m()
+ }
+ I i = { 1 }
+ assert i.m() == 1
+ '''
+
+ shouldFailWithMessages '''
+ interface I {
+ boolean equals(Object)
+ int m()
+ }
+ abstract class A implements I { // no abstract methods
+ int m() { 1 }
+ }
+ A a = { 2 }
+ ''',
+ 'Cannot assign value of type groovy.lang.Closure <java.lang.Integer> to variable of type A'
+
+ shouldFailWithMessages '''
+ interface I { // no abstract methods; every instance extends Object
+ String toString()
+ }
+ I i = { p -> "" }
+ ''',
+ 'Cannot assign value of type groovy.lang.Closure <java.lang.String> to variable of type I'
+
+ shouldFailWithMessages '''
+ interface I { // no abstract methods; every instance extends Object
+ String toString()
+ }
+ abstract class A implements I { }
+
+ A a = { "" } // implicit parameter
+ ''',
+ 'Cannot assign value of type groovy.lang.Closure <java.lang.String> to variable of type A'
+ }
+
+ // GROOVY-7927
+ void testCoerceToFunctionalInterface5() {
+ assertScript '''
+ interface SAM<T,R> { R accept(T t); }
+ SAM<Integer,Integer> s = { Integer n -> -n }
+ assert s.accept(1) == -1
+ '''
+ }
+
+ void testCoerceToFunctionalInterface6() {
+ String sam = '@FunctionalInterface interface SAM { def foo() }'
+
+ assertScript sam + '''
+ class X {
+ public SAM s
+ }
+ def x = new X()
+ x.s = {1}
+ assert x.s.foo() == 1
+ x.@s = {2}
+ assert x.s.foo() == 2
+ '''
+
+ assertScript sam + '''
+ class X {
+ SAM s
+ }
+ def x = new X(s: {3})
+ assert x.s.foo() == 3
+ '''
+ }
+
+ // GROOVY-11085
+ void testCoerceToFunctionalInterface7() {
+ for (type in ['','long','Long']) {
+ assertScript """
+ void setStrategy(Predicate<Long> tester) {
+ assert tester.test(1L)
+ }
+ strategy = { $type n -> n instanceof Long }
+ """
+ }
+ }
+
+ // GROOVY-7003
+ void testCoerceToFunctionalInterface8() {
+ assertScript '''import java.beans.*
+ class C {
+ static PropertyChangeListener listener = { PropertyChangeEvent event ->
+ result = "${event.oldValue} -> ${event.newValue}"
+ }
+ public static result
+ }
+
+ def event = new PropertyChangeEvent(new Object(), 'foo', 'bar', 'baz')
+ C.getListener().propertyChange(event)
+ assert C.result == 'bar -> baz'
+ '''
+ }
+
+ // GROOVY-8045
+ void ____CoerceToFunctionalInterface9() {
+ assertScript '''
+ def f(Supplier<Integer>... suppliers) {
+ suppliers*.get().sum()
+ }
+ Object result = f({->1},{->2})
+ assert result == 3
+ '''
+ }
+
+ // GROOVY-8168
+ @NotYetImplemented
+ void testCoerceToFunctionalInterface10() {
+ String sam = '''
+ @FunctionalInterface
+ interface Operation {
+ double calculate(int i)
+ }
+ '''
+
+ assertScript sam + '''
+ Operation operation = { return 1.0d }
+ def result = operation.calculate(2)
+ assert result == 1.0d
+ '''
+
+ shouldFailWithMessages sam + '''
+ Operation operation = { return 1.0; }
+ ''',
+ 'Cannot return value of type java.math.BigDecimal for closure expecting double'
+ }
+
+ // GROOVY-8427
+ void testCoerceToFunctionalInterface11() {
+ assertScript '''
+ def <T> void m(T a, Consumer<T> c) {
+ c.accept(a)
+ }
+
+ def c = { ->
+ int x = 0
+ m('') {
+ print 'void return'
+ }
+ }
+ c.call()
+ '''
+ }
+
+ // GROOVY-9079
+ void testCoerceToFunctionalInterface12() {
+ assertScript '''import java.util.concurrent.Callable
+ Callable<String> c = { -> return 'foo' }
+ assert c() == 'foo'
+ '''
+ }
+
+ // GROOVY-10128, GROOVY-10306
+ @NotYetImplemented
+ void testCoerceToFunctionalInterface13() {
+ assertScript '''
+ Function<String, Number> x = { s ->
+ long n = 1
+ return n
+ }
+ assert x.apply('') == 1L
+ '''
+
+ assertScript '''
+ class C {
+ byte p = 1
+ void m() {
+ byte v = 2
+ Supplier<Number> one = { -> p }
+ Supplier<Number> two = { -> v }
+ assert one.get() == (byte)1
+ assert two.get() == (byte)2
+ }
+ }
+ new C().m()
+ '''
+ }
+
+ // GROOVY-10792
+ void testCoerceToFunctionalInterface14() {
+ assertScript '''
+ @Grab('org.awaitility:awaitility-groovy:4.2.0')
+ import static org.awaitility.Awaitility.await
+
+ List<String> strings = ['x']
+ await().until { -> strings }
+ '''
+ }
+
+ // GROOVY-10905
+ @NotYetImplemented
+ void testCoerceToFunctionalInterface15() {
+ assertScript '''
+ def method(IntUnaryOperator unary) { '1a' }
+ def method(IntBinaryOperator binary) { '1b' }
+
+ assert method{ x -> } == '1a'
+ assert method{ x, y -> } == '1b'
+ assert method{ } == '1a'
+ '''
+
+ assertScript '''
+ def method(IntSupplier supplier) { '2a' }
+ def method(IntBinaryOperator binary) { '2b' }
+
+ assert method{ -> } == '2a'
+ assert method{ x, y -> } == '2b'
+ assert method{ } == '2a'
+ '''
+ }
+
+ // GROOVY-11051
+ @NotYetImplemented
+ void testCoerceToFunctionalInterface16() {
+ assertScript '''import java.util.concurrent.atomic.AtomicReference
+ def opt = new AtomicReference<Object>(null)
+ .stream().filter { it.get() }.findAny()
+ assert opt.isEmpty()
+ '''
+ }
+
+ // GROOVY-11079
+ void testCoerceToFunctionalInterface17() {
+ assertScript '''
+ @Grab('io.vavr:vavr:0.10.4')
+ import io.vavr.control.Try
+ import io.vavr.Tuple2
+ Map<String,Object> getSpec() { [:] }
+ Try<Tuple2<String,Object>> tuple() {
+ Try.success(new Tuple2("",null))
+ }
+ void test(List list) {
+ list.forEach { item ->
+ def map = getSpec()
+ tuple().onSuccess {
+ map.foo = it._1
+ }
+ }
+ }
+ test( [null] )
+ '''
+ }
}
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index 526dccd269..c31ebc2d9b 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -411,7 +411,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
def c = new HandleContainer(handle: h)
test({k->c} as Input, {k->c} as State)
'''
+ }
+ void testReturnTypeInferenceWithMethodGenerics1y() {
File parentDir = File.createTempDir()
config.with {
targetDirectory = File.createTempDir()
@@ -468,6 +470,49 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
}
}
+ void testReturnTypeInferenceWithMethodGenerics1z() {
+ File parentDir = File.createTempDir()
+ config.with {
+ targetDirectory = File.createTempDir()
+ jointCompilationOptions = [memStub: true]
+ }
+ try {
+ def a = new File(parentDir, 'A.java')
+ a.write '''
+ interface A {
+ int getCount();
+ }
+ '''
+ def b = new File(parentDir, 'B.java')
+ b.write '''
+ class B {
+ public <T extends A> T getA(String id) {
+ return null;
+ }
+ }
+ '''
+ def c = new File(parentDir, 'C.groovy')
+ c.write '''
+ class C {
+ static main(args) {
+ def b = new B()
+ b.getA("")?.count
+ }
+ }
+ '''
+
+ def loader = new GroovyClassLoader(this.class.classLoader)
+ def cu = new JavaAwareCompilationUnit(config, loader)
+ cu.addSources(a, b, c)
+ cu.compile()
+
+ loader.loadClass('C').main()
+ } finally {
+ parentDir.deleteDir()
+ config.targetDirectory.deleteDir()
+ }
+ }
+
// GROOVY-10067
@NotYetImplemented
void testReturnTypeInferenceWithMethodGenerics11() {
diff --git a/src/test/groovy/transform/stc/LambdaTest.groovy b/src/test/groovy/transform/stc/LambdaTest.groovy
index 73aa182da4..92cd43eed5 100644
--- a/src/test/groovy/transform/stc/LambdaTest.groovy
+++ b/src/test/groovy/transform/stc/LambdaTest.groovy
@@ -26,103 +26,77 @@ import static groovy.test.GroovyAssert.shouldFail
final class LambdaTest {
+ private static final imports = '''\
+ import groovy.transform.CompileStatic
+ import java.util.function.*
+ import java.util.stream.*
+ '''
+
@Test
void testFunction() {
- assertScript '''
- import groovy.transform.CompileStatic
- import java.util.stream.Collectors
-
- @CompileStatic
- def f() {
+ assertScript imports + '''
+ @CompileStatic f() {
[1, 2, 3].stream().map(e -> e + 1).collect(Collectors.toList())
}
-
assert f() == [2, 3, 4]
'''
}
@Test
void testFunction2() {
- assertScript '''
- import groovy.transform.CompileStatic
- import java.util.stream.Collectors
-
- @CompileStatic
- def f() {
+ assertScript imports + '''
+ @CompileStatic f() {
[1, 2, 3].stream().map(e -> e.plus(1)).collect(Collectors.toList())
}
-
assert f() == [2, 3, 4]
'''
}
@Test
void testFunctionWithTypeArgument() {
- assertScript '''
- import groovy.transform.CompileStatic
- import java.util.stream.Collectors
-
- @CompileStatic
- List<String> f() {
+ assertScript imports + '''
+ @CompileStatic f() {
[1, 2, 3].stream().<String>map(i -> null).collect(Collectors.toList())
}
-
assert f() == [null, null, null]
'''
}
@Test
void testBinaryOperator() {
- assertScript '''
- import groovy.transform.CompileStatic
-
- @CompileStatic
- int f() {
+ assertScript imports + '''
+ @CompileStatic f() {
[1, 2, 3].stream().reduce(7, (Integer r, Integer e) -> r + e)
}
-
assert f() == 13
'''
}
@Test // GROOVY-8917
void testBinaryOperatorWithoutExplicitTypes() {
- assertScript '''
- import groovy.transform.CompileStatic
-
- @CompileStatic
- int f() {
+ assertScript imports + '''
+ @CompileStatic f() {
[1, 2, 3].stream().reduce(7, (r, e) -> r + e)
}
-
assert f() == 13
'''
}
@Test
void testBinaryOperatorWithoutExplicitTypes2() {
- assertScript '''
- import groovy.transform.CompileStatic
- import java.util.function.BinaryOperator
-
- @CompileStatic
- int f() {
+ assertScript imports + '''
+ @CompileStatic f() {
BinaryOperator<Integer> accumulator = (r, e) -> r + e
return [1, 2, 3].stream().reduce(7, accumulator)
}
-
assert f() == 13
'''
}
@Test // GROOVY-10282
void testBiFunctionAndBinaryOperatorWithSharedTypeParameter() {
- assertScript '''
- import groovy.transform.CompileStatic
- import java.util.stream.IntStream
-
- @CompileStatic
- def f() {
+ assertScript imports + '''
+ @CompileStatic f() {
IntStream.range(0, 10).boxed().reduce('', (s, i) -> s + '-', String::concat)
}
assert f() == '----------'
@@ -130,85 +104,51 @@ final class LambdaTest {
}
@Test
- void testPredicate() {
- assertScript '''
- import groovy.transform.CompileStatic
- import java.util.stream.Collectors
- import java.util.stream.Stream
-
- @CompileStatic
- class Tester {
- static main(args) {
- def list = ['ab', 'bc', 'de']
- list.removeIf(e -> e.startsWith("a"))
- assert ['bc', 'de'] == list
- }
+ void testPredicate1() {
+ assertScript imports + '''
+ @CompileStatic f() {
+ def list = ['ab', 'bc', 'de']
+ list.removeIf(e -> e.startsWith('a'))
+ list
}
+ assert f() == ['bc', 'de']
'''
}
@Test
- void testPredicateWithoutExplicitTypeDef() {
- assertScript '''
- import groovy.transform.CompileStatic
- import java.util.stream.Collectors
- import java.util.stream.Stream
- import java.util.function.Function
- import java.util.function.Predicate
-
- @CompileStatic
- class Tester {
- static main(args) {
- List<String> myList = Arrays.asList("a1", "a2", "b2", "b1", "c2", "c1")
- Predicate<String> predicate = s -> s.startsWith("b")
- Function<String, String> mapper = s -> s.toUpperCase()
-
- List<String> result =
- myList
- .stream()
- .filter(predicate)
- .map(mapper)
- .sorted()
- .collect(Collectors.toList())
-
- assert ['B1', 'B2'] == result
- }
+ void testPredicate2() {
+ assertScript imports + '''
+ @CompileStatic f() {
+ Predicate<String> tester = s -> s.startsWith('b')
+ Function<String, String> mapper = s -> s.toUpperCase()
+ List<String> myList = Arrays.asList('a1', 'a2', 'b2', 'b1', 'c2', 'c1')
+ List<String> result = myList.stream().filter(tester).map(mapper).sorted().collect(Collectors.toList())
}
+ assert f() == ['B1', 'B2']
'''
}
@Test
void testUnaryOperator() {
- assertScript '''
- import groovy.transform.CompileStatic
- import java.util.stream.Collectors
- import java.util.stream.Stream
-
- @CompileStatic
- class Tester {
- static main(args) {
- def list = [1, 2, 3]
- list.replaceAll(e -> e + 10)
- assert [11, 12, 13] == list
- }
+ assertScript imports + '''
+ @CompileStatic f() {
+ def list = [1, 2, 3]
+ list.replaceAll(e -> e + 10)
+ list
}
+ assert f() == [11, 12, 13]
'''
}
@Test
void testBiConsumer() {
- assertScript '''
- import groovy.transform.CompileStatic
- import java.util.stream.Collectors
- import java.util.stream.Stream
-
- @CompileStatic
- class Tester {
- static main(args) {
- def map = [a: 1, b: 2, c: 3]
- map.forEach((k, v) -> System.out.println(k + ":" + v));
- }
+ assertScript imports + '''
+ @CompileStatic f() {
+ def map = [a: 1, b: 2, c: 3], list = []
+ map.forEach((k, v) -> list.add(k + v))
+ list
}
+ assert f() == ['a1', 'b2', 'c3']
'''
}