diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.1-groovy-2.5.jar b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.1-groovy-2.5.jar deleted file mode 100644 index 04229f7ac6..0000000000 Binary files a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.1-groovy-2.5.jar and /dev/null differ diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.1-groovy-3.0.jar b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.1-groovy-3.0.jar deleted file mode 100644 index 629b9adad2..0000000000 Binary files a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.1-groovy-3.0.jar and /dev/null differ diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.2-groovy-2.5.jar b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.2-groovy-2.5.jar new file mode 100644 index 0000000000..7a5cba352d Binary files /dev/null and b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.2-groovy-2.5.jar differ diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.2-groovy-3.0.jar b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.2-groovy-3.0.jar new file mode 100644 index 0000000000..640e26e5b8 Binary files /dev/null and b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.2-groovy-3.0.jar differ diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.2-groovy-4.0.jar b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.2-groovy-4.0.jar new file mode 100644 index 0000000000..f9c1b30e27 Binary files /dev/null and b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-core-2.2-groovy-4.0.jar differ diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-groovy2-compat-2.1.jar b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-groovy2-compat-2.1.jar deleted file mode 100644 index 9b6aff9a56..0000000000 Binary files a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-groovy2-compat-2.1.jar and /dev/null differ diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-groovy2-compat-2.2.jar b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-groovy2-compat-2.2.jar new file mode 100644 index 0000000000..5f49767bfe Binary files /dev/null and b/base-test/org.eclipse.jdt.groovy.core.tests.builder/lib/spock-groovy2-compat-2.2.jar differ diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/builder/BasicGroovyBuildTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/builder/BasicGroovyBuildTests.java index c9f366e4a4..7a2c4371a8 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/builder/BasicGroovyBuildTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/builder/BasicGroovyBuildTests.java @@ -102,14 +102,13 @@ private IPath[] createSimpleProject(final String name, final boolean isGroovy) t private void addJUnitAndSpock(final IPath projectPath) throws Exception { String spockCorePath; - if (!isAtLeastGroovy(30)) { - spockCorePath = "lib/spock-core-2.1-groovy-2.5.jar"; - env.addJar(projectPath, "lib/spock-groovy2-compat-2.1.jar"); + if (isAtLeastGroovy(40)) { + spockCorePath = "lib/spock-core-2.2-groovy-4.0.jar"; + } else if (isAtLeastGroovy(30)) { + spockCorePath = "lib/spock-core-2.2-groovy-3.0.jar"; } else { - spockCorePath = "lib/spock-core-2.1-groovy-3.0.jar"; - if (isAtLeastGroovy(40)) { - System.setProperty("spock.iKnowWhatImDoing.disableGroovyVersionCheck", "true"); - } + spockCorePath = "lib/spock-core-2.2-groovy-2.5.jar"; + env.addJar(projectPath, "lib/spock-groovy2-compat-2.2.jar"); } env.addJar(projectPath, spockCorePath); env.addEntry(projectPath, JavaCore.newContainerEntry(new Path("org.eclipse.jdt.junit.JUNIT_CONTAINER/5"))); diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/SpockInferencingTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/SpockInferencingTests.java index ee622b3074..d50a45f3d0 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/SpockInferencingTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/SpockInferencingTests.java @@ -28,11 +28,13 @@ public final class SpockInferencingTests extends InferencingTestSuite { @Before public void setUp() throws Exception { String spockCorePath; - if (isAtLeastGroovy(30)) { - spockCorePath = "lib/spock-core-2.1-groovy-3.0.jar"; + if (isAtLeastGroovy(40)) { + spockCorePath = "lib/spock-core-2.2-groovy-4.0.jar"; + } else if (isAtLeastGroovy(30)) { + spockCorePath = "lib/spock-core-2.2-groovy-3.0.jar"; } else { - spockCorePath = "lib/spock-core-2.1-groovy-2.5.jar"; - env.addJar(project.getFullPath(), "lib/spock-groovy2-compat-2.1.jar"); + spockCorePath = "lib/spock-core-2.2-groovy-2.5.jar"; + env.addJar(project.getFullPath(), "lib/spock-groovy2-compat-2.2.jar"); } env.addJar(project.getFullPath(), spockCorePath); env.addEntry(project.getFullPath(), JavaCore.newContainerEntry(new Path("org.eclipse.jdt.junit.JUNIT_CONTAINER/5"))); diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovyCompilerTestSuite.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovyCompilerTestSuite.java index 8830f1cf79..0e5ebd281c 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovyCompilerTestSuite.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovyCompilerTestSuite.java @@ -133,7 +133,7 @@ protected String[] getDefaultClassPaths() { String[] cps = super.getDefaultClassPaths(); String[] newcps = Arrays.copyOf(cps, cps.length + 2); - String[] groovyVersions = {"4.0.4", "3.0.12-indy", "2.5.18-indy"}; + String[] groovyVersions = {"4.0.5", "3.0.12-indy", "2.5.18-indy"}; String[] ivyVersions = {"2.5.0", "2.4.0"}; try { URL groovyJar = null; diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovySimpleTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovySimpleTests.java index b09ce61daf..6aa312c2c4 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovySimpleTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/GroovySimpleTests.java @@ -700,6 +700,23 @@ public void testVariadicCategoryMethods() { runConformTest(sources, "66436643"); } + @Test // GROOVY-10743 + public void testInterfaceCategoryMethods() { + assumeTrue(isAtLeastJava(JDK9)); + + //@formatter:off + String[] sources = { + "Main.groovy", + "use (java.util.stream.Stream) {\n" + + " assert 16.iterate({it < 500}, {it * 2}).toList() == [16, 32, 64, 128, 256]\n" + + " assert [1, 1].iterate{f -> [f[1], f.sum()]}.limit(8).toList()*.head() == [1, 1, 2, 3, 5, 8, 13, 21]\n" + + "}\n", + }; + //@formatter:on + + runConformTest(sources); + } + @Test public void testGreclipse719() { //@formatter:off @@ -6760,6 +6777,40 @@ public void testSuperDotPrivateMethod() { } } + @Test // GROOVY-10747 + public void testSuperDotClone() { + //@formatter:off + String[] sources = { + "Main.groovy", + "class C implements Cloneable {\n" + + " C clone() {\n" + + " super.clone()\n" + + " }\n" + + "}\n" + + "def pogo = new C()\n" + + "def copy = pogo.clone()\n" + + "assert !copy.is( pogo )\n" + + "assert copy instanceof C\n", + }; + //@formatter:on + + runConformTest(sources); + } + + @Test // GROOVY-10733 + public void testArrayDotClone() { + //@formatter:off + String[] sources = { + "Main.groovy", + "int[] numbers = [1,2,3]\n" + + "assert numbers.clone() == [1,2,3]\n" + + "assert numbers.clone() instanceof int[]\n", + }; + //@formatter:on + + runConformTest(sources); + } + @Test public void testPositions1() { //@formatter:off diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/InnerClassTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/InnerClassTests.java index a54c05c77c..3329516d25 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/InnerClassTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/InnerClassTests.java @@ -1816,6 +1816,29 @@ public void testAnonymousInnerClass35() { runConformTest(sources, "local then getter"); } + @Test // GROOVY-7370 + public void testAnonymousInnerClass36() { + //@formatter:off + String[] sources = { + "Script.groovy", + "class C {\n" + + " public String[] strings\n" + + " C(String... args) {\n" + + " strings = args\n" + + " }\n" + + "}\n" + + "def c = new C() { }\n" + + "assert c.strings.length == 0\n" + + "c = new C('xy') { }\n" + + "assert c.strings.length == 1\n" + + "c = new C('x','y') { }\n" + + "assert c.strings.length == 2\n", + }; + //@formatter:on + + runConformTest(sources); + } + @Test public void testMixedModeInnerProperties_GRE597() { //@formatter:off diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java index 3a4bd05678..dd6a3de0ab 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java @@ -1692,6 +1692,30 @@ public void testCompileStatic7490a() { runConformTest(sources, "works"); } + @Test + public void testCompileStatic7506() { + //@formatter:off + String[] sources = { + "Main.groovy", + "class C {\n" + + " public String[] strings\n" + + " void setP(String[] strings) {\n" + + " this.strings = strings\n" + + " }\n" + + "}\n" + + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " def c = new C()\n" + + " c.p = ['foo', 123 ]\n" + + " assert c.strings == ['foo','123']\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources); + } + @Test public void testCompileStatic7526() { //@formatter:off @@ -2451,6 +2475,27 @@ public void testCompileStatic8051() { runConformTest(sources, "1"); } + @Test + public void testCompileStatic8074() { + //@formatter:off + String[] sources = { + "Main.groovy", + "class M extends HashMap {\n" + + " def foo = 1\n" + + "}\n" + + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " def map = new M()\n" + + " map.put('foo',42)\n" + + " print map.foo\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "42"); + } + @Test public void testCompileStatic8133() { //@formatter:off @@ -2975,6 +3020,31 @@ public void testCompileStatic8693() { runConformTest(sources, "works"); } + @Test + public void testCompileStatic8737() { + //@formatter:off + String[] sources = { + "Main.groovy", + "String m(String key, Object[] args) {\n" + + " \"key=$key, args=$args\"\n" + + "}\n" + + "String m(String key, Object[] args, Object[] parts) {\n" + + " \"key=$key, args=$args, parts=$parts\"\n" + + "}\n" + + "String m(String key, Object[] args, String[] names) {\n" + + " \"key=$key, args=$args, names=$names\"\n" + + "}\n" + + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " print m('hello', ['world'] as Object[])\n" + // exact match for m(String,Object[]) + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "key=hello, args=[world]"); + } + @Test public void testCompileStatic8788() { //@formatter:off @@ -7544,4 +7614,103 @@ public void testCompileStatic10592a() { runConformTest(sources, "worksworks"); } + + @Test + public void testCompileStatic10712() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " final list = ['foo','bar']\n" + + " for (item in list.iterator()) print item.toUpperCase()\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "FOOBAR"); + } + + @Test + public void testCompileStatic10714() { + assumeTrue(isParrotParser()); + + //@formatter:off + String[] sources = { + "Main.groovy", + "import java.util.function.*\n" + + "class C {\n" + + " String which\n" + + " void m(int i) { which = 'int' }\n" + + " void m(Number n) { which = 'Number' }\n" + + "}\n" + + "interface I {\n" + + " I andThen(Consumer c)\n" + + " I andThen(BiConsumer bc)\n" + + "}\n" + + "@groovy.transform.CompileStatic\n" + + "void test(I i, C c) {\n" + + " i = i.andThen(c::m)\n" + // "andThen" is ambiguous unless parameters of "m" overloads are taken into account + "}\n" + + "C x= new C()\n" + + "test(new I() {\n" + + " I andThen(Consumer c) {\n" + + " c.accept(42)\n" + + " return this\n" + + " }\n" + + " I andThen(BiConsumer bc) {\n" + + " bc.accept(1234, null)\n" + + " return this\n" + + " }\n" + + "}, x)\n" + + "print x.which\n", + }; + //@formatter:on + + runConformTest(sources, "Number"); + } + + @Test + public void testCompileStatic10725() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " List list = ['foo','bar']\n" + + " Set> set_of_maps = []\n" + + " set_of_maps.addAll(list.collectEntries { [it, it.toUpperCase()] })\n" + + " print set_of_maps.first()\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "[foo:FOO, bar:BAR]"); + } + + @Test + public void testCompileStatic10742() { + assumeTrue(isParrotParser()); + + //@formatter:off + String[] sources = { + "Main.groovy", + "void foo(bar) { }\n" + + "@groovy.transform.CompileStatic\n" + + "void test() {\n" + + " java.util.function.Function f = this::foo\n" + + "}\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in Main.groovy (at line 4)\n" + + "\tjava.util.function.Function f = this::foo\n" + + "\t ^^^^^^^^^\n" + + "Groovy:Invalid return type: void is not convertible to java.lang.String\n" + + "----------\n"); + } } diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java index cbfcc0c7b3..fa29791f7a 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/TypeCheckedTests.java @@ -1334,6 +1334,30 @@ public void testTypeChecked7128d() { runConformTest(sources); } + @Test + public void testTypeChecked7247() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import groovy.transform.*\n" + + "import static org.codehaus.groovy.ast.ClassHelper.*\n" + + "import static org.codehaus.groovy.transform.stc.StaticTypesMarker.*\n" + + "@TypeChecked void test() {\n" + + " @ASTTest(phase=INSTRUCTION_SELECTION, value={\n" + + " def type = node.getNodeMetaData(INFERRED_TYPE)\n" + + " assert type.genericsTypes[0].type == STRING_TYPE\n" + + " assert type.genericsTypes[1].type != Integer_TYPE\n" + + " assert type.genericsTypes[1].type.isDerivedFrom(Number_TYPE)\n" + + " })\n" + + " def map = [*:[A:1], *:[B:2.3]]\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources); + } + @Test public void testTypeChecked7274() { //@formatter:off @@ -1828,6 +1852,30 @@ public void testTypeChecked8788() { runConformTest(sources); } + @Test + public void testTypeChecked8828() { + //@formatter:off + String[] sources = { + "Main.groovy", + "interface Foo {}\n" + + "interface Bar {String name()}\n" + + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " Map map = [:]\n" + + " map.values().each { foo ->\n" + + " if (foo instanceof Bar) {\n" + + " String name = foo.name()\n" + // method available through Bar + " map.put(name, foo) \n" + // second parameter expects Foo + " }\n" + + " }\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources); + } + @Test public void testTypeChecked8909() { //@formatter:off @@ -5914,6 +5962,41 @@ public void testTypeChecked10633() { runConformTest(sources); } + @Test + public void testTypeChecked10646() { + //@formatter:off + String[] sources = { + "Main.groovy", + "import groovy.transform.*\n" + + "import static org.codehaus.groovy.transform.stc.StaticTypesMarker.*\n" + + "class Model {\n" + + "}\n" + + "interface Output {\n" + + " T getT()\n" + + "}\n" + + "abstract class WhereDSL {\n" + + " abstract Type where()\n" + + "}\n" + + "abstract class Input extends WhereDSL {\n" + + " class ReferencesOuterClassTP implements Output {\n" + + " @Override T getT() { return null }\n" + + " }\n" + + "}\n" + + "@TypeChecked\n" + + "void test(Input input) {\n" + + " def output = input?.where()\n" + + " @ASTTest(phase=INSTRUCTION_SELECTION, value={\n" + + " assert node.getNodeMetaData(INFERRED_TYPE).toString(false) == 'Model'\n" + + " })\n" + + " def result = output?.getT()\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources); + } + @Test public void testTypeChecked10651() { //@formatter:off @@ -6181,6 +6264,24 @@ public void testTypeChecked10701() { runConformTest(sources, "42.0"); } + @Test + public void testTypeChecked10720() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " Double[] array = new Double[1]\n" + + " def stream = Arrays.stream(array)\n" + //stream(T[]) + " print stream.map{d -> 'string'}.toList().get(0)\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "string"); + } + @Test public void testTypeChecked10725() { //@formatter:off @@ -6198,4 +6299,89 @@ public void testTypeChecked10725() { runConformTest(sources, "[[foo:FOO, bar:BAR]]"); } + + @Test + public void testTypeChecked10741() { + //@formatter:off + String[] sources = { + "Main.groovy", + "class C { def foo }\n" + + "@groovy.transform.TypeChecked\n" + + "void test(C pogo) {\n" + + " def proc = pogo.&setFoo\n" + // Cannot find matching method C#setFoo + " proc.call('baz')\n" + + " print pogo.foo\n" + + "}\n" + + "test(new C(foo:'bar'))\n", + }; + //@formatter:on + + runConformTest(sources, "baz"); + } + + @Test + public void testTypeChecked10741a() { + //@formatter:off + String[] sources = { + "Main.groovy", + "class C { final foo }\n" + + "@groovy.transform.TypeChecked\n" + + "void test(C pogo) {\n" + + " def proc = pogo.&setFoo\n" + + "}\n" + + "test(new C(foo:'bar'))\n", + }; + //@formatter:on + + runNegativeTest(sources, + "----------\n" + + "1. ERROR in Main.groovy (at line 4)\n" + + "\tdef proc = pogo.&setFoo\n" + + "\t ^^^^^^\n" + + "Groovy:[Static type checking] - Cannot find matching method C#setFoo. Please check if the declared type is correct and if the method exists.\n" + + "----------\n"); + } + + @Test + public void testTypeChecked10744() { + //@formatter:off + String[] sources = { + "Main.groovy", + "@groovy.transform.TypeChecked\n" + + "void test() {\n" + + " Serializable x = -1\n" + + " print(x.class.name)\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources, "java.lang.Integer"); + } + + @Test + public void testTypeChecked10749() { + for (String arg : !isParrotParser() ? new String[] {"Named.&getName", "{Named named -> named.getName()}"} + : new String[] {"Named.&getName", "{Named named -> named.getName()}", + "Named::getName", "(Named named) -> named.getName()"} + ) { + //@formatter:off + String[] sources = { + "Main.groovy", + "import groovy.transform.*\n" + + "class Named {String name}\n" + + "@TypeChecked void test(){\n" + + " @ASTTest(phase=INSTRUCTION_SELECTION, value={\n" + + " def type = node.getNodeMetaData(org.codehaus.groovy.transform.stc.StaticTypesMarker.INFERRED_TYPE)\n" + + " assert type.toString(false) == 'java.util.stream.Collector>>'\n" + + " })\n" + + " def c = java.util.stream.Collectors.groupingBy(" + arg + ")\n" + + "}\n" + + "test()\n", + }; + //@formatter:on + + runConformTest(sources); + } + } } diff --git a/base/org.codehaus.groovy40/.checkstyle b/base/org.codehaus.groovy40/.checkstyle index 4f23d7ea1b..c82f784a84 100644 --- a/base/org.codehaus.groovy40/.checkstyle +++ b/base/org.codehaus.groovy40/.checkstyle @@ -57,7 +57,6 @@ - diff --git a/base/org.codehaus.groovy40/.classpath b/base/org.codehaus.groovy40/.classpath index 9da9c01bc5..7bde30d4b8 100644 --- a/base/org.codehaus.groovy40/.classpath +++ b/base/org.codehaus.groovy40/.classpath @@ -11,14 +11,14 @@ - + - + - + - + diff --git a/base/org.codehaus.groovy40/META-INF/MANIFEST.MF b/base/org.codehaus.groovy40/META-INF/MANIFEST.MF index c1b5ac0660..bc0c6ed5e4 100644 --- a/base/org.codehaus.groovy40/META-INF/MANIFEST.MF +++ b/base/org.codehaus.groovy40/META-INF/MANIFEST.MF @@ -4,99 +4,99 @@ Bundle-SymbolicName: org.codehaus.groovy Automatic-Module-Name: org.codehaus.groovy Bundle-Name: Apache Groovy Bundle-Vendor: Pivotal Software, Inc. -Bundle-Version: 4.0.4.qualifier +Bundle-Version: 4.0.5.qualifier Bundle-ClassPath: eclipse-trace.jar, groovy-eclipse.jar, groovy-parser2.jar, lib/ivy-2.5.0.jar, - lib/groovy-4.0.4.jar, - lib/groovy-test-4.0.4.jar -Export-Package: groovy.beans;version="4.0.4", - groovy.cli;version="4.0.4", - groovy.grape;version="4.0.4", - groovy.inspect;version="4.0.4", - groovy.io;version="4.0.4", - groovy.lang;version="4.0.4", - groovy.lang.groovydoc;version="4.0.4", - groovy.mock.interceptor;version="4.0.4", - groovy.namespace;version="4.0.4", - groovy.security;version="4.0.4", - groovy.test;version="4.0.4", - groovy.time;version="4.0.4", - groovy.transform;version="4.0.4", - groovy.transform.builder;version="4.0.4", - groovy.transform.options;version="4.0.4", - groovy.transform.stc;version="4.0.4", - groovy.ui;version="4.0.4", - groovy.util;version="4.0.4", - groovy.util.logging;version="4.0.4", + lib/groovy-4.0.5.jar, + lib/groovy-test-4.0.5.jar +Export-Package: groovy.beans;version="4.0.5", + groovy.cli;version="4.0.5", + groovy.grape;version="4.0.5", + groovy.inspect;version="4.0.5", + groovy.io;version="4.0.5", + groovy.lang;version="4.0.5", + groovy.lang.groovydoc;version="4.0.5", + groovy.mock.interceptor;version="4.0.5", + groovy.namespace;version="4.0.5", + groovy.security;version="4.0.5", + groovy.test;version="4.0.5", + groovy.time;version="4.0.5", + groovy.transform;version="4.0.5", + groovy.transform.builder;version="4.0.5", + groovy.transform.options;version="4.0.5", + groovy.transform.stc;version="4.0.5", + groovy.ui;version="4.0.5", + groovy.util;version="4.0.5", + groovy.util.logging;version="4.0.5", groovyjarjarantlr;x-friends:="org.codehaus.groovy.eclipse.refactoring", - org.apache.groovy.antlr;version="4.0.4", - org.apache.groovy.ast.tools;version="4.0.4", - org.apache.groovy.io;version="4.0.4", - org.apache.groovy.lang;version="4.0.4", - org.apache.groovy.lang.annotation;version="4.0.4", - org.apache.groovy.metaclass;version="4.0.4", - org.apache.groovy.plugin;version="4.0.4", - org.apache.groovy.test;version="4.0.4", - org.apache.groovy.test.transform;version="4.0.4", - org.apache.groovy.util;version="4.0.4", - org.apache.groovy.util.concurrent;version="4.0.4", - org.codehaus.groovy;version="4.0.4", - org.codehaus.groovy.antlr;version="4.0.4", - org.codehaus.groovy.antlr.parser;version="4.0.4", - org.codehaus.groovy.ast;version="4.0.4", - org.codehaus.groovy.ast.builder;version="4.0.4", - org.codehaus.groovy.ast.decompiled;version="4.0.4", - org.codehaus.groovy.ast.expr;version="4.0.4", - org.codehaus.groovy.ast.stmt;version="4.0.4", - org.codehaus.groovy.ast.tools;version="4.0.4", - org.codehaus.groovy.classgen;version="4.0.4", - org.codehaus.groovy.classgen.asm;version="4.0.4", - org.codehaus.groovy.classgen.asm.indy;version="4.0.4", - org.codehaus.groovy.classgen.asm.indy.sc;version="4.0.4", - org.codehaus.groovy.classgen.asm.sc;version="4.0.4", - org.codehaus.groovy.classgen.asm.util;version="4.0.4", - org.codehaus.groovy.control;version="4.0.4", - org.codehaus.groovy.control.customizers;version="4.0.4", - org.codehaus.groovy.control.customizers.builder;version="4.0.4", - org.codehaus.groovy.control.io;version="4.0.4", - org.codehaus.groovy.control.messages;version="4.0.4", + org.apache.groovy.antlr;version="4.0.5", + org.apache.groovy.ast.tools;version="4.0.5", + org.apache.groovy.io;version="4.0.5", + org.apache.groovy.lang;version="4.0.5", + org.apache.groovy.lang.annotation;version="4.0.5", + org.apache.groovy.metaclass;version="4.0.5", + org.apache.groovy.plugin;version="4.0.5", + org.apache.groovy.test;version="4.0.5", + org.apache.groovy.test.transform;version="4.0.5", + org.apache.groovy.util;version="4.0.5", + org.apache.groovy.util.concurrent;version="4.0.5", + org.codehaus.groovy;version="4.0.5", + org.codehaus.groovy.antlr;version="4.0.5", + org.codehaus.groovy.antlr.parser;version="4.0.5", + org.codehaus.groovy.ast;version="4.0.5", + org.codehaus.groovy.ast.builder;version="4.0.5", + org.codehaus.groovy.ast.decompiled;version="4.0.5", + org.codehaus.groovy.ast.expr;version="4.0.5", + org.codehaus.groovy.ast.stmt;version="4.0.5", + org.codehaus.groovy.ast.tools;version="4.0.5", + org.codehaus.groovy.classgen;version="4.0.5", + org.codehaus.groovy.classgen.asm;version="4.0.5", + org.codehaus.groovy.classgen.asm.indy;version="4.0.5", + org.codehaus.groovy.classgen.asm.indy.sc;version="4.0.5", + org.codehaus.groovy.classgen.asm.sc;version="4.0.5", + org.codehaus.groovy.classgen.asm.util;version="4.0.5", + org.codehaus.groovy.control;version="4.0.5", + org.codehaus.groovy.control.customizers;version="4.0.5", + org.codehaus.groovy.control.customizers.builder;version="4.0.5", + org.codehaus.groovy.control.io;version="4.0.5", + org.codehaus.groovy.control.messages;version="4.0.5", org.codehaus.groovy.eclipse, - org.codehaus.groovy.reflection;version="4.0.4", - org.codehaus.groovy.reflection.android;version="4.0.4", - org.codehaus.groovy.reflection.stdclasses;version="4.0.4", - org.codehaus.groovy.reflection.v7;version="4.0.4", - org.codehaus.groovy.runtime;version="4.0.4", - org.codehaus.groovy.runtime.callsite;version="4.0.4", - org.codehaus.groovy.runtime.dgmimpl;version="4.0.4", - org.codehaus.groovy.runtime.dgmimpl.arrays;version="4.0.4", - org.codehaus.groovy.runtime.m12n;version="4.0.4", - org.codehaus.groovy.runtime.memoize;version="4.0.4", - org.codehaus.groovy.runtime.metaclass;version="4.0.4", - org.codehaus.groovy.runtime.powerassert;version="4.0.4", - org.codehaus.groovy.runtime.typehandling;version="4.0.4", - org.codehaus.groovy.runtime.wrappers;version="4.0.4", - org.codehaus.groovy.syntax;version="4.0.4", - org.codehaus.groovy.tools;version="4.0.4", - org.codehaus.groovy.tools.ast;version="4.0.4", - org.codehaus.groovy.tools.gse;version="4.0.4", - org.codehaus.groovy.tools.javac;version="4.0.4", - org.codehaus.groovy.tools.shell;version="4.0.4", - org.codehaus.groovy.tools.shell.util;version="4.0.4", - org.codehaus.groovy.transform;version="4.0.4", - org.codehaus.groovy.transform.sc;version="4.0.4", - org.codehaus.groovy.transform.sc.transformers;version="4.0.4", - org.codehaus.groovy.transform.stc;version="4.0.4", - org.codehaus.groovy.transform.tailrec;version="4.0.4", - org.codehaus.groovy.transform.trait;version="4.0.4", - org.codehaus.groovy.util;version="4.0.4", - org.codehaus.groovy.vmplugin;version="4.0.4", - org.codehaus.groovy.vmplugin.v7;version="4.0.4", - org.codehaus.groovy.vmplugin.v8;version="4.0.4", - org.codehaus.groovy.vmplugin.v9;version="4.0.4", - org.codehaus.groovy.vmplugin.v10;version="4.0.4", - org.codehaus.groovy.vmplugin.v16;version="4.0.4" + org.codehaus.groovy.reflection;version="4.0.5", + org.codehaus.groovy.reflection.android;version="4.0.5", + org.codehaus.groovy.reflection.stdclasses;version="4.0.5", + org.codehaus.groovy.reflection.v7;version="4.0.5", + org.codehaus.groovy.runtime;version="4.0.5", + org.codehaus.groovy.runtime.callsite;version="4.0.5", + org.codehaus.groovy.runtime.dgmimpl;version="4.0.5", + org.codehaus.groovy.runtime.dgmimpl.arrays;version="4.0.5", + org.codehaus.groovy.runtime.m12n;version="4.0.5", + org.codehaus.groovy.runtime.memoize;version="4.0.5", + org.codehaus.groovy.runtime.metaclass;version="4.0.5", + org.codehaus.groovy.runtime.powerassert;version="4.0.5", + org.codehaus.groovy.runtime.typehandling;version="4.0.5", + org.codehaus.groovy.runtime.wrappers;version="4.0.5", + org.codehaus.groovy.syntax;version="4.0.5", + org.codehaus.groovy.tools;version="4.0.5", + org.codehaus.groovy.tools.ast;version="4.0.5", + org.codehaus.groovy.tools.gse;version="4.0.5", + org.codehaus.groovy.tools.javac;version="4.0.5", + org.codehaus.groovy.tools.shell;version="4.0.5", + org.codehaus.groovy.tools.shell.util;version="4.0.5", + org.codehaus.groovy.transform;version="4.0.5", + org.codehaus.groovy.transform.sc;version="4.0.5", + org.codehaus.groovy.transform.sc.transformers;version="4.0.5", + org.codehaus.groovy.transform.stc;version="4.0.5", + org.codehaus.groovy.transform.tailrec;version="4.0.5", + org.codehaus.groovy.transform.trait;version="4.0.5", + org.codehaus.groovy.util;version="4.0.5", + org.codehaus.groovy.vmplugin;version="4.0.5", + org.codehaus.groovy.vmplugin.v7;version="4.0.5", + org.codehaus.groovy.vmplugin.v8;version="4.0.5", + org.codehaus.groovy.vmplugin.v9;version="4.0.5", + org.codehaus.groovy.vmplugin.v10;version="4.0.5", + org.codehaus.groovy.vmplugin.v16;version="4.0.5" Require-Bundle: org.eclipse.core.runtime, org.junit;resolution:=optional Bundle-RequiredExecutionEnvironment: JavaSE-1.8 diff --git a/base/org.codehaus.groovy40/VERSION b/base/org.codehaus.groovy40/VERSION index 26cdcc6e16..1a9290911d 100644 --- a/base/org.codehaus.groovy40/VERSION +++ b/base/org.codehaus.groovy40/VERSION @@ -9,3 +9,4 @@ 2022-04-19: GROOVY_4_0_2 2022-05-30: GROOVY_4_0_3 2022-07-20: GROOVY_4_0_4 +2022-09-07: GROOVY_4_0_5 diff --git a/base/org.codehaus.groovy40/about.html b/base/org.codehaus.groovy40/about.html index 89e42b3733..e48be1284c 100644 --- a/base/org.codehaus.groovy40/about.html +++ b/base/org.codehaus.groovy40/about.html @@ -24,12 +24,12 @@

License

Third Party Content

-

groovy-4.0.4.jar

-

groovy-test-4.0.4.jar

+

groovy-4.0.5.jar

+

groovy-test-4.0.5.jar

-

groovy-console-4.0.4.jar

-

groovy-groovysh-4.0.4.jar

-

groovy-swing-4.0.4.jar

-

groovy-templates-4.0.4.jar

-

groovy-xml-4.0.4.jar

-

javaparser-core-3.24.2.jar

+

groovy-console-4.0.5.jar

+

groovy-groovysh-4.0.5.jar

+

groovy-swing-4.0.5.jar

+

groovy-templates-4.0.5.jar

+

groovy-xml-4.0.5.jar

+

javaparser-core-3.24.4.jar

    -
  • Obtained from: https://dist.apache.org/repos/dist/release/groovy/4.0.4/distribution/apache-groovy-binary-4.0.4.zip
  • +
  • Obtained from: https://dist.apache.org/repos/dist/release/groovy/4.0.5/distribution/apache-groovy-binary-4.0.5.zip
  • License kind: ASL
  • License URL: https://www.apache.org/licenses/LICENSE-2.0.html
  • License text: asl2-license.txt
  • @@ -62,7 +62,7 @@

    javaparser-core-3.24.2.jar

    jline-2.14.6.jar

      -
    • Obtained from: https://dist.apache.org/repos/dist/release/groovy/4.0.4/distribution/apache-groovy-binary-4.0.4.zip
    • +
    • Obtained from: https://dist.apache.org/repos/dist/release/groovy/4.0.5/distribution/apache-groovy-binary-4.0.5.zip
    • License kind: BSD
    • License URL: https://www.opensource.org/licenses/bsd-license.php
    • License text: jline2-license.txt
    • diff --git a/base/org.codehaus.groovy40/lib/console/groovy-console-4.0.4.jar b/base/org.codehaus.groovy40/lib/console/groovy-console-4.0.5.jar similarity index 75% rename from base/org.codehaus.groovy40/lib/console/groovy-console-4.0.4.jar rename to base/org.codehaus.groovy40/lib/console/groovy-console-4.0.5.jar index a997af7461..1ced5ed44e 100644 Binary files a/base/org.codehaus.groovy40/lib/console/groovy-console-4.0.4.jar and b/base/org.codehaus.groovy40/lib/console/groovy-console-4.0.5.jar differ diff --git a/base/org.codehaus.groovy40/lib/console/groovy-swing-4.0.4.jar b/base/org.codehaus.groovy40/lib/console/groovy-swing-4.0.5.jar similarity index 82% rename from base/org.codehaus.groovy40/lib/console/groovy-swing-4.0.4.jar rename to base/org.codehaus.groovy40/lib/console/groovy-swing-4.0.5.jar index 0d38b0cc37..325374b2e9 100644 Binary files a/base/org.codehaus.groovy40/lib/console/groovy-swing-4.0.4.jar and b/base/org.codehaus.groovy40/lib/console/groovy-swing-4.0.5.jar differ diff --git a/base/org.codehaus.groovy40/lib/console/groovy-templates-4.0.4.jar b/base/org.codehaus.groovy40/lib/console/groovy-templates-4.0.5.jar similarity index 67% rename from base/org.codehaus.groovy40/lib/console/groovy-templates-4.0.4.jar rename to base/org.codehaus.groovy40/lib/console/groovy-templates-4.0.5.jar index 04bef4b83c..ad1090ea8a 100644 Binary files a/base/org.codehaus.groovy40/lib/console/groovy-templates-4.0.4.jar and b/base/org.codehaus.groovy40/lib/console/groovy-templates-4.0.5.jar differ diff --git a/base/org.codehaus.groovy40/lib/console/groovy-xml-4.0.4.jar b/base/org.codehaus.groovy40/lib/console/groovy-xml-4.0.5.jar similarity index 95% rename from base/org.codehaus.groovy40/lib/console/groovy-xml-4.0.4.jar rename to base/org.codehaus.groovy40/lib/console/groovy-xml-4.0.5.jar index be7427a0d6..bc2e7ab7ca 100644 Binary files a/base/org.codehaus.groovy40/lib/console/groovy-xml-4.0.4.jar and b/base/org.codehaus.groovy40/lib/console/groovy-xml-4.0.5.jar differ diff --git a/base/org.codehaus.groovy40/lib/console/javaparser-core-3.24.2.jar b/base/org.codehaus.groovy40/lib/console/javaparser-core-3.24.4.jar similarity index 80% rename from base/org.codehaus.groovy40/lib/console/javaparser-core-3.24.2.jar rename to base/org.codehaus.groovy40/lib/console/javaparser-core-3.24.4.jar index 2240501b0f..b160424296 100644 Binary files a/base/org.codehaus.groovy40/lib/console/javaparser-core-3.24.2.jar and b/base/org.codehaus.groovy40/lib/console/javaparser-core-3.24.4.jar differ diff --git a/base/org.codehaus.groovy40/lib/groovy-4.0.4-javadoc.jar b/base/org.codehaus.groovy40/lib/groovy-4.0.5-javadoc.jar similarity index 53% rename from base/org.codehaus.groovy40/lib/groovy-4.0.4-javadoc.jar rename to base/org.codehaus.groovy40/lib/groovy-4.0.5-javadoc.jar index e4f2afd5d9..7c692f462b 100644 Binary files a/base/org.codehaus.groovy40/lib/groovy-4.0.4-javadoc.jar and b/base/org.codehaus.groovy40/lib/groovy-4.0.5-javadoc.jar differ diff --git a/base/org.codehaus.groovy40/lib/groovy-4.0.4-sources.jar b/base/org.codehaus.groovy40/lib/groovy-4.0.5-sources.jar similarity index 80% rename from base/org.codehaus.groovy40/lib/groovy-4.0.4-sources.jar rename to base/org.codehaus.groovy40/lib/groovy-4.0.5-sources.jar index 7282c167f2..0311dada34 100644 Binary files a/base/org.codehaus.groovy40/lib/groovy-4.0.4-sources.jar and b/base/org.codehaus.groovy40/lib/groovy-4.0.5-sources.jar differ diff --git a/base/org.codehaus.groovy40/lib/groovy-4.0.4.jar b/base/org.codehaus.groovy40/lib/groovy-4.0.5.jar similarity index 86% rename from base/org.codehaus.groovy40/lib/groovy-4.0.4.jar rename to base/org.codehaus.groovy40/lib/groovy-4.0.5.jar index 43c31b94e7..62d9ff6fc2 100644 Binary files a/base/org.codehaus.groovy40/lib/groovy-4.0.4.jar and b/base/org.codehaus.groovy40/lib/groovy-4.0.5.jar differ diff --git a/base/org.codehaus.groovy40/lib/groovy-test-4.0.4-javadoc.jar b/base/org.codehaus.groovy40/lib/groovy-test-4.0.5-javadoc.jar similarity index 71% rename from base/org.codehaus.groovy40/lib/groovy-test-4.0.4-javadoc.jar rename to base/org.codehaus.groovy40/lib/groovy-test-4.0.5-javadoc.jar index e30eb8ddd9..0cd8825991 100644 Binary files a/base/org.codehaus.groovy40/lib/groovy-test-4.0.4-javadoc.jar and b/base/org.codehaus.groovy40/lib/groovy-test-4.0.5-javadoc.jar differ diff --git a/base/org.codehaus.groovy40/lib/groovy-test-4.0.4-sources.jar b/base/org.codehaus.groovy40/lib/groovy-test-4.0.5-sources.jar similarity index 93% rename from base/org.codehaus.groovy40/lib/groovy-test-4.0.4-sources.jar rename to base/org.codehaus.groovy40/lib/groovy-test-4.0.5-sources.jar index b5be1cb7ac..6a63d1d286 100644 Binary files a/base/org.codehaus.groovy40/lib/groovy-test-4.0.4-sources.jar and b/base/org.codehaus.groovy40/lib/groovy-test-4.0.5-sources.jar differ diff --git a/base/org.codehaus.groovy40/lib/groovy-test-4.0.4.jar b/base/org.codehaus.groovy40/lib/groovy-test-4.0.5.jar similarity index 94% rename from base/org.codehaus.groovy40/lib/groovy-test-4.0.4.jar rename to base/org.codehaus.groovy40/lib/groovy-test-4.0.5.jar index 7cacb32dc8..9354a52fe6 100644 Binary files a/base/org.codehaus.groovy40/lib/groovy-test-4.0.4.jar and b/base/org.codehaus.groovy40/lib/groovy-test-4.0.5.jar differ diff --git a/base/org.codehaus.groovy40/lib/shell/groovy-groovysh-4.0.4.jar b/base/org.codehaus.groovy40/lib/shell/groovy-groovysh-4.0.5.jar similarity index 91% rename from base/org.codehaus.groovy40/lib/shell/groovy-groovysh-4.0.4.jar rename to base/org.codehaus.groovy40/lib/shell/groovy-groovysh-4.0.5.jar index 62d602e204..d5284b6148 100644 Binary files a/base/org.codehaus.groovy40/lib/shell/groovy-groovysh-4.0.4.jar and b/base/org.codehaus.groovy40/lib/shell/groovy-groovysh-4.0.5.jar differ diff --git a/base/org.codehaus.groovy40/pom.xml b/base/org.codehaus.groovy40/pom.xml index d22e59274d..d6b883bd46 100644 --- a/base/org.codehaus.groovy40/pom.xml +++ b/base/org.codehaus.groovy40/pom.xml @@ -8,7 +8,7 @@ org.codehaus.groovy.eclipse org.codehaus.groovy - 4.0.4-SNAPSHOT + 4.0.5-SNAPSHOT eclipse-plugin diff --git a/base/org.codehaus.groovy40/src/groovy/grape/GrapeIvy.groovy b/base/org.codehaus.groovy40/src/groovy/grape/GrapeIvy.groovy index 45a8b09788..554bd00028 100644 --- a/base/org.codehaus.groovy40/src/groovy/grape/GrapeIvy.groovy +++ b/base/org.codehaus.groovy40/src/groovy/grape/GrapeIvy.groovy @@ -202,6 +202,30 @@ class GrapeIvy implements GrapeEngine { throw new RuntimeException('grab requires at least a module: or artifactId: or artifact: argument') } + // check for malformed components of the coordinates + dep.each { k, v -> + if (v instanceof CharSequence) { + if (k.toString().contains('v')) { // revision, version, rev + if (!(v ==~ '[^\\/:"<>|]*')) { + throw new RuntimeException("Grab: invalid value of '$v' for $k: should not contain any of / \\ : \" < > |") + } + } else { + if (!(v ==~ '[-._a-zA-Z0-9]*')) { + throw new RuntimeException("Grab: invalid value of '$v' for $k: should only contain - . _ a-z A-Z 0-9") + } + } + } + } + + // check for mutually exclusive arguments + Set keys = (Set) dep.keySet() + keys.each { key -> + Set badArgs = MUTUALLY_EXCLUSIVE_KEYS[key] + if (badArgs && !badArgs.disjoint(keys)) { + throw new RuntimeException("Grab: mutually exclusive arguments: ${keys.intersect(badArgs) + key}") + } + } + String groupId = dep.group ?: dep.groupId ?: dep.organisation ?: dep.organization ?: dep.org ?: '' // TODO: accept ranges and decode them? except '1.0.0'..<'2.0.0' won't work in groovy String version = dep.version ?: dep.revision ?: dep.rev ?: '*' @@ -511,11 +535,11 @@ class GrapeIvy implements GrapeEngine { if (report.getDownloadSize() && reportDownloads) { System.err.println("Downloaded ${report.getDownloadSize() >> 10} Kbytes in ${report.getDownloadTime()}ms:\n ${report.getAllArtifactsReports()*.toString().join('\n ')}") } - md = report.getModuleDescriptor() if (!args.preserveFiles) { - cacheManager.getResolvedIvyFileInCache(md.getModuleRevisionId()).delete() - cacheManager.getResolvedIvyPropertiesInCache(md.getModuleRevisionId()).delete() + def revision = report.getModuleDescriptor().getModuleRevisionId() + cacheManager.getResolvedIvyPropertiesInCache(revision).delete() + cacheManager.getResolvedIvyFileInCache(revision).delete() } report @@ -648,15 +672,6 @@ class GrapeIvy implements GrapeEngine { } URI[] resolve(ClassLoader loader, Map args, List depsInfo, Map... dependencies) { - // check for mutually exclusive arguments - Set keys = (Set) args.keySet() - keys.each { key -> - Set badArgs = MUTUALLY_EXCLUSIVE_KEYS[key] - if (badArgs && !badArgs.disjoint(keys)) { - throw new RuntimeException("Mutually exclusive arguments passed into grab: ${keys.intersect(badArgs) + key}") - } - } - // check the kill switch if (!enableGrapes) { return new URI[0] diff --git a/base/org.codehaus.groovy40/src/org/apache/groovy/parser/antlr4/AstBuilder.java b/base/org.codehaus.groovy40/src/org/apache/groovy/parser/antlr4/AstBuilder.java index 0b25d719f7..1fb76bed56 100644 --- a/base/org.codehaus.groovy40/src/org/apache/groovy/parser/antlr4/AstBuilder.java +++ b/base/org.codehaus.groovy40/src/org/apache/groovy/parser/antlr4/AstBuilder.java @@ -4602,9 +4602,7 @@ public GenericsType visitTypeArgument(final TypeArgumentContext ctx) { if (!asBoolean(ctx.type())) { GenericsType genericsType = new GenericsType(baseType); genericsType.setWildcard(true); - /* GRECLIPSE edit -- already set - genericsType.setName(QUESTION_STR); - */ + return configureAST(genericsType, ctx); } @@ -4623,11 +4621,7 @@ public GenericsType visitTypeArgument(final TypeArgumentContext ctx) { return configureAST(genericsType, ctx); } else if (asBoolean(ctx.type())) { - /* GRECLIPSE edit - ClassNode baseType = configureAST(this.visitType(ctx.type()), ctx); - */ ClassNode baseType = this.visitType(ctx.type()); - // GRECLIPSE end return configureAST(this.createGenericsType(baseType), ctx); } diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/ClassNode.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/ClassNode.java index 859fb1d53d..725f3748fa 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/ClassNode.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/ClassNode.java @@ -100,7 +100,7 @@ * through a Class instance *
    • Labels:
      * ClassNodes created through ClassHelper.makeWithoutCaching. They - * are place holders, its redirect points to the real structure, which can + * are placeholders, its redirect points to the real structure, which can * be a label too, but following all redirects it should end with a ClassNode * from one of the other two categories. If ResolveVisitor finds such a * node, it tries to set the redirects. Any such label created after diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/GenericsType.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/GenericsType.java index 443a883cb7..6d89411599 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/GenericsType.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/GenericsType.java @@ -132,7 +132,9 @@ private static StringBuilder appendName(final ClassNode theType, final StringBui if (Modifier.isStatic(theType.getModifiers()) || theType.isInterface()) { sb.append(parentClassNodeName); } else { - sb.append(genericsBounds(theType.getOuterClass(), new HashSet<>())); + ClassNode outerClass = theType.getNodeMetaData("outer.class"); + if (outerClass == null) outerClass = theType.getOuterClass(); + sb.append(genericsBounds(outerClass, new HashSet<>())); } sb.append('.'); sb.append(theType.getName(), parentClassNodeName.length() + 1, theType.getName().length()); diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/ImportNode.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/ImportNode.java index 4699a2c061..90b6688185 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/ImportNode.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/ImportNode.java @@ -93,6 +93,31 @@ public ImportNode(final ClassNode type, final String fieldName, final String ali this.fieldName = requireNonNull(fieldName); } + // GRECLIPSE add + private Expression aliasExpr; + private Expression fieldNameExpr; + + public Expression getAliasExpr() { + return aliasExpr; + } + + public void setAliasExpr(Expression aliasExpr) { + this.aliasExpr = aliasExpr; + } + + public Expression getFieldNameExpr() { + return fieldNameExpr; + } + + public void setFieldNameExpr(Expression fieldNameExpr) { + this.fieldNameExpr = fieldNameExpr; + } + + public String toString() { + return super.toString() + '[' + getText() + ']'; + } + // GRECLIPSE end + /** * @return the text display of this import */ @@ -149,31 +174,6 @@ public void setType(final ClassNode type) { this.type = requireNonNull(type); } - // GRECLIPSE add - private Expression aliasExpr; - private Expression fieldNameExpr; - - public Expression getAliasExpr() { - return aliasExpr; - } - - public void setAliasExpr(Expression aliasExpr) { - this.aliasExpr = aliasExpr; - } - - public Expression getFieldNameExpr() { - return fieldNameExpr; - } - - public void setFieldNameExpr(Expression fieldNameExpr) { - this.fieldNameExpr = fieldNameExpr; - } - - public String toString() { - return super.toString() + '[' + getText() + ']'; - } - // GRECLIPSE end - @Override public void visit(final GroovyCodeVisitor visitor) { } diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/Parameter.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/Parameter.java index 1853b794bb..71d79ae294 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/Parameter.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/Parameter.java @@ -59,7 +59,7 @@ public Parameter(ClassNode type, String name, Expression defaultValue) { @Override public String toString() { - return super.toString() + "[name:" + name + ((type == null) ? "" : " type: " + type.getName()) + ", hasDefaultValue: " + this.hasInitialExpression() + "]"; + return super.toString() + "[name: " + name + (type == null ? "" : ", type: " + type.toString(false)) + ", hasDefaultValue: " + this.hasInitialExpression() + "]"; } @Override diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/tools/GenericsUtils.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/tools/GenericsUtils.java index 413732cee4..c713ecdcd4 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/tools/GenericsUtils.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/ast/tools/GenericsUtils.java @@ -495,9 +495,9 @@ public static Map createGenericsSpec(final ClassNode type, fi // class C extends A { } // the type "A -> A" will produce [X:Number,Y:Object,Z:String] + ClassNode oc = type.getNodeMetaData("outer.class"); // GROOVY-10646: outer class type parameters + Map newSpec = oc != null ? createGenericsSpec(oc, oldSpec) : new HashMap<>(); GenericsType[] gt = type.getGenericsTypes(), rgt = type.redirect().getGenericsTypes(); - - Map newSpec = new HashMap<>(); if (gt != null && rgt != null) { for (int i = 0, n = gt.length; i < n; i += 1) { newSpec.put(rgt[i].getName(), correctToGenericsSpec(oldSpec, gt[i])); diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java index 2e3f502c58..e51f92b01e 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java @@ -74,6 +74,7 @@ import static org.codehaus.groovy.ast.ClassHelper.isBigIntegerType; import static org.codehaus.groovy.ast.ClassHelper.isClassType; import static org.codehaus.groovy.ast.ClassHelper.isGeneratedFunction; +import static org.codehaus.groovy.ast.ClassHelper.isObjectType; import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean; import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; import static org.codehaus.groovy.ast.ClassHelper.isStringType; @@ -92,7 +93,6 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.chooseBestMethod; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments; -import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType; import static groovyjarjarasm.asm.Opcodes.AALOAD; import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC; @@ -622,7 +622,7 @@ public void makeSingleArgumentCall(final Expression receiver, final String messa "this error and file a bug report at https://issues.apache.org/jira/browse/GROOVY"); } - private boolean trySubscript(final Expression receiver, final String message, final Expression arguments, ClassNode rType, final ClassNode aType, boolean safe) { + private boolean trySubscript(final Expression receiver, final String message, final Expression arguments, final ClassNode rType, final ClassNode aType, final boolean safe) { if (getWrapper(rType).isDerivedFrom(Number_TYPE) && getWrapper(aType).isDerivedFrom(Number_TYPE)) { if ("plus".equals(message) || "minus".equals(message) || "multiply".equals(message) || "div".equals(message)) { @@ -645,9 +645,9 @@ && getWrapper(aType).isDerivedFrom(Number_TYPE)) { return true; } else { // check if a getAt method can be found on the receiver - ClassNode current = rType; + ClassNode current = isClassClassNodeWrappingConcreteType(rType) ? rType.getGenericsTypes()[0].getType() : rType; MethodNode getAtNode = null; - while (current != null && getAtNode == null) { + while (current != null && !isObjectType(current) && getAtNode == null) { getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(aType, "index")}); if (getAtNode == null) { getAtNode = getCompatibleMethod(current, "getAt", aType); @@ -675,18 +675,10 @@ && getWrapper(aType).isDerivedFrom(Number_TYPE)) { return true; } - // make sure Map#getAt() and List#getAt handled with the bracket syntax are properly compiled + // make sure Map#getAt and List#getAt handled with the bracket syntax are properly compiled ClassNode[] args = {aType}; - boolean acceptAnyMethod = - MAP_TYPE.equals(rType) || rType.implementsInterface(MAP_TYPE) - || LIST_TYPE.equals(rType) || rType.implementsInterface(LIST_TYPE); List nodes = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), rType, message, args); - if (nodes.isEmpty()) { - // retry with raw types - rType = rType.getPlainNodeReference(); - nodes = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), rType, message, args); - } - if (nodes.size() == 1 || (nodes.size() > 1 && acceptAnyMethod)) { + if (nodes.size() == 1 || (nodes.size() > 1 && (isOrImplements(rType, MAP_TYPE) || isOrImplements(rType, LIST_TYPE)))) { MethodCallExpression call = callX(receiver, message, arguments); call.setImplicitThis(false); call.setMethodTarget(nodes.get(0)); @@ -695,7 +687,7 @@ && getWrapper(aType).isDerivedFrom(Number_TYPE)) { call.visit(controller.getAcg()); return true; } - if (implementsInterfaceOrIsSubclassOf(rType, MAP_TYPE)) { + if (isOrImplements(rType, MAP_TYPE)) { // fallback to Map#get MethodCallExpression call = callX(receiver, "get", arguments); call.setImplicitThis(false); diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java index e026e2b986..d5e059a0e5 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesMethodReferenceExpressionWriter.java @@ -52,12 +52,14 @@ import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX; import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS; import static org.codehaus.groovy.ast.tools.GeneralUtils.varX; +import static org.codehaus.groovy.ast.tools.GenericsUtils.extractPlaceholders; import static org.codehaus.groovy.ast.tools.ParameterUtils.isVargs; import static org.codehaus.groovy.ast.tools.ParameterUtils.parametersCompatible; import static org.codehaus.groovy.runtime.DefaultGroovyMethods.last; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.filterMethodsByVisibility; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsForClassNode; import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isAssignableTo; +import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.resolveClassNodeGenerics; /** * Generates bytecode for method reference expressions in statically-compiled code. @@ -72,17 +74,14 @@ public StaticTypesMethodReferenceExpressionWriter(final WriterController control @Override public void writeMethodReferenceExpression(final MethodReferenceExpression methodReferenceExpression) { - ClassNode functionalInterfaceType = getFunctionalInterfaceType(methodReferenceExpression); - if (!ClassHelper.isFunctionalInterface(functionalInterfaceType)) { - // generate the default bytecode; most likely a method closure + ClassNode functionalInterfaceType = getFunctionalInterfaceType(methodReferenceExpression); + MethodNode abstractMethod = ClassHelper.findSAM(functionalInterfaceType); + if (abstractMethod == null || !functionalInterfaceType.isInterface()) { + // generate the default bytecode -- most likely a method closure super.writeMethodReferenceExpression(methodReferenceExpression); return; } - ClassNode redirect = functionalInterfaceType.redirect(); - MethodNode abstractMethod = ClassHelper.findSAM(redirect); - String abstractMethodDesc = createMethodDescriptor(abstractMethod); - ClassNode classNode = controller.getClassNode(); Expression typeOrTargetRef = methodReferenceExpression.getExpression(); boolean isClassExpression = (typeOrTargetRef instanceof ClassExpression); @@ -104,7 +103,8 @@ public void writeMethodReferenceExpression(final MethodReferenceExpression metho methodRefMethod = findMethodRefMethod(methodRefName, parametersWithExactType, typeOrTargetRef, typeOrTargetRefType); } - validate(methodReferenceExpression, typeOrTargetRef, typeOrTargetRefType, methodRefName, parametersWithExactType, methodRefMethod); + validate(methodReferenceExpression, typeOrTargetRefType, methodRefName, methodRefMethod, parametersWithExactType, + resolveClassNodeGenerics(extractPlaceholders(functionalInterfaceType), null, abstractMethod.getReturnType())); if (isExtensionMethod(methodRefMethod)) { ExtensionMethodNode extensionMethodNode = (ExtensionMethodNode) methodRefMethod; @@ -169,7 +169,7 @@ public void writeMethodReferenceExpression(final MethodReferenceExpression metho try { Handle bootstrapMethod = createBootstrapMethod(classNode.isInterface(), false); Object[] bootstrapArgs = createBootstrapMethodArguments( - abstractMethodDesc, + createMethodDescriptor(abstractMethod), referenceKind, methodRefMethod.getDeclaringClass(), methodRefMethod, @@ -181,25 +181,21 @@ public void writeMethodReferenceExpression(final MethodReferenceExpression metho } if (isClassExpression) { - controller.getOperandStack().push(redirect); + controller.getOperandStack().push(functionalInterfaceType); } else { - controller.getOperandStack().replace(redirect, 1); + controller.getOperandStack().replace(functionalInterfaceType, 1); } } - private void validate(final MethodReferenceExpression methodReferenceExpression, final Expression typeOrTargetRef, final ClassNode typeOrTargetRefType, final String methodRefName, final Parameter[] parametersWithExactType, final MethodNode methodRefMethod) { - if (methodRefMethod == null) { - addFatalError("Failed to find the expected method[" - + methodRefName + "(" - + Arrays.stream(parametersWithExactType) - .map(e -> e.getType().getText()) - .collect(Collectors.joining(",")) - + ")] in the type[" + typeOrTargetRefType.getText() + "]", methodReferenceExpression); - } else if (parametersWithExactType.length > 0 && isTypeReferringInstanceMethod(typeOrTargetRef, methodRefMethod)) { - ClassNode firstParameterType = parametersWithExactType[0].getType(); - if (!isAssignableTo(firstParameterType, typeOrTargetRefType)) { - throw new RuntimeParserException("Invalid receiver type: " + firstParameterType.getText() + " is not compatible with " + typeOrTargetRefType.getText(), typeOrTargetRef); - } + private void validate(final MethodReferenceExpression methodReference, final ClassNode targetType, final String methodName, final MethodNode methodNode, final Parameter[] samParameters, final ClassNode samReturnType) { + if (methodNode == null) { + String error = String.format("Failed to find the expected method[%s(%s)] in the type[%s]", + methodName, Arrays.stream(samParameters).map(e -> e.getType().getText()).collect(Collectors.joining(",")), targetType.getText()); + addFatalError(error, methodReference); + } else if (methodNode.isVoidMethod() && !ClassHelper.isPrimitiveVoid(samReturnType)) { + addFatalError("Invalid return type: void is not convertible to " + samReturnType.getText(), methodReference); + } else if (samParameters.length > 0 && isTypeReferringInstanceMethod(methodReference.getExpression(), methodNode) && !isAssignableTo(samParameters[0].getType(), targetType)) { + throw new RuntimeParserException("Invalid receiver type: " + samParameters[0].getType().getText() + " is not compatible with " + targetType.getText(), methodReference.getExpression()); } } diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/control/SourceUnit.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/control/SourceUnit.java index e4e4ecef61..bc21a4b2af 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/control/SourceUnit.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/control/SourceUnit.java @@ -307,11 +307,11 @@ public String getSample(int line, int column, Janitor janitor) { if (column > 40) { int start = column - 30 - 1; - int end = (column + 10 > text.length() ? text.length() : column + 10 - 1); - if (start >= text.length() || end < start) + int length = text.length(); + int end = (column + 10 > length ? length : column + 10 - 1); + if (start >= length || end < start) return null; // can happen with CR only files GROOVY-10676 - sample = " " + text.substring(start, end) + Utilities.eol() + " " + - marker.substring(start); + sample = " " + text.substring(start, end) + Utilities.eol() + " " + marker.substring(start); } else { sample = " " + text + Utilities.eol() + " " + marker; } diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java index 92b15352da..23acf6bec9 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/TupleConstructorASTTransformation.java @@ -275,13 +275,13 @@ private static void createConstructor(final AbstractASTTransformation xform, fin boolean hasMapCons = AnnotatedNodeUtils.hasAnnotation(cNode, MapConstructorASTTransformation.MY_TYPE); int modifiers = getVisibility(anno, cNode, ConstructorNode.class, groovyjarjarasm.asm.Opcodes.ACC_PUBLIC); - ConstructorNode ctorNode = addGeneratedConstructor(cNode, modifiers, params.toArray(Parameter.EMPTY_ARRAY), ClassNode.EMPTY_ARRAY, body); + ConstructorNode consNode = addGeneratedConstructor(cNode, modifiers, params.toArray(Parameter.EMPTY_ARRAY), ClassNode.EMPTY_ARRAY, body); // GRECLIPSE add -- apply compact constructor source position to primary constructor - java.util.Optional.ofNullable(cNode.getNodeMetaData("compact.constructor")).ifPresent(ctorNode::setSourcePosition); + java.util.Optional.ofNullable(cNode.getNodeMetaData("compact.constructor")).ifPresent(consNode::setSourcePosition); // GRECLIPSE end if (cNode.getNodeMetaData("_RECORD_HEADER") != null) { - ctorNode.addAnnotations(cNode.getAnnotations()); - ctorNode.putNodeMetaData("_SKIPPABLE_ANNOTATIONS", Boolean.TRUE); + consNode.addAnnotations(cNode.getAnnotations()); + consNode.putNodeMetaData("_SKIPPABLE_ANNOTATIONS", Boolean.TRUE); } if (namedVariant) { BlockStatement inner = new BlockStatement(); @@ -292,9 +292,9 @@ private static void createConstructor(final AbstractASTTransformation xform, fin List propNames = new ArrayList<>(); Map seen = new HashMap<>(); for (Parameter p : params) { - if (!processImplicitNamedParam(xform, ctorNode, mapParam, inner, args, propNames, p, false, seen)) return; + if (!processImplicitNamedParam(xform, consNode, mapParam, inner, args, propNames, p, false, seen)) return; } - NamedVariantASTTransformation.createMapVariant(xform, ctorNode, anno, mapParam, genParams, cNode, inner, args, propNames); + NamedVariantASTTransformation.createMapVariant(xform, consNode, anno, mapParam, genParams, cNode, inner, args, propNames); } if (sourceUnit != null && !body.isEmpty()) { diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java deleted file mode 100644 index 727a3afff3..0000000000 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingSupport.java +++ /dev/null @@ -1,2390 +0,0 @@ -/* - * 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 org.codehaus.groovy.transform.stc; - -import org.apache.groovy.util.Maps; -import org.codehaus.groovy.GroovyBugError; -import org.codehaus.groovy.ast.ClassNode; -import org.codehaus.groovy.ast.ConstructorNode; -import org.codehaus.groovy.ast.GenericsType; -import org.codehaus.groovy.ast.GenericsType.GenericsTypeName; -import org.codehaus.groovy.ast.InnerClassNode; -import org.codehaus.groovy.ast.MethodNode; -import org.codehaus.groovy.ast.Parameter; -import org.codehaus.groovy.ast.Variable; -import org.codehaus.groovy.ast.expr.ArgumentListExpression; -import org.codehaus.groovy.ast.expr.ArrayExpression; -import org.codehaus.groovy.ast.expr.BinaryExpression; -import org.codehaus.groovy.ast.expr.CastExpression; -import org.codehaus.groovy.ast.expr.ClosureExpression; -import org.codehaus.groovy.ast.expr.ConstantExpression; -import org.codehaus.groovy.ast.expr.Expression; -import org.codehaus.groovy.ast.expr.ListExpression; -import org.codehaus.groovy.ast.expr.MapExpression; -import org.codehaus.groovy.ast.expr.VariableExpression; -import org.codehaus.groovy.ast.stmt.ReturnStatement; -import org.codehaus.groovy.ast.tools.GeneralUtils; -import org.codehaus.groovy.ast.tools.GenericsUtils; -import org.codehaus.groovy.ast.tools.ParameterUtils; -import org.codehaus.groovy.ast.tools.WideningCategories; -import org.codehaus.groovy.control.CompilationUnit; -import org.codehaus.groovy.control.CompilerConfiguration; -import org.codehaus.groovy.control.Phases; -import org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl; -import org.codehaus.groovy.syntax.Types; -import org.codehaus.groovy.tools.GroovyClass; -import org.codehaus.groovy.transform.trait.Traits; -import groovyjarjarasm.asm.Opcodes; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.StringJoiner; -import java.util.TreeSet; -import java.util.UUID; -import java.util.regex.Matcher; -import java.util.stream.BaseStream; - -import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod; -import static org.apache.groovy.ast.tools.ClassNodeUtils.samePackageName; -import static org.apache.groovy.ast.tools.ExpressionUtils.isNullConstant; -import static org.codehaus.groovy.ast.ClassHelper.BigInteger_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.Byte_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.CLASS_Type; -import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.COLLECTION_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.Character_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.DEPRECATED_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.Double_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.Enum_Type; -import static org.codehaus.groovy.ast.ClassHelper.Float_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.GSTRING_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.Integer_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.Long_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.Number_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.Short_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.byte_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.char_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.double_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.findSAM; -import static org.codehaus.groovy.ast.ClassHelper.float_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.getNextSuperClass; -import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper; -import static org.codehaus.groovy.ast.ClassHelper.getWrapper; -import static org.codehaus.groovy.ast.ClassHelper.int_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.isBigDecimalType; -import static org.codehaus.groovy.ast.ClassHelper.isBigIntegerType; -import static org.codehaus.groovy.ast.ClassHelper.isClassType; -import static org.codehaus.groovy.ast.ClassHelper.isGStringType; -import static org.codehaus.groovy.ast.ClassHelper.isGroovyObjectType; -import static org.codehaus.groovy.ast.ClassHelper.isNumberType; -import static org.codehaus.groovy.ast.ClassHelper.isObjectType; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean; -import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType; -import static org.codehaus.groovy.ast.ClassHelper.isSAMType; -import static org.codehaus.groovy.ast.ClassHelper.isStringType; -import static org.codehaus.groovy.ast.ClassHelper.isWrapperBoolean; -import static org.codehaus.groovy.ast.ClassHelper.long_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.make; -import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching; -import static org.codehaus.groovy.ast.ClassHelper.short_TYPE; -import static org.codehaus.groovy.ast.ClassHelper.void_WRAPPER_TYPE; -import static org.codehaus.groovy.ast.tools.WideningCategories.implementsInterfaceOrSubclassOf; -import static org.codehaus.groovy.ast.tools.WideningCategories.isFloatingCategory; -import static org.codehaus.groovy.ast.tools.WideningCategories.isLongCategory; -import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound; -import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean; -import static org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport.closeQuietly; -import static org.codehaus.groovy.syntax.Types.BITWISE_AND; -import static org.codehaus.groovy.syntax.Types.BITWISE_AND_EQUAL; -import static org.codehaus.groovy.syntax.Types.BITWISE_OR; -import static org.codehaus.groovy.syntax.Types.BITWISE_OR_EQUAL; -import static org.codehaus.groovy.syntax.Types.BITWISE_XOR; -import static org.codehaus.groovy.syntax.Types.BITWISE_XOR_EQUAL; -import static org.codehaus.groovy.syntax.Types.COMPARE_EQUAL; -import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN; -import static org.codehaus.groovy.syntax.Types.COMPARE_GREATER_THAN_EQUAL; -import static org.codehaus.groovy.syntax.Types.COMPARE_IDENTICAL; -import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN; -import static org.codehaus.groovy.syntax.Types.COMPARE_LESS_THAN_EQUAL; -import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_EQUAL; -import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IDENTICAL; -import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_IN; -import static org.codehaus.groovy.syntax.Types.COMPARE_NOT_INSTANCEOF; -import static org.codehaus.groovy.syntax.Types.COMPARE_TO; -import static org.codehaus.groovy.syntax.Types.DIVIDE; -import static org.codehaus.groovy.syntax.Types.DIVIDE_EQUAL; -import static org.codehaus.groovy.syntax.Types.INTDIV; -import static org.codehaus.groovy.syntax.Types.INTDIV_EQUAL; -import static org.codehaus.groovy.syntax.Types.KEYWORD_IN; -import static org.codehaus.groovy.syntax.Types.KEYWORD_INSTANCEOF; -import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT; -import static org.codehaus.groovy.syntax.Types.LEFT_SHIFT_EQUAL; -import static org.codehaus.groovy.syntax.Types.LEFT_SQUARE_BRACKET; -import static org.codehaus.groovy.syntax.Types.LOGICAL_AND; -import static org.codehaus.groovy.syntax.Types.LOGICAL_OR; -import static org.codehaus.groovy.syntax.Types.MATCH_REGEX; -import static org.codehaus.groovy.syntax.Types.MINUS; -import static org.codehaus.groovy.syntax.Types.MINUS_EQUAL; -import static org.codehaus.groovy.syntax.Types.MOD; -import static org.codehaus.groovy.syntax.Types.MOD_EQUAL; -import static org.codehaus.groovy.syntax.Types.MULTIPLY; -import static org.codehaus.groovy.syntax.Types.MULTIPLY_EQUAL; -import static org.codehaus.groovy.syntax.Types.PLUS; -import static org.codehaus.groovy.syntax.Types.PLUS_EQUAL; -import static org.codehaus.groovy.syntax.Types.POWER; -import static org.codehaus.groovy.syntax.Types.POWER_EQUAL; -import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT; -import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_EQUAL; -import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED; -import static org.codehaus.groovy.syntax.Types.RIGHT_SHIFT_UNSIGNED_EQUAL; - -/** - * Support methods for {@link StaticTypeCheckingVisitor}. - */ -public abstract class StaticTypeCheckingSupport { - - protected static final ClassNode Matcher_TYPE = makeWithoutCaching(Matcher.class); - protected static final ClassNode ArrayList_TYPE = makeWithoutCaching(ArrayList.class); - protected static final ClassNode BaseStream_TYPE = makeWithoutCaching(BaseStream.class); - protected static final ClassNode Collection_TYPE = COLLECTION_TYPE; // TODO: deprecate? - protected static final ClassNode Deprecated_TYPE = DEPRECATED_TYPE; // TODO: deprecate? - protected static final ClassNode LinkedHashMap_TYPE = makeWithoutCaching(LinkedHashMap.class); - protected static final ClassNode LinkedHashSet_TYPE = makeWithoutCaching(LinkedHashSet.class); - - protected static final Map NUMBER_TYPES = Maps.of( - byte_TYPE, 0, - Byte_TYPE, 0, - short_TYPE, 1, - Short_TYPE, 1, - int_TYPE, 2, - Integer_TYPE, 2, - Long_TYPE, 3, - long_TYPE, 3, - float_TYPE, 4, - Float_TYPE, 4, - double_TYPE, 5, - Double_TYPE, 5 - ); - - protected static final Map NUMBER_OPS = Maps.of( - "plus", PLUS, - "minus", MINUS, - "multiply", MULTIPLY, - "div", DIVIDE, - "or", BITWISE_OR, - "and", BITWISE_AND, - "xor", BITWISE_XOR, - "mod", MOD, - "intdiv", INTDIV, - "leftShift", LEFT_SHIFT, - "rightShift", RIGHT_SHIFT, - "rightShiftUnsigned", RIGHT_SHIFT_UNSIGNED - ); - - protected static final ClassNode GSTRING_STRING_CLASSNODE = lowestUpperBound( - STRING_TYPE, - GSTRING_TYPE - ); - - /** - * This is for internal use only. When an argument method is null, we cannot determine its type, so - * we use this one as a wildcard. - */ - protected static final ClassNode UNKNOWN_PARAMETER_TYPE = make(""); - - /** - * This comparator is used when we return the list of methods from DGM which name correspond to a given - * name. As we also lookup for DGM methods of superclasses or interfaces, it may be possible to find - * two methods which have the same name and the same arguments. In that case, we should not add the method - * from superclass or interface otherwise the system won't be able to select the correct method, resulting - * in an ambiguous method selection for similar methods. - */ - protected static final Comparator DGM_METHOD_NODE_COMPARATOR = (mn1, mn2) -> { - if (mn1.getName().equals(mn2.getName())) { - Parameter[] pa1 = mn1.getParameters(); - Parameter[] pa2 = mn2.getParameters(); - if (pa1.length == pa2.length) { - boolean allEqual = true; - for (int i = 0, n = pa1.length; i < n && allEqual; i += 1) { - allEqual = pa1[i].getType().equals(pa2[i].getType()); - } - if (allEqual) { - if (mn1 instanceof ExtensionMethodNode && mn2 instanceof ExtensionMethodNode) { - return StaticTypeCheckingSupport.DGM_METHOD_NODE_COMPARATOR.compare(((ExtensionMethodNode) mn1).getExtensionMethodNode(), ((ExtensionMethodNode) mn2).getExtensionMethodNode()); - } - return 0; - } - } else { - return pa1.length - pa2.length; - } - } - return 1; - }; - - protected static final ExtensionMethodCache EXTENSION_METHOD_CACHE = ExtensionMethodCache.INSTANCE; - - public static void clearExtensionMethodCache() { - EXTENSION_METHOD_CACHE.cache.clearAll(); - } - - // GRECLIPSE add - public static void clearExtensionMethodCache(final ClassLoader loader) { - EXTENSION_METHOD_CACHE.cache.remove(loader); - } - // GRECLIPSE end - - /** - * Returns true for expressions of the form x[...] - * - * @param expression an expression - * @return true for array access expressions - */ - protected static boolean isArrayAccessExpression(final Expression expression) { - return expression instanceof BinaryExpression && isArrayOp(((BinaryExpression) expression).getOperation().getType()); - } - - /** - * Called on method call checks in order to determine if a method call corresponds to the - * idiomatic o.with { ... } structure - * - * @param name name of the method called - * @param arguments method call arguments - * @return true if the name is "with" and arguments consist of a single closure - */ - public static boolean isWithCall(final String name, final Expression arguments) { - if ("with".equals(name) && arguments instanceof ArgumentListExpression) { - List args = ((ArgumentListExpression) arguments).getExpressions(); - if (args.size() == 1 && args.get(0) instanceof ClosureExpression) { - return true; - } - } - return false; - } - - /** - * Given a variable expression, returns the ultimately accessed variable. - * - * @param ve a variable expression - * @return the target variable - */ - protected static Variable findTargetVariable(final VariableExpression ve) { - Variable accessedVariable = ve.getAccessedVariable(); - if (accessedVariable != null && accessedVariable != ve) { - if (accessedVariable instanceof VariableExpression) { - return findTargetVariable((VariableExpression) accessedVariable); - } - return accessedVariable; - } - return ve; - } - - /** - * @deprecated Use {@link #findDGMMethodsForClassNode(ClassLoader, ClassNode, String)} instead - */ - @Deprecated - protected static Set findDGMMethodsForClassNode(final ClassNode clazz, final String name) { - return findDGMMethodsForClassNode(MetaClassRegistryImpl.class.getClassLoader(), clazz, name); - } - - public static Set findDGMMethodsForClassNode(final ClassLoader loader, final ClassNode clazz, final String name) { - TreeSet accumulator = new TreeSet<>(DGM_METHOD_NODE_COMPARATOR); - findDGMMethodsForClassNode(loader, clazz, name, accumulator); - return accumulator; - } - - /** - * @deprecated Use {@link #findDGMMethodsForClassNode(ClassLoader, ClassNode, String, TreeSet)} instead - */ - @Deprecated - protected static void findDGMMethodsForClassNode(final ClassNode clazz, final String name, final TreeSet accumulator) { - findDGMMethodsForClassNode(MetaClassRegistryImpl.class.getClassLoader(), clazz, name, accumulator); - } - - protected static void findDGMMethodsForClassNode(final ClassLoader loader, final ClassNode clazz, final String name, final TreeSet accumulator) { - List fromDGM = EXTENSION_METHOD_CACHE.get(loader).get(clazz.getName()); - if (fromDGM != null) { - for (MethodNode node : fromDGM) { - if (node.getName().equals(name)) accumulator.add(node); - } - } - for (ClassNode node : clazz.getInterfaces()) { - findDGMMethodsForClassNode(loader, node, name, accumulator); - } - if (clazz.isArray()) { - ClassNode componentClass = clazz.getComponentType(); - if (!isObjectType(componentClass) && !isPrimitiveType(componentClass)) { - if (componentClass.isInterface()) { - findDGMMethodsForClassNode(loader, OBJECT_TYPE.makeArray(), name, accumulator); - } else { - findDGMMethodsForClassNode(loader, componentClass.getSuperClass().makeArray(), name, accumulator); - } - } - } - if (clazz.getSuperClass() != null) { - findDGMMethodsForClassNode(loader, clazz.getSuperClass(), name, accumulator); - } else if (!isObjectType(clazz)) { - findDGMMethodsForClassNode(loader, OBJECT_TYPE, name, accumulator); - } - } - - /** - * Checks that arguments and parameter types match. - * - * @return -1 if arguments do not match, 0 if arguments are of the exact type and > 0 when one or more argument is - * not of the exact type but still match - */ - public static int allParametersAndArgumentsMatch(Parameter[] parameters, final ClassNode[] argumentTypes) { - if (parameters == null) { - parameters = Parameter.EMPTY_ARRAY; - } - int dist = 0; - if (argumentTypes.length < parameters.length) { - return -1; - } - // we already know there are at least params.length elements in both arrays - for (int i = 0, n = parameters.length; i < n; i += 1) { - ClassNode paramType = parameters[i].getType(); - ClassNode argType = argumentTypes[i]; - if (!isAssignableTo(argType, paramType)) { - return -1; - } else if (!paramType.equals(argType)) { - dist += getDistance(argType, paramType); - } - } - return dist; - } - - /** - * Checks that arguments and parameter types match, expecting that the number of parameters is strictly greater - * than the number of arguments, allowing possible inclusion of default parameters. - * - * @return -1 if arguments do not match, 0 if arguments are of the exact type and >0 when one or more argument is - * not of the exact type but still match - */ - static int allParametersAndArgumentsMatchWithDefaultParams(final Parameter[] parameters, final ClassNode[] argumentTypes) { - int dist = 0; - ClassNode ptype = null; - for (int i = 0, j = 0, n = parameters.length; i < n; i += 1) { - Parameter param = parameters[i]; - ClassNode paramType = param.getType(); - ClassNode arg = (j >= argumentTypes.length ? null : argumentTypes[j]); - if (arg == null || !isAssignableTo(arg, paramType)) { - if (!param.hasInitialExpression() && (ptype == null || !ptype.equals(paramType))) { - return -1; // no default value - } - // a default value exists, we can skip this param - ptype = null; - } else { - j += 1; - if (!paramType.equals(arg)) { - dist += getDistance(arg, paramType); - } - if (param.hasInitialExpression()) { - ptype = arg; - } else { - ptype = null; - } - } - } - return dist; - } - - /** - * Checks that excess arguments match the vararg signature parameter. - * - * @return -1 if no match, 0 if all arguments matches the vararg type and >0 if one or more vararg argument is - * assignable to the vararg type, but still not an exact match - */ - static int excessArgumentsMatchesVargsParameter(final Parameter[] parameters, final ClassNode[] argumentTypes) { - // we already know parameter length is bigger zero and last is a vargs - // the excess arguments are all put in an array for the vargs call - // so check against the component type - int dist = 0; - ClassNode vargsBase = parameters[parameters.length - 1].getType().getComponentType(); - for (int i = parameters.length; i < argumentTypes.length; i += 1) { - if (!isAssignableTo(argumentTypes[i], vargsBase)) return -1; - else dist += getClassDistance(vargsBase, argumentTypes[i]); - } - return dist; - } - - /** - * Checks if the last argument matches the vararg type. - * - * @return -1 if no match, 0 if the last argument is exactly the vararg type and 1 if of an assignable type - */ - static int lastArgMatchesVarg(final Parameter[] parameters, final ClassNode... argumentTypes) { - if (!isVargs(parameters)) return -1; - int lastParamIndex = parameters.length - 1; - if (lastParamIndex == argumentTypes.length) return 0; - // two cases remain: - // the argument is wrapped in the vargs array or - // the argument is an array that can be used for the vargs part directly - // testing only the wrapping case since the non-wrapping is done already - ClassNode arrayType = parameters[lastParamIndex].getType(); - ClassNode elementType = arrayType.getComponentType(); - ClassNode argumentType = argumentTypes[argumentTypes.length - 1]; - if (isNumberType(elementType) && isNumberType(argumentType) && !getWrapper(elementType).equals(getWrapper(argumentType))) return -1; - return isAssignableTo(argumentType, elementType) ? Math.min(getDistance(argumentType, arrayType), getDistance(argumentType, elementType)) : -1; - } - - /** - * Checks if a class node is assignable to another. This is used for example in - * assignment checks where you want to verify that the assignment is valid. - * - * @return true if the class node is assignable to the other class node, false otherwise - */ - public static boolean isAssignableTo(ClassNode type, ClassNode toBeAssignedTo) { - if (type == toBeAssignedTo || type == UNKNOWN_PARAMETER_TYPE) return true; - if (isPrimitiveType(type)) type = getWrapper(type); - if (isPrimitiveType(toBeAssignedTo)) toBeAssignedTo = getWrapper(toBeAssignedTo); - if (NUMBER_TYPES.containsKey(type.redirect()) && NUMBER_TYPES.containsKey(toBeAssignedTo.redirect())) { - return NUMBER_TYPES.get(type.redirect()) <= NUMBER_TYPES.get(toBeAssignedTo.redirect()); - } - if (type.isArray() && toBeAssignedTo.isArray()) { - return isAssignableTo(type.getComponentType(), toBeAssignedTo.getComponentType()); - } - if (type.isDerivedFrom(GSTRING_TYPE) && isStringType(toBeAssignedTo)) { - return true; - } - if (isStringType(type) && toBeAssignedTo.isDerivedFrom(GSTRING_TYPE)) { - return true; - } - if (implementsInterfaceOrIsSubclassOf(type, toBeAssignedTo)) { - if (toBeAssignedTo.getGenericsTypes() != null) { // perform additional check on generics - GenericsType gt = toBeAssignedTo.isGenericsPlaceHolder() ? toBeAssignedTo.getGenericsTypes()[0] : GenericsUtils.buildWildcardType(toBeAssignedTo); - return gt.isCompatibleWith(type); - } - return true; - } - // GROOVY-10067: unresolved argument like "N extends Number" for parameter like "Integer" - if (type.isGenericsPlaceHolder() && type.getUnresolvedName().charAt(0) == '#') { - return type.getGenericsTypes()[0].isCompatibleWith(toBeAssignedTo); - } - return (type.isDerivedFrom(CLOSURE_TYPE) && isSAMType(toBeAssignedTo)); - } - - @Deprecated - static boolean isVargs(final Parameter[] parameters) { - return ParameterUtils.isVargs(parameters); - } - - public static boolean isCompareToBoolean(final int op) { - return op == COMPARE_LESS_THAN || op == COMPARE_LESS_THAN_EQUAL - || op == COMPARE_GREATER_THAN || op == COMPARE_GREATER_THAN_EQUAL; - } - - static boolean isArrayOp(final int op) { - return op == LEFT_SQUARE_BRACKET; - } - - static boolean isBoolIntrinsicOp(final int op) { - switch (op) { - case LOGICAL_AND: - case LOGICAL_OR: - case COMPARE_NOT_IDENTICAL: - case COMPARE_IDENTICAL: - case MATCH_REGEX: - case KEYWORD_INSTANCEOF: - case COMPARE_NOT_INSTANCEOF: - return true; - default: - return false; - } - } - - static boolean isPowerOperator(final int op) { - return op == POWER || op == POWER_EQUAL; - } - - static String getOperationName(final int op) { - switch (op) { - case COMPARE_EQUAL: - case COMPARE_NOT_EQUAL: - // this is only correct in this specific context; normally - // we would have to compile against compareTo if available - // but since we don't compile here, this one is enough - return "equals"; - - case COMPARE_TO: - case COMPARE_LESS_THAN: - case COMPARE_LESS_THAN_EQUAL: - case COMPARE_GREATER_THAN: - case COMPARE_GREATER_THAN_EQUAL: - return "compareTo"; - - case BITWISE_AND: - case BITWISE_AND_EQUAL: - return "and"; - - case BITWISE_OR: - case BITWISE_OR_EQUAL: - return "or"; - - case BITWISE_XOR: - case BITWISE_XOR_EQUAL: - return "xor"; - - case PLUS: - case PLUS_EQUAL: - return "plus"; - - case MINUS: - case MINUS_EQUAL: - return "minus"; - - case MULTIPLY: - case MULTIPLY_EQUAL: - return "multiply"; - - case DIVIDE: - case DIVIDE_EQUAL: - return "div"; - - case INTDIV: - case INTDIV_EQUAL: - return "intdiv"; - - case MOD: - case MOD_EQUAL: - return "mod"; - - case POWER: - case POWER_EQUAL: - return "power"; - - case LEFT_SHIFT: - case LEFT_SHIFT_EQUAL: - return "leftShift"; - - case RIGHT_SHIFT: - case RIGHT_SHIFT_EQUAL: - return "rightShift"; - - case RIGHT_SHIFT_UNSIGNED: - case RIGHT_SHIFT_UNSIGNED_EQUAL: - return "rightShiftUnsigned"; - - case KEYWORD_IN: - return "isCase"; - - case COMPARE_NOT_IN: - return "isNotCase"; - - default: - return null; - } - } - - static boolean isShiftOperation(final String name) { - return "leftShift".equals(name) || "rightShift".equals(name) || "rightShiftUnsigned".equals(name); - } - - /** - * Returns true for operations that are of the class, that given a common type class for left and right, the - * operation "left op right" will have a result in the same type class In Groovy on numbers that is +,-,* as well as - * their variants with equals. - */ - static boolean isOperationInGroup(final int op) { - switch (op) { - case PLUS: - case PLUS_EQUAL: - case MINUS: - case MINUS_EQUAL: - case MULTIPLY: - case MULTIPLY_EQUAL: - return true; - default: - return false; - } - } - - static boolean isBitOperator(final int op) { - switch (op) { - case BITWISE_OR_EQUAL: - case BITWISE_OR: - case BITWISE_AND_EQUAL: - case BITWISE_AND: - case BITWISE_XOR_EQUAL: - case BITWISE_XOR: - return true; - default: - return false; - } - } - - public static boolean isAssignment(final int op) { - return Types.isAssignment(op); - } - - /** - * Returns true or false depending on whether the right classnode can be assigned to the left classnode. This method - * should not add errors by itself: we let the caller decide what to do if an incompatible assignment is found. - * - * @param left the class to be assigned to - * @param right the assignee class - * @return false if types are incompatible - */ - public static boolean checkCompatibleAssignmentTypes(final ClassNode left, final ClassNode right) { - return checkCompatibleAssignmentTypes(left, right, null); - } - - public static boolean checkCompatibleAssignmentTypes(final ClassNode left, final ClassNode right, final Expression rightExpression) { - return checkCompatibleAssignmentTypes(left, right, rightExpression, true); - } - - /** - * Everything that can be done by {@code castToType} should be allowed for assignment. - * - * @see org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation#castToType(Object,Class) - */ - public static boolean checkCompatibleAssignmentTypes(final ClassNode left, final ClassNode right, final Expression rightExpression, final boolean allowConstructorCoercion) { - if (!isPrimitiveType(left) && isNullConstant(rightExpression)) { - return true; - } - - if (left.isArray()) { - if (right.isArray()) { - return checkCompatibleAssignmentTypes(left.getComponentType(), right.getComponentType(), rightExpression, false); - } - if (GeneralUtils.isOrImplements(right, Collection_TYPE) && !(rightExpression instanceof ListExpression)) { - GenericsType elementType = GenericsUtils.parameterizeType(right, Collection_TYPE).getGenericsTypes()[0]; - return OBJECT_TYPE.equals(left.getComponentType()) // Object[] can accept any collection element type(s) - || (elementType.getLowerBound() == null && isCovariant(extractType(elementType), left.getComponentType())); - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GROOVY-8984: "? super T" is only compatible with an Object[] target - } - if (GeneralUtils.isOrImplements(right, BaseStream_TYPE)) { - GenericsType elementType = GenericsUtils.parameterizeType(right, BaseStream_TYPE).getGenericsTypes()[0]; - return isObjectType(left.getComponentType()) // Object[] can accept any stream API element type(s) - || (elementType.getLowerBound() == null && isCovariant(extractType(elementType), getWrapper(left.getComponentType()))); - } - } - - ClassNode leftRedirect = left.redirect(); - ClassNode rightRedirect = right.redirect(); - if (leftRedirect == rightRedirect) return true; - - if (leftRedirect == VOID_TYPE) return rightRedirect == void_WRAPPER_TYPE; - if (leftRedirect == void_WRAPPER_TYPE) return rightRedirect == VOID_TYPE; - - if (isLongCategory(getUnwrapper(leftRedirect))) { - // byte, char, int, long or short can be assigned any base number - if (isNumberType(rightRedirect) /*|| rightRedirect == char_TYPE*/) { - return true; - } - if (leftRedirect == char_TYPE && rightRedirect == Character_TYPE) return true; - if (leftRedirect == Character_TYPE && rightRedirect == char_TYPE) return true; - if ((leftRedirect == char_TYPE || leftRedirect == Character_TYPE) && rightRedirect == STRING_TYPE) { - return rightExpression instanceof ConstantExpression && rightExpression.getText().length() == 1; - } - } else if (isFloatingCategory(getUnwrapper(leftRedirect))) { - // float or double can be assigned any base number type or BigDecimal - if (isNumberType(rightRedirect) || isBigDecimalType(rightRedirect)) { - return true; - } - } else if (left.isGenericsPlaceHolder()) { // must precede non-final types - return right.getUnresolvedName().charAt(0) != '#' // RHS not adaptable - ? left.getGenericsTypes()[0].isCompatibleWith(right) // GROOVY-7307, GROOVY-9952, et al. - : implementsInterfaceOrSubclassOf(leftRedirect, rightRedirect); // GROOVY-10067, GROOVY-10342 - - } else if (isBigDecimalType(leftRedirect) || Number_TYPE.equals(leftRedirect)) { - // BigDecimal or Number can be assigned any derivitave of java.lang.Number - if (isNumberType(rightRedirect) || rightRedirect.isDerivedFrom(Number_TYPE)) { - return true; - } - } else if (isBigIntegerType(leftRedirect)) { - // BigInteger can be assigned byte, char, int, long, short or BigInteger - if (isLongCategory(getUnwrapper(rightRedirect)) || rightRedirect.isDerivedFrom(BigInteger_TYPE)) { - return true; - } - } else if (leftRedirect.isDerivedFrom(Enum_Type)) { - // Enum types can be assigned String or GString (triggers `valueOf` call) - if (rightRedirect == STRING_TYPE || isGStringOrGStringStringLUB(rightRedirect)) { - return true; - } - } else if (isWildcardLeftHandSide(leftRedirect)) { - // Object, String, [Bb]oolean or Class can be assigned anything (except null to boolean) - return !(leftRedirect == boolean_TYPE && isNullConstant(rightExpression)); - } - - // if right is array, map or collection we try invoking the constructor - if (allowConstructorCoercion && isGroovyConstructorCompatible(rightExpression)) { - // TODO: in case of the array we could maybe make a partial check - if (rightRedirect.isArray() && !leftRedirect.isArray()) { - return false; - } - return true; - } - - // simple sub-type check - if (!left.isInterface() ? right.isDerivedFrom(left) : GeneralUtils.isOrImplements(right, left)) return true; - - if (right.isDerivedFrom(CLOSURE_TYPE) && isSAMType(left)) { - return true; - } - - // GROOVY-7316, GROOVY-10256: "Type x = m()" given "def T m()"; T adapts to target - return right.isGenericsPlaceHolder() && right.asGenericsType().isCompatibleWith(left); - } - - private static boolean isGroovyConstructorCompatible(final Expression rightExpression) { - return rightExpression instanceof ListExpression - || rightExpression instanceof MapExpression - || rightExpression instanceof ArrayExpression; - } - - /** - * Tells if a class is one of the "accept all" classes as the left hand side of an - * assignment. - * - * @param node the classnode to test - * @return true if it's an Object, String, boolean, Boolean or Class. - */ - public static boolean isWildcardLeftHandSide(final ClassNode node) { - return (isObjectType(node) - || isStringType(node) - || isPrimitiveBoolean(node) - || isWrapperBoolean(node) - || isClassType(node)); - } - - public static boolean isBeingCompiled(final ClassNode node) { - return (node.getCompileUnit() != null); - } - - @Deprecated - static boolean checkPossibleLooseOfPrecision(final ClassNode left, final ClassNode right, final Expression rightExpr) { - return checkPossibleLossOfPrecision(left, right, rightExpr); - } - - static boolean checkPossibleLossOfPrecision(final ClassNode left, final ClassNode right, final Expression rightExpr) { - if (left == right || left.equals(right)) return false; // identical types - int leftIndex = NUMBER_TYPES.get(left); - int rightIndex = NUMBER_TYPES.get(right); - if (leftIndex >= rightIndex) return false; - // here we must check if the right number is short enough to fit in the left type - if (rightExpr instanceof ConstantExpression) { - Object value = ((ConstantExpression) rightExpr).getValue(); - if (!(value instanceof Number)) return true; - Number number = (Number) value; - switch (leftIndex) { - case 0: { // byte - byte val = number.byteValue(); - if (number instanceof Short) { - return !Short.valueOf(val).equals(number); - } - if (number instanceof Integer) { - return !Integer.valueOf(val).equals(number); - } - if (number instanceof Long) { - return !Long.valueOf(val).equals(number); - } - if (number instanceof Float) { - return !Float.valueOf(val).equals(number); - } - return !Double.valueOf(val).equals(number); - } - case 1: { // short - short val = number.shortValue(); - if (number instanceof Integer) { - return !Integer.valueOf(val).equals(number); - } - if (number instanceof Long) { - return !Long.valueOf(val).equals(number); - } - if (number instanceof Float) { - return !Float.valueOf(val).equals(number); - } - return !Double.valueOf(val).equals(number); - } - case 2: { // integer - int val = number.intValue(); - if (number instanceof Long) { - return !Long.valueOf(val).equals(number); - } - if (number instanceof Float) { - return !Float.valueOf(val).equals(number); - } - return !Double.valueOf(val).equals(number); - } - case 3: { // long - long val = number.longValue(); - if (number instanceof Float) { - return !Float.valueOf(val).equals(number); - } - return !Double.valueOf(val).equals(number); - } - case 4: { // float - float val = number.floatValue(); - return !Double.valueOf(val).equals(number); - } - default: // double - return false; // no possible loss here - } - } - return true; // possible loss of precision - } - - static String toMethodParametersString(final String methodName, final ClassNode... parameters) { - if (parameters == null || parameters.length == 0) return methodName + "()"; - - StringJoiner joiner = new StringJoiner(", ", methodName + "(", ")"); - for (ClassNode parameter : parameters) { - joiner.add(prettyPrintType(parameter)); - } - return joiner.toString(); - } - - /** - * Returns string representation of type with generics. Arrays are indicated - * with trailing "[]". - */ - static String prettyPrintType(final ClassNode type) { - if (type.getUnresolvedName().charAt(0) == '#') { - return type.redirect().toString(false); - } - return type.toString(false); - } - - /** - * Returns string representation of type *no* generics. Arrays are indicated - * with trailing "[]". - */ - static String prettyPrintTypeName(final ClassNode type) { - if (type.isArray()) { - return prettyPrintTypeName(type.getComponentType()) + "[]"; - } - return type.isGenericsPlaceHolder() ? type.getUnresolvedName() : type.getText(); - } - - public static boolean implementsInterfaceOrIsSubclassOf(final ClassNode type, final ClassNode superOrInterface) { - boolean result = (type.equals(superOrInterface) - || type.isDerivedFrom(superOrInterface) - || type.implementsInterface(superOrInterface) - || type == UNKNOWN_PARAMETER_TYPE); - if (result) { - return true; - } - if (superOrInterface instanceof WideningCategories.LowestUpperBoundClassNode) { - WideningCategories.LowestUpperBoundClassNode cn = (WideningCategories.LowestUpperBoundClassNode) superOrInterface; - result = implementsInterfaceOrIsSubclassOf(type, cn.getSuperClass()); - if (result) { - for (ClassNode interfaceNode : cn.getInterfaces()) { - result = type.implementsInterface(interfaceNode); - if (!result) break; - } - } - if (result) return true; - } else if (superOrInterface instanceof UnionTypeClassNode) { - UnionTypeClassNode union = (UnionTypeClassNode) superOrInterface; - for (ClassNode delegate : union.getDelegates()) { - if (implementsInterfaceOrIsSubclassOf(type, delegate)) return true; - } - } - if (type.isArray() && superOrInterface.isArray()) { - return implementsInterfaceOrIsSubclassOf(type.getComponentType(), superOrInterface.getComponentType()); - } - if (isGroovyObjectType(superOrInterface) && isBeingCompiled(type) && !type.isInterface()) {//TODO: !POJO !Trait - return true; - } - return false; - } - - static int getPrimitiveDistance(ClassNode primA, ClassNode primB) { - return Math.abs(NUMBER_TYPES.get(primA) - NUMBER_TYPES.get(primB)); - } - - static int getDistance(final ClassNode receiver, final ClassNode compare) { - if (receiver.isArray() && compare.isArray()) { - return getDistance(receiver.getComponentType(), compare.getComponentType()); - } - int dist = 0; - ClassNode unwrapReceiver = getUnwrapper(receiver); - ClassNode unwrapCompare = getUnwrapper(compare); - if (isPrimitiveType(unwrapReceiver) - && isPrimitiveType(unwrapCompare) - && unwrapReceiver != unwrapCompare) { - dist = getPrimitiveDistance(unwrapReceiver, unwrapCompare); - } - // Add a penalty against boxing or unboxing, to get a resolution similar to JLS 15.12.2 - // (http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12.2). - if (isPrimitiveType(receiver) ^ isPrimitiveType(compare)) { - dist = (dist + 1) << 1; - } - if (unwrapCompare.equals(unwrapReceiver) - || receiver == UNKNOWN_PARAMETER_TYPE) { - return dist; - } - if (receiver.isArray()) { - dist += 256; // GROOVY-5114: Object[] vs Object - } - if (compare.isInterface()) { MethodNode sam; - if (receiver.implementsInterface(compare)) { - return dist + getMaximumInterfaceDistance(receiver, compare); - } else if (receiver.equals(CLOSURE_TYPE) && (sam = findSAM(compare)) != null) { - // GROOVY-9881: in case of multiple overloads, give preference to equal parameter count - Integer closureParamCount = receiver.getNodeMetaData(StaticTypesMarker.CLOSURE_ARGUMENTS); - if (closureParamCount != null && closureParamCount == sam.getParameters().length) dist -= 1; - - return dist + 13; // GROOVY-9852: @FunctionalInterface vs Object - } - } - ClassNode cn = isPrimitiveType(receiver) && !isPrimitiveType(compare) ? getWrapper(receiver) : receiver; - while (cn != null && !cn.equals(compare)) { - cn = cn.getSuperClass(); - dist += 1; - if (isObjectType(cn)) - dist += 1; - dist = (dist + 1) << 1; - } - return dist; - } - - private static int getMaximumInterfaceDistance(final ClassNode c, final ClassNode interfaceClass) { - // -1 means a mismatch - if (c == null) return -1; - // 0 means a direct match - if (c.equals(interfaceClass)) return 0; - ClassNode[] interfaces = c.getInterfaces(); - int max = -1; - for (ClassNode anInterface : interfaces) { - int sub = getMaximumInterfaceDistance(anInterface, interfaceClass); - // we need to keep the -1 to track the mismatch, a +1 - // by any means could let it look like a direct match - // we want to add one, because there is an interface between - // the interface we search for and the interface we are in. - if (sub != -1) { - sub += 1; - } - // we are interested in the longest path only - max = Math.max(max, sub); - } - // we do not add one for super classes, only for interfaces - int superClassMax = getMaximumInterfaceDistance(c.getSuperClass(), interfaceClass); - return Math.max(max, superClassMax); - } - - /** - * @deprecated Use {@link #findDGMMethodsByNameAndArguments(ClassLoader, ClassNode, String, ClassNode[], List)} instead - */ - @Deprecated - public static List findDGMMethodsByNameAndArguments(final ClassNode receiver, final String name, final ClassNode[] args) { - return findDGMMethodsByNameAndArguments(MetaClassRegistryImpl.class.getClassLoader(), receiver, name, args); - } - - public static List findDGMMethodsByNameAndArguments(final ClassLoader loader, final ClassNode receiver, final String name, final ClassNode[] args) { - return findDGMMethodsByNameAndArguments(loader, receiver, name, args, new LinkedList<>()); - } - - /** - * @deprecated Use {@link #findDGMMethodsByNameAndArguments(ClassLoader, ClassNode, String, ClassNode[], List)} instead - */ - @Deprecated - public static List findDGMMethodsByNameAndArguments(final ClassNode receiver, final String name, final ClassNode[] args, final List methods) { - return findDGMMethodsByNameAndArguments(MetaClassRegistryImpl.class.getClassLoader(), receiver, name, args, methods); - } - - public static List findDGMMethodsByNameAndArguments(final ClassLoader loader, final ClassNode receiver, final String name, final ClassNode[] args, final List methods) { - methods.addAll(findDGMMethodsForClassNode(loader, receiver, name)); - return methods.isEmpty() ? methods : chooseBestMethod(receiver, methods, args); - } - - /** - * Returns true if the provided class node, when considered as a receiver of a message or as a parameter, - * is using a placeholder in its generics type. In this case, we're facing unchecked generics and type - * checking is limited (ex: void foo(Set s) { s.keySet() } - * - * @param node the node to test - * @return true if it is using any placeholder in generics types - */ - public static boolean isUsingUncheckedGenerics(final ClassNode node) { - return GenericsUtils.hasUnresolvedGenerics(node); - } - - /** - * Returns the method(s) which best fit the argument types. - * - * @return zero or more results - */ - public static List chooseBestMethod(final ClassNode receiver, final Collection methods, final ClassNode... argumentTypes) { - if (!asBoolean(methods)) { - return Collections.emptyList(); - } - - int bestDist = Integer.MAX_VALUE; - List bestChoices = new LinkedList<>(); - boolean noCulling = methods.size() <= 1 || "".equals(methods.iterator().next().getName()); - Iterable candidates = noCulling ? methods : removeCovariantsAndInterfaceEquivalents(methods); - - for (MethodNode candidate : candidates) { - MethodNode safeNode = candidate; - ClassNode[] safeArgs = argumentTypes; - boolean isExtensionMethod = candidate instanceof ExtensionMethodNode; - if (isExtensionMethod) { - int nArgs = argumentTypes.length; - safeArgs = new ClassNode[nArgs + 1]; - System.arraycopy(argumentTypes, 0, safeArgs, 1, nArgs); - safeArgs[0] = receiver; // prepend self-type as first argument - safeNode = ((ExtensionMethodNode) candidate).getExtensionMethodNode(); - } - - /* TODO: corner case - class B extends A {} - Animal foo(A a) {} - Person foo(B b) {} - - B b = new B() - Person p = foo(b) - */ - - ClassNode declaringClass = candidate.getDeclaringClass(); - ClassNode actualReceiver = receiver != null ? receiver : declaringClass; - - Map spec; - if (candidate.isStatic()) { - spec = Collections.emptyMap(); // none visible - } else { - spec = GenericsUtils.makeDeclaringAndActualGenericsTypeMapOfExactType(declaringClass, actualReceiver); - GenericsType[] methodGenerics = candidate.getGenericsTypes(); - if (methodGenerics != null) { // GROOVY-10322: remove hidden type parameters - for (int i = 0, n = methodGenerics.length; i < n && !spec.isEmpty(); i += 1) { - for (Iterator it = spec.keySet().iterator(); it.hasNext(); ) { - if (it.next().getName().equals(methodGenerics[i].getName())) it.remove(); - } - } - } - } - - Parameter[] params = makeRawTypes(safeNode.getParameters(), spec); - int dist = measureParametersAndArgumentsDistance(params, safeArgs); - if (dist >= 0) { - dist += getClassDistance(declaringClass, actualReceiver); - dist += getExtensionDistance(isExtensionMethod); - if (dist < bestDist) { - bestDist = dist; - bestChoices.clear(); - bestChoices.add(candidate); - } else if (dist == bestDist) { - bestChoices.add(candidate); - } - } - } - if (bestChoices.size() > 1) { - // GROOVY-6849: prefer extension method in case of ambiguity - List onlyExtensionMethods = new LinkedList<>(); - for (MethodNode choice : bestChoices) { - if (choice instanceof ExtensionMethodNode) { - onlyExtensionMethods.add(choice); - } - } - if (onlyExtensionMethods.size() == 1) { - return onlyExtensionMethods; - } - } - return bestChoices; - } - - private static int measureParametersAndArgumentsDistance(final Parameter[] parameters, final ClassNode[] argumentTypes) { - int dist = -1; - if (parameters.length == argumentTypes.length) { - int allMatch = allParametersAndArgumentsMatch(parameters, argumentTypes); - int endMatch = -1; - if (isVargs(parameters) && firstParametersAndArgumentsMatch(parameters, argumentTypes) >= 0) { - endMatch = lastArgMatchesVarg(parameters, argumentTypes); - if (endMatch >= 0) { - endMatch += getVarargsDistance(parameters); - } - } - dist = (allMatch >= 0 ? Math.max(allMatch, endMatch) : endMatch); - } else if (isVargs(parameters)) { - dist = firstParametersAndArgumentsMatch(parameters, argumentTypes); - if (dist >= 0) { - // varargs methods must not be preferred to methods without varargs - // for example : - // int sum(int x) should be preferred to int sum(int x, int... y) - dist += getVarargsDistance(parameters); - // there are three case for vargs - // (1) varg part is left out (there's one less argument than there are parameters) - // (2) last argument is put in the vargs array - // that case is handled above already when params and args have the same length - if (parameters.length < argumentTypes.length) { - // (3) there is more than one argument for the vargs array - int excessArgumentsDistance = excessArgumentsMatchesVargsParameter(parameters, argumentTypes); - if (excessArgumentsDistance >= 0) { - dist += excessArgumentsDistance; - } else { - dist = -1; - } - } - } - } - return dist; - } - - private static int firstParametersAndArgumentsMatch(final Parameter[] parameters, final ClassNode[] safeArgumentTypes) { - int dist = 0; - // check first parameters - if (parameters.length > 0) { - Parameter[] firstParams = new Parameter[parameters.length - 1]; - System.arraycopy(parameters, 0, firstParams, 0, firstParams.length); - dist = allParametersAndArgumentsMatch(firstParams, safeArgumentTypes); - } - return dist; - } - - private static int getVarargsDistance(final Parameter[] parameters) { - return 256 - parameters.length; // ensure exact matches are preferred over vargs - } - - private static int getClassDistance(final ClassNode declaringClassForDistance, final ClassNode actualReceiverForDistance) { - if (actualReceiverForDistance.equals(declaringClassForDistance)) { - return 0; - } - return getDistance(actualReceiverForDistance, declaringClassForDistance); - } - - private static int getExtensionDistance(final boolean isExtensionMethodNode) { - return isExtensionMethodNode ? 0 : 1; - } - - private static Parameter[] makeRawTypes(final Parameter[] parameters, final Map genericsPlaceholderAndTypeMap) { - return Arrays.stream(parameters).map(param -> { - String name = param.getType().getUnresolvedName(); - Optional value = genericsPlaceholderAndTypeMap.entrySet().stream() - .filter(e -> e.getKey().getName().equals(name)).findFirst().map(Map.Entry::getValue); - ClassNode type = value.map(gt -> !gt.isPlaceholder() ? gt.getType() : makeRawType(gt.getType())).orElseGet(() -> makeRawType(param.getType())); - - return new Parameter(type, param.getName()); - }).toArray(Parameter[]::new); - } - - private static ClassNode makeRawType(final ClassNode receiver) { - if (receiver.isArray()) { - return makeRawType(receiver.getComponentType()).makeArray(); - } - ClassNode raw = receiver.getPlainNodeReference(); - raw.setUsingGenerics(false); - raw.setGenericsTypes(null); - return raw; - } - - private static List removeCovariantsAndInterfaceEquivalents(final Collection collection) { - List toBeRemoved = new ArrayList<>(); - List list = new ArrayList<>(new LinkedHashSet<>(collection)); - for (int i = 0, n = list.size(); i < n - 1; i += 1) { - MethodNode one = list.get(i); - if (toBeRemoved.contains(one)) continue; - for (int j = i + 1; j < n; j += 1) { - MethodNode two = list.get(j); - if (toBeRemoved.contains(two)) continue; - if (one.getParameters().length == two.getParameters().length) { - ClassNode oneDC = one.getDeclaringClass(), twoDC = two.getDeclaringClass(); - if (oneDC == twoDC) { - if (ParameterUtils.parametersEqual(one.getParameters(), two.getParameters())) { - ClassNode oneRT = one.getReturnType(), twoRT = two.getReturnType(); - if (isCovariant(oneRT, twoRT)) { - toBeRemoved.add(two); - } else if (isCovariant(twoRT, oneRT)) { - toBeRemoved.add(one); - } - } else { - // imperfect solution to determining if two methods are - // equivalent, for example String#compareTo(Object) and - // String#compareTo(String) -- in that case, the Object - // version is marked as synthetic - if (one.isSynthetic() && !two.isSynthetic()) { - toBeRemoved.add(one); - } else if (two.isSynthetic() && !one.isSynthetic()) { - toBeRemoved.add(two); - } - } - } else if (!oneDC.equals(twoDC)) { - if (ParameterUtils.parametersEqual(one.getParameters(), two.getParameters())) { - // GROOVY-6882, GROOVY-6970: drop overridden or interface equivalent method - if (twoDC.isInterface() ? oneDC.implementsInterface(twoDC) - : oneDC.isDerivedFrom(twoDC)) { - toBeRemoved.add(two); - } else if (oneDC.isInterface() ? twoDC.isInterface() - : twoDC.isDerivedFrom(oneDC)) { - toBeRemoved.add(one); - } - } - } - } - } - } - if (toBeRemoved.isEmpty()) return list; - - List result = new LinkedList<>(list); - result.removeAll(toBeRemoved); - return result; - } - - private static boolean isCovariant(final ClassNode one, final ClassNode two) { - if (one.isArray() && two.isArray()) { - return isCovariant(one.getComponentType(), two.getComponentType()); - } - return (one.isDerivedFrom(two) || one.implementsInterface(two)); - } - - /** - * Given a receiver and a method node, parameterize the method arguments using - * available generic type information. - * - * @param receiver the class - * @param m the method - * @return the parameterized arguments - */ - public static Parameter[] parameterizeArguments(final ClassNode receiver, final MethodNode m) { - Map genericFromReceiver = GenericsUtils.extractPlaceholders(receiver); - Map contextPlaceholders = extractGenericsParameterMapOfThis(m); - Parameter[] methodParameters = m.getParameters(); - Parameter[] params = new Parameter[methodParameters.length]; - for (int i = 0, n = methodParameters.length; i < n; i += 1) { - Parameter methodParameter = methodParameters[i]; - ClassNode paramType = methodParameter.getType(); - params[i] = buildParameter(genericFromReceiver, contextPlaceholders, methodParameter, paramType); - } - return params; - } - - /** - * Given a parameter, builds a new parameter for which the known generics placeholders are resolved. - * - * @param genericFromReceiver resolved generics from the receiver of the message - * @param placeholdersFromContext resolved generics from the method context - * @param methodParameter the method parameter for which we want to resolve generic types - * @param paramType the (unresolved) type of the method parameter - * @return a new parameter with the same name and type as the original one, but with resolved generic types - */ - private static Parameter buildParameter(final Map genericFromReceiver, final Map placeholdersFromContext, final Parameter methodParameter, final ClassNode paramType) { - if (genericFromReceiver.isEmpty() && (placeholdersFromContext == null || placeholdersFromContext.isEmpty())) { - return methodParameter; - } - if (paramType.isArray()) { - ClassNode componentType = paramType.getComponentType(); - Parameter subMethodParameter = new Parameter(componentType, methodParameter.getName()); - Parameter component = buildParameter(genericFromReceiver, placeholdersFromContext, subMethodParameter, componentType); - return new Parameter(component.getType().makeArray(), component.getName()); - } - ClassNode resolved = resolveClassNodeGenerics(genericFromReceiver, placeholdersFromContext, paramType); - - return new Parameter(resolved, methodParameter.getName()); - } - - /** - * Returns true if a class node makes use of generic types. If the class node represents an - * array type, then checks if the component type is using generics. - * - * @param cn a class node for which to check if it is using generics - * @return true if the type (or component type) is using generics - */ - public static boolean isUsingGenericsOrIsArrayUsingGenerics(final ClassNode cn) { - if (cn.isArray()) { - return isUsingGenericsOrIsArrayUsingGenerics(cn.getComponentType()); - } - return (cn.isUsingGenerics() && cn.getGenericsTypes() != null); - } - - /** - * Given a generics type representing SomeClass<T,V> and a resolved placeholder map, returns a new generics type - * for which placeholders are resolved recursively. - */ - protected static GenericsType fullyResolve(GenericsType gt, final Map placeholders) { - GenericsType fromMap = placeholders.get(new GenericsTypeName(gt.getName())); - if (gt.isPlaceholder() && fromMap != null) { - gt = fromMap; - } - - ClassNode type = fullyResolveType(gt.getType(), placeholders); - ClassNode lowerBound = gt.getLowerBound(); - if (lowerBound != null) lowerBound = fullyResolveType(lowerBound, placeholders); - ClassNode[] upperBounds = gt.getUpperBounds(); - if (upperBounds != null) { - ClassNode[] copy = new ClassNode[upperBounds.length]; - for (int i = 0, upperBoundsLength = upperBounds.length; i < upperBoundsLength; i++) { - final ClassNode upperBound = upperBounds[i]; - copy[i] = fullyResolveType(upperBound, placeholders); - } - upperBounds = copy; - } - GenericsType genericsType = new GenericsType(type, upperBounds, lowerBound); - genericsType.setWildcard(gt.isWildcard()); - return genericsType; - } - - protected static ClassNode fullyResolveType(final ClassNode type, final Map placeholders) { - if (type.isArray()) { - return fullyResolveType(type.getComponentType(), placeholders).makeArray(); - } - if (!type.isUsingGenerics()) { - return type; - } - if (type.isGenericsPlaceHolder()) { - GenericsType gt = placeholders.get(new GenericsTypeName(type.getUnresolvedName())); - if (gt != null) { - return gt.getType(); - } - ClassNode cn = type.redirect(); - return cn != type ? cn : OBJECT_TYPE; - } - - GenericsType[] gts = type.getGenericsTypes(); - if (asBoolean(gts)) { - gts = gts.clone(); - for (int i = 0, n = gts.length; i < n; i += 1) { - GenericsType gt = gts[i]; - if (gt.isPlaceholder()) { String name = gt.getName(); - gt = placeholders.get(new GenericsTypeName(name)); - if (gt == null) gt = extractType(gts[i]).asGenericsType(); - // GROOVY-10364: skip placeholder from the enclosing context - if (gt.isPlaceholder() && gt.getName().equals(name)) continue; - - gts[i] = gt; - } else { - gts[i] = fullyResolve(gt, placeholders); - } - } - } - - ClassNode cn = type.getPlainNodeReference(); - cn.setGenericsTypes(gts); - return cn; - } - - /** - * Checks that the parameterized generics of an argument are compatible with the generics of the parameter. - * - * @param parameterType the parameter type of a method - * @param argumentType the type of the argument passed to the method - */ - protected static boolean typeCheckMethodArgumentWithGenerics(final ClassNode parameterType, final ClassNode argumentType, final boolean lastArg) { - if (UNKNOWN_PARAMETER_TYPE == argumentType) { // argument is null - return !isPrimitiveType(parameterType); - } - if (!isAssignableTo(argumentType, parameterType)) { - if (!lastArg || !parameterType.isArray() - || !isAssignableTo(argumentType, parameterType.getComponentType())) { - return false; // incompatible assignment - } - } - if (parameterType.isUsingGenerics() && argumentType.isUsingGenerics()) { - GenericsType gt = GenericsUtils.buildWildcardType(parameterType); - if (!gt.isCompatibleWith(argumentType)) { - boolean samCoercion = isSAMType(parameterType) && argumentType.equals(CLOSURE_TYPE); - if (!samCoercion) return false; - } - } else if (parameterType.isArray() && argumentType.isArray()) { - // verify component type - return typeCheckMethodArgumentWithGenerics(parameterType.getComponentType(), argumentType.getComponentType(), lastArg); - } else if (lastArg && parameterType.isArray()) { - // verify component type, but if we reach that point, the only possibility is that the argument is - // the last one of the call, so we're in the cast of a vargs call - // (otherwise, we face a type checker bug) - return typeCheckMethodArgumentWithGenerics(parameterType.getComponentType(), argumentType, lastArg); - } - return true; - } - - protected static boolean typeCheckMethodsWithGenerics(final ClassNode receiver, final ClassNode[] argumentTypes, final MethodNode candidateMethod) { - if (candidateMethod instanceof ExtensionMethodNode) { - ClassNode[] realTypes = new ClassNode[argumentTypes.length + 1]; - realTypes[0] = receiver; // object expression is implicit argument - System.arraycopy(argumentTypes, 0, realTypes, 1, argumentTypes.length); - MethodNode realMethod = ((ExtensionMethodNode) candidateMethod).getExtensionMethodNode(); - return typeCheckMethodsWithGenerics(realMethod.getDeclaringClass(), realTypes, realMethod, true); - } - - if (receiver.isUsingGenerics() - && isClassType(receiver) - && !isClassType(candidateMethod.getDeclaringClass())) { - return typeCheckMethodsWithGenerics(receiver.getGenericsTypes()[0].getType(), argumentTypes, candidateMethod); - } - - return typeCheckMethodsWithGenerics(receiver, argumentTypes, candidateMethod, false); - } - - private static boolean typeCheckMethodsWithGenerics(final ClassNode receiver, final ClassNode[] argumentTypes, final MethodNode candidateMethod, final boolean isExtensionMethod) { - Parameter[] parameters = candidateMethod.getParameters(); - if (parameters.length == 0 || parameters.length > argumentTypes.length) { - // this is a limitation that must be removed in a future version; we - // cannot check generic type arguments if there is default argument! - return true; - } - - boolean failure = false; - Set fixedPlaceHolders = Collections.emptySet(); - Map candidateGenerics = new HashMap<>(); - // correct receiver for inner class - // we assume the receiver is an instance of the declaring class of the - // candidate method, but findMethod() returns also outer class methods - // for that receiver; for now we skip receiver-based checks in that case - boolean skipBecauseOfInnerClassNotReceiver = !implementsInterfaceOrIsSubclassOf(receiver, candidateMethod.getDeclaringClass()); - if (!skipBecauseOfInnerClassNotReceiver) { - if (candidateMethod instanceof ConstructorNode) { - candidateGenerics = GenericsUtils.extractPlaceholders(receiver); - fixedPlaceHolders = new HashSet<>(candidateGenerics.keySet()); - } else { - failure = inferenceCheck(fixedPlaceHolders, candidateGenerics, candidateMethod.getDeclaringClass(), receiver, false); - - GenericsType[] gts = candidateMethod.getGenericsTypes(); - if (candidateMethod.isStatic()) { - candidateGenerics.clear(); // not in scope - } else if (gts != null) { - // first remove hidden params - for (GenericsType gt : gts) { - candidateGenerics.remove(new GenericsTypeName(gt.getName())); - } - // GROOVY-8034: non-static method may use class generics - gts = applyGenericsContext(candidateGenerics, gts); - } - GenericsUtils.extractPlaceholders(GenericsUtils.makeClassSafe0(OBJECT_TYPE, gts), candidateGenerics); - - // the outside context parts till now define placeholder we are not allowed to - // generalize, thus we save that for later use... - // extension methods are special, since they set the receiver as - // first parameter. While we normally allow generalization for the first - // parameter, in case of an extension method we must not. - fixedPlaceHolders = extractResolvedPlaceHolders(candidateGenerics); - } - } - - int lastParamIndex = parameters.length - 1; - for (int i = 0, n = argumentTypes.length; i < n; i += 1) { - ClassNode parameterType = parameters[Math.min(i, lastParamIndex)].getOriginType(); - ClassNode argumentType = StaticTypeCheckingVisitor.wrapTypeIfNecessary(argumentTypes[i]); - failure |= inferenceCheck(fixedPlaceHolders, candidateGenerics, parameterType, argumentType, i >= lastParamIndex); - - if (i == 0 && isExtensionMethod) { // re-load fixed names for extension - fixedPlaceHolders = extractResolvedPlaceHolders(candidateGenerics); - } - } - return !failure; - } - - private static Set extractResolvedPlaceHolders(final Map resolvedMethodGenerics) { - if (resolvedMethodGenerics.isEmpty()) return Collections.emptySet(); - Set result = new HashSet<>(); - for (Map.Entry entry : resolvedMethodGenerics.entrySet()) { - GenericsType value = entry.getValue(); - if (value.isPlaceholder()) continue; - result.add(entry.getKey()); - } - return result; - } - - private static boolean inferenceCheck(final Set fixedPlaceHolders, final Map resolvedMethodGenerics, ClassNode type, final ClassNode wrappedArgument, final boolean lastArg) { - // GROOVY-8090: handle generics varargs like "T x = ...; Arrays.asList(x)" - if (lastArg && type.isArray() && type.getComponentType().isGenericsPlaceHolder() - && !wrappedArgument.isArray() && wrappedArgument.isGenericsPlaceHolder()) { - type = type.getComponentType(); - } - // the context we compare with in the end is the one of the callsite - // so far we specified the context of the method declaration only - // thus for each argument, we try to find the connected generics first - Map connections = new LinkedHashMap<>(); - extractGenericsConnections(connections, wrappedArgument, type); - - // each new connection must comply with previous connections - for (Map.Entry entry : connections.entrySet()) { - GenericsType candidate = entry.getValue(), resolved = resolvedMethodGenerics.get(entry.getKey()); - if (resolved == null || (candidate.isPlaceholder() && !hasNonTrivialBounds(candidate))) continue; - - if (!compatibleConnection(resolved, candidate)) { - if (!resolved.isPlaceholder() && !resolved.isWildcard() - && !fixedPlaceHolders.contains(entry.getKey())) { - // GROOVY-5692, GROOVY-10006: multiple witnesses - if (compatibleConnection(candidate, resolved)) { - // was "T=Integer" and now is "T=Number" or "T=Object" - resolvedMethodGenerics.put(entry.getKey(), candidate); - continue; - } else if (!candidate.isPlaceholder() && !candidate.isWildcard()) { - // combine "T=Integer" and "T=String" to produce "T=? extends Serializable & Comparable<...>" - ClassNode lub = lowestUpperBound(candidate.getType(), resolved.getType()); - resolvedMethodGenerics.put(entry.getKey(), lub.asGenericsType()); - continue; - } - } - return true; // incompatible - } - } - - connections.keySet().removeAll(fixedPlaceHolders); // GROOVY-10337 - - // apply the new information to refine the method level information so - // that the information slowly becomes information for the callsite - applyGenericsConnections(connections, resolvedMethodGenerics); - // since it is possible that the callsite uses some generics as well, - // we may have to add additional elements here - addMissingEntries(connections, resolvedMethodGenerics); - // to finally see if the parameter and the argument fit together, - // we use the provided information to transform the parameter - // into something that can exist in the callsite context - ClassNode resolvedType = applyGenericsContext(resolvedMethodGenerics, type); - return !typeCheckMethodArgumentWithGenerics(resolvedType, wrappedArgument, lastArg); - } - - private static boolean compatibleConnection(final GenericsType resolved, final GenericsType connection) { - if (resolved.isPlaceholder() - && resolved.getUpperBounds() != null - && resolved.getUpperBounds().length == 1 - && !resolved.getUpperBounds()[0].isGenericsPlaceHolder() - && resolved.getUpperBounds()[0].getName().equals("java.lang.Object")) { - return true; - } - - ClassNode resolvedType; - if (hasNonTrivialBounds(resolved)) { - resolvedType = getCombinedBoundType(resolved); - resolvedType = resolvedType.redirect().getPlainNodeReference(); - } else if (!resolved.isPlaceholder()) { - resolvedType = resolved.getType().getPlainNodeReference(); - } else { - return true; - } - - GenericsType gt; - if (connection.isWildcard()) { - gt = connection; - } else { // test compatibility with "? super Type" - ClassNode lowerBound = connection.getType().getPlainNodeReference(); - if (hasNonTrivialBounds(connection)) { - lowerBound.setGenericsTypes(new GenericsType[] {connection}); - } - gt = new GenericsType(makeWithoutCaching("?"), null, lowerBound); - gt.setWildcard(true); - } - return gt.isCompatibleWith(resolvedType); - } - - private static void addMissingEntries(final Map connections, final Map resolved) { - for (Map.Entry entry : connections.entrySet()) { - if (resolved.containsKey(entry.getKey())) continue; - GenericsType gt = entry.getValue(); - ClassNode cn = gt.getType(); - if (cn.redirect() == UNKNOWN_PARAMETER_TYPE) continue; - resolved.put(entry.getKey(), gt); - } - } - - public static ClassNode resolveClassNodeGenerics(Map resolvedPlaceholders, final Map placeholdersFromContext, final ClassNode currentType) { - ClassNode type = currentType; // GROOVY-10280, et al. - type = applyGenericsContext(resolvedPlaceholders, type); - type = applyGenericsContext(placeholdersFromContext, type); - return type; - } - - static void applyGenericsConnections(final Map connections, final Map resolvedPlaceholders) { - if (connections == null || connections.isEmpty()) return; - for (Map.Entry entry : resolvedPlaceholders.entrySet()) { - // entry could be T=T, T=T extends U, T=V, T=String, T=? extends String, etc. - GenericsType oldValue = entry.getValue(); - if (oldValue.isPlaceholder()) { // T=T or V, not T=String or ? ... - GenericsTypeName name = new GenericsTypeName(oldValue.getName()); - GenericsType newValue = connections.get(name); // find "V" in T=V - if (newValue == oldValue) continue; - if (newValue == null) { - newValue = connections.get(entry.getKey()); - if (newValue != null) { // GROOVY-10315, GROOVY-10317 - newValue = getCombinedGenericsType(oldValue, newValue); - } - } - if (newValue == null) { - entry.setValue(newValue = applyGenericsContext(connections, oldValue)); - } else if (!newValue.isPlaceholder() || newValue != resolvedPlaceholders.get(name)) { - // GROOVY-6787: Don't override the original if the replacement doesn't respect the bounds otherwise - // the original bounds are lost, which can result in accepting an incompatible type as an argument! - ClassNode replacementType = extractType(newValue); - ClassNode suitabilityType = !replacementType.isGenericsPlaceHolder() - ? replacementType : Optional.ofNullable(replacementType.getGenericsTypes()) - .map(gts -> extractType(gts[0])).orElse(replacementType.redirect()); - - if (oldValue.isCompatibleWith(suitabilityType)) { - if (newValue.isWildcard() && newValue.getLowerBound() == null && newValue.getUpperBounds() == null) { - // GROOVY-9998: apply upper/lower bound for unknown - entry.setValue(replacementType.asGenericsType()); - } else { - entry.setValue(newValue); - } - } - } - } - } - } - - private static ClassNode extractType(GenericsType gt) { - ClassNode cn; - if (!gt.isPlaceholder()) { - cn = getCombinedBoundType(gt); - } else { - // discard the placeholder - cn = gt.getType().redirect(); - - if (gt.getType().getGenericsTypes() != null) - gt = gt.getType().getGenericsTypes()[0]; - - if (gt.getLowerBound() != null) { - cn = gt.getLowerBound(); - } else if (asBoolean(gt.getUpperBounds())) { - cn = gt.getUpperBounds()[0]; - } - } - return cn; - } - - private static boolean equalIncludingGenerics(final GenericsType one, final GenericsType two) { - if (one == two) return true; - if (one.isWildcard() != two.isWildcard()) return false; - if (one.isPlaceholder() != two.isPlaceholder()) return false; - if (!equalIncludingGenerics(one.getType(), two.getType())) return false; - ClassNode lower1 = one.getLowerBound(); - ClassNode lower2 = two.getLowerBound(); - if ((lower1 == null) ^ (lower2 == null)) return false; - if (lower1 != lower2) { - if (!equalIncludingGenerics(lower1, lower2)) return false; - } - ClassNode[] upper1 = one.getUpperBounds(); - ClassNode[] upper2 = two.getUpperBounds(); - if ((upper1 == null) ^ (upper2 == null)) return false; - if (upper1 != upper2) { - if (upper1.length != upper2.length) return false; - for (int i = 0, n = upper1.length; i < n; i += 1) { - if (!equalIncludingGenerics(upper1[i], upper2[i])) return false; - } - } - return true; - } - - private static boolean equalIncludingGenerics(final ClassNode one, final ClassNode two) { - if (one == two) return true; - if (one.isGenericsPlaceHolder() != two.isGenericsPlaceHolder()) return false; - if (!one.equals(two)) return false; - GenericsType[] gt1 = one.getGenericsTypes(); - GenericsType[] gt2 = two.getGenericsTypes(); - if ((gt1 == null) ^ (gt2 == null)) return false; - if (gt1 != gt2) { - if (gt1.length != gt2.length) return false; - for (int i = 0, n = gt1.length; i < n; i += 1) { - if (!equalIncludingGenerics(gt1[i], gt2[i])) return false; - } - } - return true; - } - - /** - * Uses supplied type to make a connection from usage to declaration. - *

      - * The method operates in two modes: - *

        - *
      • For type !instanceof target a structural compare will be done - * (for example Type<T> and List<E> to get E -> T) - *
      • If type equals target, a structural match is done as well - * (for example Collection<T> and Collection<E> to get E -> T) - *
      • Otherwise we climb the hierarchy to find a case of type equals target - * to then execute the structural match, while applying possibly existing - * generics contexts on the way (for example for IntRange and Collection<E> - * to get E -> Integer, since IntRange is an AbstractList<Integer>) - *
      - * Should the target not have any generics this method does nothing. - */ - static void extractGenericsConnections(final Map connections, final ClassNode type, final ClassNode target) { - if (target == null || target == type || (!target.isGenericsPlaceHolder() && !isUsingGenericsOrIsArrayUsingGenerics(target))) return; - if (type == null || type == UNKNOWN_PARAMETER_TYPE) return; - - if (target.isGenericsPlaceHolder()) { - storeGenericsConnection(connections, target.getUnresolvedName(), new GenericsType(type)); - - } else if (type.isGenericsPlaceHolder()) { - // "T extends java.util.List -> java.util.List" vs "java.util.List" - extractGenericsConnections(connections, extractType(new GenericsType(type)), target); - - } else if (type.isArray() && target.isArray()) { - extractGenericsConnections(connections, type.getComponentType(), target.getComponentType()); - - } else if (type.equals(CLOSURE_TYPE) && isSAMType(target)) { - // GROOVY-9974, GROOVY-10052: Lambda, Closure, Pointer or Reference for SAM-type receiver - ClassNode returnType = StaticTypeCheckingVisitor.wrapTypeIfNecessary(GenericsUtils.parameterizeSAM(target).getV2()); - extractGenericsConnections(connections, type.getGenericsTypes(), new GenericsType[] {returnType.asGenericsType()}); - - } else if (type.equals(target)) { - extractGenericsConnections(connections, type.getGenericsTypes(), target.getGenericsTypes()); - - } else if (implementsInterfaceOrIsSubclassOf(type, target)) { - ClassNode superClass = getNextSuperClass(type, target); - if (superClass != null) { - if (GenericsUtils.hasUnresolvedGenerics(superClass)) { - Map spec = GenericsUtils.createGenericsSpec(type); - if (!spec.isEmpty()) { - superClass = GenericsUtils.correctToGenericsSpecRecurse(spec, superClass); - } - } - extractGenericsConnections(connections, superClass, target); - } else { - // if we reach here, we have an unhandled case - throw new GroovyBugError("The type " + type + " seems not to normally extend " + target + ". Sorry, I cannot handle this."); - } - } - } - - private static void extractGenericsConnections(final Map connections, final GenericsType[] usage, final GenericsType[] declaration) { - // if declaration does not provide generics, there is no connection to make - if (usage == null || declaration == null || declaration.length == 0) return; - if (usage.length != declaration.length) return; - - // both have generics - for (int i = 0, n = usage.length; i < n; i += 1) { - GenericsType ui = usage[i], di = declaration[i]; - if (di.isPlaceholder()) { - storeGenericsConnection(connections, di.getName(), ui); - } else if (di.isWildcard()) { - ClassNode lowerBound = di.getLowerBound(), upperBounds[] = di.getUpperBounds(); - if (ui.isWildcard()) { - extractGenericsConnections(connections, ui.getLowerBound(), lowerBound); - extractGenericsConnections(connections, ui.getUpperBounds(), upperBounds); - } else if (!isUnboundedWildcard(di)) { - ClassNode boundType = lowerBound != null ? lowerBound : upperBounds[0]; - if (boundType.isGenericsPlaceHolder()) { // GROOVY-9998 - String placeholderName = boundType.getUnresolvedName(); - ui = new GenericsType(ui.getType()); ui.setWildcard(true); - storeGenericsConnection(connections, placeholderName, ui); - } else { // di like "? super Collection" and ui like "List" - extractGenericsConnections(connections, ui.getType(), boundType); - } - } - } else { - extractGenericsConnections(connections, ui.getType(), di.getType()); - } - } - } - - private static void extractGenericsConnections(final Map connections, final ClassNode[] usage, final ClassNode[] declaration) { - if (usage == null || declaration == null || declaration.length == 0) return; - // both have generics - for (int i = 0, n = usage.length; i < n; i += 1) { - ClassNode ui = usage[i]; - ClassNode di = declaration[i]; - if (di.isGenericsPlaceHolder()) { - storeGenericsConnection(connections, di.getUnresolvedName(), new GenericsType(ui)); - } else if (di.isUsingGenerics()) { - extractGenericsConnections(connections, ui.getGenericsTypes(), di.getGenericsTypes()); - } - } - } - - private static void storeGenericsConnection(final Map connections, final String placeholderName, final GenericsType gt) { - //connections.merge(new GenericsTypeName(placeholderName), gt, (gt1, gt2) -> getCombinedGenericsType(gt1, gt2)); - connections.put(new GenericsTypeName(placeholderName), gt); - } - - static GenericsType[] getGenericsWithoutArray(final ClassNode type) { - if (type.isArray()) return getGenericsWithoutArray(type.getComponentType()); - return type.getGenericsTypes(); - } - - static GenericsType[] applyGenericsContext(final Map spec, final GenericsType[] gts) { - if (gts == null || spec == null || spec.isEmpty()) return gts; - - int n = gts.length; - if (n == 0) return gts; - GenericsType[] newGTs = new GenericsType[n]; - for (int i = 0; i < n; i += 1) { - newGTs[i] = applyGenericsContext(spec, gts[i]); - } - return newGTs; - } - - private static GenericsType applyGenericsContext(final Map spec, final GenericsType gt) { - if (gt.isPlaceholder()) { - GenericsTypeName name = new GenericsTypeName(gt.getName()); - GenericsType specType = spec.get(name); - if (specType != null) return specType; - if (hasNonTrivialBounds(gt)) { - GenericsType newGT = new GenericsType(gt.getType(), applyGenericsContext(spec, gt.getUpperBounds()), applyGenericsContext(spec, gt.getLowerBound())); - newGT.setPlaceholder(true); - return newGT; - } - return gt; - } else if (gt.isWildcard()) { - GenericsType newGT = new GenericsType(gt.getType(), applyGenericsContext(spec, gt.getUpperBounds()), applyGenericsContext(spec, gt.getLowerBound())); - newGT.setWildcard(true); - return newGT; - } - ClassNode type = gt.getType(); - ClassNode newType; - if (type.isArray()) { - newType = applyGenericsContext(spec, type.getComponentType()).makeArray(); - } else { - if (type.getGenericsTypes() == null) { - return gt; - } - newType = type.getPlainNodeReference(); - newType.setGenericsPlaceHolder(type.isGenericsPlaceHolder()); - newType.setGenericsTypes(applyGenericsContext(spec, type.getGenericsTypes())); - } - return new GenericsType(newType); - } - - private static boolean hasNonTrivialBounds(final GenericsType gt) { - if (gt.isWildcard()) { - return true; - } - if (gt.getLowerBound() != null) { - return true; - } - ClassNode[] upperBounds = gt.getUpperBounds(); - if (upperBounds != null) { - return (upperBounds.length != 1 || upperBounds[0].isGenericsPlaceHolder() || !isObjectType(upperBounds[0])); - } - return false; - } - - static ClassNode[] applyGenericsContext(final Map spec, final ClassNode[] types) { - if (types == null) return null; - final int nTypes = types.length; - ClassNode[] newTypes = new ClassNode[nTypes]; - for (int i = 0; i < nTypes; i += 1) { - newTypes[i] = applyGenericsContext(spec, types[i]); - } - return newTypes; - } - - static ClassNode applyGenericsContext(final Map spec, final ClassNode type) { - if (type == null || !isUsingGenericsOrIsArrayUsingGenerics(type)) { - return type; - } - if (type.isArray()) { - return applyGenericsContext(spec, type.getComponentType()).makeArray(); - } - - GenericsType[] gt = type.getGenericsTypes(); - if (asBoolean(spec)) { - gt = applyGenericsContext(spec, gt); - } - if (!type.isGenericsPlaceHolder()) { // convert Type to Type<...> - ClassNode cn = type.getPlainNodeReference(); - cn.setGenericsTypes(gt); - return cn; - } - - if (!gt[0].isPlaceholder()) { // convert T to Type or Type<...> - return getCombinedBoundType(gt[0]); - } - - if (type.getGenericsTypes()[0] != gt[0]) { // convert T to X - ClassNode cn = make(gt[0].getName()); - cn.setRedirect(gt[0].getType()); - cn.setGenericsPlaceHolder(true); - cn.setGenericsTypes(gt); - return cn; - } - - return type; // nothing to do - } - - static ClassNode getCombinedBoundType(final GenericsType genericsType) { - // TODO: This method should really return some kind of meta ClassNode - // representing the combination of all bounds. The code here just picks - // something out to be able to proceed and is not actually correct. - if (hasNonTrivialBounds(genericsType)) { - if (genericsType.getLowerBound() != null) return OBJECT_TYPE; // GROOVY-10328 - if (genericsType.getUpperBounds() != null) return genericsType.getUpperBounds()[0]; - } - return genericsType.getType(); - } - - static GenericsType getCombinedGenericsType(GenericsType gt1, GenericsType gt2) { - // GROOVY-9998, GROOVY-10499: unpack "?" that is from "? extends T" - if (isUnboundedWildcard(gt1)) gt1 = gt1.getType().asGenericsType(); - if (isUnboundedWildcard(gt2)) gt2 = gt2.getType().asGenericsType(); - ClassNode cn1 = GenericsUtils.makeClassSafe0(CLASS_Type, gt1); - ClassNode cn2 = GenericsUtils.makeClassSafe0(CLASS_Type, gt2); - ClassNode lub = lowestUpperBound(cn1, cn2); - return lub.getGenericsTypes()[0]; - } - - /** - * Apply the bounds from the declared type when the using type simply declares a parameter as an unbounded wildcard. - * - * @param type A parameterized type - * @return A parameterized type with more precise wildcards - */ - static ClassNode boundUnboundedWildcards(final ClassNode type) { - if (type.isArray()) { - return boundUnboundedWildcards(type.getComponentType()).makeArray(); - } - ClassNode redirect = type.redirect(); - if (redirect == null || redirect == type || !isUsingGenericsOrIsArrayUsingGenerics(redirect)) { - return type; - } - ClassNode newType = type.getPlainNodeReference(); - newType.setGenericsPlaceHolder(type.isGenericsPlaceHolder()); - newType.setGenericsTypes(boundUnboundedWildcards(type.getGenericsTypes(), redirect.getGenericsTypes())); - return newType; - } - - private static GenericsType[] boundUnboundedWildcards(final GenericsType[] actual, final GenericsType[] declared) { - int n = actual.length; GenericsType[] newTypes = new GenericsType[n]; - for (int i = 0; i < n; i += 1) { - newTypes[i] = boundUnboundedWildcard(actual[i], declared[i]); - } - return newTypes; - } - - private static GenericsType boundUnboundedWildcard(final GenericsType actual, final GenericsType declared) { - if (!isUnboundedWildcard(actual)) return actual; - ClassNode lowerBound = declared.getLowerBound(); - ClassNode[] upperBounds = declared.getUpperBounds(); - if (lowerBound != null) { - assert upperBounds == null; - } else if (upperBounds == null) { - upperBounds = new ClassNode[]{OBJECT_TYPE}; - } else if (declared.isPlaceholder()) { - upperBounds = upperBounds.clone(); - for (int i = 0, n = upperBounds.length; i < n; i += 1) { - // GROOVY-10055, GROOVY-10619: purge self references - if (GenericsUtils.extractPlaceholders(upperBounds[i]) - .containsKey(new GenericsTypeName(declared.getName()))) - upperBounds[i] = upperBounds[i].getPlainNodeReference(); - } - } - GenericsType newType = new GenericsType(makeWithoutCaching("?"), upperBounds, lowerBound); - newType.setWildcard(true); - return newType; - } - - public static boolean isUnboundedWildcard(final GenericsType gt) { - if (gt.isWildcard() && gt.getLowerBound() == null) { - ClassNode[] upperBounds = gt.getUpperBounds(); - return (upperBounds == null || upperBounds.length == 0 || (upperBounds.length == 1 - && isObjectType(upperBounds[0]) && !upperBounds[0].isGenericsPlaceHolder())); - } - return false; - } - - static Map extractGenericsParameterMapOfThis(final TypeCheckingContext context) { - ClassNode cn = context.getEnclosingClassNode(); - MethodNode mn = context.getEnclosingMethod(); - // GROOVY-9570: find the innermost class or method - if (cn != null && cn.getEnclosingMethod() == mn) { - return extractGenericsParameterMapOfThis(cn); - } else { - return extractGenericsParameterMapOfThis(mn); - } - } - - private static Map extractGenericsParameterMapOfThis(final MethodNode mn) { - if (mn == null) return null; - - Map map = null; - if (!mn.isStatic()) { - map = extractGenericsParameterMapOfThis(mn.getDeclaringClass()); - } - - GenericsType[] gts = mn.getGenericsTypes(); - if (gts != null) { - if (map == null) map = new HashMap<>(); - for (GenericsType gt : gts) { - assert gt.isPlaceholder(); - map.put(new GenericsTypeName(gt.getName()), gt); - } - } - - return map; - } - - private static Map extractGenericsParameterMapOfThis(final ClassNode cn) { - if (cn == null) return null; - - Map map = null; - if ((cn.getModifiers() & Opcodes.ACC_STATIC) == 0) { - if (cn.getEnclosingMethod() != null) { - map = extractGenericsParameterMapOfThis(cn.getEnclosingMethod()); - } else if (cn.getOuterClass() != null) { - map = extractGenericsParameterMapOfThis(cn.getOuterClass()); - } - } - - if (!(cn instanceof InnerClassNode && ((InnerClassNode) cn).isAnonymous())) { - GenericsType[] gts = cn.getGenericsTypes(); - if (gts != null) { - if (map == null) map = new HashMap<>(); - for (GenericsType gt : gts) { - assert gt.isPlaceholder(); - map.put(new GenericsTypeName(gt.getName()), gt); - } - } - } - - return map; - } - - /** - * Filter methods according to visibility - * - * @param methodNodeList method nodes to filter - * @param enclosingClassNode the enclosing class - * @return filtered method nodes - * @since 3.0.0 - */ - public static List filterMethodsByVisibility(final List methodNodeList, final ClassNode enclosingClassNode) { - if (!asBoolean(methodNodeList)) { - return StaticTypeCheckingVisitor.EMPTY_METHODNODE_LIST; - } - - List result = new LinkedList<>(); - - boolean isEnclosingInnerClass = enclosingClassNode instanceof InnerClassNode; - List outerClasses = enclosingClassNode.getOuterClasses(); - - outer: - for (MethodNode methodNode : methodNodeList) { - if (methodNode instanceof ExtensionMethodNode) { - result.add(methodNode); - continue; - } - - ClassNode declaringClass = methodNode.getDeclaringClass(); - - if (isEnclosingInnerClass) { - for (ClassNode outerClass : outerClasses) { - if (outerClass.isDerivedFrom(declaringClass)) { - if (outerClass.equals(declaringClass)) { - result.add(methodNode); - continue outer; - } else { - if (methodNode.isPublic() || methodNode.isProtected()) { - result.add(methodNode); - continue outer; - } - } - } - } - } - - if (declaringClass instanceof InnerClassNode) { - if (declaringClass.getOuterClasses().contains(enclosingClassNode)) { - result.add(methodNode); - continue; - } - } - - if (methodNode.isPrivate() && !enclosingClassNode.equals(declaringClass)) { - continue; - } - if (methodNode.isProtected() - && !enclosingClassNode.isDerivedFrom(declaringClass) - && !samePackageName(enclosingClassNode, declaringClass)) { - continue; - } - if (methodNode.isPackageScope() && !samePackageName(enclosingClassNode, declaringClass)) { - continue; - } - - result.add(methodNode); - } - - return result; - } - - /** - * @return true if the class node is either a GString or the LUB of String and GString. - */ - public static boolean isGStringOrGStringStringLUB(final ClassNode node) { - return isGStringType(node) || GSTRING_STRING_CLASSNODE.equals(node); - } - - /** - * @param node the node to be tested - * @return true if the node is using generics types and one of those types is a gstring or string/gstring lub - */ - public static boolean isParameterizedWithGStringOrGStringString(final ClassNode node) { - if (node.isArray()) return isParameterizedWithGStringOrGStringString(node.getComponentType()); - if (node.isUsingGenerics()) { - GenericsType[] genericsTypes = node.getGenericsTypes(); - if (genericsTypes != null) { - for (GenericsType genericsType : genericsTypes) { - if (isGStringOrGStringStringLUB(genericsType.getType())) return true; - } - } - } - return node.getSuperClass() != null && isParameterizedWithGStringOrGStringString(node.getUnresolvedSuperClass()); - } - - /** - * @param node the node to be tested - * @return true if the node is using generics types and one of those types is a string - */ - public static boolean isParameterizedWithString(final ClassNode node) { - if (node.isArray()) return isParameterizedWithString(node.getComponentType()); - if (node.isUsingGenerics()) { - GenericsType[] genericsTypes = node.getGenericsTypes(); - if (genericsTypes != null) { - for (GenericsType genericsType : genericsTypes) { - if (STRING_TYPE.equals(genericsType.getType())) return true; - } - } - } - return node.getSuperClass() != null && isParameterizedWithString(node.getUnresolvedSuperClass()); - } - - /** - * Determines if node is a raw type or references any generics placeholders. - */ - public static boolean missesGenericsTypes(ClassNode cn) { - while (cn.isArray()) cn = cn.getComponentType(); - GenericsType[] cnGenerics = cn.getGenericsTypes(); - GenericsType[] rnGenerics = cn.redirect().getGenericsTypes(); - return cnGenerics == null || cnGenerics.length == 0 ? rnGenerics != null : GenericsUtils.hasUnresolvedGenerics(cn); - } - - /** - * A helper method that can be used to evaluate expressions as found in annotation - * parameters. For example, it will evaluate a constant, be it referenced directly as - * an integer or as a reference to a field. - *

      - * If this method throws an exception, then the expression cannot be evaluated on its own. - * - * @param expr the expression to be evaluated - * @param config the compiler configuration - * @return the result of the expression - */ - public static Object evaluateExpression(final Expression expr, final CompilerConfiguration config) { - Expression ce = expr instanceof CastExpression ? ((CastExpression) expr).getExpression() : expr; - if (ce instanceof ConstantExpression) { - if (expr.getType().equals(ce.getType())) - return ((ConstantExpression) ce).getValue(); - } else if (ce instanceof ListExpression) { - if (expr.getType().isArray() && expr.getType().getComponentType().equals(STRING_TYPE)) - return ((ListExpression) ce).getExpressions().stream().map(e -> evaluateExpression(e, config)).toArray(String[]::new); - } - - String className = "Expression$" + UUID.randomUUID().toString().replace('-', '$'); - ClassNode simpleClass = new ClassNode(className, Opcodes.ACC_PUBLIC, OBJECT_TYPE); - addGeneratedMethod(simpleClass, "eval", Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, OBJECT_TYPE, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, new ReturnStatement(expr)); - - // adjust configuration so class can be inspected by this JVM - CompilerConfiguration cc = new CompilerConfiguration(config); - cc.setPreviewFeatures(false); // unlikely to be required by expression - cc.setTargetBytecode(CompilerConfiguration.DEFAULT.getTargetBytecode()); - - CompilationUnit cu = new CompilationUnit(cc); - try { - cu.addClassNode(simpleClass); - cu.compile(Phases.CLASS_GENERATION); - List classes = cu.getClasses(); - Class aClass = cu.getClassLoader().defineClass(className, classes.get(0).getBytes()); - try { - return aClass.getMethod("eval").invoke(null); - } catch (ReflectiveOperationException e) { - throw new GroovyBugError(e); - } - } finally { - closeQuietly(cu.getClassLoader()); - } - } - - /** - * Collects all interfaces of a class node, including those defined by the - * super class. - * - * @param node a class for which we want to retrieve all interfaces - * @return a set of interfaces implemented by this class node - */ - @Deprecated - public static Set collectAllInterfaces(final ClassNode node) { - return GeneralUtils.getInterfacesAndSuperInterfaces(node); - } - - @Deprecated - public static ClassNode getCorrectedClassNode(final ClassNode cn, final ClassNode sc, final boolean completed) { - if (completed && GenericsUtils.hasUnresolvedGenerics(cn)) return sc.getPlainNodeReference(); - return GenericsUtils.correctToGenericsSpecRecurse(GenericsUtils.createGenericsSpec(cn), sc); - } - - /** - * Returns true if the class node represents a the class node for the Class class - * and if the parametrized type is a neither a placeholder or a wildcard. For example, - * the class node Class<Foo> where Foo is a class would return true, but the class - * node for Class<?> would return false. - * - * @param classNode a class node to be tested - * @return true if it is the class node for Class and its generic type is a real class - */ - public static boolean isClassClassNodeWrappingConcreteType(final ClassNode classNode) { - GenericsType[] genericsTypes = classNode.getGenericsTypes(); - return isClassType(classNode) - && classNode.isUsingGenerics() - && genericsTypes != null - && !genericsTypes[0].isPlaceholder() - && !genericsTypes[0].isWildcard(); - } - - public static List findSetters(final ClassNode cn, final String setterName, final boolean voidOnly) { - List result = new ArrayList<>(); - if (!cn.isInterface()) { - for (MethodNode method : cn.getMethods(setterName)) { - if (isSetter(method, voidOnly)) result.add(method); - } - } - for (ClassNode in : cn.getAllInterfaces()) { - for (MethodNode method : in.getDeclaredMethods(setterName)) { - if (isSetter(method, voidOnly)) result.add(method); - } - } - return result; - } - - private static boolean isSetter(final MethodNode mn, final boolean voidOnly) { - return (!voidOnly || mn.isVoidMethod()) && mn.getParameters().length == 1; - } - - public static ClassNode isTraitSelf(final VariableExpression vexp) { - if (Traits.THIS_OBJECT.equals(vexp.getName())) { - Variable accessedVariable = vexp.getAccessedVariable(); - ClassNode type = accessedVariable != null ? accessedVariable.getType() : null; - if (accessedVariable instanceof Parameter - && Traits.isTrait(type)) { - return type; - } - } - return null; - } - - //-------------------------------------------------------------------------- - - /** - * A DGM-like class which adds support for method calls which are handled - * specifically by the Groovy compiler. - */ - public static class ObjectArrayStaticTypesHelper { - public static T getAt(final T[] array, final int index) { - return array != null ? array[index] : null; - } - public static void putAt(final T[] array, final int index, final U value) { - if (array != null) { - array[index] = value; - } - } - } - - public static class BooleanArrayStaticTypesHelper { - public static Boolean getAt(final boolean[] array, final int index) { - return array != null ? array[index] : null; - } - public static void putAt(final boolean[] array, final int index, final boolean value) { - if (array != null) { - array[index] = value; - } - } - } - - public static class CharArrayStaticTypesHelper { - public static Character getAt(final char[] array, final int index) { - return array != null ? array[index] : null; - } - public static void putAt(final char[] array, final int index, final char value) { - if (array != null) { - array[index] = value; - } - } - } - - public static class ByteArrayStaticTypesHelper { - public static Byte getAt(final byte[] array, final int index) { - return array != null ? array[index] : null; - } - public static void putAt(final byte[] array, final int index, final byte value) { - if (array != null) { - array[index] = value; - } - } - } - - public static class ShortArrayStaticTypesHelper { - public static Short getAt(final short[] array, final int index) { - return array != null ? array[index] : null; - } - public static void putAt(final short[] array, final int index, final short value) { - if (array != null) { - array[index] = value; - } - } - } - - public static class IntArrayStaticTypesHelper { - public static Integer getAt(final int[] array, final int index) { - return array != null ? array[index] : null; - } - public static void putAt(final int[] array, final int index, final int value) { - if (array != null) { - array[index] = value; - } - } - } - - public static class LongArrayStaticTypesHelper { - public static Long getAt(final long[] array, final int index) { - return array != null ? array[index] : null; - } - public static void putAt(final long[] array, final int index, final long value) { - if (array != null) { - array[index] = value; - } - } - } - - public static class FloatArrayStaticTypesHelper { - public static Float getAt(final float[] array, final int index) { - return array != null ? array[index] : null; - } - public static void putAt(final float[] array, final int index, final float value) { - if (array != null) { - array[index] = value; - } - } - } - - public static class DoubleArrayStaticTypesHelper { - public static Double getAt(final double[] array, final int index) { - return array != null ? array[index] : null; - } - public static void putAt(final double[] array, final int index, final double value) { - if (array != null) { - array[index] = value; - } - } - } -} diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java index afb5e40ec1..d2817314b1 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java @@ -78,6 +78,7 @@ import org.codehaus.groovy.ast.expr.PropertyExpression; import org.codehaus.groovy.ast.expr.RangeExpression; import org.codehaus.groovy.ast.expr.SpreadExpression; +import org.codehaus.groovy.ast.expr.SpreadMapExpression; import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; import org.codehaus.groovy.ast.expr.TernaryExpression; import org.codehaus.groovy.ast.expr.TupleExpression; @@ -854,9 +855,10 @@ && isAssignment(enclosingBinaryExpression.getOperation().getType())) { if (!isEmptyDeclaration && isAssignment(op)) { if (rightExpression instanceof ConstructorCallExpression) inferDiamondType((ConstructorCallExpression) rightExpression, lType); - // GRECLIPSE edit -- GROOVY-10235, GROOVY-10324, et al. + + // handle unchecked assignment: List list = [] resultType = adjustForTargetType(resultType, lType); - // GRECLIPSE end + ClassNode originType = getOriginalDeclarationType(leftExpression); typeCheckAssignment(expression, leftExpression, originType, rightExpression, resultType); // check for implicit conversion like "String a = 123", "int[] b = [1,2,3]", "List c = [].stream()", etc. @@ -936,7 +938,7 @@ private void applyTargetType(final ClassNode target, final Expression source) { if (source instanceof ClosureExpression) { inferParameterAndReturnTypesOfClosureOnRHS(target, (ClosureExpression) source); } else if (source instanceof MethodReferenceExpression) { - LambdaExpression lambdaExpression = constructLambdaExpressionForMethodReference(target); + LambdaExpression lambdaExpression = constructLambdaExpressionForMethodReference(target, (MethodReferenceExpression) source); inferParameterAndReturnTypesOfClosureOnRHS(target, lambdaExpression); source.putNodeMetaData(CONSTRUCTED_LAMBDA_EXPRESSION, lambdaExpression); @@ -1033,6 +1035,9 @@ private boolean ensureValidSetter(final Expression expression, final Expression for (MethodNode setter : setterInfo.setters) { ClassNode lType = setterType.apply(setter); ClassNode rType = getDeclaredOrInferredType(valueExpression); + if (lType.isArray() && valueExpression instanceof ListExpression) { + rType = inferLoopElementType(rType).makeArray(); // GROOVY-7506 + } if (checkCompatibleAssignmentTypes(lType, rType, valueExpression, false)) { methodTarget = setterCall.apply(castX(lType, valueExpression)); if (methodTarget != null) { @@ -1279,13 +1284,13 @@ private void addMapAssignmentConstructorErrors(final ClassNode leftRedirect, fin checkGroovyConstructorMap(leftExpression, leftRedirect, mapExpression); } - private void checkTypeGenerics(final ClassNode leftExpressionType, final ClassNode wrappedRHS, final Expression rightExpression) { + private void checkTypeGenerics(final ClassNode leftExpressionType, final ClassNode rightExpressionType, final Expression rightExpression) { if (leftExpressionType.isUsingGenerics() - && !isNullConstant(rightExpression) + && !missesGenericsTypes(rightExpressionType) && !(rightExpression instanceof ClosureExpression) // GROOVY-10277 - && !UNKNOWN_PARAMETER_TYPE.equals(wrappedRHS) && !missesGenericsTypes(wrappedRHS) - && !GenericsUtils.buildWildcardType(leftExpressionType).isCompatibleWith(wrappedRHS)) - addStaticTypeError("Incompatible generic argument types. Cannot assign " + prettyPrintType(wrappedRHS) + " to: " + prettyPrintType(leftExpressionType), rightExpression); + && !isNullConstant(rightExpression) && !UNKNOWN_PARAMETER_TYPE.equals(rightExpressionType) + && !GenericsUtils.buildWildcardType(leftExpressionType).isCompatibleWith(wrapTypeIfNecessary(rightExpressionType))) + addStaticTypeError("Incompatible generic argument types. Cannot assign " + prettyPrintType(rightExpressionType) + " to: " + prettyPrintType(leftExpressionType), rightExpression); } private boolean hasGStringStringError(final ClassNode leftExpressionType, final ClassNode wrappedRHS, final Expression rightExpression) { @@ -1316,23 +1321,22 @@ protected void typeCheckAssignment(final BinaryExpression assignmentExpression, // TODO: need errors for write-only too! if (addedReadOnlyPropertyError(leftExpression)) return; - ClassNode rTypeWrapped = adjustTypeForSpreading(rightExpressionType, leftExpression); + ClassNode rType = adjustTypeForSpreading(rightExpressionType, leftExpression); - if (!checkCompatibleAssignmentTypes(leftExpressionType, rTypeWrapped, rightExpression)) { - if (!extension.handleIncompatibleAssignment(leftExpressionType, rightExpressionType, assignmentExpression)) { + if (!checkCompatibleAssignmentTypes(leftExpressionType, rType, rightExpression)) { + if (!extension.handleIncompatibleAssignment(leftExpressionType, rType, assignmentExpression)) { addAssignmentError(leftExpressionType, rightExpressionType, rightExpression); } } else { ClassNode lTypeRedirect = leftExpressionType.redirect(); - addPrecisionErrors(lTypeRedirect, leftExpressionType, rightExpressionType, rightExpression); + addPrecisionErrors(lTypeRedirect, leftExpressionType, rType, rightExpression); if (rightExpression instanceof ListExpression) { addListAssignmentConstructorErrors(lTypeRedirect, leftExpressionType, rightExpressionType, rightExpression, assignmentExpression); } else if (rightExpression instanceof MapExpression) { addMapAssignmentConstructorErrors(lTypeRedirect, leftExpression, rightExpression); } - if (!hasGStringStringError(leftExpressionType, rTypeWrapped, rightExpression) - && !isConstructorAbbreviation(leftExpressionType, rightExpression)) { - checkTypeGenerics(leftExpressionType, rTypeWrapped, rightExpression); + if (!hasGStringStringError(leftExpressionType, rType, rightExpression) && !isConstructorAbbreviation(leftExpressionType, rightExpression)) { + checkTypeGenerics(leftExpressionType, rType, rightExpression); } } } @@ -2015,6 +2019,10 @@ public static ClassNode inferLoopElementType(final ClassNode collectionType) { ClassNode col = GenericsUtils.parameterizeType(collectionType, ENUMERATION_TYPE); componentType = getCombinedBoundType(col.getGenericsTypes()[0]); + } else if (isOrImplements(collectionType, Iterator_TYPE)) { // GROOVY-10712 + ClassNode col = GenericsUtils.parameterizeType(collectionType, Iterator_TYPE); + componentType = getCombinedBoundType(col.getGenericsTypes()[0]); + } else if (isStringType(collectionType)) { componentType = STRING_TYPE; } else { @@ -2288,17 +2296,11 @@ && findMethod(receiver, "", init(argumentTypes)).size() == 1) { && parameters.length == argumentTypes.length - 1) { ctor = typeCheckMapConstructor(call, receiver, arguments); } else { - /* GRECLIPSE edit -- GROOVY-10698 - if (asBoolean(receiver.getGenericsTypes())) { // GROOVY-10283, GROOVY-10316, GROOVY-10482, GROOVY-10624 - Map context = extractPlaceHolders(receiver, ctor.getDeclaringClass()); - parameters = Arrays.stream(parameters).map(p -> new Parameter(applyGenericsContext(context, p.getType()), p.getName())).toArray(Parameter[]::new); - } - */ - GenericsType[] typeParameters = ctor.getDeclaringClass().getGenericsTypes(); if (typeParameters != null && typeParameters.length > 0) { + GenericsType[] typeParameters = ctor.getDeclaringClass().getGenericsTypes(); + if (typeParameters != null) { // GROOVY-10283, GROOVY-10316, GROOVY-10482, GROOVY-10624, GROOVY-10698 Map context = extractGenericsConnectionsFromArguments(typeParameters, parameters, argumentList, receiver.getGenericsTypes()); if (!context.isEmpty()) parameters = Arrays.stream(parameters).map(p -> new Parameter(applyGenericsContext(context, p.getType()), p.getName())).toArray(Parameter[]::new); } - // GRECLIPSE end resolvePlaceholdersFromImplicitTypeHints(argumentTypes, argumentList, parameters); typeCheckMethodsWithGenericsOrFail(receiver, argumentTypes, ctor, call); visitMethodCallArguments(receiver, argumentList, true, ctor); @@ -2466,6 +2468,11 @@ && isStringType(getType(nameExpr))) { receiverType = wrapTypeIfNecessary(currentReceiver.getType()); candidates = findMethodsWithGenerated(receiverType, nameText); + // GROOVY-10741: check for reference to a property node's method + MethodNode generated = findPropertyMethod(receiverType, nameText); + if (generated != null && candidates.stream().noneMatch(mn -> mn.getName().equals(generated.getName()))){ + candidates.add(generated); + } candidates.addAll(findDGMMethodsForClassNode(getSourceUnit().getClassLoader(), receiverType, nameText)); candidates = filterMethodsByVisibility(candidates, typeCheckingContext.getEnclosingClassNode()); @@ -2482,10 +2489,13 @@ && isStringType(getType(nameExpr))) { } if (!candidates.isEmpty()) { + int nParameters = candidates.stream().mapToInt(m -> m.getParameters().length).reduce((i,j) -> i == j ? i : -1).getAsInt(); Map gts = GenericsUtils.extractPlaceholders(receiverType); candidates.stream().map(candidate -> applyGenericsContext(gts, candidate.getReturnType())) .reduce(WideningCategories::lowestUpperBound).ifPresent(returnType -> { - storeType(expression, wrapClosureType(returnType)); + ClassNode closureType = wrapClosureType(returnType); + closureType.putNodeMetaData(CLOSURE_ARGUMENTS, nParameters); // GROOVY-10714 + storeType(expression, closureType); }); expression.putNodeMetaData(MethodNode.class, candidates); } else if (!(expression instanceof MethodReferenceExpression)) { @@ -2500,6 +2510,23 @@ private static ClassNode wrapClosureType(final ClassNode returnType) { return makeClassSafe0(CLOSURE_TYPE, wrapTypeIfNecessary(returnType).asGenericsType()); } + private static MethodNode findPropertyMethod(final ClassNode type, final String name) { + for (ClassNode cn = type; cn != null; cn = cn.getSuperClass()) { + for (PropertyNode pn : cn.getProperties()) { + if (name.equals(pn.getGetterNameOrDefault())) { + MethodNode node = new MethodNode(name, Opcodes.ACC_PUBLIC | (pn.isStatic() ? Opcodes.ACC_STATIC : 0), pn.getType(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null); + node.setDeclaringClass(pn.getDeclaringClass()); + return node; + } else if (name.equals(pn.getSetterNameOrDefault()) && !Modifier.isFinal(pn.getModifiers())) { + MethodNode node = new MethodNode(name, Opcodes.ACC_PUBLIC | (pn.isStatic() ? Opcodes.ACC_STATIC : 0), VOID_TYPE, new Parameter[]{new Parameter(pn.getType(), pn.getName())}, ClassNode.EMPTY_ARRAY, null); + node.setDeclaringClass(pn.getDeclaringClass()); + return node; + } + } + } + return null; + } + protected DelegationMetadata getDelegationMetadata(final ClosureExpression expression) { return expression.getNodeMetaData(DELEGATION_METADATA); } @@ -3506,7 +3533,20 @@ && implementsInterfaceOrIsSubclassOf(receiverType, node.getDeclaringClass()))) { if (areCategoryMethodCalls(mn, name, args)) { addCategoryMethodCallError(call); } - mn = disambiguateMethods(mn, chosenReceiver != null ? chosenReceiver.getType() : null, args, call); + + { + ClassNode obj = chosenReceiver != null ? chosenReceiver.getType() : null; + if (mn.size() > 1 && obj instanceof UnionTypeClassNode) { // GROOVY-8965: support duck-typing using dynamic resolution + ClassNode returnType = mn.stream().map(MethodNode::getReturnType).reduce(WideningCategories::lowestUpperBound).get(); + call.putNodeMetaData(DYNAMIC_RESOLUTION, returnType); + + MethodNode tmp = new MethodNode(name, 0, returnType, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null); + tmp.setDeclaringClass(obj); // for storeTargetMethod + mn = Collections.singletonList(tmp); + } else { + mn = disambiguateMethods(mn, obj, args, call); + } + } if (mn.size() == 1) { MethodNode targetMethodCandidate = mn.get(0); @@ -3664,7 +3704,7 @@ private void inferMethodReferenceType(final ClassNode receiver, final ArgumentLi newArgumentExpressions.add(argumentExpression); } else { methodReferencePositions.add(i); - newArgumentExpressions.add(constructLambdaExpressionForMethodReference(paramType)); + newArgumentExpressions.add(constructLambdaExpressionForMethodReference(paramType, (MethodReferenceExpression) argumentExpression)); } } } @@ -3680,13 +3720,26 @@ private void inferMethodReferenceType(final ClassNode receiver, final ArgumentLi } } - private LambdaExpression constructLambdaExpressionForMethodReference(final ClassNode functionalInterfaceType) { - Parameter[] parameters = findSAM(functionalInterfaceType).getParameters().clone(); - for (int i = 0, n = parameters.length; i < n; i += 1) { - parameters[i] = new Parameter(dynamicType(), "p" + System.nanoTime()); + private LambdaExpression constructLambdaExpressionForMethodReference(final ClassNode functionalInterfaceType, final MethodReferenceExpression methodReference) { + Parameter[] parameters = findSAM(functionalInterfaceType).getParameters(); + int nParameters = parameters.length; + if (nParameters > 0) { + ClassNode firstParamType = dynamicType(); + // GROOVY-10734: Type::instanceMethod has implied first param + List candidates = methodReference.getNodeMetaData(MethodNode.class); + if (candidates != null && !candidates.isEmpty()) { + ClassNode objExpType = getType(methodReference.getExpression()); + if (isClassClassNodeWrappingConcreteType(objExpType) + && candidates.stream().allMatch(mn -> !mn.isStatic())) { + firstParamType = objExpType.getGenericsTypes()[0].getType(); + } + } + parameters = new Parameter[nParameters]; + for (int i = 0; i < nParameters; i += 1) { + parameters[i] = new Parameter(i == 0 ? firstParamType : dynamicType(), "p" + i); + } } - - return new LambdaExpression(parameters, EmptyStatement.INSTANCE); + return new LambdaExpression(parameters, GENERATED_EMPTY_STATEMENT); } /** @@ -3727,8 +3780,8 @@ private static ClassNode adjustWithTraits(final MethodNode directMethodCallCandi * @param args the argument classes */ private static void addArrayMethods(final List methods, final ClassNode receiver, final String name, final ClassNode[] args) { - if (args.length != 1) return; if (!receiver.isArray()) return; + if (args == null || args.length != 1) return; if (!isIntCategory(getUnwrapper(args[0]))) return; if ("getAt".equals(name)) { MethodNode node = new MethodNode(name, Opcodes.ACC_PUBLIC, receiver.getComponentType(), new Parameter[]{new Parameter(args[0], "arg")}, null, null); @@ -3777,19 +3830,11 @@ protected List> makeOwnerList(final Expression objectExpression Receiver.make(typeCheckingContext.getEnclosingClassNode())); addReceivers(owners, enclosingClass, typeCheckingContext.delegationMetadata.getParent(), "owner."); } else { - /* GRECLIPSE edit -- GROOVY-8965, GROOVY-10180, GROOVY-10668 - if (!typeCheckingContext.temporaryIfBranchTypeInformation.isEmpty()) { - List potentialReceiverType = getTemporaryTypesForExpression(objectExpression); - if (potentialReceiverType != null && !potentialReceiverType.isEmpty()) { - for (ClassNode node : potentialReceiverType) { - owners.add(Receiver.make(node)); - } - } - } - */ List temporaryTypes = getTemporaryTypesForExpression(objectExpression); - if (asBoolean(temporaryTypes)) owners.add(Receiver.make(lowestUpperBound(temporaryTypes))); - // GRECLIPSE end + int temporaryTypesCount = (temporaryTypes != null ? temporaryTypes.size() : 0); + if (temporaryTypesCount > 0) { // GROOVY-8965, GROOVY-10180, GROOVY-10668 + owners.add(Receiver.make(lowestUpperBound(temporaryTypes))); + } if (typeCheckingContext.lastImplicitItType != null && objectExpression instanceof VariableExpression && ((Variable) objectExpression).getName().equals("it")) { @@ -3812,6 +3857,9 @@ protected List> makeOwnerList(final Expression objectExpression } } } + if (temporaryTypesCount > 1 && !(objectExpression instanceof VariableExpression)) { + owners.add(Receiver.make(new UnionTypeClassNode(temporaryTypes.toArray(ClassNode.EMPTY_ARRAY)))); + } } return owners; } @@ -4213,12 +4261,10 @@ public void visitTernaryExpression(final TernaryExpression expression) { } Expression trueExpression = expression.getTrueExpression(); ClassNode typeOfTrue = findCurrentInstanceOfClass(trueExpression, null); - trueExpression.visit(this); - if (typeOfTrue == null) typeOfTrue = getType(trueExpression); + typeOfTrue = Optional.ofNullable(typeOfTrue).orElse(visitValueExpression(trueExpression)); typeCheckingContext.popTemporaryTypeInfo(); // instanceof doesn't apply to false branch Expression falseExpression = expression.getFalseExpression(); - falseExpression.visit(this); - ClassNode typeOfFalse = getType(falseExpression); + ClassNode typeOfFalse = visitValueExpression(falseExpression); ClassNode resultType; if (isNullConstant(trueExpression) && isNullConstant(falseExpression)) { // GROOVY-5523 @@ -4238,12 +4284,23 @@ && isOrImplements(typeOfFalse, typeOfTrue))) { // List/Collection/Iterable : [] popAssignmentTracking(oldTracker); } + /** + * @param expr true or false branch of ternary expression + * @return the inferred type of {@code expr} + */ + private ClassNode visitValueExpression(final Expression expr) { + if (expr instanceof ClosureExpression) { + applyTargetType(checkForTargetType(expr, null), expr); + } + expr.visit(this); + return getType(expr); + } + /** * @param expr true or false branch of ternary expression * @param type the inferred type of {@code expr} */ private ClassNode checkForTargetType(final Expression expr, final ClassNode type) { - ClassNode sourceType = type; ClassNode targetType = null; MethodNode enclosingMethod = typeCheckingContext.getEnclosingMethod(); MethodCall enclosingMethodCall = (MethodCall)typeCheckingContext.getEnclosingMethodCall(); @@ -4262,34 +4319,20 @@ && isTypeSource(expr, enclosingExpression.getRightExpression())) { && isTypeSource(expr, enclosingMethod)) { targetType = enclosingMethod.getReturnType(); } - /* GRECLIPSE edit - if (expr instanceof ConstructorCallExpression) { // GROOVY-9972, GROOVY-9983 - // GROOVY-10114: type parameter(s) could be inferred from call arguments - if (targetType == null) targetType = sourceType.getPlainNodeReference(); - inferDiamondType((ConstructorCallExpression) expr, targetType); - return sourceType; - } - - if (targetType == null) return sourceType; - if (!isPrimitiveType(getUnwrapper(targetType)) && !isObjectType(targetType) - && !sourceType.isGenericsPlaceHolder() && missesGenericsTypes(sourceType)) { - // unchecked assignment with ternary/elvis, like "List list = listOfT ?: []" - // the inferred type is the RHS type "completed" with generics information from LHS - return GenericsUtils.parameterizeType(targetType, sourceType.getPlainNodeReference()); + if (expr instanceof ClosureExpression) { // GROOVY-10271, GROOVY-10272 + return isSAMType(targetType) ? targetType : type; } - return sourceType != UNKNOWN_PARAMETER_TYPE ? sourceType : targetType; - */ if (targetType == null) - targetType = sourceType.getPlainNodeReference(); - if (sourceType == UNKNOWN_PARAMETER_TYPE) return targetType; + targetType = type.getPlainNodeReference(); + if (type == UNKNOWN_PARAMETER_TYPE) return targetType; - if (expr instanceof ConstructorCallExpression) + if (expr instanceof ConstructorCallExpression) { // GROOVY-9972, GROOVY-9983, GROOVY-10114 inferDiamondType((ConstructorCallExpression) expr, targetType); + } - return adjustForTargetType(sourceType, targetType); // GROOVY-10688 - // GRECLIPSE end + return adjustForTargetType(type, targetType); // GROOVY-10688 } private static ClassNode adjustForTargetType(final ClassNode resultType, final ClassNode targetType) { @@ -4521,15 +4564,13 @@ protected ClassNode getResultType(ClassNode left, final int op, final ClassNode } if (isArrayOp(op)) { - // using getPNR() to ignore generics at this point - // and a different binary expression not to pollute the AST - BinaryExpression newExpr = binX(leftExpression, expr.getOperation(), rightExpression); - newExpr.setSourcePosition(expr); - MethodNode method = findMethodOrFail(newExpr, left.getPlainNodeReference(), "getAt", right.getPlainNodeReference()); - if (method != null && implementsInterfaceOrIsSubclassOf(right, RANGE_TYPE)) { + Expression copy = binX(leftExpression, expr.getOperation(), rightExpression); + copy.setSourcePosition(expr); // do not propagate BINARY_EXP_TARGET, etc. + MethodNode method = findMethodOrFail(copy, left, "getAt", rightRedirect); + if (method != null && !isNumberCategory(getWrapper(rightRedirect))) { return inferReturnTypeGenerics(left, method, rightExpression); } - return method != null ? inferComponentType(left, right) : null; + return inferComponentType(left, right); } String operationName = getOperationName(op); @@ -4829,14 +4870,18 @@ protected List findMethodsWithGenerated(final ClassNode receiver, fi } List methods = receiver.getMethods(name); - if (receiver.isAbstract()) { - collectAllInterfaceMethodsByName(receiver, name, methods); - } else { // GROOVY-9890: always search for default methods - List interfaceMethods = new ArrayList<>(); - collectAllInterfaceMethodsByName(receiver, name, interfaceMethods); - interfaceMethods.stream().filter(mn -> mn.isDefault() || (mn.isPublic() - && !mn.isStatic() && !mn.isAbstract() && Traits.isTrait(mn.getDeclaringClass())) - ).forEach(methods::add); + + // GROOVY-5166, GROOVY-9890, GROOVY-10700: non-static interface/trait methods + Set done = new HashSet<>(); + for (ClassNode next = receiver; next != null; next = next.getSuperClass()) { + done.add(next); + for (ClassNode face : next.getAllInterfaces()) { + if (done.add(face)) { + for (MethodNode mn : face.getDeclaredMethods(name)) { + if (mn.isPublic() && !mn.isStatic()) methods.add(mn); + } + } + } } if (receiver.isInterface()) { @@ -5050,18 +5095,6 @@ public static String extractPropertyNameFromMethodName(final String prefix, fina return null; } - private static void collectAllInterfaceMethodsByName(final ClassNode type, final String name, final List methods) { - Set done = new LinkedHashSet<>(); - for (ClassNode next = type; next != null; next = next.getSuperClass()) { - done.add(next); - for (ClassNode face : next.getAllInterfaces()) { - if (done.add(face)) { - methods.addAll(face.getDeclaredMethods(name)); - } - } - } - } - protected ClassNode getType(final ASTNode node) { ClassNode type = node.getNodeMetaData(INFERRED_TYPE); if (type != null) { @@ -5312,16 +5345,25 @@ protected ClassNode inferMapExpressionType(final MapExpression map) { if (genericsTypes == null || genericsTypes.length < 2 || (genericsTypes.length == 2 && isObjectType(genericsTypes[0].getType()) && isObjectType(genericsTypes[1].getType()))) { + ClassNode keyType; + ClassNode valueType; List keyTypes = new ArrayList<>(nExpressions); List valueTypes = new ArrayList<>(nExpressions); for (MapEntryExpression entryExpression : entryExpressions) { - keyTypes.add(getType(entryExpression.getKeyExpression())); - valueTypes.add(getType(entryExpression.getValueExpression())); - } - ClassNode keyType = lowestUpperBound(keyTypes); - ClassNode valueType = lowestUpperBound(valueTypes); + valueType = getType(entryExpression.getValueExpression()); + if (!(entryExpression.getKeyExpression() instanceof SpreadMapExpression)) { + keyType = getType(entryExpression.getKeyExpression()); + } else { // GROOVY-7247 + valueType = GenericsUtils.parameterizeType(valueType, MAP_TYPE); + keyType = getCombinedBoundType(valueType.getGenericsTypes()[0]); + valueType = getCombinedBoundType(valueType.getGenericsTypes()[1]); + } + keyTypes.add(keyType); + valueTypes.add(valueType); + } + keyType = lowestUpperBound(keyTypes); + valueType = lowestUpperBound(valueTypes); if (!isObjectType(keyType) || !isObjectType(valueType)) { - mapType = mapType.getPlainNodeReference(); mapType.setGenericsTypes(new GenericsType[]{new GenericsType(wrapTypeIfNecessary(keyType)), new GenericsType(wrapTypeIfNecessary(valueType))}); } } @@ -5591,7 +5633,7 @@ private static void extractGenericsConnectionsForSuperClassAndInterfaces(final M * @param samType the target type for the argument expression * @return SAM type augmented using information from the argument expression */ - private static ClassNode convertClosureTypeToSAMType(final Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType) { + private static ClassNode convertClosureTypeToSAMType(Expression expression, final ClassNode closureType, final MethodNode sam, final ClassNode samType) { Map samTypeConnections = GenericsUtils.extractPlaceholders(samType); samTypeConnections.replaceAll((xx, gt) -> // GROOVY-9762, GROOVY-9803: reduce "? super T" to "T" Optional.ofNullable(gt.getLowerBound()).map(GenericsType::new).orElse(gt) @@ -5599,9 +5641,7 @@ private static ClassNode convertClosureTypeToSAMType(final Expression expression ClassNode closureReturnType = closureType.getGenericsTypes()[0].getType(); Parameter[] parameters = sam.getParameters(); - if (parameters.length > 0 - && expression instanceof MethodPointerExpression - && GenericsUtils.hasUnresolvedGenerics(closureReturnType)) { + if (parameters.length > 0 && expression instanceof MethodPointerExpression) { // try to resolve referenced method type parameters in return type MethodPointerExpression mp = (MethodPointerExpression) expression; List candidates = mp.getNodeMetaData(MethodNode.class); @@ -5609,7 +5649,7 @@ private static ClassNode convertClosureTypeToSAMType(final Expression expression ClassNode[] paramTypes = applyGenericsContext(samTypeConnections, extractTypesFromParameters(parameters)); ClassNode[] matchTypes = candidates.stream() .map(candidate -> collateMethodReferenceParameterTypes(mp, candidate)) - .filter(candidate -> checkSignatureSuitability(candidate, paramTypes)) + .filter(signature -> checkSignatureSuitability(signature, paramTypes)) .findFirst().orElse(null); // TODO: order signatures by param distance if (matchTypes != null) { Map connections = new HashMap<>(); @@ -5621,6 +5661,8 @@ private static ClassNode convertClosureTypeToSAMType(final Expression expression closureReturnType = applyGenericsContext(connections, closureReturnType); // apply known generics connections to the SAM's placeholders in the return type closureReturnType = applyGenericsContext(samTypeConnections, closureReturnType); + + expression = new ClosureExpression(Arrays.stream(matchTypes).map(t -> new Parameter(t,"")).toArray(Parameter[]::new), null); } } } @@ -5630,7 +5672,11 @@ private static ClassNode convertClosureTypeToSAMType(final Expression expression // repeat the same for each parameter given in the ClosureExpression if (parameters.length > 0 && expression instanceof ClosureExpression) { - return closureType; // TODO + ClassNode[] paramTypes = applyGenericsContext(samTypeConnections, extractTypesFromParameters(parameters)); + int i = 0; + // GROOVY-10054, GROOVY-10699, GROOVY-10749, et al. + for (Parameter p : getParametersSafe((ClosureExpression) expression)) + if (!p.isDynamicTyped()) extractGenericsConnections(samTypeConnections, p.getType(), paramTypes[i++]); } return applyGenericsContext(samTypeConnections, samType.redirect()); @@ -5661,8 +5707,9 @@ private static boolean checkSignatureSuitability(final ClassNode[] receiverTypes return false; } for (int i = 0; i < n; i += 1) { - // for method closure, SAM parameters act like arguments - if (!isAssignableTo(providerTypes[i], receiverTypes[i])) { + // for method closure SAM parameters act like arguments + if (!isAssignableTo(providerTypes[i], receiverTypes[i]) + && !providerTypes[i].isGenericsPlaceHolder()) { return false; } } diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/trait/TraitComposer.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/trait/TraitComposer.java index 9027285a8f..60146e6d54 100644 --- a/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/trait/TraitComposer.java +++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/transform/trait/TraitComposer.java @@ -185,9 +185,10 @@ private static void applyTrait(final ClassNode trait, final ClassNode cNode, fin cNode.addInterface(fieldHelperClassNode); // implementation of methods List declaredMethods = new LinkedList<>(); + int pos = 0; // keep direct getters at start but in declaration order for (MethodNode declaredMethod : fieldHelperClassNode.getAllDeclaredMethods()) { if (declaredMethod.getName().endsWith(Traits.DIRECT_GETTER_SUFFIX)) { - declaredMethods.add(0, declaredMethod); + declaredMethods.add(pos++, declaredMethod); } else { declaredMethods.add(declaredMethod); } @@ -196,7 +197,7 @@ private static void applyTrait(final ClassNode trait, final ClassNode cNode, fin if (staticFieldHelperClassNode != null) { for (MethodNode declaredMethod : staticFieldHelperClassNode.getAllDeclaredMethods()) { if (declaredMethod.getName().endsWith(Traits.DIRECT_GETTER_SUFFIX)) { - declaredMethods.add(0, declaredMethod); + declaredMethods.add(pos++, declaredMethod); } else { declaredMethods.add(declaredMethod); } diff --git a/extras/groovy-eclipse-batch-builder/build.properties b/extras/groovy-eclipse-batch-builder/build.properties index 01d52ab8ed..d75f56edcf 100644 --- a/extras/groovy-eclipse-batch-builder/build.properties +++ b/extras/groovy-eclipse-batch-builder/build.properties @@ -1,7 +1,7 @@ # version numbers version2.5=2.5.18-01 version3.0=3.0.12-02 -version4.0=4.0.4-01 +version4.0=4.0.5-01 # uncomment to do a particular build -- only one should be uncommented at a time #do-25-build=true diff --git a/extras/groovy-eclipse-compiler-tests/pom.xml b/extras/groovy-eclipse-compiler-tests/pom.xml index 1f21af6fc0..d7ab425956 100644 --- a/extras/groovy-eclipse-compiler-tests/pom.xml +++ b/extras/groovy-eclipse-compiler-tests/pom.xml @@ -9,9 +9,9 @@ org.apache - 4.0.4 + 4.0.5 - 4.0.4-01 + 4.0.5-01 3.6.2 3.7.1 diff --git a/extras/groovy-eclipse-compiler-tests/src/it/groovy-for-tests/pom.xml b/extras/groovy-eclipse-compiler-tests/src/it/groovy-for-tests/pom.xml index e83322a509..4f1dae2185 100644 --- a/extras/groovy-eclipse-compiler-tests/src/it/groovy-for-tests/pom.xml +++ b/extras/groovy-eclipse-compiler-tests/src/it/groovy-for-tests/pom.xml @@ -32,7 +32,7 @@ org.spockframework spock-core - 2.2-M1-groovy-4.0 + 2.2-groovy-4.0 test diff --git a/ide/Feature-org.codehaus.groovy40.feature/feature.xml b/ide/Feature-org.codehaus.groovy40.feature/feature.xml index 32ad11fcc7..e6cda8b307 100644 --- a/ide/Feature-org.codehaus.groovy40.feature/feature.xml +++ b/ide/Feature-org.codehaus.groovy40.feature/feature.xml @@ -22,7 +22,7 @@ id="org.codehaus.groovy" download-size="0" install-size="0" - version="4.0.4.qualifier" + version="4.0.5.qualifier" />