Skip to content
Permalink
Browse files
GROOVY-10342: STC: type parameter can accept parameterized return values
  • Loading branch information
eric-milles committed May 19, 2022
1 parent d4a0cbc commit 006b5987a7d91c4a4754d9e4a1191484e43f9868
Showing 3 changed files with 60 additions and 20 deletions.
@@ -121,6 +121,7 @@
import static org.codehaus.groovy.ast.ClassHelper.makeWithoutCaching;
import static org.codehaus.groovy.ast.ClassHelper.short_TYPE;
import static org.codehaus.groovy.ast.ClassHelper.void_WRAPPER_TYPE;
import static org.codehaus.groovy.ast.tools.WideningCategories.implementsInterfaceOrSubclassOf;
import static org.codehaus.groovy.ast.tools.WideningCategories.isFloatingCategory;
import static org.codehaus.groovy.ast.tools.WideningCategories.isLongCategory;
import static org.codehaus.groovy.ast.tools.WideningCategories.lowestUpperBound;
@@ -698,11 +699,21 @@ public static boolean checkCompatibleAssignmentTypes(final ClassNode left, final
if (isNumberType(rightRedirect) /*|| rightRedirect == char_TYPE*/) {
return true;
}
if (leftRedirect == char_TYPE && rightRedirect == Character_TYPE) return true;
if (leftRedirect == Character_TYPE && rightRedirect == char_TYPE) return true;
if ((leftRedirect == char_TYPE || leftRedirect == Character_TYPE) && rightRedirect == STRING_TYPE) {
return rightExpression instanceof ConstantExpression && rightExpression.getText().length() == 1;
}
} else if (isFloatingCategory(getUnwrapper(leftRedirect))) {
// float or double can be assigned any base number type or BigDecimal
if (isNumberType(rightRedirect) || isBigDecimalType(rightRedirect)) {
return true;
}
} else if (left.isGenericsPlaceHolder()) { // must precede non-final types
return right.getUnresolvedName().charAt(0) != '#' // RHS not adaptable
? left.getGenericsTypes()[0].isCompatibleWith(right) // GROOVY-7307, GROOVY-9952, et al.
: implementsInterfaceOrSubclassOf(leftRedirect, rightRedirect); // GROOVY-10067, GROOVY-10342

} else if (isBigDecimalType(leftRedirect) || Number_TYPE.equals(leftRedirect)) {
// BigDecimal or Number can be assigned any derivitave of java.lang.Number
if (isNumberType(rightRedirect) || rightRedirect.isDerivedFrom(Number_TYPE)) {
@@ -713,22 +724,16 @@ public static boolean checkCompatibleAssignmentTypes(final ClassNode left, final
if (isLongCategory(getUnwrapper(rightRedirect)) || rightRedirect.isDerivedFrom(BigInteger_TYPE)) {
return true;
}
} else if (leftRedirect.isDerivedFrom(Enum_Type)) {
// Enum types can be assigned String or GString (triggers `valueOf` call)
if (rightRedirect == STRING_TYPE || isGStringOrGStringStringLUB(rightRedirect)) {
return true;
}
} else if (isWildcardLeftHandSide(leftRedirect)) {
// Object, String, [Bb]oolean or Class can be assigned anything (except null to boolean)
return !(leftRedirect == boolean_TYPE && isNullConstant(rightExpression));
}

if (leftRedirect == char_TYPE && rightRedirect == Character_TYPE) return true;
if (leftRedirect == Character_TYPE && rightRedirect == char_TYPE) return true;
if ((leftRedirect == char_TYPE || leftRedirect == Character_TYPE) && rightRedirect == STRING_TYPE) {
return rightExpression instanceof ConstantExpression && rightExpression.getText().length() == 1;
}

// if left is an enum and right is String or GString we do valueOf
if (leftRedirect.isDerivedFrom(Enum_Type) && (rightRedirect == STRING_TYPE || isGStringType(rightRedirect))) {
return true;
}

// if right is array, map or collection we try invoking the constructor
if (allowConstructorCoercion && isGroovyConstructorCompatible(rightExpression)) {
// TODO: in case of the array we could maybe make a partial check
@@ -738,16 +743,14 @@ public static boolean checkCompatibleAssignmentTypes(final ClassNode left, final
return true;
}

// simple sub-type check
if (!left.isInterface() ? right.isDerivedFrom(left) : GeneralUtils.isOrImplements(right, left)) return true;
if (implementsInterfaceOrSubclassOf(right, left)) {
return true;
}

if (right.isDerivedFrom(CLOSURE_TYPE) && isSAMType(left)) {
return true;
}

if (left.isGenericsPlaceHolder()) {
return left.getGenericsTypes()[0].isCompatibleWith(right);
}
// GROOVY-7316, GROOVY-10256: "Type x = m()" given "def <T> T m()"; T adapts to target
return right.isGenericsPlaceHolder() && right.asGenericsType().isCompatibleWith(left);
}
@@ -865,6 +868,9 @@ static String toMethodParametersString(final String methodName, final ClassNode.
* with trailing "[]".
*/
static String prettyPrintType(final ClassNode type) {
if (type.getUnresolvedName().charAt(0) == '#') {
return type.redirect().toString(false);
}
return type.toString(false);
}

@@ -848,7 +848,10 @@ && isAssignment(enclosingBinaryExpression.getOperation().getType())) {
if (rightExpression instanceof ConstructorCallExpression)
inferDiamondType((ConstructorCallExpression) rightExpression, lType);

if (lType.isUsingGenerics() && missesGenericsTypes(resultType)) {
if (lType.isUsingGenerics()
&& missesGenericsTypes(resultType)
// GROOVY-10324, GROOVY-10342, et al.
&& !resultType.isGenericsPlaceHolder()) {
// unchecked assignment
// List<Type> list = new LinkedList()
// Iterable<Type> iter = new LinkedList()
@@ -858,16 +861,18 @@ && isAssignment(enclosingBinaryExpression.getOperation().getType())) {
// the inferred type of the binary expression is the type of the RHS
// "completed" with generics type information available from the LHS
if (lType.equals(resultType)) {
// GROOVY-6126, GROOVY-6558, GROOVY-6564, et al.
if (!lType.isGenericsPlaceHolder()) resultType = lType;
} else if (!resultType.isGenericsPlaceHolder()) { // GROOVY-10324
} else {
// GROOVY-5640, GROOVY-9033, GROOVY-10220, GROOVY-10235, et al.
Map<GenericsTypeName, GenericsType> gt = new HashMap<>();
extractGenericsConnections(gt, resultType, resultType.redirect());
ClassNode sc = resultType;
do { sc = getNextSuperClass(sc, lType);
} while (sc != null && !sc.equals(lType));
extractGenericsConnections(gt, lType, sc);

resultType = applyGenericsContext(gt, resultType.redirect());// GROOVY-10235, et al.
resultType = applyGenericsContext(gt, resultType.redirect());
}
}

@@ -500,7 +500,7 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
chars()
}
''',
'Cannot return value of type #T for method returning java.util.List'
'Cannot return value of type java.lang.CharSequence for method returning java.util.List'
}

// GROOVY-10098
@@ -1471,6 +1471,35 @@ class GenericsSTCTest extends StaticTypeCheckingTestCase {
'''
}

// GROOVY-10342
void testAssignmentShouldWorkForParameterizedType2() {
assertScript '''
class C<T> {
T t
}
def <X> X m() {
123
}
def <N extends Number> void test() {
int x = m()
Integer y = m()
C<Integer> z = new C<>(); z.t = m()
C<N> c_of_n = new C<N>(); c_of_n.t = m() // Cannot assign value of type #X to variable of type N
}
test()
'''

shouldFailWithMessages '''
def <X extends CharSequence> X m() {
}
def <N extends Number> void test() {
N n = m()
}
''',
'Cannot assign value of type java.lang.CharSequence to variable of type N'
}

// GROOVY-9555
void testAssignmentShouldWorkForProperUpperBound() {
assertScript '''

0 comments on commit 006b598

Please sign in to comment.