Skip to content

Commit

Permalink
GROOVY-5106, GROOVY-11508: re-implement trait with new type args warning
Browse files Browse the repository at this point in the history
(closes #2117)
  • Loading branch information
eric-milles committed Oct 25, 2024
1 parent b13ce1c commit 9d0d671
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 47 deletions.
12 changes: 9 additions & 3 deletions src/main/java/org/codehaus/groovy/classgen/Verifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,15 @@ private static void checkForDuplicateInterfaces(final ClassNode cn) {
for (ClassNode t : set) { // find match and check generics
if (t.equals(in)) {
String one = in.toString(false), two = t.toString(false);
if (!one.equals(two))
throw new RuntimeParserException("The interface " + in.getNameWithoutPackage() +
" cannot be implemented more than once with different arguments: " + one + " and " + two, cn);
if (!one.equals(two)) {
if (Traits.isTrait(in)) { // GROOVY-11508
cn.getModule().getContext().addWarning("The trait " + in.getNameWithoutPackage() +
" is implemented more than once with different arguments: " + one + " and " + two, cn);
} else {
throw new RuntimeParserException("The interface " + in.getNameWithoutPackage() +
" cannot be implemented more than once with different arguments: " + one + " and " + two, cn);
}
}
break;
}
}
Expand Down
85 changes: 49 additions & 36 deletions src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -263,63 +263,76 @@ protected SourceUnit getSourceUnit() {
return source;
}

@Override
public void visitField(final FieldNode node) {
Map<GenericsTypeName, GenericsType> oldNames = genericParameterNames;
if (!canSeeTypeVars(node.getModifiers(), node.getDeclaringClass())) {
genericParameterNames = Collections.emptyMap();
}

if (!fieldTypesChecked.contains(node)) {
resolveOrFail(node.getType(), node);
}
super.visitField(node);

genericParameterNames = oldNames;
}

@Override
public void visitProperty(final PropertyNode node) {
Map<GenericsTypeName, GenericsType> oldNames = genericParameterNames;
if (!canSeeTypeVars(node.getModifiers(), node.getDeclaringClass())) {
genericParameterNames = Collections.emptyMap();
}

resolveOrFail(node.getType(), node);
fieldTypesChecked.add(node.getField());

super.visitProperty(node);

genericParameterNames = oldNames;
}

private static boolean canSeeTypeVars(final int mods, final ClassNode node) {
return !Modifier.isStatic(mods) || Traits.isTrait(node); // GROOVY-8864, GROOVY-11508
}

@Override
protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) {
VariableScope oldScope = currentScope;
currentScope = node.getVariableScope();
Map<GenericsTypeName, GenericsType> oldNames = genericParameterNames;
genericParameterNames = node.isStatic() && !Traits.isTrait(node.getDeclaringClass())
? new HashMap<>() : new HashMap<>(genericParameterNames);
genericParameterNames =
canSeeTypeVars(node.getModifiers(), node.getDeclaringClass())
? new HashMap<>(genericParameterNames) : new HashMap<>();

resolveGenericsHeader(node.getGenericsTypes());

resolveOrFail(node.getReturnType(), node);
for (Parameter p : node.getParameters()) {
p.setInitialExpression(transform(p.getInitialExpression()));
resolveOrFail(p.getType(), p.getType());
ClassNode t = p.getType();
resolveOrFail(t, t);
visitAnnotations(p);
}
resolveOrFail(node.getReturnType(), node);
if (node.getExceptions() != null) {
for (ClassNode t : node.getExceptions()) {
resolveOrFail(t, node);
resolveOrFail(t, t);
}
}

checkGenericsCyclicInheritance(node.getGenericsTypes());

MethodNode oldCurrentMethod = currentMethod;
currentMethod = node;

super.visitConstructorOrMethod(node, isConstructor);

currentMethod = oldCurrentMethod;
genericParameterNames = oldNames;
currentScope = oldScope;
}

@Override
public void visitField(final FieldNode node) {
ClassNode t = node.getType();
if (!fieldTypesChecked.contains(node)) {
resolveOrFail(t, node);
}
super.visitField(node);
}

@Override
public void visitProperty(final PropertyNode node) {
Map<GenericsTypeName, GenericsType> oldPNames = genericParameterNames;
if (node.isStatic() && !Traits.isTrait(node.getDeclaringClass())) {
genericParameterNames = new HashMap<>();
}

ClassNode t = node.getType();
resolveOrFail(t, node);
super.visitProperty(node);
fieldTypesChecked.add(node.getField());

genericParameterNames = oldPNames;
}

private void resolveOrFail(final ClassNode type, final ASTNode node) {
resolveOrFail(type, "", node);
}
Expand Down Expand Up @@ -1098,14 +1111,14 @@ protected Expression transformBinaryExpression(final BinaryExpression be) {
protected Expression transformClosureExpression(final ClosureExpression ce) {
boolean oldInClosure = inClosure;
inClosure = true;
for (Parameter para : getParametersSafe(ce)) {
ClassNode t = para.getType();
resolveOrFail(t, ce);
visitAnnotations(para);
if (para.hasInitialExpression()) {
para.setInitialExpression(transform(para.getInitialExpression()));
for (Parameter p : getParametersSafe(ce)) {
ClassNode t = p.getType();
resolveOrFail(t, t);
visitAnnotations(p);
if (p.hasInitialExpression()) {
p.setInitialExpression(transform(p.getInitialExpression()));
}
visitAnnotations(para);
visitAnnotations(p);
}

Statement code = ce.getCode();
Expand Down
45 changes: 45 additions & 0 deletions src/test/org/codehaus/groovy/transform/traitx/Groovy11508.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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.traitx

import org.junit.Test

import static groovy.test.GroovyAssert.assertScript

final class Groovy11508 {
@Test
void testGenericsAppliedToStaticMethodsForTraits() {
assertScript '''
trait T<U> {
static U getYou() {
}
}
class C implements T<C> {
}
class D extends C implements T<D> {
}
C c = C.you
D d = D.you
assert C.getMethod('getYou').returnType == C
assert D.getMethod('getYou').returnType == D
'''
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,27 @@
* specific language governing permissions and limitations
* under the License.
*/
package groovy.bugs
package org.codehaus.groovy.transform.traitx

import groovy.test.GroovyTestCase
import org.junit.Test

class Groovy8864Bug extends GroovyTestCase {
import static groovy.test.GroovyAssert.assertScript

final class Groovy8864 {
@Test
void testGenericsAppliedToStaticMethodsForTraits() {
assertScript '''
trait Foo<T> {
static T INSTANCE
static T get(T arg) {
null
}
static T get(T t) {
}
}
class Bar implements Foo<Bar> {
}
class Bar implements Foo<Bar> {}
assert Bar.getMethod("get", [Bar] as Class[]).returnType == Bar
assert Bar.getDeclaredField("Foo__INSTANCE").type == Bar
assert Bar.getMethod("setINSTANCE", [Bar] as Class[])
assert Bar.getMethod("setINSTANCE", new Class[]{Bar})
assert Bar.getMethod("getINSTANCE").returnType == Bar
'''
}
Expand Down

0 comments on commit 9d0d671

Please sign in to comment.