diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java index 88d0734bbf..0564ae057c 100644 --- a/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java +++ b/base-test/org.eclipse.jdt.groovy.core.tests.builder/src/org/eclipse/jdt/core/groovy/tests/search/GenericInferencingTests.java @@ -665,14 +665,60 @@ public void testMapOfMaps() { } @Test - public void testTypeExtendsMap() { + public void testTypeExtendsMap1() { String contents = - "interface Config extends Map {}\n" + - "void meth(Config config) {\n" + + "interface I extends Map {}\n" + + "void m(I config) {\n" + " def xxx = config.whatever\n" + + " def yyy = config['whatever']\n" + "}\n"; assertType(contents, "xxx", "java.lang.Number"); + assertType(contents, "yyy", "java.lang.Number"); + } + + @Test + public void testTypeExtendsMap2() { + String contents = + "interface I extends Map {}\n" + + "abstract class A implements I {}\n" + + "void m(A config) {\n" + + " def xxx = config.whatever\n" + + " def yyy = config['whatever']\n" + + "}\n"; + + assertType(contents, "xxx", "java.lang.Number"); + assertType(contents, "yyy", "java.lang.Number"); + } + + @Test // https://github.com/groovy/groovy-eclipse/issues/1189 + public void testTypeExtendsMap3() { + String contents = + "interface I extends Map {}\n" + + "abstract class A implements I {}\n" + + "class C extends A {}\n" + + "void m(C config) {\n" + + " def xxx = config.whatever\n" + + " def yyy = config['whatever']\n" + + "}\n"; + + assertType(contents, "xxx", "java.lang.Number"); + assertType(contents, "yyy", "java.lang.Number"); + } + + @Test // https://github.com/groovy/groovy-eclipse/issues/1189 + public void testTypeExtendsMap4() { + String contents = + "interface I extends Map {}\n" + + "abstract class A implements I {}\n" + + "class C extends A {}\n" + + "void m(C config) {\n" + + " def xxx = config.whatever\n" + + " def yyy = config['whatever']\n" + + "}\n"; + + assertType(contents, "xxx", "java.lang.Number"); + assertType(contents, "yyy", "java.lang.Number"); } @Test diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/GroovyUtils.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/GroovyUtils.java index aaf77d58d3..0db60c29d3 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/GroovyUtils.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/core/util/GroovyUtils.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -50,6 +51,7 @@ import org.codehaus.groovy.ast.expr.TernaryExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.tools.GeneralUtils; +import org.codehaus.groovy.ast.tools.GenericsUtils; import org.codehaus.groovy.control.CompilePhase; import org.codehaus.groovy.runtime.MetaClassHelper; import org.codehaus.groovy.transform.ASTTransformation; @@ -131,6 +133,24 @@ public static String[] splitName(ClassNode node) { return new String[] {name.substring(0, Math.max(0, index)), name.substring(index + 1)}; } + public static Set getAllInterfaces(ClassNode node) { + Set result = new LinkedHashSet<>(); + if (node.isInterface()) result.add(node); + addAllInterfaces(result, node); + return result; + } + + private static void addAllInterfaces(Set result, ClassNode source) { + for (ClassNode face : source.getInterfaces()) { + face = GenericsUtils.parameterizeType(source, face); + if (result.add(face)) addAllInterfaces(result, face); + } + ClassNode sc = source.redirect().getUnresolvedSuperClass(false); + if (sc != null && !sc.equals(ClassHelper.OBJECT_TYPE)) { + addAllInterfaces(result, GenericsUtils.parameterizeType(source, sc)); + } + } + public static Stream getAnnotations(AnnotatedNode node, String name) { return node.getAnnotations().stream().filter(an -> an.getClassNode().getName().equals(name)); } diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/CategoryTypeLookup.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/CategoryTypeLookup.java index c94cb7607a..96c73a5c51 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/CategoryTypeLookup.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/CategoryTypeLookup.java @@ -92,7 +92,7 @@ public TypeLookupResult lookupType(final Expression node, final VariableScope sc ClassNode resolvedType = method.getReturnType(); // getAt(Object,String):Object supersedes getAt(Map,Object):V when first param is String or GString; restore return type V for user experience if ("getAt".equals(simpleName) && VariableScope.OBJECT_CLASS_NODE.equals(resolvedType) && isOrImplements(selfType, VariableScope.MAP_CLASS_NODE)) { - for (ClassNode face : selfType.getAllInterfaces()) { + for (ClassNode face : GroovyUtils.getAllInterfaces(selfType)) { if (face.equals(VariableScope.MAP_CLASS_NODE)) { // Map GenericsType[] generics = GroovyUtils.getGenericsTypes(face); if (generics.length == 2) resolvedType = generics[1].getType(); diff --git a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/SimpleTypeLookup.java b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/SimpleTypeLookup.java index b44283afad..2b16b4edbf 100644 --- a/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/SimpleTypeLookup.java +++ b/base/org.eclipse.jdt.groovy.core/src/org/eclipse/jdt/groovy/search/SimpleTypeLookup.java @@ -633,7 +633,7 @@ protected ASTNode findDeclaration(final String name, final ClassNode declaringTy resolvedType = VariableScope.VOID_CLASS_NODE; } else { resolvedType = VariableScope.OBJECT_CLASS_NODE; - for (ClassNode face : declaringType.getAllInterfaces()) { + for (ClassNode face : GroovyUtils.getAllInterfaces(declaringType)) { if (face.equals(VariableScope.MAP_CLASS_NODE)) { // Map GenericsType[] generics = GroovyUtils.getGenericsTypes(face); if (generics.length == 2) resolvedType = generics[1].getType();