Skip to content

Commit

Permalink
Fix for #1189: check super class interfaces for Map
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Nov 4, 2020
1 parent e982ca9 commit 51ddcac
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -665,14 +665,60 @@ public void testMapOfMaps() {
}

@Test
public void testTypeExtendsMap() {
public void testTypeExtendsMap1() {
String contents =
"interface Config extends Map<String, Number> {}\n" +
"void meth(Config config) {\n" +
"interface I extends Map<String, Number> {}\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<String, Number> {}\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<String, Number> {}\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<T> extends Map<String, T> {}\n" +
"abstract class A<U> implements I<U> {}\n" +
"class C extends A<Number> {}\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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<ClassNode> getAllInterfaces(ClassNode node) {
Set<ClassNode> result = new LinkedHashSet<>();
if (node.isInterface()) result.add(node);
addAllInterfaces(result, node);
return result;
}

private static void addAllInterfaces(Set<ClassNode> 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<AnnotationNode> getAnnotations(AnnotatedNode node, String name) {
return node.getAnnotations().stream().filter(an -> an.getClassNode().getName().equals(name));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public TypeLookupResult lookupType(final Expression node, final VariableScope sc
ClassNode resolvedType = method.getReturnType();
// getAt(Object,String):Object supersedes getAt(Map<K,V>,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<K,V>
GenericsType[] generics = GroovyUtils.getGenericsTypes(face);
if (generics.length == 2) resolvedType = generics[1].getType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<K,V>
GenericsType[] generics = GroovyUtils.getGenericsTypes(face);
if (generics.length == 2) resolvedType = generics[1].getType();
Expand Down

0 comments on commit 51ddcac

Please sign in to comment.