| File: Completion\CompletionProviders\ObjectAndWithInitializerCompletionProvider.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.Collections.Generic; using System.Collections.Immutable; 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.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; // Provides symbol completions in object and 'with' initializers: // - new() { $$ // - new C() { $$ // - expr with { $$ [ExportCompletionProvider(nameof(ObjectAndWithInitializerCompletionProvider), LanguageNames.CSharp), Shared] [ExtensionOrder(After = nameof(ObjectCreationCompletionProvider))] [method: ImportingConstructor] [method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] internal sealed class ObjectAndWithInitializerCompletionProvider() : AbstractObjectInitializerCompletionProvider { internal override string Language => LanguageNames.CSharp; protected override async Task<bool> IsExclusiveAsync(Document document, int position, CancellationToken cancellationToken) { // We're exclusive if this context could only be an object initializer and not also a // collection initializer. If we're initializing something that could be initialized as // an object or as a collection, say we're not exclusive. That way the rest of // intellisense can be used in the collection initializer. // // Consider this case: // class c : IEnumerable<int> // { // public void Add(int addend) { } // public int goo; // } // void goo() // { // var b = new c {| // } // There we could initialize b using either an object initializer or a collection // initializer. Since we don't know which the user will use, we'll be non-exclusive, so // the other providers can help the user write the collection initializer, if they want // to. var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); if (tree.IsInNonUserCode(position, cancellationToken)) return false; var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken); token = token.GetPreviousTokenIfTouchingWord(position); if (token.Parent == null) return false; if (token.Parent.Parent is not ExpressionSyntax expression) return false; var semanticModel = await document.ReuseExistingSpeculativeModelAsync(expression, cancellationToken).ConfigureAwait(false); var initializedType = semanticModel.GetTypeInfo(expression, cancellationToken).Type; if (initializedType == null) return false; var enclosingSymbol = semanticModel.GetEnclosingNamedTypeOrAssembly(position, cancellationToken); // Non-exclusive if initializedType can be initialized as a collection. if (initializedType.CanSupportCollectionInitializer(enclosingSymbol)) return false; // By default, only our member names will show up. return true; } public override bool IsInsertionTrigger(SourceText text, int characterPosition, CompletionOptions options) => CompletionUtilities.IsTriggerCharacter(text, characterPosition, options) || text[characterPosition] == ' '; public override ImmutableHashSet<char> TriggerCharacters { get; } = CompletionUtilities.CommonTriggerCharacters.Add(' '); protected override (ITypeSymbol type, Location location, bool isObjectInitializer)? GetInitializedType( Document document, SemanticModel semanticModel, int position, CancellationToken cancellationToken) { var tree = semanticModel.SyntaxTree; if (tree.IsInNonUserCode(position, cancellationToken)) return null; var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken); token = token.GetPreviousTokenIfTouchingWord(position); if (token.Kind() is not SyntaxKind.CommaToken and not SyntaxKind.OpenBraceToken) return null; if (token.Parent == null || token.Parent.Parent == null) return null; // If we got a comma, we can syntactically find out if we're in an ObjectInitializerExpression or WithExpression if (token.Kind() == SyntaxKind.CommaToken && token.Parent.Kind() is not (SyntaxKind.ObjectInitializerExpression or SyntaxKind.WithInitializerExpression)) { return null; } var type = GetInitializedType(token, document, semanticModel, cancellationToken); if (type is null) return null; return (type, token.GetLocation(), token.Parent.Kind() is not SyntaxKind.WithInitializerExpression); } private static ITypeSymbol? GetInitializedType(SyntaxToken token, Document document, SemanticModel semanticModel, CancellationToken cancellationToken) { var parent = token.Parent?.Parent; // new() { $$ // new Goo { $$ if (parent is (kind: SyntaxKind.ObjectCreationExpression or SyntaxKind.ImplicitObjectCreationExpression)) { return semanticModel.GetTypeInfo(parent, cancellationToken).Type; } // Nested: new Goo { bar = { $$ if (parent.IsKind(SyntaxKind.SimpleAssignmentExpression)) { // Use the type inferrer to get the type being initialized. var typeInferenceService = document.GetRequiredLanguageService<ITypeInferenceService>(); var parentInitializer = token.GetAncestor<InitializerExpressionSyntax>()!; return typeInferenceService.InferType(semanticModel, parentInitializer, objectAsDefault: false, cancellationToken: cancellationToken); } // expr with { $$ if (parent is WithExpressionSyntax withExpression) { return semanticModel.GetTypeInfo(withExpression.Expression, cancellationToken).Type; } return null; } protected override HashSet<string> GetInitializedMembers(SyntaxTree tree, int position, CancellationToken cancellationToken) { var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken) .GetPreviousTokenIfTouchingWord(position); // We should have gotten back a { or , if (token.Kind() is SyntaxKind.CommaToken or SyntaxKind.OpenBraceToken && token.Parent is InitializerExpressionSyntax initializer) { return [.. initializer.Expressions.OfType<AssignmentExpressionSyntax>() .Where(b => b.OperatorToken.Kind() == SyntaxKind.EqualsToken) .Select(b => b.Left) .OfType<IdentifierNameSyntax>() .Select(i => i.Identifier.ValueText)]; } return []; } protected override bool IsInitializableFieldOrProperty(ISymbol fieldOrProperty, INamedTypeSymbol containingType) { if (fieldOrProperty is IPropertySymbol property && property.Parameters.Any(static p => !p.IsOptional)) return false; return base.IsInitializableFieldOrProperty(fieldOrProperty, containingType); } protected override string EscapeIdentifier(ISymbol symbol) => symbol.Name.EscapeIdentifier(); }