Repository: dotnet/roslyn
File: src/Features/CSharp/Portable/Completion/CompletionProviders/CompletionUtilities.cs (C#)

Code before:
                {
                    if (propertyDeclaration.AccessorList is { Accessors: [var firstAccessor, ..] })
                    {
                        // select the last statement of the first accessor
                        if (firstAccessor.Body is { Statements: [.., var lastStatement] })
                            return lastStatement.Span;

                        if (firstAccessor.ExpressionBody is { Expression: { } expression })
                            return expression.Span;
                    }
                    else if (propertyDeclaration is PropertyDeclarationSyntax { ExpressionBody.Expression: { } expression })
                    {
                        // expression-bodied property: select the expression
                        return expression.Span;
                    }

                    // property: no accessors; move caret to the end of the declaration
                    return new TextSpan(propertyDeclaration.Span.End, 0);
                }

            default:
                throw ExceptionUtilities.Unreachable();
        }
    }

    public static bool IsSpeculativeTypeParameterContext(SyntaxTree syntaxTree, int position, SemanticModel? semanticModel, bool inMemberDeclarationOnly, CancellationToken cancellationToken)
    {
        var spanStart = position;

        // We could be in the middle of a ref/generic/tuple type, instead of a simple T case.
        // If we managed to walk out and get a different SpanStart, we treat it as a simple $$T case.
        while (true)
        {
            var oldSpanStart = spanStart;

            spanStart = WalkOutOfGenericType(syntaxTree, spanStart, semanticModel, cancellationToken);
            spanStart = WalkOutOfTupleType(syntaxTree, spanStart, cancellationToken);
            spanStart = WalkOutOfRefType(syntaxTree, spanStart, cancellationToken);

            if (spanStart == oldSpanStart)
            {
                break;
            }
        }

        var token = syntaxTree.FindTokenOnLeftOfPosition(spanStart, cancellationToken);

        return syntaxTree.IsMemberDeclarationContext(spanStart, context: null, SyntaxKindSet.AllMemberModifiers, SyntaxKindSet.NonEnumTypeDeclarations, canBePartial: true, cancellationToken) ||
               syntaxTree.IsGlobalMemberDeclarationContext(spanStart, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) ||
               (!inMemberDeclarationOnly && (syntaxTree.IsStatementContext(spanStart, token, cancellationToken) ||
                                             syntaxTree.IsGlobalStatementContext(spanStart, cancellationToken) ||

Review comment (suggestion):
can you put a doc comment on what a 'Speculative type parameter context' is?  at first i thought this was about SpecultiveSemanticModels :)

Code after:
                {
                    if (propertyDeclaration.AccessorList is { Accessors: [var firstAccessor, ..] })
                    {
                        // select the last statement of the first accessor
                        if (firstAccessor.Body is { Statements: [.., var lastStatement] })
                            return lastStatement.Span;

                        if (firstAccessor.ExpressionBody is { Expression: { } expression })
                            return expression.Span;
                    }
                    else if (propertyDeclaration is PropertyDeclarationSyntax { ExpressionBody.Expression: { } expression })
                    {
                        // expression-bodied property: select the expression
                        return expression.Span;
                    }

                    // property: no accessors; move caret to the end of the declaration
                    return new TextSpan(propertyDeclaration.Span.End, 0);
                }

            default:
                throw ExceptionUtilities.Unreachable();
        }
    }

    /// <summary>
    /// Determines whether the specified position in the syntax tree is a valid context for speculatively typing
    /// a type parameter (which might be undeclared yet). This handles cases where the user may be in the middle of typing a generic type, tuple
    /// and ref type as well.
    /// 
    /// For example, when you typed `public TBuilder$$`, you might want to type `public TBuilder M&lt;TBuilder&gt;(){}`,
    /// so TBuilder is a valid speculative type parameter context.
    /// </summary>
    public static bool IsSpeculativeTypeParameterContext(SyntaxTree syntaxTree, int position, SemanticModel? semanticModel, bool includeStatementContexts, CancellationToken cancellationToken)
    {
        var spanStart = position;

        // We could be in the middle of a ref/generic/tuple type, instead of a simple T case.
        // If we managed to walk out and get a different SpanStart, we treat it as a simple $$T case.
        while (true)
        {
            var oldSpanStart = spanStart;

            spanStart = WalkOutOfGenericType(syntaxTree, spanStart, semanticModel, cancellationToken);
            spanStart = WalkOutOfTupleType(syntaxTree, spanStart, cancellationToken);
            spanStart = WalkOutOfRefType(syntaxTree, spanStart, cancellationToken);

            if (spanStart == oldSpanStart)
            {
                break;
            }