| File: Completion\Providers\AbstractObjectInitializerCompletionProvider.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.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.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Completion.Providers; internal abstract class AbstractObjectInitializerCompletionProvider : LSPCompletionProvider { protected abstract (ITypeSymbol type, Location location, bool isObjectInitializer)? GetInitializedType(Document document, SemanticModel semanticModel, int position, CancellationToken cancellationToken); protected abstract HashSet<string> GetInitializedMembers(SyntaxTree tree, int position, CancellationToken cancellationToken); protected abstract string EscapeIdentifier(ISymbol symbol); public override async Task ProvideCompletionsAsync(CompletionContext context) { var document = context.Document; var position = context.Position; var cancellationToken = context.CancellationToken; var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); if (GetInitializedType(document, semanticModel, position, cancellationToken) is not var (type, initializerLocation, isObjectInitializer)) return; if (type is ITypeParameterSymbol typeParameterSymbol) type = typeParameterSymbol.GetNamedTypeSymbolConstraint(); if (type is not INamedTypeSymbol initializedType) return; if (await IsExclusiveAsync(document, position, cancellationToken).ConfigureAwait(false)) context.IsExclusive = true; var enclosing = semanticModel.GetEnclosingNamedType(position, cancellationToken); Contract.ThrowIfNull(enclosing); // Find the members that can be initialized. If we have a NamedTypeSymbol, also get the overridden members. // Include extension members to support extension properties in object initializers. var members = semanticModel .LookupSymbols(position, initializedType, includeReducedExtensionMethods: true) .Where(m => IsInitializableFieldOrProperty(m, enclosing)); // Filter out those members that have already been typed var alreadyTypedMembers = GetInitializedMembers(semanticModel.SyntaxTree, position, cancellationToken); var uninitializedMembers = members.Where(m => !alreadyTypedMembers.Contains(m.Name)); // Sort the members by name so if we preselect one, it'll be stable uninitializedMembers = uninitializedMembers .Where(m => m.IsEditorBrowsable(context.CompletionOptions.MemberDisplayOptions.HideAdvancedMembers, semanticModel.Compilation)) .OrderBy(m => m.Name); var firstUninitializedRequiredMember = true; foreach (var uninitializedMember in uninitializedMembers) { var rules = s_rules; // We'll hard select the first required member to make it a bit easier to type out an object initializer // with a bunch of members. if (firstUninitializedRequiredMember && isObjectInitializer && uninitializedMember.IsRequired()) { rules = rules.WithSelectionBehavior(CompletionItemSelectionBehavior.HardSelection).WithMatchPriority(MatchPriority.Preselect); firstUninitializedRequiredMember = false; } context.AddItem(SymbolCompletionItem.CreateWithSymbolId( displayText: EscapeIdentifier(uninitializedMember), displayTextSuffix: "", insertionText: null, symbols: [uninitializedMember], contextPosition: initializerLocation.SourceSpan.Start, inlineDescription: isObjectInitializer && uninitializedMember.IsRequired() ? FeaturesResources.Required : null, rules: rules)); } } internal override Task<CompletionDescription> GetDescriptionWorkerAsync(Document document, CompletionItem item, CompletionOptions options, SymbolDescriptionOptions displayOptions, CancellationToken cancellationToken) => SymbolCompletionItem.GetDescriptionAsync(item, document, displayOptions, cancellationToken); protected abstract Task<bool> IsExclusiveAsync(Document document, int position, CancellationToken cancellationToken); private static readonly CompletionItemRules s_rules = CompletionItemRules.Create(enterKeyRule: EnterKeyRule.Never); protected virtual bool IsInitializableFieldOrProperty(ISymbol fieldOrProperty, INamedTypeSymbol containingType) { if (!fieldOrProperty.IsStatic && !fieldOrProperty.IsImplicitlyDeclared && fieldOrProperty.CanBeReferencedByName && fieldOrProperty is IFieldSymbol or IPropertySymbol && fieldOrProperty.IsAccessibleWithin(containingType)) { if (fieldOrProperty.IsWriteableFieldOrProperty() || fieldOrProperty.ContainingType.IsAnonymousType || CanSupportObjectInitializer(fieldOrProperty)) { return true; } } return false; static bool CanSupportObjectInitializer(ISymbol fieldOrProperty) { Debug.Assert(!fieldOrProperty.IsWriteableFieldOrProperty(), "Assertion failed - expected writable field/property check before calling this method."); return MemberTypeCanSupportObjectInitializer(fieldOrProperty switch { IFieldSymbol fieldSymbol => fieldSymbol.Type, IPropertySymbol propertySymbol => propertySymbol.Type, _ => throw ExceptionUtilities.Unreachable() }); } static bool MemberTypeCanSupportObjectInitializer(ITypeSymbol type) { // NOTE: While in C# it is legal to write 'Member = {}' on a member of any of // the ruled out types below, it has no effects and is thus a needless recommendation // Example of the above case: /* class C { string S { get; } } new C() { S = {}, }; */ // We avoid some types that are common and easy to rule out var definition = type.OriginalDefinition; switch (definition.SpecialType) { case SpecialType.System_Enum: case SpecialType.System_String: case SpecialType.System_Object: case SpecialType.System_Delegate: case SpecialType.System_MulticastDelegate: // We cannot use collection initializers in symbols of type `System.Array` (as opposed to actual // instantiations like `int[]`). case SpecialType.System_Array: // We cannot add to an enumerable or enumerator // so we cannot use a collection initializer case SpecialType.System_Collections_IEnumerable: case SpecialType.System_Collections_IEnumerator: case SpecialType.System_Collections_Generic_IEnumerable_T: case SpecialType.System_Collections_Generic_IEnumerator_T: return false; } // - Delegate types have no settable members, which is the case for Delegate and MulticastDelegate too // - Non-settable struct members cannot be used in object initializers // - Pointers and function pointers do not have accessible members return type.TypeKind is not (TypeKind.Delegate or TypeKind.Struct or TypeKind.FunctionPointer or TypeKind.Pointer); } } }