private static async Task <CompletionChange> GetConversionChangeAsync(
            Document document, CompletionItem item, CancellationToken cancellationToken)
        {
            var position = SymbolCompletionItem.GetContextPosition(item);
            var text     = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var(dotToken, _) = GetDotAndExpressionStart(root, position, cancellationToken);

            var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken
                ? dotToken.GetPreviousToken()
                : (SyntaxToken?)null;

            var expression = (ExpressionSyntax)dotToken.GetRequiredParent();

            expression = expression.GetRootConditionalAccessExpression() ?? expression;

            var replacement = questionToken != null
                ? $"(({item.DisplayText}){text.ToString(TextSpan.FromBounds(expression.SpanStart, questionToken.Value.FullSpan.Start))}){questionToken.Value}"
                : $"(({item.DisplayText}){text.ToString(TextSpan.FromBounds(expression.SpanStart, dotToken.SpanStart))})";

            // If we're at `x.$$.y` then we only want to replace up through the first dot.
            var tokenOnLeft    = root.FindTokenOnLeftOfPosition(position, includeSkipped: true);
            var fullTextChange = new TextChange(
                TextSpan.FromBounds(
                    expression.SpanStart,
                    tokenOnLeft.Kind() == SyntaxKind.DotDotToken ? tokenOnLeft.SpanStart + 1 : tokenOnLeft.Span.End),
                replacement);

            var newPosition = expression.SpanStart + replacement.Length;

            return(CompletionChange.Create(fullTextChange, newPosition));
        }
        private static async Task <CompletionChange> GetConversionChangeAsync(
            Document document, CompletionItem item, CancellationToken cancellationToken)
        {
            var position = SymbolCompletionItem.GetContextPosition(item);
            var text     = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);

            var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var(dotToken, _) = GetDotAndExpressionStart(root, position, cancellationToken);

            var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken
                ? dotToken.GetPreviousToken()
                : (SyntaxToken?)null;

            var expression = (ExpressionSyntax)dotToken.GetRequiredParent();

            expression = expression.GetRootConditionalAccessExpression() ?? expression;

            using var _ = ArrayBuilder <TextChange> .GetInstance(out var builder);

            // First, add the cast prior to the expression.
            var castText = $"(({item.DisplayText})";

            builder.Add(new TextChange(new TextSpan(expression.SpanStart, 0), castText));

            // The expression went up to either a `.`, `..`, `?.` or `?..`
            //
            // In the case of `expr.` produce `((T)expr)$$`
            //
            // In the case of `expr..` produce ((T)expr)$$.
            //
            // In the case of `expr?.` produce `((T)expr)?$$`
            if (questionToken == null)
            {
                // Always eat the first dot in `.` or `..` and replace that with the paren.
                builder.Add(new TextChange(new TextSpan(dotToken.SpanStart, 1), ")"));
            }
            else
            {
                // Place a paren before the question.
                builder.Add(new TextChange(new TextSpan(questionToken.Value.SpanStart, 0), ")"));
                // then remove the first dot that comes after.
                builder.Add(new TextChange(new TextSpan(dotToken.SpanStart, 1), ""));
            }

            // If the user partially wrote out the conversion type, delete what they've written.
            var tokenOnLeft = root.FindTokenOnLeftOfPosition(position, includeSkipped: true);

            if (CSharpSyntaxFacts.Instance.IsWord(tokenOnLeft))
            {
                builder.Add(new TextChange(tokenOnLeft.Span, ""));
            }

            var newText    = text.WithChanges(builder);
            var allChanges = builder.ToImmutable();

            // Collapse all text changes down to a single change (for clients that only care about that), but also keep
            // all the individual changes around for clients that prefer the fine-grained information.
            return(CompletionChange.Create(
                       CodeAnalysis.Completion.Utilities.Collapse(newText, allChanges),
                       allChanges));
        }