public void visitBinaryExpression()

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();
        }
    }