private void Recurse(SyntaxTreeAnalysisContext context, ReportDiagnostic severity, ArrayBuilder <SyntaxNode> stack)
        {
            var tree = context.Tree;
            var cancellationToken = context.CancellationToken;

            var root = tree.GetRoot(cancellationToken);
            var text = tree.GetText(cancellationToken);

            stack.Add(root);
            while (stack.Count > 0)
            {
                cancellationToken.ThrowIfCancellationRequested();

                var current = stack.Last();
                stack.RemoveLast();

                // Don't bother analyzing nodes that have syntax errors in them.
                if (current.ContainsDiagnostics && current.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error))
                {
                    continue;
                }

                foreach (var child in current.ChildNodesAndTokens())
                {
                    if (child.IsNode)
                    {
                        stack.Add(child.AsNode() !);
                    }
                    else if (child.IsToken)
                    {
                        ProcessToken(context, severity, text, child.AsToken());
                    }
                }
            }
        }
Exemplo n.º 2
0
        protected SyntaxNode CreateInterpolatedString(
            Document document, bool isVerbatimStringLiteral, ArrayBuilder <SyntaxNode> pieces)
        {
            var syntaxFacts = document.GetLanguageService <ISyntaxFactsService>();
            var generator   = SyntaxGenerator.GetGenerator(document);
            var startToken  = generator.CreateInterpolatedStringStartToken(isVerbatimStringLiteral)
                              .WithLeadingTrivia(pieces.First().GetLeadingTrivia());
            var endToken = generator.CreateInterpolatedStringEndToken()
                           .WithTrailingTrivia(pieces.Last().GetTrailingTrivia());

            using var _ = ArrayBuilder <SyntaxNode> .GetInstance(pieces.Count, out var content);

            var previousContentWasStringLiteralExpression = false;

            foreach (var piece in pieces)
            {
                var isCharacterLiteral = syntaxFacts.IsCharacterLiteralExpression(piece);
                var currentContentIsStringOrCharacterLiteral = syntaxFacts.IsStringLiteralExpression(piece) || isCharacterLiteral;
                if (currentContentIsStringOrCharacterLiteral)
                {
                    var text = piece.GetFirstToken().Text;
                    var textWithEscapedBraces = text.Replace("{", "{{").Replace("}", "}}");
                    var textWithoutQuotes     = GetTextWithoutQuotes(textWithEscapedBraces, isVerbatimStringLiteral, isCharacterLiteral);
                    if (previousContentWasStringLiteralExpression)
                    {
                        // Last part we added to the content list was also an interpolated-string-text-node.
                        // We need to combine these as the API for creating an interpolated strings
                        // does not expect to be given a list containing non-contiguous string nodes.
                        // Essentially if we combine '"A" + 1 + "B" + "C"' into '$"A{1}BC"' it must be:
                        //      {InterpolatedStringText}{Interpolation}{InterpolatedStringText}
                        // not:
                        //      {InterpolatedStringText}{Interpolation}{InterpolatedStringText}{InterpolatedStringText}
                        var existingInterpolatedStringTextNode = content.Last();
                        var newText = ConcatenateTextToTextNode(generator, existingInterpolatedStringTextNode, textWithoutQuotes);
                        content[^ 1] = newText;
Exemplo n.º 3
0
        internal static MethodScope GetMethodScope(this ArrayBuilder <ISymUnmanagedScope> scopes, int methodToken, int methodVersion)
        {
            if (scopes.Count == 0)
            {
                return(null);
            }
            var scope = scopes.Last();

            return(new MethodScope(methodToken, methodVersion, scope.GetStartOffset(), scope.GetEndOffset()));
        }
        protected SyntaxNode CreateInterpolatedString(
            Document document, bool isVerbatimStringLiteral, ArrayBuilder <SyntaxNode> pieces)
        {
            var syntaxFacts = document.GetLanguageService <ISyntaxFactsService>();
            var generator   = SyntaxGenerator.GetGenerator(document);
            var startToken  = generator.CreateInterpolatedStringStartToken(isVerbatimStringLiteral)
                              .WithLeadingTrivia(pieces.First().GetLeadingTrivia());
            var endToken = generator.CreateInterpolatedStringEndToken()
                           .WithTrailingTrivia(pieces.Last().GetTrailingTrivia());

            using var _ = ArrayBuilder <SyntaxNode> .GetInstance(pieces.Count, out var content);

            var previousContentWasStringLiteralExpression = false;

            foreach (var piece in pieces)
            {
                var isCharacterLiteral = syntaxFacts.IsCharacterLiteralExpression(piece);
                var currentContentIsStringOrCharacterLiteral = syntaxFacts.IsStringLiteralExpression(piece) || isCharacterLiteral;
                if (currentContentIsStringOrCharacterLiteral)
                {
                    var text = piece.GetFirstToken().Text;
                    var textWithEscapedBraces = text.Replace("{", "{{").Replace("}", "}}");
                    var textWithoutQuotes     = GetTextWithoutQuotes(textWithEscapedBraces, isVerbatimStringLiteral, isCharacterLiteral);
                    if (previousContentWasStringLiteralExpression)
                    {
                        // Last part we added to the content list was also an interpolated-string-text-node.
                        // We need to combine these as the API for creating an interpolated strings
                        // does not expect to be given a list containing non-contiguous string nodes.
                        // Essentially if we combine '"A" + 1 + "B" + "C"' into '$"A{1}BC"' it must be:
                        //      {InterpolatedStringText}{Interpolation}{InterpolatedStringText}
                        // not:
                        //      {InterpolatedStringText}{Interpolation}{InterpolatedStringText}{InterpolatedStringText}
                        var existingInterpolatedStringTextNode = content.Last();
                        var newText = ConcatinateTextToTextNode(generator, existingInterpolatedStringTextNode, textWithoutQuotes);
                        content[content.Count - 1] = newText;
                    }
                    else
                    {
                        // This is either the first string literal we have encountered or it is the most recent one we've seen
                        // after adding an interpolation.  Add a new interpolated-string-text-node to the list.
                        content.Add(generator.InterpolatedStringText(generator.InterpolatedStringTextToken(textWithoutQuotes)));
                    }
                }
                else
                {
                    content.Add(generator.Interpolation(piece.WithoutTrivia()));
                }
                // Update this variable to be true every time we encounter a new string literal expression
                // so we know to concatenate future string literals together if we encounter them.
                previousContentWasStringLiteralExpression = currentContentIsStringOrCharacterLiteral;
            }

            return(generator.InterpolatedStringExpression(startToken, content, endToken));
        }
            internal BoundExpression PopLast()
            {
                if (init.Count == 0)
                {
                    return(null);
                }

                var last = init.Last();

                init.RemoveLast();
                return(last);
            }
        public static void AddExistingItems(BaseObjectCreationExpressionSyntax objectCreation, ArrayBuilder <SyntaxNodeOrToken> nodesAndTokens)
        {
            if (objectCreation.Initializer != null)
            {
                nodesAndTokens.AddRange(objectCreation.Initializer.Expressions.GetWithSeparators());
            }

            // If we have an odd number of elements already, add a comma at the end so that we can add the rest of the
            // items afterwards without a syntax issue.
            if (nodesAndTokens.Count % 2 == 1)
            {
                var last = nodesAndTokens.Last();
                nodesAndTokens.RemoveLast();
                nodesAndTokens.Add(last.WithTrailingTrivia());
                nodesAndTokens.Add(Token(SyntaxKind.CommaToken).WithTrailingTrivia(last.GetTrailingTrivia()));
            }
        }
Exemplo n.º 7
0
        private static ArrayBuilder <T> CreateBuilderFromSequence(IEnumerable <T> sequence)
        {
            Debug.Assert(sequence != null);

            var builder = new ArrayBuilder <T>();

            int count = 0;

            foreach (T item in sequence)
            {
                count++;
                builder.Add(item);

                Assert.Equal(count, builder.Count);
                Assert.Equal(CalculateExpectedCapacity(count), builder.Capacity);
                VerifyBuilderContents(sequence.Take(count), builder);
                Assert.Equal(sequence.First(), builder.First());
                Assert.Equal(item, builder.Last());
            }

            return(builder);
        }
Exemplo n.º 8
0
        /// <summary>
        /// Takes the outputs from the previous deconstructionStep and depending on the structure of variables, will:
        /// - generate further deconstructions,
        /// - or simply conversions and assignments.
        ///
        /// Returns true for success, but false if has errors.
        /// </summary>
        private bool DeconstructOrAssignOutputs(
                        BoundDeconstructionDeconstructStep deconstructionStep,
                        ArrayBuilder<DeconstructionVariable> variables,
                        CSharpSyntaxNode syntax,
                        DiagnosticBag diagnostics,
                        ArrayBuilder<BoundDeconstructionDeconstructStep> deconstructionSteps,
                        ArrayBuilder<BoundDeconstructionAssignmentStep> conversionSteps,
                        ArrayBuilder<BoundDeconstructionAssignmentStep> assignmentSteps,
                        ArrayBuilder<BoundDeconstructionConstructionStep> constructionStepsOpt)
        {
            bool hasErrors = false;
            var constructionInputs = constructionStepsOpt == null ? null : ArrayBuilder<BoundDeconstructValuePlaceholder>.GetInstance();

            int count = variables.Count;
            for (int i = 0; i < count; i++)
            {
                var variable = variables[i];
                var valuePlaceholder = deconstructionStep.OutputPlaceholders[i];

                if (variable.HasNestedVariables)
                {
                    var nested = variable.NestedVariables;
                    if (!DeconstructIntoSteps(valuePlaceholder, syntax, diagnostics, nested, deconstructionSteps, conversionSteps, assignmentSteps, constructionStepsOpt))
                    {
                        hasErrors = true;
                    }
                    else if (constructionInputs != null)
                    {
                        constructionInputs.Add(constructionStepsOpt.Last().OutputPlaceholder);
                    }
                }
                else
                {
                    var conversion = MakeDeconstructionAssignmentStep(variable.Single, valuePlaceholder, syntax, diagnostics);
                    conversionSteps.Add(conversion);

                    var assignment = MakeDeconstructionAssignmentStep(variable.Single, conversion.OutputPlaceholder, syntax, diagnostics);
                    assignmentSteps.Add(assignment);

                    if (constructionInputs != null)
                    {
                        constructionInputs.Add(conversion.OutputPlaceholder);
                    }
                }
            }

            if (constructionStepsOpt != null)
            {
                if (hasErrors)
                {
                    constructionInputs.Free();
                }
                else
                {
                    var construct = MakeDeconstructionConstructionStep(syntax, diagnostics, constructionInputs.ToImmutableAndFree());
                    constructionStepsOpt.Add(construct);
                }
            }

            return !hasErrors;
        }
Exemplo n.º 9
0
        private SyntaxTriviaList RewriteTrivia(
            SyntaxTriviaList triviaList,
            int depth,
            bool isTrailing,
            bool indentAfterLineBreak,
            bool mustHaveSeparator,
            int lineBreaksAfter)
        {
            ArrayBuilder <SyntaxTrivia> currentTriviaList = ArrayBuilder <SyntaxTrivia> .GetInstance(triviaList.Count);

            try
            {
                foreach (var trivia in triviaList)
                {
                    if (trivia.IsKind(SyntaxKind.WhitespaceTrivia) ||
                        trivia.IsKind(SyntaxKind.EndOfLineTrivia) ||
                        trivia.FullWidth == 0)
                    {
                        continue;
                    }

                    var needsSeparator =
                        (currentTriviaList.Count > 0 && NeedsSeparatorBetween(currentTriviaList.Last())) ||
                        (currentTriviaList.Count == 0 && isTrailing);

                    var needsLineBreak = NeedsLineBreakBefore(trivia, isTrailing) ||
                                         (currentTriviaList.Count > 0 && NeedsLineBreakBetween(currentTriviaList.Last(), trivia, isTrailing));

                    if (needsLineBreak && !_afterLineBreak)
                    {
                        currentTriviaList.Add(GetEndOfLine());
                        _afterLineBreak   = true;
                        _afterIndentation = false;
                    }

                    if (_afterLineBreak)
                    {
                        if (!_afterIndentation && NeedsIndentAfterLineBreak(trivia))
                        {
                            currentTriviaList.Add(this.GetIndentation(GetDeclarationDepth(trivia)));
                            _afterIndentation = true;
                        }
                    }
                    else if (needsSeparator)
                    {
                        currentTriviaList.Add(GetSpace());
                        _afterLineBreak   = false;
                        _afterIndentation = false;
                    }

                    if (trivia.HasStructure)
                    {
                        var tr = this.VisitStructuredTrivia(trivia);
                        currentTriviaList.Add(tr);
                    }
                    else if (trivia.IsKind(SyntaxKind.DocumentationCommentExteriorTrivia))
                    {
                        // recreate exterior to remove any leading whitespace
                        currentTriviaList.Add(s_trimmedDocCommentExterior);
                    }
                    else
                    {
                        currentTriviaList.Add(trivia);
                    }

                    if (NeedsLineBreakAfter(trivia, isTrailing) &&
                        (currentTriviaList.Count == 0 || !EndsInLineBreak(currentTriviaList.Last())))
                    {
                        currentTriviaList.Add(GetEndOfLine());
                        _afterLineBreak   = true;
                        _afterIndentation = false;
                    }
                }

                if (lineBreaksAfter > 0)
                {
                    if (currentTriviaList.Count > 0 &&
                        EndsInLineBreak(currentTriviaList.Last()))
                    {
                        lineBreaksAfter--;
                    }

                    for (int i = 0; i < lineBreaksAfter; i++)
                    {
                        currentTriviaList.Add(GetEndOfLine());
                        _afterLineBreak   = true;
                        _afterIndentation = false;
                    }
                }
                else if (indentAfterLineBreak && _afterLineBreak && !_afterIndentation)
                {
                    currentTriviaList.Add(this.GetIndentation(depth));
                    _afterIndentation = true;
                }
                else if (mustHaveSeparator)
                {
                    currentTriviaList.Add(GetSpace());
                    _afterLineBreak   = false;
                    _afterIndentation = false;
                }

                if (currentTriviaList.Count == 0)
                {
                    return(default(SyntaxTriviaList));
                }
                else if (currentTriviaList.Count == 1)
                {
                    return(SyntaxFactory.TriviaList(currentTriviaList.First()));
                }
                else
                {
                    return(SyntaxFactory.TriviaList(currentTriviaList));
                }
            }
            finally
            {
                currentTriviaList.Free();
            }
        }
Exemplo n.º 10
0
        /// <summary>
        /// The strategy of this rewrite is to do rewrite "locally".
        /// We analyze arguments of the concat in a shallow fashion assuming that
        /// lowering and optimizations (including this one) is already done for the arguments.
        /// Based on the arguments we select the most appropriate pattern for the current node.
        ///
        /// NOTE: it is not guaranteed that the node that we chose will be the most optimal since we have only
        ///       local information - i.e. we look at the arguments, but we do not know about siblings.
        ///       When we move to the parent, the node may be rewritten by this or some another optimization.
        ///
        /// Example:
        ///     result = ( "abc" + "def" + null ?? expr1 + "moo" + "baz" ) + expr2
        ///
        /// Will rewrite into:
        ///     result = Concat("abcdef", expr2)
        ///
        /// However there will be transient nodes like  Concat(expr1 + "moo")  that will not be present in the
        /// resulting tree.
        ///
        /// </summary>
        private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOperatorKind operatorKind, BoundExpression loweredLeft, BoundExpression loweredRight, TypeSymbol type)
        {
            Debug.Assert(
                operatorKind == BinaryOperatorKind.StringConcatenation ||
                operatorKind == BinaryOperatorKind.StringAndObjectConcatenation ||
                operatorKind == BinaryOperatorKind.ObjectAndStringConcatenation);

            if (_inExpressionLambda)
            {
                return(RewriteStringConcatInExpressionLambda(syntax, operatorKind, loweredLeft, loweredRight, type));
            }

            // avoid run time boxing and ToString operations if we can reasonably convert to a string at compile time
            loweredLeft  = ConvertConcatExprToStringIfPossible(syntax, loweredLeft);
            loweredRight = ConvertConcatExprToStringIfPossible(syntax, loweredRight);

            // try fold two args without flattening.
            var folded = TryFoldTwoConcatOperands(syntax, loweredLeft, loweredRight);

            if (folded != null)
            {
                return(folded);
            }

            // flatten and merge -  ( expr1 + "A" ) + ("B" + expr2) ===> (expr1 + "AB" + expr2)
            ArrayBuilder <BoundExpression> leftFlattened = ArrayBuilder <BoundExpression> .GetInstance();

            ArrayBuilder <BoundExpression> rightFlattened = ArrayBuilder <BoundExpression> .GetInstance();

            FlattenConcatArg(loweredLeft, leftFlattened);
            FlattenConcatArg(loweredRight, rightFlattened);

            if (leftFlattened.Any() && rightFlattened.Any())
            {
                folded = TryFoldTwoConcatOperands(syntax, leftFlattened.Last(), rightFlattened.First());
                if (folded != null)
                {
                    rightFlattened[0] = folded;
                    leftFlattened.RemoveLast();
                }
            }

            leftFlattened.AddRange(rightFlattened);
            rightFlattened.Free();

            BoundExpression result;

            switch (leftFlattened.Count)
            {
            case 0:
                result = _factory.StringLiteral(string.Empty);
                break;

            case 1:
                result = leftFlattened[0];
                break;

            case 2:
                var left  = leftFlattened[0];
                var right = leftFlattened[1];
                result = RewriteStringConcatenationTwoExprs(syntax, left, right);
                break;

            case 3:
                var first  = leftFlattened[0];
                var second = leftFlattened[1];
                var third  = leftFlattened[2];
                result = RewriteStringConcatenationThreeExprs(syntax, first, second, third);
                break;

            default:
                result = RewriteStringConcatenationManyExprs(syntax, leftFlattened.ToImmutable());
                break;
            }

            leftFlattened.Free();
            return(result);
        }
Exemplo n.º 11
0
        private SyntaxTriviaList RewriteTrivia(
            SyntaxTriviaList triviaList,
            int depth,
            bool isTrailing,
            bool mustBeIndented,
            bool mustHaveSeparator,
            int lineBreaksAfter)
        {
            ArrayBuilder <SyntaxTrivia> currentTriviaList = ArrayBuilder <SyntaxTrivia> .GetInstance(triviaList.Count);

            try
            {
                foreach (var trivia in triviaList)
                {
                    if (trivia.CSharpKind() == SyntaxKind.WhitespaceTrivia ||
                        trivia.CSharpKind() == SyntaxKind.EndOfLineTrivia ||
                        trivia.FullWidth == 0)
                    {
                        continue;
                    }

                    var needsSeparator =
                        (currentTriviaList.Count > 0 && NeedsSeparatorBetween(currentTriviaList.Last())) ||
                        (currentTriviaList.Count == 0 && isTrailing);
                    var needsLineBreak = NeedsLineBreakBefore(trivia) || (currentTriviaList.Count > 0 && NeedsLineBreakBetween(currentTriviaList.Last(), trivia, isTrailing));

                    if (needsLineBreak && !afterLineBreak)
                    {
                        currentTriviaList.Add(GetCarriageReturnLineFeed());
                        afterLineBreak   = true;
                        afterIndentation = false;
                    }

                    if (afterLineBreak)
                    {
                        if (!afterIndentation && NeedsIndentAfterLineBreak(trivia))
                        {
                            currentTriviaList.Add(this.GetIndentation(GetDeclarationDepth(trivia)));
                            afterIndentation = true;
                        }
                    }
                    else if (needsSeparator)
                    {
                        currentTriviaList.Add(GetSpace());
                        afterLineBreak   = false;
                        afterIndentation = false;
                    }

                    if (trivia.HasStructure)
                    {
                        var tr = this.VisitStructuredTrivia(trivia);
                        currentTriviaList.Add(tr);
                    }
                    else
                    {
                        currentTriviaList.Add(trivia);
                    }

                    if (NeedsLineBreakAfter(trivia, isTrailing))
                    {
                        currentTriviaList.Add(GetCarriageReturnLineFeed());
                        afterLineBreak   = true;
                        afterIndentation = false;
                    }
                }

                if (lineBreaksAfter > 0)
                {
                    if (currentTriviaList.Count > 0 && EndsInLineBreak(currentTriviaList.Last()))
                    {
                        lineBreaksAfter--;
                    }

                    for (int i = 0; i < lineBreaksAfter; i++)
                    {
                        currentTriviaList.Add(GetCarriageReturnLineFeed());
                        afterLineBreak   = true;
                        afterIndentation = false;
                    }
                }
                else if (mustBeIndented)
                {
                    currentTriviaList.Add(this.GetIndentation(depth));
                    afterIndentation = true;
                }
                else if (mustHaveSeparator)
                {
                    currentTriviaList.Add(GetSpace());
                    afterLineBreak   = false;
                    afterIndentation = false;
                }

                if (currentTriviaList.Count == 0)
                {
                    return(default(SyntaxTriviaList));
                }
                else if (currentTriviaList.Count == 1)
                {
                    return(SyntaxFactory.TriviaList(currentTriviaList.First()));
                }
                else
                {
                    return(SyntaxFactory.TriviaList(currentTriviaList));
                }
            }
            finally
            {
                currentTriviaList.Free();
            }
        }
Exemplo n.º 12
0
        /// <summary>
        /// Takes the outputs from the previous deconstructionStep and depending on the structure of variables, will:
        /// - generate further deconstructions,
        /// - or simply conversions and assignments.
        ///
        /// Returns true for success, but false if has errors.
        /// </summary>
        private bool DeconstructOrAssignOutputs(
            BoundDeconstructionDeconstructStep deconstructionStep,
            ArrayBuilder <DeconstructionVariable> variables,
            CSharpSyntaxNode syntax,
            DiagnosticBag diagnostics,
            ArrayBuilder <BoundDeconstructionDeconstructStep> deconstructionSteps,
            ArrayBuilder <BoundDeconstructionAssignmentStep> conversionSteps,
            ArrayBuilder <BoundDeconstructionAssignmentStep> assignmentSteps,
            ArrayBuilder <BoundDeconstructionConstructionStep> constructionStepsOpt)
        {
            bool hasErrors          = false;
            var  constructionInputs = constructionStepsOpt == null ? null : ArrayBuilder <BoundDeconstructValuePlaceholder> .GetInstance();

            int count = variables.Count;

            for (int i = 0; i < count; i++)
            {
                var variable         = variables[i];
                var valuePlaceholder = deconstructionStep.OutputPlaceholders[i];

                if (variable.HasNestedVariables)
                {
                    var nested = variable.NestedVariables;
                    if (!DeconstructIntoSteps(valuePlaceholder, syntax, diagnostics, nested, deconstructionSteps, conversionSteps, assignmentSteps, constructionStepsOpt))
                    {
                        hasErrors = true;
                    }
                    else if (constructionInputs != null)
                    {
                        constructionInputs.Add(constructionStepsOpt.Last().OutputPlaceholder);
                    }
                }
                else
                {
                    var conversion = MakeDeconstructionAssignmentStep(variable.Single, valuePlaceholder, syntax, diagnostics);
                    conversionSteps.Add(conversion);

                    var assignment = MakeDeconstructionAssignmentStep(variable.Single, conversion.OutputPlaceholder, syntax, diagnostics);
                    assignmentSteps.Add(assignment);

                    if (constructionInputs != null)
                    {
                        constructionInputs.Add(conversion.OutputPlaceholder);
                    }
                }
            }

            if (constructionStepsOpt != null)
            {
                if (hasErrors)
                {
                    constructionInputs.Free();
                }
                else
                {
                    var construct = MakeDeconstructionConstructionStep(syntax, diagnostics, constructionInputs.ToImmutableAndFree());
                    constructionStepsOpt.Add(construct);
                }
            }

            return(!hasErrors);
        }
Exemplo n.º 13
0
        /// <summary>
        /// The strategy of this rewrite is to do rewrite "locally".
        /// We analyze arguments of the concat in a shallow fashion assuming that
        /// lowering and optimizations (including this one) is already done for the arguments.
        /// Based on the arguments we select the most appropriate pattern for the current node.
        ///
        /// NOTE: it is not guaranteed that the node that we chose will be the most optimal since we have only
        ///       local information - i.e. we look at the arguments, but we do not know about siblings.
        ///       When we move to the parent, the node may be rewritten by this or some another optimization.
        ///
        /// Example:
        ///     result = ( "abc" + "def" + null ?? expr1 + "moo" + "baz" ) + expr2
        ///
        /// Will rewrite into:
        ///     result = Concat("abcdef", expr2)
        ///
        /// However there will be transient nodes like  Concat(expr1 + "moo")  that will not be present in the
        /// resulting tree.
        ///
        /// </summary>
        private BoundExpression RewriteStringConcatenation(SyntaxNode syntax, BinaryOperatorKind operatorKind, BoundExpression loweredLeft, BoundExpression loweredRight, TypeSymbol type)
        {
            Debug.Assert(
                operatorKind == BinaryOperatorKind.StringConcatenation ||
                operatorKind == BinaryOperatorKind.StringAndObjectConcatenation ||
                operatorKind == BinaryOperatorKind.ObjectAndStringConcatenation);

            if (_inExpressionLambda)
            {
                return(RewriteStringConcatInExpressionLambda(syntax, operatorKind, loweredLeft, loweredRight, type));
            }

            // Convert both sides to a string (calling ToString if necessary)
            loweredLeft  = ConvertConcatExprToString(syntax, loweredLeft);
            loweredRight = ConvertConcatExprToString(syntax, loweredRight);

            Debug.Assert(loweredLeft.Type.IsStringType() || loweredLeft.ConstantValue?.IsNull == true || loweredLeft.Type.IsErrorType());
            Debug.Assert(loweredRight.Type.IsStringType() || loweredRight.ConstantValue?.IsNull == true || loweredRight.Type.IsErrorType());

            // try fold two args without flattening.
            var folded = TryFoldTwoConcatOperands(syntax, loweredLeft, loweredRight);

            if (folded != null)
            {
                return(folded);
            }

            // flatten and merge -  ( expr1 + "A" ) + ("B" + expr2) ===> (expr1 + "AB" + expr2)
            ArrayBuilder <BoundExpression> leftFlattened = ArrayBuilder <BoundExpression> .GetInstance();

            ArrayBuilder <BoundExpression> rightFlattened = ArrayBuilder <BoundExpression> .GetInstance();

            FlattenConcatArg(loweredLeft, leftFlattened);
            FlattenConcatArg(loweredRight, rightFlattened);

            if (leftFlattened.Any() && rightFlattened.Any())
            {
                folded = TryFoldTwoConcatOperands(syntax, leftFlattened.Last(), rightFlattened.First());
                if (folded != null)
                {
                    rightFlattened[0] = folded;
                    leftFlattened.RemoveLast();
                }
            }

            leftFlattened.AddRange(rightFlattened);
            rightFlattened.Free();

            BoundExpression result;

            switch (leftFlattened.Count)
            {
            case 0:
                result = _factory.StringLiteral(string.Empty);
                break;

            case 1:
                // All code paths which reach here (through TryFoldTwoConcatOperands) have already called
                // RewriteStringConcatenationOneExpr if necessary
                result = leftFlattened[0];
                break;

            case 2:
                var left  = leftFlattened[0];
                var right = leftFlattened[1];
                result = RewriteStringConcatenationTwoExprs(syntax, left, right);
                break;

            case 3:
            {
                var first  = leftFlattened[0];
                var second = leftFlattened[1];
                var third  = leftFlattened[2];
                result = RewriteStringConcatenationThreeExprs(syntax, first, second, third);
            }
            break;

            case 4:
            {
                var first  = leftFlattened[0];
                var second = leftFlattened[1];
                var third  = leftFlattened[2];
                var fourth = leftFlattened[3];
                result = RewriteStringConcatenationFourExprs(syntax, first, second, third, fourth);
            }
            break;

            default:
                result = RewriteStringConcatenationManyExprs(syntax, leftFlattened.ToImmutable());
                break;
            }

            leftFlattened.Free();
            return(result);
        }