| File: Completion\CompletionProviders\CSharpSuggestionModeCompletionProvider.cs | Web Access |
| Project: src\roslyn\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features) |
// 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.Composition; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers; [ExportCompletionProvider(nameof(CSharpSuggestionModeCompletionProvider), LanguageNames.CSharp), Shared] [ExtensionOrder(After = nameof(ObjectAndWithInitializerCompletionProvider))] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class CSharpSuggestionModeCompletionProvider() : AbstractSuggestionModeCompletionProvider { internal override string Language => LanguageNames.CSharp; protected override async Task<CompletionItem?> GetSuggestionModeItemAsync( Document document, int position, TextSpan itemSpan, CompletionTrigger trigger, CancellationToken cancellationToken = default) { if (trigger.Kind != CompletionTriggerKind.Snippets) { var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var token = tree .FindTokenOnLeftOfPosition(position, cancellationToken) .GetPreviousTokenIfTouchingWord(position); if (token.Kind() == SyntaxKind.None) return null; var semanticModel = await document.ReuseExistingSpeculativeModelAsync(token.Parent, cancellationToken).ConfigureAwait(false); var typeInferrer = document.GetRequiredLanguageService<ITypeInferenceService>(); if (IsLambdaExpression(semanticModel, tree, position, token, typeInferrer, cancellationToken)) { return CreateSuggestionModeItem(CSharpFeaturesResources.lambda_expression, CSharpFeaturesResources.Autoselect_disabled_due_to_potential_lambda_declaration); } else if (IsAnonymousObjectCreation(token)) { return CreateSuggestionModeItem(CSharpFeaturesResources.member_name, CSharpFeaturesResources.Autoselect_disabled_due_to_possible_explicitly_named_anonymous_type_member_creation); } else if (IsPotentialPatternVariableDeclaration(tree.FindTokenOnLeftOfPosition(position, cancellationToken))) { return CreateSuggestionModeItem(CSharpFeaturesResources.pattern_variable, CSharpFeaturesResources.Autoselect_disabled_due_to_potential_pattern_variable_declaration); } else if (token.IsPreProcessorExpressionContext()) { return CreateEmptySuggestionModeItem(); } else if (token.IsKindOrHasMatchingText(SyntaxKind.FromKeyword) || token.IsKindOrHasMatchingText(SyntaxKind.JoinKeyword)) { return CreateSuggestionModeItem(CSharpFeaturesResources.range_variable, CSharpFeaturesResources.Autoselect_disabled_due_to_potential_range_variable_declaration); } else if (tree.IsNamespaceDeclarationNameContext(position, cancellationToken)) { return CreateSuggestionModeItem(FeaturesResources.namespace_name, CSharpFeaturesResources.Autoselect_disabled_due_to_namespace_declaration); } else if (tree.IsPartialTypeDeclarationNameContext(position, cancellationToken, out var typeDeclaration)) { switch (typeDeclaration.Keyword.Kind()) { case SyntaxKind.ClassKeyword: return CreateSuggestionModeItem(FeaturesResources.class_name, CSharpFeaturesResources.Autoselect_disabled_due_to_type_declaration); case SyntaxKind.StructKeyword: return CreateSuggestionModeItem(CSharpFeaturesResources.struct_name, CSharpFeaturesResources.Autoselect_disabled_due_to_type_declaration); case SyntaxKind.InterfaceKeyword: return CreateSuggestionModeItem(FeaturesResources.interface_name, CSharpFeaturesResources.Autoselect_disabled_due_to_type_declaration); } } else if (tree.IsPossibleDeconstructionDesignation(position, cancellationToken)) { return CreateSuggestionModeItem(CSharpFeaturesResources.designation_name, CSharpFeaturesResources.Autoselect_disabled_due_to_possible_deconstruction_declaration); } } return null; } private static bool IsAnonymousObjectCreation(SyntaxToken token) { if (token.Parent is AnonymousObjectCreationExpressionSyntax) { // We'll show the builder after an open brace or comma, because that's where the // user can start declaring new named parts. return token.Kind() is SyntaxKind.OpenBraceToken or SyntaxKind.CommaToken; } return false; } private static bool IsLambdaExpression(SemanticModel semanticModel, SyntaxTree tree, int position, SyntaxToken token, ITypeInferenceService typeInferrer, CancellationToken cancellationToken) { // Not after `new` if (token.IsKind(SyntaxKind.NewKeyword) && token.Parent.IsKind(SyntaxKind.ObjectCreationExpression)) { return false; } // Typing a generic type parameter, the tree might look like a binary expression around the < token. // If we infer a delegate type here (because that's what on the other side of the binop), // ignore it. if (token.Kind() == SyntaxKind.LessThanToken && token.Parent is BinaryExpressionSyntax) { return false; } // We might be in the arguments to a parenthesized lambda if (token.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken) { if (token.Parent is not null and ParameterListSyntax) { return token.Parent.Parent is not null and ParenthesizedLambdaExpressionSyntax; } } // A lambda that is being typed may be parsed as a tuple without names // For example, "(a, b" could be the start of either a tuple or lambda // But "(a: b, c" cannot be a lambda if (tree.IsPossibleTupleContext(token, position) && token.Parent is TupleExpressionSyntax tupleExpression && !tupleExpression.HasNames()) { position = token.Parent.SpanStart; } // Walk up a single level to allow for typing the beginning of a lambda: // new AssemblyLoadEventHandler(($$ if (token.Kind() == SyntaxKind.OpenParenToken && token.GetRequiredParent().Kind() == SyntaxKind.ParenthesizedExpression) { position = token.GetRequiredParent().SpanStart; } // WorkItem 834609: Automatic brace completion inserts the closing paren, making it // like a cast. if (token.Kind() == SyntaxKind.OpenParenToken && token.GetRequiredParent().Kind() == SyntaxKind.CastExpression) { position = token.GetRequiredParent().SpanStart; } // In the following situation, the type inferrer will infer Task to support target type preselection // Action a = Task.$$ // We need to explicitly exclude invocation/member access from suggestion mode var previousToken = token.GetPreviousTokenIfTouchingWord(position); if (previousToken.IsKind(SyntaxKind.DotToken) && previousToken.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression)) { return false; } // async lambda: // Goo(async($$ // Goo(async(p1, $$ if (token.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken && token.Parent.IsKind(SyntaxKind.ArgumentList) && token.Parent.Parent is InvocationExpressionSyntax invocation && invocation.Expression is IdentifierNameSyntax identifier) { if (identifier.Identifier.IsKindOrHasMatchingText(SyntaxKind.AsyncKeyword)) { return true; } } // If we're an argument to a function with multiple overloads, // open the builder if any overload takes a delegate at our argument position var inferredTypeInfo = typeInferrer.GetTypeInferenceInfo(semanticModel, position, cancellationToken: cancellationToken); return inferredTypeInfo.Any(static (type, semanticModel) => GetDelegateType(type, semanticModel.Compilation).IsDelegateType(), semanticModel); } private static ITypeSymbol? GetDelegateType(TypeInferenceInfo typeInferenceInfo, Compilation compilation) { var typeSymbol = typeInferenceInfo.InferredType; if (typeInferenceInfo.IsParams) { if (typeInferenceInfo.InferredType.IsSpanOrReadOnlySpan()) { typeSymbol = typeInferenceInfo.InferredType.GetTypeArguments().Single(); } else { var ienumerableType = typeInferenceInfo.InferredType .GetAllInterfacesIncludingThis() .FirstOrDefault(i => i.OriginalDefinition.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T); if (ienumerableType != null) typeSymbol = ienumerableType.TypeArguments.Single(); } } return typeSymbol.GetDelegateType(compilation); } private static bool IsPotentialPatternVariableDeclaration(SyntaxToken token) { var patternSyntax = token.GetAncestor<PatternSyntax>(); if (patternSyntax == null) { return false; } for (var current = patternSyntax; current != null; current = current.Parent as PatternSyntax) { // Patterns containing 'or' cannot contain valid variable declarations, e.g. 'e is 1 or int $$' if (current.IsKind(SyntaxKind.OrPattern)) { return false; } // Patterns containing 'not' cannot be valid variable declarations, e.g. 'e is not int $$' and 'e is not (1 and int $$)' if (current.IsKind(SyntaxKind.NotPattern)) { return false; } } // e is int o$$ // e is { P: 1 } o$$ var lastTokenInPattern = patternSyntax.GetLastToken(); if (lastTokenInPattern.Parent is SingleVariableDesignationSyntax variableDesignationSyntax && token.Parent == variableDesignationSyntax) { return patternSyntax is DeclarationPatternSyntax or RecursivePatternSyntax; } return false; } }