private BoundExpression ApplyConvertedTypes(BoundExpression expr, TupleBinaryOperatorInfo @operator, bool isRight, DiagnosticBag diagnostics)
        {
            TypeSymbol convertedType = isRight ? @operator.RightConvertedTypeOpt : @operator.LeftConvertedTypeOpt;

            if (convertedType is null)
            {
                if (@operator.InfoKind == TupleBinaryOperatorInfoKind.Multiple && expr.Kind == BoundKind.TupleLiteral)
                {
                    // Although the tuple will remain typeless, we'll give elements converted types as possible
                    var multiple = (TupleBinaryOperatorInfo.Multiple)@operator;
                    if (multiple.Operators.Length == 0)
                    {
                        return(expr);
                    }

                    var tuple = (BoundTupleLiteral)expr;
                    ImmutableArray <BoundExpression> arguments = tuple.Arguments;
                    int length = arguments.Length;
                    Debug.Assert(length == multiple.Operators.Length);

                    var builder = ArrayBuilder <BoundExpression> .GetInstance(length);

                    for (int i = 0; i < length; i++)
                    {
                        builder.Add(ApplyConvertedTypes(arguments[i], multiple.Operators[i], isRight, diagnostics));
                    }

                    return(tuple.Update(argumentNamesOpt: default, inferredNamesOpt: default, builder.ToImmutableAndFree(), tuple.Type));
        private BoundExpression RewriteTupleOperator(TupleBinaryOperatorInfo @operator,
                                                     BoundExpression left, BoundExpression right, TypeSymbol boolType,
                                                     ArrayBuilder <LocalSymbol> temps, BinaryOperatorKind operatorKind)
        {
            switch (@operator.InfoKind)
            {
            case TupleBinaryOperatorInfoKind.Multiple:
                return(RewriteTupleNestedOperators((TupleBinaryOperatorInfo.Multiple)@operator, left, right, boolType, temps, operatorKind));

            case TupleBinaryOperatorInfoKind.Single:
                return(RewriteTupleSingleOperator((TupleBinaryOperatorInfo.Single)@operator, left, right, boolType, operatorKind));

            case TupleBinaryOperatorInfoKind.NullNull:
                var nullnull = (TupleBinaryOperatorInfo.NullNull)@operator;
                return(new BoundLiteral(left.Syntax, ConstantValue.Create(nullnull.Kind == BinaryOperatorKind.Equal), boolType));

            default:
                throw ExceptionUtilities.UnexpectedValue(@operator.InfoKind);
            }
        }
        /// <summary>
        /// Walk down tuple literals and replace all the side-effecting elements that need saving with temps.
        /// Expressions that are not tuple literals need saving, and tuple literals that are involved in a simple comparison rather than a tuple comparison.
        /// </summary>
        private BoundExpression ReplaceTerminalElementsWithTemps(BoundExpression expr, TupleBinaryOperatorInfo operators, ArrayBuilder <BoundExpression> initEffects, ArrayBuilder <LocalSymbol> temps)
        {
            if (operators.InfoKind == TupleBinaryOperatorInfoKind.Multiple)
            {
                // Example:
                // in `(expr1, expr2) == (..., ...)` we need to save `expr1` and `expr2`
                if (expr.Kind == BoundKind.TupleLiteral)
                {
                    var tuple    = (BoundTupleLiteral)expr;
                    var multiple = (TupleBinaryOperatorInfo.Multiple)operators;
                    var builder  = ArrayBuilder <BoundExpression> .GetInstance(tuple.Arguments.Length);

                    for (int i = 0; i < tuple.Arguments.Length; i++)
                    {
                        var argument    = tuple.Arguments[i];
                        var newArgument = ReplaceTerminalElementsWithTemps(argument, multiple.Operators[i], initEffects, temps);
                        builder.Add(newArgument);
                    }
                    return(new BoundTupleLiteral(tuple.Syntax, tuple.ArgumentNamesOpt, tuple.InferredNamesOpt, builder.ToImmutableAndFree(), tuple.Type, tuple.HasErrors));
                }
            }

            // Examples:
            // in `expr == (..., ...)` we need to save `expr` because it's not a tuple literal
            // in `(..., expr) == (..., (..., ...))` we need to save `expr` because it is used in a simple comparison
            BoundExpression loweredExpr = VisitExpression(expr);

            if ((object)loweredExpr.Type != null)
            {
                BoundExpression value = NullableAlwaysHasValue(loweredExpr);
                if (value != null)
                {
                    // Optimization: if the nullable expression always has a value, we'll replace that value
                    // with a temp saving that value
                    BoundExpression savedValue     = EvaluateSideEffectingArgumentToTemp(value, initEffects, temps);
                    var             objectCreation = (BoundObjectCreationExpression)loweredExpr;
                    return(objectCreation.UpdateArgumentsAndInitializer(ImmutableArray.Create(savedValue), objectCreation.ArgumentRefKindsOpt, objectCreation.InitializerExpressionOpt));
                }
            }

            // Note: lowered nullable expressions that never have a value also don't have side-effects
            return(EvaluateSideEffectingArgumentToTemp(loweredExpr, initEffects, temps));
        }