/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Features/CSharp/Portable/SignatureHelp/ElementAccessExpressionSignatureHelpProvider.cs
397 строк
18 KB
Cyrus Najmabadi
Remove unnnecessary null suppressions in Roslyn.sln
17 окт 2025, 22:55
17 окт 2025, 22:55
ec722c4
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SignatureHelp; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.SignatureHelp; using static SyntaxFactory; [ExportSignatureHelpProvider("ElementAccessExpressionSignatureHelpProvider", LanguageNames.CSharp), Shared] internal sealed class ElementAccessExpressionSignatureHelpProvider : AbstractCSharpSignatureHelpProvider { private static readonly ImmutableArray<char> s_triggerCharacters = ['[', ',']; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public ElementAccessExpressionSignatureHelpProvider() { } public override ImmutableArray<char> TriggerCharacters => s_triggerCharacters; public override ImmutableArray<char> RetriggerCharacters => [']']; private static bool TryGetElementAccessExpression(SyntaxNode root, int position, ISyntaxFactsService syntaxFacts, SignatureHelpTriggerReason triggerReason, CancellationToken cancellationToken, [NotNullWhen(true)] out ExpressionSyntax? identifier, out SyntaxToken openBrace) { return CompleteElementAccessExpression.TryGetSyntax(root, position, syntaxFacts, triggerReason, cancellationToken, out identifier, out openBrace) || IncompleteElementAccessExpression.TryGetSyntax(root, position, syntaxFacts, triggerReason, cancellationToken, out identifier, out openBrace) || ConditionalAccessExpression.TryGetSyntax(root, position, syntaxFacts, triggerReason, cancellationToken, out identifier, out openBrace); } protected override async Task<SignatureHelpItems?> GetItemsWorkerAsync(Document document, int position, SignatureHelpTriggerInfo triggerInfo, MemberDisplayOptions options, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); if (!TryGetElementAccessExpression(root, position, document.GetRequiredLanguageService<ISyntaxFactsService>(), triggerInfo.TriggerReason, cancellationToken, out var expression, out var openBrace)) { return null; } var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var expressionSymbol = semanticModel.GetSymbolInfo(expression, cancellationToken).GetAnySymbol(); // goo?[$$] if (expressionSymbol is INamedTypeSymbol namedType) { if (namedType.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T && expression.IsKind(SyntaxKind.NullableType) && expression.IsChildNode<ArrayTypeSyntax>(a => a.ElementType)) { // Speculatively bind the type part of the nullable as an expression var nullableTypeSyntax = (NullableTypeSyntax)expression; var speculativeBinding = semanticModel.GetSpeculativeSymbolInfo(position, nullableTypeSyntax.ElementType, SpeculativeBindingOption.BindAsExpression); expressionSymbol = speculativeBinding.GetAnySymbol(); expression = nullableTypeSyntax.ElementType; } } if (expressionSymbol is not null and INamedTypeSymbol) { return null; } if (!TryGetIndexers(position, semanticModel, expression, cancellationToken, out var indexers, out var expressionType) && !TryGetComIndexers(semanticModel, expression, cancellationToken, out indexers, out expressionType)) { return null; } var within = semanticModel.GetEnclosingNamedTypeOrAssembly(position, cancellationToken); if (within == null) { return null; } var accessibleIndexers = indexers.WhereAsArray( m => m.IsAccessibleWithin(within, throughType: expressionType)); if (!accessibleIndexers.Any()) { return null; } accessibleIndexers = accessibleIndexers.FilterToVisibleAndBrowsableSymbols(options.HideAdvancedMembers, semanticModel.Compilation, inclusionFilter: static s => true) .Sort(semanticModel, expression.SpanStart); var structuralTypeDisplayService = document.GetRequiredLanguageService<IStructuralTypeDisplayService>(); var documentationCommentFormattingService = document.GetRequiredLanguageService<IDocumentationCommentFormattingService>(); var textSpan = GetTextSpan(expression, openBrace); var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>(); return CreateSignatureHelpItems([.. accessibleIndexers.Select(p => Convert(p, openBrace, semanticModel, structuralTypeDisplayService, documentationCommentFormattingService))], textSpan, GetCurrentArgumentState(root, position, syntaxFacts, textSpan, cancellationToken), selectedItemIndex: null, parameterIndexOverride: -1); } private static TextSpan GetTextSpan(ExpressionSyntax expression, SyntaxToken openBracket) { if (openBracket.Parent is BracketedArgumentListSyntax) { if (expression.Parent is ConditionalAccessExpressionSyntax conditional) { return TextSpan.FromBounds(conditional.Span.Start, openBracket.FullSpan.End); } else { return CompleteElementAccessExpression.GetTextSpan(openBracket); } } else if (openBracket.Parent is ArrayRankSpecifierSyntax) { return IncompleteElementAccessExpression.GetTextSpan(expression, openBracket); } throw ExceptionUtilities.Unreachable(); } private static SignatureHelpState? GetCurrentArgumentState(SyntaxNode root, int position, ISyntaxFactsService syntaxFacts, TextSpan currentSpan, CancellationToken cancellationToken) { if (!TryGetElementAccessExpression( root, position, syntaxFacts, SignatureHelpTriggerReason.InvokeSignatureHelpCommand, cancellationToken, out var expression, out var openBracket) || currentSpan.Start != expression.SpanStart) { return null; } // If the user is actively typing, it's likely that we're in a broken state and the // syntax tree will be incorrect. Because of this we need to synthesize a new // bracketed argument list so we can correctly map the cursor to the current argument // and then we need to account for this and offset the position check accordingly. int offset; BracketedArgumentListSyntax argumentList; var newBracketedArgumentList = ParseBracketedArgumentList(openBracket.Parent!.ToString()); if (expression.Parent is ConditionalAccessExpressionSyntax) { // The typed code looks like: <expression>?[ var elementBinding = ElementBindingExpression(newBracketedArgumentList); var conditionalAccessExpression = ConditionalAccessExpression(expression, elementBinding); offset = expression.SpanStart - conditionalAccessExpression.SpanStart; argumentList = ((ElementBindingExpressionSyntax)conditionalAccessExpression.WhenNotNull).ArgumentList; } else { // The typed code looks like: // <expression>[ // or // <identifier>?[ var elementAccessExpression = ElementAccessExpression(expression, newBracketedArgumentList); offset = expression.SpanStart - elementAccessExpression.SpanStart; argumentList = elementAccessExpression.ArgumentList; } position -= offset; return SignatureHelpUtilities.GetSignatureHelpState(argumentList, position); } private static bool TryGetComIndexers( SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken, out ImmutableArray<IPropertySymbol> indexers, out ITypeSymbol? expressionType) { indexers = [.. semanticModel.GetMemberGroup(expression, cancellationToken).OfType<IPropertySymbol>()]; if (indexers.Any() && expression is MemberAccessExpressionSyntax memberAccessExpression) { expressionType = semanticModel.GetTypeInfo(memberAccessExpression.Expression, cancellationToken).Type; return true; } expressionType = null; return false; } private static bool TryGetIndexers( int position, SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken, out ImmutableArray<IPropertySymbol> indexers, out ITypeSymbol? expressionType) { expressionType = semanticModel.GetTypeInfo(expression, cancellationToken).Type; if (expressionType == null) { indexers = []; return false; } if (expressionType is IErrorTypeSymbol errorType) { // If `expression` is a QualifiedNameSyntax then GetTypeInfo().Type won't have any CandidateSymbols, so // we should then fall back to getting the actual symbol for the expression. expressionType = errorType.CandidateSymbols.FirstOrDefault().GetSymbolType() ?? semanticModel.GetSymbolInfo(expression).GetAnySymbol().GetSymbolType(); } indexers = [.. semanticModel.LookupSymbols(position, expressionType, WellKnownMemberNames.Indexer).OfType<IPropertySymbol>()]; return true; } private static SignatureHelpItem Convert( IPropertySymbol indexer, SyntaxToken openToken, SemanticModel semanticModel, IStructuralTypeDisplayService structuralTypeDisplayService, IDocumentationCommentFormattingService documentationCommentFormattingService) { var position = openToken.SpanStart; var item = CreateItem(indexer, semanticModel, position, structuralTypeDisplayService, indexer.IsParams(), indexer.GetDocumentationPartsFactory(semanticModel, position, documentationCommentFormattingService), GetPreambleParts(indexer, position, semanticModel), GetSeparatorParts(), GetPostambleParts(), [.. indexer.Parameters.Select(p => Convert(p, semanticModel, position, documentationCommentFormattingService))]); return item; } private static IList<SymbolDisplayPart> GetPreambleParts( IPropertySymbol indexer, int position, SemanticModel semanticModel) { var result = new List<SymbolDisplayPart>(); if (indexer.ReturnsByRef) { result.Add(Keyword(SyntaxKind.RefKeyword)); result.Add(Space()); } else if (indexer.ReturnsByRefReadonly) { result.Add(Keyword(SyntaxKind.RefKeyword)); result.Add(Space()); result.Add(Keyword(SyntaxKind.ReadOnlyKeyword)); result.Add(Space()); } result.AddRange(indexer.Type.ToMinimalDisplayParts(semanticModel, position)); result.Add(Space()); result.AddRange(indexer.ContainingType.ToMinimalDisplayParts(semanticModel, position)); if (indexer.Name != WellKnownMemberNames.Indexer) { result.Add(Punctuation(SyntaxKind.DotToken)); result.Add(new SymbolDisplayPart(SymbolDisplayPartKind.PropertyName, indexer, indexer.Name)); } result.Add(Punctuation(SyntaxKind.OpenBracketToken)); return result; } private static IList<SymbolDisplayPart> GetPostambleParts() => [Punctuation(SyntaxKind.CloseBracketToken)]; private static class CompleteElementAccessExpression { internal static bool IsTriggerToken(SyntaxToken token) { return !token.IsKind(SyntaxKind.None) && token.ValueText.Length == 1 && s_triggerCharacters.Contains(token.ValueText[0]) && token.Parent is BracketedArgumentListSyntax && token.Parent.Parent is ElementAccessExpressionSyntax; } internal static bool IsArgumentListToken(ElementAccessExpressionSyntax expression, SyntaxToken token) { return expression.ArgumentList.Span.Contains(token.SpanStart) && token != expression.ArgumentList.CloseBracketToken; } internal static TextSpan GetTextSpan(SyntaxToken openBracket) { Contract.ThrowIfFalse(openBracket.Parent is BracketedArgumentListSyntax && openBracket.Parent.Parent is ElementAccessExpressionSyntax or ElementBindingExpressionSyntax); return SignatureHelpUtilities.GetSignatureHelpSpan((BracketedArgumentListSyntax)openBracket.Parent); } internal static bool TryGetSyntax(SyntaxNode root, int position, ISyntaxFactsService syntaxFacts, SignatureHelpTriggerReason triggerReason, CancellationToken cancellationToken, [NotNullWhen(true)] out ExpressionSyntax? identifier, out SyntaxToken openBrace) { if (CommonSignatureHelpUtilities.TryGetSyntax( root, position, syntaxFacts, triggerReason, IsTriggerToken, IsArgumentListToken, cancellationToken, out ElementAccessExpressionSyntax? elementAccessExpression)) { identifier = elementAccessExpression.Expression; openBrace = elementAccessExpression.ArgumentList.OpenBracketToken; return true; } identifier = null; openBrace = default; return false; } } /// Error tolerance case for /// "goo[$$]" or "goo?[$$]" /// which is parsed as an ArrayTypeSyntax variable declaration instead of an ElementAccessExpression private static class IncompleteElementAccessExpression { internal static bool IsArgumentListToken(ArrayTypeSyntax node, SyntaxToken token) { return node.RankSpecifiers.Span.Contains(token.SpanStart) && token != node.RankSpecifiers.First().CloseBracketToken; } internal static bool IsTriggerToken(SyntaxToken token) { return !token.IsKind(SyntaxKind.None) && token.ValueText.Length == 1 && s_triggerCharacters.Contains(token.ValueText[0]) && token.Parent is ArrayRankSpecifierSyntax; } internal static TextSpan GetTextSpan(SyntaxNode expression, SyntaxToken openBracket) { Contract.ThrowIfFalse(openBracket.Parent is ArrayRankSpecifierSyntax && openBracket.Parent.Parent is ArrayTypeSyntax); return TextSpan.FromBounds(expression.SpanStart, openBracket.Parent.Span.End); } internal static bool TryGetSyntax(SyntaxNode root, int position, ISyntaxFactsService syntaxFacts, SignatureHelpTriggerReason triggerReason, CancellationToken cancellationToken, [NotNullWhen(true)] out ExpressionSyntax? identifier, out SyntaxToken openBrace) { if (CommonSignatureHelpUtilities.TryGetSyntax( root, position, syntaxFacts, triggerReason, IsTriggerToken, IsArgumentListToken, cancellationToken, out ArrayTypeSyntax? arrayTypeSyntax)) { identifier = arrayTypeSyntax.ElementType; openBrace = arrayTypeSyntax.RankSpecifiers.First().OpenBracketToken; return true; } identifier = null; openBrace = default; return false; } } /// Error tolerance case for /// "new String()?[$$]" /// which is parsed as a BracketedArgumentListSyntax parented by an ElementBindingExpressionSyntax parented by a ConditionalAccessExpressionSyntax private static class ConditionalAccessExpression { internal static bool IsTriggerToken(SyntaxToken token) { return !token.IsKind(SyntaxKind.None) && token.ValueText.Length == 1 && s_triggerCharacters.Contains(token.ValueText[0]) && token.Parent is BracketedArgumentListSyntax && token.Parent.Parent is ElementBindingExpressionSyntax && token.Parent.Parent.Parent is ConditionalAccessExpressionSyntax; } internal static bool IsArgumentListToken(ElementBindingExpressionSyntax expression, SyntaxToken token) { return expression.ArgumentList.Span.Contains(token.SpanStart) && token != expression.ArgumentList.CloseBracketToken; } internal static bool TryGetSyntax(SyntaxNode root, int position, ISyntaxFactsService syntaxFacts, SignatureHelpTriggerReason triggerReason, CancellationToken cancellationToken, [NotNullWhen(true)] out ExpressionSyntax? identifier, out SyntaxToken openBrace) { if (CommonSignatureHelpUtilities.TryGetSyntax( root, position, syntaxFacts, triggerReason, IsTriggerToken, IsArgumentListToken, cancellationToken, out ElementBindingExpressionSyntax? elementBindingExpression)) { // Find the first conditional access expression that starts left of our open bracket var conditionalAccess = elementBindingExpression.FirstAncestorOrSelf<ConditionalAccessExpressionSyntax, ElementBindingExpressionSyntax>( (c, elementBindingExpression) => c.SpanStart < elementBindingExpression.SpanStart, elementBindingExpression)!; identifier = conditionalAccess.Expression; openBrace = elementBindingExpression.ArgumentList.OpenBracketToken; return true; } identifier = null; openBrace = default; return false; } } }