Skip to content

Commit 09247dd

Browse files
committed
GROOVY-5106, GROOVY-10439, GROOVY-11508: re-implement with new type args
3_0_X backport
1 parent 2827e14 commit 09247dd

File tree

6 files changed

+240
-77
lines changed

6 files changed

+240
-77
lines changed

src/main/java/org/codehaus/groovy/classgen/Verifier.java

+38-1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
104104
import static org.codehaus.groovy.ast.tools.GeneralUtils.declS;
105105
import static org.codehaus.groovy.ast.tools.GeneralUtils.fieldX;
106+
import static org.codehaus.groovy.ast.tools.GeneralUtils.getInterfacesAndSuperInterfaces;
106107
import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
107108
import static org.codehaus.groovy.ast.tools.GeneralUtils.param;
108109
import static org.codehaus.groovy.ast.tools.GeneralUtils.stmt;
@@ -272,6 +273,10 @@ public void variableNotAlwaysInitialized(final VariableExpression var) {
272273
};
273274
}
274275

276+
private static Set<ClassNode> getAllInterfaces(final ClassNode cn) {
277+
return getInterfacesAndSuperInterfaces(cn);
278+
}
279+
275280
private static void checkForDuplicateInterfaces(final ClassNode cn) {
276281
ClassNode[] interfaces = cn.getInterfaces();
277282
int nInterfaces = interfaces.length;
@@ -284,6 +289,38 @@ private static void checkForDuplicateInterfaces(final ClassNode cn) {
284289
throw new RuntimeParserException("Duplicate interfaces in implements list: " + interfaceNames, cn);
285290
}
286291
}
292+
293+
// GROOVY-5106: check for same interface with different type argument(s)
294+
List< Set<ClassNode> > allInterfaces = new ArrayList<>(nInterfaces + 1);
295+
for (ClassNode in : interfaces) allInterfaces.add(getAllInterfaces(in));
296+
allInterfaces.add(getAllInterfaces(cn.getUnresolvedSuperClass()));
297+
if (nInterfaces == 1 && allInterfaces.get(1).isEmpty())
298+
return; // no peer interface(s) to verify
299+
300+
for (int i = 0; i < nInterfaces; i += 1) {
301+
for (ClassNode in : allInterfaces.get(i)) {
302+
if (in.redirect().getGenericsTypes() != null) {
303+
for (int j = i + 1; j < nInterfaces + 1; j += 1) {
304+
Set<ClassNode> set = allInterfaces.get(j);
305+
if (set.contains(in)) {
306+
for (ClassNode t : set) { // find match and check generics
307+
if (t.equals(in)) {
308+
String one = in.toString(false), two = t.toString(false);
309+
if (!one.equals(two)) {
310+
String warning = String.format(
311+
"The %s %s is implemented more than once with different arguments: %s and %s",
312+
(Traits.isTrait(in) ? "trait" : "interface"), in.getNameWithoutPackage(), one, two);
313+
Token token = new Token(0, "", cn.getLineNumber(), cn.getColumnNumber()); // ASTNode to CSTNode
314+
cn.getModule().getContext().getErrorCollector().addWarning(1, warning, token, cn.getModule().getContext());
315+
}
316+
break;
317+
}
318+
}
319+
}
320+
}
321+
}
322+
}
323+
}
287324
}
288325

289326
private static void checkForDuplicateMethods(final ClassNode cn) {
@@ -622,7 +659,7 @@ private GroovyRuntimeException newVariableError(final String name, final ASTNode
622659

623660
@Override
624661
public void visitMethod(final MethodNode node) {
625-
// GROOVY-3712: if it's an MOP method, it's an error as they aren't supposed to exist before ACG is invoked
662+
// GROOVY-3712: if it's a MOP method, it's an error as they aren't supposed to exist before ACG is invoked
626663
if (MopWriter.isMopMethod(node.getName())) {
627664
throw new RuntimeParserException("Found unexpected MOP methods in the class node for " + classNode.getName() + "(" + node.getName() + ")", classNode);
628665
}

src/main/java/org/codehaus/groovy/control/ResolveVisitor.java

+69-56
Original file line numberDiff line numberDiff line change
@@ -267,60 +267,74 @@ protected SourceUnit getSourceUnit() {
267267
return source;
268268
}
269269

270+
@Override
271+
public void visitField(final FieldNode node) {
272+
Map<GenericsTypeName, GenericsType> oldNames = genericParameterNames;
273+
if (!canSeeTypeVars(node.getModifiers(), node.getDeclaringClass())) {
274+
genericParameterNames = Collections.emptyMap();
275+
}
276+
277+
if (!fieldTypesChecked.contains(node)) {
278+
resolveOrFail(node.getType(), node);
279+
}
280+
super.visitField(node);
281+
282+
genericParameterNames = oldNames;
283+
}
284+
285+
@Override
286+
public void visitProperty(final PropertyNode node) {
287+
Map<GenericsTypeName, GenericsType> oldNames = genericParameterNames;
288+
if (!canSeeTypeVars(node.getModifiers(), node.getDeclaringClass())) {
289+
genericParameterNames = Collections.emptyMap();
290+
}
291+
292+
resolveOrFail(node.getType(), node);
293+
fieldTypesChecked.add(node.getField());
294+
295+
super.visitProperty(node);
296+
297+
genericParameterNames = oldNames;
298+
}
299+
300+
private static boolean canSeeTypeVars(final int mods, final ClassNode node) {
301+
return !Modifier.isStatic(mods) || Traits.isTrait(node); // GROOVY-8864, GROOVY-11508
302+
}
303+
270304
@Override
271305
protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) {
272306
VariableScope oldScope = currentScope;
273307
currentScope = node.getVariableScope();
274308
Map<GenericsTypeName, GenericsType> oldNames = genericParameterNames;
275-
genericParameterNames = node.isStatic() && !Traits.isTrait(node.getDeclaringClass())
276-
? new HashMap<>() : new HashMap<>(genericParameterNames);
309+
genericParameterNames =
310+
canSeeTypeVars(node.getModifiers(), node.getDeclaringClass())
311+
? new HashMap<>(genericParameterNames) : new HashMap<>();
277312

278313
resolveGenericsHeader(node.getGenericsTypes());
279314

315+
resolveOrFail(node.getReturnType(), node);
280316
for (Parameter p : node.getParameters()) {
281317
p.setInitialExpression(transform(p.getInitialExpression()));
282-
resolveOrFail(p.getType(), p.getType());
318+
ClassNode t = p.getType();
319+
resolveOrFail(t, t);
283320
visitAnnotations(p);
284321
}
285-
ClassNode[] exceptions = node.getExceptions();
286-
for (ClassNode t : exceptions) {
287-
resolveOrFail(t, node);
322+
if (node.getExceptions() != null) {
323+
for (ClassNode t : node.getExceptions()) {
324+
resolveOrFail(t, t);
325+
}
288326
}
289-
resolveOrFail(node.getReturnType(), node);
290327

291328
MethodNode oldCurrentMethod = currentMethod;
292329
currentMethod = node;
330+
293331
super.visitConstructorOrMethod(node, isConstructor);
294332

295333
currentMethod = oldCurrentMethod;
296334
genericParameterNames = oldNames;
297335
currentScope = oldScope;
298336
}
299337

300-
@Override
301-
public void visitField(final FieldNode node) {
302-
ClassNode t = node.getType();
303-
if (!fieldTypesChecked.contains(node)) {
304-
resolveOrFail(t, node);
305-
}
306-
super.visitField(node);
307-
}
308-
309-
@Override
310-
public void visitProperty(final PropertyNode node) {
311-
Map<GenericsTypeName, GenericsType> oldPNames = genericParameterNames;
312-
if (node.isStatic() && !Traits.isTrait(node.getDeclaringClass())) {
313-
genericParameterNames = new HashMap<>();
314-
}
315-
316-
ClassNode t = node.getType();
317-
resolveOrFail(t, node);
318-
super.visitProperty(node);
319-
fieldTypesChecked.add(node.getField());
320-
321-
genericParameterNames = oldPNames;
322-
}
323-
324338
private void resolveOrFail(final ClassNode type, final ASTNode node) {
325339
resolveOrFail(type, "", node);
326340
}
@@ -731,7 +745,7 @@ private boolean resolveAliasFromModule(final ClassNode type) {
731745
if (importNode != null && importNode != currImportNode) {
732746
// static alias only for inner classes and must be at end of chain
733747
ClassNode tmp = new ConstructedNestedClass(importNode.getType(), importNode.getFieldName());
734-
if (resolve(tmp, false, false, true) && (tmp.getModifiers() & Opcodes.ACC_STATIC) != 0) {
748+
if (resolve(tmp, false, false, true) && Modifier.isStatic(tmp.getModifiers())) {
735749
type.setRedirect(tmp.redirect());
736750
return true;
737751
}
@@ -752,7 +766,7 @@ private boolean resolveAliasFromModule(final ClassNode type) {
752766
// At this point we know that we have a match for pname. This may
753767
// mean, that name[pname.length()..<-1] is a static inner class.
754768
// For this the rest of the name does not need any dots in its name.
755-
// It is either completely a inner static class or it is not.
769+
// It is either completely an inner static class or it is not.
756770
// Since we do not want to have useless lookups we create the name
757771
// completely and use a ConstructedClassWithPackage to prevent lookups against the package.
758772
String className = aliasedNode.getNameWithoutPackage() + "$" + name.substring(pname.length() + 1).replace('.', '$');
@@ -813,7 +827,7 @@ protected boolean resolveFromModule(final ClassNode type, final boolean testModu
813827
// check package this class is defined in. The usage of ConstructedClassWithPackage here
814828
// means, that the module package will not be involved when the
815829
// compiler tries to find an inner class.
816-
ClassNode tmp = new ConstructedClassWithPackage(module.getPackageName(), name);
830+
ClassNode tmp = new ConstructedClassWithPackage(module.getPackageName(), name);
817831
if (resolve(tmp, false, false, false)) {
818832
ambiguousClass(type, tmp, name);
819833
return true;
@@ -823,15 +837,15 @@ protected boolean resolveFromModule(final ClassNode type, final boolean testModu
823837
for (ImportNode importNode : module.getStaticImports().values()) {
824838
if (importNode.getFieldName().equals(name)) {
825839
ClassNode tmp = new ConstructedNestedClass(importNode.getType(), name);
826-
if (resolve(tmp, false, false, true) && (tmp.getModifiers() & Opcodes.ACC_STATIC) != 0) {
840+
if (resolve(tmp, false, false, true) && Modifier.isStatic(tmp.getModifiers())) {
827841
type.setRedirect(tmp.redirect());
828842
return true;
829843
}
830844
}
831845
}
832846
for (ImportNode importNode : module.getStaticStarImports().values()) {
833847
ClassNode tmp = new ConstructedNestedClass(importNode.getType(), name);
834-
if (resolve(tmp, false, false, true) && (tmp.getModifiers() & Opcodes.ACC_STATIC) != 0) {
848+
if (resolve(tmp, false, false, true) && Modifier.isStatic(tmp.getModifiers())) {
835849
ambiguousClass(type, tmp, name);
836850
return true;
837851
}
@@ -840,7 +854,7 @@ protected boolean resolveFromModule(final ClassNode type, final boolean testModu
840854
for (ImportNode importNode : module.getStarImports()) {
841855
if (importNode.getType() != null) {
842856
ClassNode tmp = new ConstructedNestedClass(importNode.getType(), name);
843-
if (resolve(tmp, false, false, true) && (tmp.getModifiers() & Opcodes.ACC_STATIC) != 0) {
857+
if (resolve(tmp, false, false, true) && Modifier.isStatic(tmp.getModifiers())) {
844858
ambiguousClass(type, tmp, name);
845859
return true;
846860
}
@@ -862,7 +876,7 @@ protected boolean resolveToOuter(final ClassNode type) {
862876

863877
// We do not need to check instances of LowerCaseClass
864878
// to be a Class, because unless there was an import for
865-
// for this we do not lookup these cases. This was a decision
879+
// this we do not look up these cases. This was a decision
866880
// made on the mailing list. To ensure we will not visit this
867881
// method again we set a NO_CLASS for this name
868882
if (type instanceof LowerCaseClass) {
@@ -951,7 +965,7 @@ private static String lookupClassName(final PropertyExpression pe) {
951965
doInitialClassTest = classNameInfo.getV2();
952966
}
953967

954-
if (null == name || name.length() == 0) return null;
968+
if (name == null || name.length() == 0) return null;
955969

956970
return name.toString();
957971
}
@@ -1089,7 +1103,7 @@ private void checkThisAndSuperAsPropertyAccess(final PropertyExpression expressi
10891103
addError("The class '" + type.getName() + "' needs to be an outer class of '" +
10901104
currentClass.getName() + "' when using '.this' or '.super'.", expression);
10911105
}
1092-
if ((currentClass.getModifiers() & Opcodes.ACC_STATIC) == 0) return;
1106+
if (!Modifier.isStatic(currentClass.getModifiers())) return;
10931107
if (currentScope != null && !currentScope.isInStaticContext()) return;
10941108
addError("The usage of 'Class.this' and 'Class.super' within static nested class '" +
10951109
currentClass.getName() + "' is not allowed in a static context.", expression);
@@ -1100,13 +1114,6 @@ protected Expression transformVariableExpression(final VariableExpression ve) {
11001114
visitAnnotations(ve);
11011115
Variable v = ve.getAccessedVariable();
11021116

1103-
if(!(v instanceof DynamicVariable) && !checkingVariableTypeInDeclaration) {
1104-
/*
1105-
* GROOVY-4009: when a normal variable is simply being used, there is no need to try to
1106-
* resolve its type. Variable type resolve should proceed only if the variable is being declared.
1107-
*/
1108-
return ve;
1109-
}
11101117
if (v instanceof DynamicVariable) {
11111118
String name = ve.getName();
11121119
ClassNode t = ClassHelper.make(name);
@@ -1132,8 +1139,14 @@ protected Expression transformVariableExpression(final VariableExpression ve) {
11321139
for (VariableScope scope = currentScope; scope != null && !scope.isRoot(); scope = scope.getParent()) {
11331140
if (scope.removeReferencedClassVariable(ve.getName()) == null) break;
11341141
}
1135-
return new ClassExpression(t);
1142+
ClassExpression ce = new ClassExpression(t);
1143+
ce.setSourcePosition(ve);
1144+
return ce;
11361145
}
1146+
} else if (!checkingVariableTypeInDeclaration) {
1147+
// GROOVY-4009: When a normal variable is simply being used, there is no need to try to
1148+
// resolve its type. Variable type resolve should proceed only if the variable is being declared.
1149+
return ve;
11371150
}
11381151
resolveOrFail(ve.getType(), ve);
11391152
ClassNode origin = ve.getOriginType();
@@ -1206,14 +1219,14 @@ protected Expression transformBinaryExpression(final BinaryExpression be) {
12061219
protected Expression transformClosureExpression(final ClosureExpression ce) {
12071220
boolean oldInClosure = inClosure;
12081221
inClosure = true;
1209-
for (Parameter para : getParametersSafe(ce)) {
1210-
ClassNode t = para.getType();
1211-
resolveOrFail(t, ce);
1212-
visitAnnotations(para);
1213-
if (para.hasInitialExpression()) {
1214-
para.setInitialExpression(transform(para.getInitialExpression()));
1215-
}
1216-
visitAnnotations(para);
1222+
for (Parameter p : getParametersSafe(ce)) {
1223+
ClassNode t = p.getType();
1224+
resolveOrFail(t, t);
1225+
visitAnnotations(p);
1226+
if (p.hasInitialExpression()) {
1227+
p.setInitialExpression(transform(p.getInitialExpression()));
1228+
}
1229+
visitAnnotations(p);
12171230
}
12181231

12191232
Statement code = ce.getCode();

src/test/groovy/InterfaceTest.groovy

+44-11
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,54 @@
1919
package groovy
2020

2121
import gls.CompilableTestSupport
22+
import org.codehaus.groovy.control.CompilationUnit
2223

23-
class InterfaceTest extends CompilableTestSupport {
24+
final class InterfaceTest extends CompilableTestSupport {
2425

2526
void testGenericsInInterfaceMembers() {
2627
// control
27-
shouldCompile """
28-
interface I1 {
29-
public <T> T copy1(T arg)
30-
public <U extends CharSequence> U copy2(U arg)
31-
public <V, W> V copy3(W arg)
32-
public <N extends Number> void foo()
33-
}
34-
"""
28+
shouldCompile '''
29+
interface I {
30+
def <T> T m1(T x)
31+
def <U extends CharSequence> U m2(U x)
32+
def <V, W> V m3(W x)
33+
def <N extends Number> void m4( )
34+
}
35+
'''
3536
// erroneous
36-
shouldNotCompile "interface I2 { public <?> copy1(arg) }"
37-
shouldNotCompile "interface I3 { public <? extends CharSequence> copy1(arg) }"
37+
shouldNotCompile 'interface I { def <?> m(x) }'
38+
shouldNotCompile 'interface I { def <? extends CharSequence> m(x) }'
39+
}
40+
41+
// GROOVY-5106
42+
void testReImplementsInterface1() {
43+
new CompilationUnit(new GroovyClassLoader(this.class.classLoader)).with {
44+
addSource 'X', '''
45+
interface I<T> {}
46+
interface J<T> extends I<T> {}
47+
class X implements I<String>, J<Number> {}
48+
'''
49+
compile()
50+
51+
assert errorCollector.errorCount == 0
52+
assert errorCollector.warningCount == 1
53+
assert errorCollector.warnings[0].message == 'The interface I is implemented more than once with different arguments: I <String> and I <java.lang.Number>'
54+
}
55+
}
56+
57+
// GROOVY-5106
58+
void testReImplementsInterface2() {
59+
new CompilationUnit(new GroovyClassLoader(this.class.classLoader)).with {
60+
addSource 'X', '''
61+
interface I<T> {}
62+
class X implements I<Number> {}
63+
class Y extends X implements I<String> {}
64+
'''
65+
compile()
66+
67+
assert errorCollector.errorCount == 0
68+
assert errorCollector.warningCount == 1
69+
assert errorCollector.warnings[0].message == 'The interface I is implemented more than once with different arguments: I <String> and I <java.lang.Number>'
70+
}
3871
}
3972
}

0 commit comments

Comments
 (0)