in src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java [4630:4749]
protected ClassNode getResultType(ClassNode left, final int op, final ClassNode right, final BinaryExpression expr) {
ClassNode leftRedirect = left.redirect();
ClassNode rightRedirect = right.redirect();
Expression leftExpression = expr.getLeftExpression();
Expression rightExpression = expr.getRightExpression();
if (op == EQUAL || op == ELVIS_EQUAL) {
if (leftExpression instanceof VariableExpression) {
ClassNode initialType = getOriginalDeclarationType(leftExpression);
if (isDynamicTyped(initialType)) { // GROOVY-11353, GROOVY-11375
ClassNode inferredType = leftExpression.getNodeMetaData(INFERRED_TYPE);
if (inferredType != null ? !isPrimitiveType(inferredType) : Boolean.TRUE.equals(initialType.getNodeMetaData("non-primitive type"))) {
initialType = OBJECT_TYPE;
}
}
if (isPrimitiveType(rightRedirect) && (initialType.isDerivedFrom(Number_TYPE) || (isObjectType(initialType) && !isDynamicTyped(initialType)))) {
return getWrapper(right);
}
if (isPrimitiveType(initialType) && (isNumberType(initialType) ? rightRedirect.isDerivedFrom(Number_TYPE) : rightRedirect == getWrapper(initialType))) { // GROOVY-10359, GROOVY-6574
return getUnwrapper(right);
}
// as anything can be assigned to a String, Class or [Bb]oolean, return the left type instead
if (isWildcardLeftHandSide(initialType) && !isObjectType(initialType)) {
return initialType;
}
}
if (!isObjectType(leftRedirect)) {
if (rightExpression instanceof ListExpression) {
if (LIST_TYPE.equals(leftRedirect)
|| ITERABLE_TYPE.equals(leftRedirect)
|| Collection_TYPE.equals(leftRedirect)
|| ArrayList_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912
return getLiteralResultType(left, right, ArrayList_TYPE); // GROOVY-7128
}
if (SET_TYPE.equals(leftRedirect)
|| LinkedHashSet_TYPE.isDerivedFrom(leftRedirect)) { // GROOVY-6912
return getLiteralResultType(left, right, LinkedHashSet_TYPE); // GROOVY-7128
}
} else if (rightExpression instanceof MapExpression) {
if (MAP_TYPE.equals(leftRedirect)
|| LinkedHashMap_TYPE.isDerivedFrom(leftRedirect)) {
return getLiteralResultType(left, right, LinkedHashMap_TYPE); // GROOVY-7128, GROOVY-9844
}
} else if (rightExpression instanceof ClosureExpression
|| rightExpression instanceof MethodPointerExpression) {
if (isSAMType(leftRedirect)) {
return left; // coercion
}
}
}
return right;
}
if (isBoolIntrinsicOp(op)) {
return boolean_TYPE;
}
if (op == FIND_REGEX) {
return Matcher_TYPE;
}
if (isArrayOp(op)) {
if (isOrImplements(left, MAP_TYPE) && (isStringType(right) || isGStringOrGStringStringLUB(right))) { // GROOVY-5700, GROOVY-6668, GROOVY-8212, GROOVY-8788
PropertyExpression prop = propX(leftExpression, rightExpression); // m['xx'] -> m.xx
return existsProperty(prop, !typeCheckingContext.isTargetOfEnclosingAssignment(expr))
? getType(prop) : getTypeForMapPropertyExpression(left, prop);
}
Expression copy = binX(leftExpression, expr.getOperation(), rightExpression);
copy.setSourcePosition(expr); // do not propagate BINARY_EXP_TARGET, etc.
MethodNode method = findMethodOrFail(copy, left, "getAt", rightRedirect);
if (method != null && !isNumberCategory(getWrapper(rightRedirect))) {
return inferReturnTypeGenerics(left, method, rightExpression);
}
return inferComponentType(left, right);
}
String operationName = getOperationName(op);
if (operationName == null) throw new GroovyBugError(
"Unknown result type for binary operator " + op);
// the left operand is determining the result of the operation
// for primitives and their wrapper we use a fixed table here:
ClassNode mathResultType = getMathResultType(op, leftRedirect, rightRedirect, operationName);
if (mathResultType != null) {
return mathResultType;
}
// GROOVY-9006: compare to null for types that overload equals
if ("equals".equals(operationName) && (left == UNKNOWN_PARAMETER_TYPE
|| right == UNKNOWN_PARAMETER_TYPE)) {
return boolean_TYPE;
}
// GROOVY-5890: do not mix Class<Type> with Type
if (leftExpression instanceof ClassExpression) {
left = CLASS_Type.getPlainNodeReference();
}
MethodNode method = findMethodOrFail(expr, left, operationName, right);
if (method != null) {
if (op == COMPARE_NOT_IN && isDefaultExtension(method)) {
// GROOVY-10915: check if left implements its own isCase method
MethodNode isCase = findMethodOrFail(expr, left, "isCase", right);
if (isCase != null && !isDefaultExtension(isCase)) return null; // require dynamic dispatch
}
storeTargetMethod(expr, method);
typeCheckMethodsWithGenericsOrFail(left, new ClassNode[]{right}, method, expr);
if (isAssignment(op)) return left;
if (!"compareTo".equals(operationName))
return inferReturnTypeGenerics(left, method, args(rightExpression));
}
if (isCompareToBoolean(op)) return boolean_TYPE;
if (op == COMPARE_TO) return int_TYPE;
return null;
}