Skip to content

Commit

Permalink
GROOVY-10153, GROOVY-10671, GROOVY-10674: wildcard implicit bounding
Browse files Browse the repository at this point in the history
3_0_X backport
  • Loading branch information
eric-milles committed Dec 2, 2023
1 parent 7e51c63 commit bfa540e
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,21 @@ public void visitEnd() {
finished(baseType);
} else {
ClassNode parameterizedType = baseType.getPlainNodeReference();
if (!arguments.isEmpty()) { // else GROOVY-10234: no type arguments -> raw type
if (arguments.isEmpty()) {
// GROOVY-10234: no type arguments -> raw type
} else {
try { // see ResolveVisitor#resolveWildcardBounding
for (int i = 0, n = arguments.size(); i < n; i += 1) {
GenericsType argument = arguments.get(i);
if (!argument.isWildcard() || argument.getUpperBounds() != null) continue;
ClassNode[] implicitBounds = baseType.getGenericsTypes()[i].getUpperBounds();
if (implicitBounds != null && !ClassHelper.OBJECT_TYPE.equals(implicitBounds[0])) {
argument.getType().setRedirect(implicitBounds[0]); // GROOVY-10671: bound isn't Object
}
}
} catch (StackOverflowError ignore) {
// TODO: self-referential type parameter
}
parameterizedType.setGenericsTypes(arguments.toArray(GenericsType.EMPTY_ARRAY));
}
finished(parameterizedType);
Expand Down
56 changes: 38 additions & 18 deletions src/main/java/org/codehaus/groovy/control/ResolveVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,9 @@ protected boolean resolve(final ClassNode type) {
}

protected boolean resolve(final ClassNode type, final boolean testModuleImports, final boolean testDefaultImports, final boolean testStaticInnerClasses) {
resolveGenericsTypes(type.getGenericsTypes());
GenericsType[] genericsTypes = type.getGenericsTypes();
resolveGenericsTypes(genericsTypes);

if (type.isPrimaryClassNode()) return true;
if (type.isArray()) {
ClassNode element = type.getComponentType();
Expand All @@ -481,30 +483,34 @@ protected boolean resolve(final ClassNode type, final boolean testModuleImports,
}
if (type.isResolved()) return true;

// test if vanilla name is current class name
if (currentClass == type) return true;

String typeName = type.getName();

GenericsType genericsType = genericParameterNames.get(new GenericsTypeName(typeName));
if (genericsType != null) {
type.setRedirect(genericsType.getType());
type.setGenericsTypes(new GenericsType[]{genericsType});
GenericsType typeParameter = genericParameterNames.get(new GenericsTypeName(typeName));
if (typeParameter != null) {
type.setDeclaringClass(typeParameter.getType().getDeclaringClass());
type.setGenericsTypes(new GenericsType[]{typeParameter});
type.setRedirect(typeParameter.getType());
type.setGenericsPlaceHolder(true);
return true;
}

boolean resolved;
if (currentClass.getNameWithoutPackage().equals(typeName)) {
type.setRedirect(currentClass);
return true;
resolved = true;
} else {
resolved = (!type.hasPackageName() && resolveNestedClass(type))
|| resolveFromModule(type, testModuleImports)
|| resolveFromCompileUnit(type)
|| (testDefaultImports && !type.hasPackageName() && resolveFromDefaultImports(type))
|| resolveToOuter(type)
|| (testStaticInnerClasses && type.hasPackageName() && resolveFromStaticInnerClasses(type));
}

return (!type.hasPackageName() && resolveNestedClass(type)) ||
resolveFromModule(type, testModuleImports) ||
resolveFromCompileUnit(type) ||
(testDefaultImports && !type.hasPackageName() && resolveFromDefaultImports(type)) ||
resolveToOuter(type) ||
(testStaticInnerClasses && type.hasPackageName() && resolveFromStaticInnerClasses(type));
// GROOVY-10153: handle "C<? super T>"
if (resolved && genericsTypes != null) {
resolveWildcardBounding(genericsTypes, type);
}
return resolved;
}

protected boolean resolveNestedClass(final ClassNode type) {
Expand Down Expand Up @@ -1663,16 +1669,30 @@ private boolean resolveGenericsType(final GenericsType genericsType) {
type.setRedirect(gt.getType());
genericsType.setPlaceholder(true);
}

if (genericsType.getLowerBound() != null) {
resolveOrFail(genericsType.getLowerBound(), genericsType);
}

if (resolveGenericsTypes(type.getGenericsTypes())) {
genericsType.setResolved(genericsType.getType().isResolved());
}
return genericsType.isResolved();
}

/**
* For cases like "Foo&lt;? super Bar> -> Foo&lt;T extends Baz>" there is an
* implicit upper bound on the wildcard type argument. It was unavailable at
* the time "? super Bar" was resolved but is present in type's redirect now.
*/
private static void resolveWildcardBounding(final GenericsType[] typeArguments, final ClassNode type) {
for (int i = 0, n = typeArguments.length; i < n; i += 1) { GenericsType argument= typeArguments[i];
if (!argument.isWildcard() || argument.getUpperBounds() != null) continue;
GenericsType[] parameters = type.redirect().getGenericsTypes();
if (parameters != null && i < parameters.length) {
ClassNode implicitBound = parameters[i].getType();
if (!ClassHelper.OBJECT_TYPE.equals(implicitBound))
argument.getType().setRedirect(implicitBound);
}
}
}

private static Map<String, ClassNode> findHierClasses(final ClassNode currentClass) {
Expand Down
14 changes: 12 additions & 2 deletions src/test/groovy/transform/stc/GenericsSTCTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -1736,7 +1736,6 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
}

// GROOVY-10674
@NotYetImplemented
void testDiamondInferenceFromConstructor36() {
assertScript '''
class Foo<BB extends Bar<Byte>, X extends BB> {
Expand Down Expand Up @@ -3517,7 +3516,6 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
}

// GROOVY-10153
@NotYetImplemented
void testCompatibleArgumentsForPlaceholders11() {
for (t in ['A', 'B', 'C']) {
assertScript """
Expand Down Expand Up @@ -5403,6 +5401,18 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
'''
}

// GROOVY-10671
void testAssertJ() {
assertScript '''
@Grab('org.assertj:assertj-core:3.23.1')
import static org.assertj.core.api.Assertions.assertThat
def strings = (Collection<String>) ['a','b']
assertThat(strings).as('assertion description')
.containsExactlyInAnyOrderElementsOf(['a','b'])
'''
}

// GROOVY-10673, GROOVY-11057
void testMockito() {
assertScript '''
Expand Down

0 comments on commit bfa540e

Please sign in to comment.