diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java index 480c5509803..4308fa98838 100644 --- a/src/main/java/groovy/lang/MetaClassImpl.java +++ b/src/main/java/groovy/lang/MetaClassImpl.java @@ -2676,11 +2676,13 @@ public void addMetaBeanProperty(MetaBeanProperty mp) { } /** - *

Retrieves a property on the given receiver for the specified arguments. The sender is the class that is requesting the property from the object. - * The MetaClass will attempt to establish the method to invoke based on the name and arguments provided. - * - *

The useSuper and fromInsideClass help the Groovy runtime perform optimisations on the call to go directly - * to the super class if necessary + * Writes a property on the given receiver for the specified arguments. The + * sender is the class that is requesting the property from the object. The + * MetaClass will attempt to establish the method to invoke based on the name + * and arguments provided. + *

+ * The useSuper and fromInsideClass help the runtime perform optimisations + * on the call to go directly to the super class if necessary * * @param sender The java.lang.Class instance that is mutating the property * @param object The Object which the property is being set on @@ -2751,10 +2753,13 @@ public void setProperty(final Class sender, final Object object, final String na method = listeners.get(name); ambiguousListener = (method == AMBIGUOUS_LISTENER_METHOD); if (method != null && !ambiguousListener && newValue instanceof Closure) { + // bean.name = { -> } is short for bean.addSomeListener({ -> }); + // where "name" derives from the SomeListener interface's method + var listener = method.getParameterTypes()[0].getTheClass(); Object proxy = Proxy.newProxyInstance( - theClass.getClassLoader(), - new Class[]{method.getParameterTypes()[0].getTheClass()}, - new ConvertedClosure((Closure) newValue, name)); + listener.getClassLoader(), + new Class[]{listener}, + new ConvertedClosure((Closure) newValue, name)); arguments = new Object[]{proxy}; newValue = proxy; } else { diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/TypeTransformers.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/TypeTransformers.java index 55c77b65bd7..673cc756c0f 100644 --- a/src/main/java/org/codehaus/groovy/vmplugin/v8/TypeTransformers.java +++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/TypeTransformers.java @@ -141,10 +141,10 @@ protected static MethodHandle addTransformer(MethodHandle handle, int pos, Objec } /** - * creates a method handle able to transform the given Closure into a SAM type - * if the given parameter is a SAM type + * Creates a method handle that transforms the given Closure into the given + * parameter type, if it is a SAM type. */ - private static MethodHandle createSAMTransform(Object arg, Class parameter) { + private static MethodHandle createSAMTransform(Object closure, Class parameter) { Method method = CachedSAMClass.getSAMMethod(parameter); if (method == null) return null; // TODO: have to think about how to optimize this! @@ -164,17 +164,14 @@ private static MethodHandle createSAMTransform(Object arg, Class parameter) { } // the following code will basically do this: // return Proxy.newProxyInstance( - // arg.getClass().getClassLoader(), + // parameter.getClassLoader(), // new Class[]{parameter}, - // new ConvertedClosure((Closure) arg)); + // new ConvertedClosure((Closure)closure, method.getName())); // TO_REFLECTIVE_PROXY will do that for us, though // input is the closure, the method name, the class loader and the - // class[]. All of that but the closure must be provided here + // class array. All of that but the closure must be provided here. MethodHandle ret = TO_REFLECTIVE_PROXY; - ret = MethodHandles.insertArguments(ret, 1, - method.getName(), - arg.getClass().getClassLoader(), - new Class[]{parameter}); + ret = MethodHandles.insertArguments(ret, 1, method.getName(), parameter.getClassLoader(), new Class[]{parameter}); return ret; } else { // the following code will basically do this: @@ -207,8 +204,8 @@ public static MethodHandle applyUnsharpFilter(MethodHandle handle, int pos, Meth } /** - * returns a transformer later applied as filter to transform one - * number into another + * Returns a transformer later applied as filter to transform one + * number into another. */ private static MethodHandle selectNumberTransformer(Class param, Object arg) { param = TypeHelper.getWrapperClass(param); diff --git a/src/test/groovy/bugs/Groovy9873.groovy b/src/test/groovy/bugs/Groovy9873.groovy new file mode 100644 index 00000000000..65afad43ed9 --- /dev/null +++ b/src/test/groovy/bugs/Groovy9873.groovy @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.bugs + +import org.codehaus.groovy.control.CompilerConfiguration +import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit +import org.junit.Test + +import static groovy.test.GroovyAssert.assertScript + +final class Groovy9873 { + + @Test + void testCoerceClosure1() { + assertScript ''' + @Grab('io.vavr:vavr:0.10.4;transitive=false') + import io.vavr.control.Try + class C { } + C resolve() {new C()} + Try.of(this::resolve) + ''' + } + + @Test + void testCoerceClosure2() { + def config = new CompilerConfiguration().tap { + jointCompilationOptions = [memStub: true] + targetDirectory = File.createTempDir() + } + File parentDir = File.createTempDir() + try { + def c = new File(parentDir, 'C.groovy') + c.write ''' + class C { + private T t + C(T item) { + t = item + } + static C of(U item) { + new C(item) + } + def C map(F func) { + new C(func.apply(t)) + } + } + ''' + def d = new File(parentDir, 'D.groovy') + d.write ''' + class D { + static Set wrap(W o) { + Collections.singleton(o) + } + } + ''' + def f = new File(parentDir, 'F.groovy') + f.write ''' + interface F { + Y apply(X x) + } + ''' + def g = new File(parentDir, 'G.groovy') + g.write ''' + def c = C.of(123) + def d = c.map(D.&wrap) + def e = d.map(x -> x.first().intValue()) + ''' + + def loader = new GroovyClassLoader(this.class.classLoader) + def cu = new JavaAwareCompilationUnit(config, loader) + cu.addSources(c, d, f, g) + cu.compile() + + loader.loadClass('G').main() + } finally { + parentDir.deleteDir() + config.targetDirectory.deleteDir() + } + } + + @Test + void testCoerceClosure3() { + def config = new CompilerConfiguration().tap { + jointCompilationOptions = [memStub: true] + targetDirectory = File.createTempDir() + } + File parentDir = File.createTempDir() + try { + def f = new File(parentDir, 'F.groovy') + f.write ''' + class FInfo extends EventObject { + FInfo() { super(null) } + } + interface FListener extends EventListener { + void somethingHappened(FInfo i) + } + ''' + def g = new File(parentDir, 'G.groovy') + g.write ''' + class H { + void addFListener(FListener f) { + f.somethingHappened(null) + } + void removeFListener(FListener f) { + } + } + + new H().somethingHappened = { info -> } + ''' + + def loader = new GroovyClassLoader(this.class.classLoader) + def cu = new JavaAwareCompilationUnit(config, loader) + cu.addSources(f, g) + cu.compile() + + loader.loadClass('G').main() + } finally { + parentDir.deleteDir() + config.targetDirectory.deleteDir() + } + } +} diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy index faf5e8809c6..2d41d771a61 100644 --- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy +++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy @@ -2653,13 +2653,9 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase { } } - void test() { - def c = C.of(42) - def d = c.map($toSet) - def e = d.map(x -> x.first().intValue()) - } - - test() + def c = C.of(42) + def d = c.map($toSet) + def e = d.map(x -> x.first().intValue()) """ } }