in src/main/java/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java [813:980]
public void visitBinaryExpression(final BinaryExpression expression) {
BinaryExpression enclosingBinaryExpression = typeCheckingContext.getEnclosingBinaryExpression();
typeCheckingContext.pushEnclosingBinaryExpression(expression);
try {
int op = expression.getOperation().getType();
Expression leftExpression = expression.getLeftExpression();
Expression rightExpression = expression.getRightExpression();
if (isAssignment(op) && op != EQUAL) {
ExpressionTransformer cloneMaker = new ExpressionTransformer() {
@Override
public Expression transform(final Expression expr) {
if (expr instanceof VariableExpression) {
return ((VariableExpression) expr).clone();
}
return expr.transformExpression(this);
}
};
// GROOVY-10628, GROOVY-11563: for "x op= y", find type as if it was "x = x op y"
rightExpression = (op == ELVIS_EQUAL ? elvisX(cloneMaker.transform(leftExpression), rightExpression) :
binX(cloneMaker.transform(leftExpression), Token.newSymbol(TokenUtil.removeAssignment(op), expression.getOperation().getStartLine(), expression.getOperation().getStartColumn()), rightExpression));
rightExpression.setSourcePosition(expression);
visitBinaryExpression(assignX(leftExpression, rightExpression, expression));
expression.copyNodeMetaData(rightExpression);
return;
}
leftExpression.visit(this);
ClassNode lType = getType(leftExpression);
var setterInfo = removeSetterInfo(leftExpression);
if (setterInfo != null) {
if (ensureValidSetter(expression, leftExpression, rightExpression, setterInfo)) {
return;
}
} else {
if (op == EQUAL) {
lType = getOriginalDeclarationType(leftExpression);
applyTargetType(lType, rightExpression);
}
rightExpression.visit(this);
}
ClassNode rType = isNullConstant(rightExpression)
? isPrimitiveType(lType) ? OBJECT_TYPE : UNKNOWN_PARAMETER_TYPE
: getInferredTypeFromTempInfo(rightExpression, getType(rightExpression));
ClassNode resultType;
if (op == KEYWORD_IN || op == COMPARE_NOT_IN) {
// for the "in" or "!in" operator, the receiver and the arguments are reversed
BinaryExpression reverseExpression = binX(rightExpression, expression.getOperation(), leftExpression);
resultType = getResultType(rType, op, lType, reverseExpression);
if (resultType == null) resultType = boolean_TYPE; // GROOVY-10239
storeTargetMethod(expression, reverseExpression.getNodeMetaData(DIRECT_METHOD_CALL_TARGET));
} else {
resultType = getResultType(lType, op, rType, expression);
}
if (resultType == null) {
resultType = lType;
}
if (isArrayOp(op)) {
if (leftExpression instanceof VariableExpression) {//GROOVY-6782
if (leftExpression.getNodeMetaData(INFERRED_TYPE) == null) {
leftExpression.removeNodeMetaData(INFERRED_RETURN_TYPE);
storeType(leftExpression, lType);
}
}
if (!lType.isArray()
&& enclosingBinaryExpression != null
&& enclosingBinaryExpression.getLeftExpression() == expression
&& isAssignment(enclosingBinaryExpression.getOperation().getType())) {
// left hand side of a subscript assignment: map['key'] = ...
Expression enclosingExpressionRHS = enclosingBinaryExpression.getRightExpression();
if (!(enclosingExpressionRHS instanceof ClosureExpression)) {
enclosingExpressionRHS.visit(this);
}
ClassNode[] arguments = {rType, isNullConstant(enclosingExpressionRHS) // GROOVY-11621
? UNKNOWN_PARAMETER_TYPE : getType(enclosingExpressionRHS)};
List<MethodNode> methods = findMethod(lType, "putAt", arguments);
if (methods.isEmpty()) {
addNoMatchingMethodError(lType, "putAt", arguments, enclosingBinaryExpression);
} else if (methods.size() == 1) {
typeCheckMethodsWithGenericsOrFail(lType, arguments, methods.get(0), enclosingExpressionRHS);
}
}
}
boolean isEmptyDeclaration = (expression instanceof DeclarationExpression
&& (rightExpression instanceof EmptyExpression || rType == UNKNOWN_PARAMETER_TYPE));
if (isEmptyDeclaration) {
expression.putNodeMetaData(INFERRED_TYPE, lType);
// GROOVY-11353: "def var = null" cannot be a primitive type
if (isDynamicTyped(lType) && rType == UNKNOWN_PARAMETER_TYPE)
lType.putNodeMetaData("non-primitive type", Boolean.TRUE);
} else if (isAssignment(op)) {
if (rightExpression instanceof ConstructorCallExpression)
inferDiamondType((ConstructorCallExpression) rightExpression, lType);
// handle unchecked assignment: List<Type> list = []
resultType = adjustForTargetType(resultType, lType);
ClassNode originType = getOriginalDeclarationType(leftExpression);
typeCheckAssignment(expression, leftExpression, originType, rightExpression, resultType);
// check for implicit conversion like "String a = 123", "int[] b = [1,2,3]", "List c = [].stream()", etc.
if (!implementsInterfaceOrIsSubclassOf(wrapTypeIfNecessary(resultType), wrapTypeIfNecessary(originType))) {
resultType = originType;
} else {
// GROOVY-7549: RHS type may not be accessible to enclosing class
int modifiers = resultType.getModifiers();
ClassNode enclosingType = typeCheckingContext.getEnclosingClassNode();
if (!Modifier.isPublic(modifiers) && !enclosingType.equals(resultType)
&& !getNestHost(enclosingType).equals(getNestHost(resultType))
&& (Modifier.isPrivate(modifiers) || !inSamePackage(enclosingType, resultType))) {
resultType = originType; // TODO: Find accessible type in hierarchy of resultType?
} else if (GenericsUtils.hasUnresolvedGenerics(resultType)) { // GROOVY-9033, GROOVY-10089, et al.
Map<GenericsTypeName, GenericsType> enclosing = extractGenericsParameterMapOfThis(typeCheckingContext);
resultType = fullyResolveType(resultType, Optional.ofNullable(enclosing).orElseGet(Collections::emptyMap));
}
}
// track conditional assignment
if (leftExpression instanceof VariableExpression
&& typeCheckingContext.ifElseForWhileAssignmentTracker != null) {
var targetVariable = ((VariableExpression) leftExpression).getAccessedVariable();
if (targetVariable instanceof Parameter) {
targetVariable = new ParameterVariableExpression((Parameter) targetVariable);
}
if (targetVariable instanceof VariableExpression) {
recordAssignment((VariableExpression) targetVariable, resultType);
}
}
storeType(leftExpression, resultType);
// propagate closure parameter information
if (leftExpression instanceof VariableExpression
// GROOVY-11400: save to declared variable; skip field, property, parameter or dynamic variable
&& ((VariableExpression) leftExpression).getAccessedVariable() instanceof VariableExpression) {
var targetVariable = (VariableExpression) ((VariableExpression) leftExpression).getAccessedVariable();
if (rightExpression instanceof ClosureExpression) {
var closure = (ClosureExpression) rightExpression;
if (!hasImplicitParameter(closure)) // GROOVY-11394: arrow means zero parameters
targetVariable.putNodeMetaData(CLOSURE_ARGUMENTS, getParametersSafe(closure));
} else if (rightExpression instanceof VariableExpression
&& ((VariableExpression) rightExpression).getAccessedVariable() instanceof VariableExpression) {
var sourceVariable = (VariableExpression) ((VariableExpression) rightExpression).getAccessedVariable();
targetVariable.putNodeMetaData(CLOSURE_ARGUMENTS, sourceVariable.getNodeMetaData(CLOSURE_ARGUMENTS));
}
}
} else if (op == KEYWORD_INSTANCEOF) {
pushInstanceOfTypeInfo(leftExpression, rightExpression);
} else if (op == COMPARE_NOT_INSTANCEOF) { // GROOVY-6429, GROOVY-8321, GROOVY-8412, GROOVY-8523, GROOVY-9931
putNotInstanceOfTypeInfo(extractTemporaryTypeInfoKey(leftExpression), Collections.singleton(rightExpression.getType()));
}
if (!isEmptyDeclaration) {
storeType(expression, resultType);
validateResourceInARM(expression, resultType);
}
// GROOVY-5874: if left expression is a closure shared variable, a second pass should be done
if (leftExpression instanceof VariableExpression && ((VariableExpression) leftExpression).isClosureSharedVariable()) {
typeCheckingContext.secondPassExpressions.add(new SecondPassExpression<>(expression));
}
} finally {
typeCheckingContext.popEnclosingBinaryExpression();
}
}