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']
         '''
     }