File: src\Analyzers\Core\CodeFixes\ImplementInterface\ImplementInterfaceGenerator.cs
Web Access
Project: src\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.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.ImplementType;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Roslyn.Utilities;
 
#if CODE_STYLE
using DeclarationModifiers = Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers;
#else
using DeclarationModifiers = Microsoft.CodeAnalysis.Editing.DeclarationModifiers;
#endif
 
namespace Microsoft.CodeAnalysis.ImplementInterface;
 
using static ImplementHelpers;
 
internal abstract partial class AbstractImplementInterfaceService
{
    private sealed partial class ImplementInterfaceGenerator
    {
        private readonly Document Document;
        private readonly AbstractImplementInterfaceService Service;
 
        private readonly ImplementInterfaceInfo State;
        private readonly ImplementTypeOptions Options;
        private readonly ImplementInterfaceConfiguration Configuration;
 
        private bool Explicitly => Configuration.Explicitly;
        private bool Abstractly => Configuration.Abstractly;
        private bool OnlyRemaining => Configuration.OnlyRemaining;
        private bool ImplementDisposePattern => Configuration.ImplementDisposePattern;
        private ISymbol? ThroughMember => Configuration.ThroughMember;
 
        internal ImplementInterfaceGenerator(
            AbstractImplementInterfaceService service,
            Document document,
            ImplementInterfaceInfo state,
            ImplementTypeOptions options,
            ImplementInterfaceConfiguration configuration)
        {
            Service = service;
            Document = document;
            State = state;
            Options = options;
            Configuration = configuration;
        }
 
        public Task<Document> ImplementInterfaceAsync(CancellationToken cancellationToken)
        {
            var unimplementedMembers = Explicitly
                ? OnlyRemaining
                    ? State.MembersWithoutExplicitOrImplicitImplementation
                    : State.MembersWithoutExplicitImplementation
                : State.MembersWithoutExplicitOrImplicitImplementationWhichCanBeImplicitlyImplemented;
 
            return ImplementDisposePattern
                ? ImplementDisposePatternAsync(unimplementedMembers, cancellationToken)
                : ImplementInterfaceAsync(unimplementedMembers, extraMembers: [], cancellationToken);
        }
 
        private async Task<Document> ImplementInterfaceAsync(
            ImmutableArray<(INamedTypeSymbol type, ImmutableArray<ISymbol> members)> unimplementedMembers,
            ImmutableArray<ISymbol> extraMembers,
            CancellationToken cancellationToken)
        {
            var tree = await this.Document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var compilation = await this.Document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
 
            var isComImport = unimplementedMembers.Any(static t => t.type.IsComImport);
 
            var memberDefinitions = GenerateMembers(
                compilation, tree.Options, unimplementedMembers, Options.PropertyGenerationBehavior);
 
            // Only group the members in the destination if the user wants that *and* 
            // it's not a ComImport interface.  Member ordering in ComImport interfaces 
            // matters, so we don't want to much with them.
            var groupMembers = !isComImport &&
                Options.InsertionBehavior == ImplementTypeInsertionBehavior.WithOtherMembersOfTheSameKind;
 
            return await CodeGenerator.AddMemberDeclarationsAsync(
                new CodeGenerationSolutionContext(
                    this.Document.Project.Solution,
                    new CodeGenerationContext(
                        contextLocation: State.ContextNode.GetLocation(),
                        autoInsertionLocation: groupMembers,
                        sortMembers: groupMembers)),
                State.ClassOrStructType,
                memberDefinitions.Concat(extraMembers),
                cancellationToken).ConfigureAwait(false);
        }
 
        private ImmutableArray<ISymbol> GenerateMembers(
            Compilation compilation,
            ParseOptions options,
            ImmutableArray<(INamedTypeSymbol type, ImmutableArray<ISymbol> members)> unimplementedMembers,
            ImplementTypePropertyGenerationBehavior propertyGenerationBehavior)
        {
            // As we go along generating members we may end up with conflicts.  For example, say
            // you have "interface IGoo { string Bar { get; } }" and "interface IQuux { int Bar
            // { get; } }" and we need to implement both 'Bar' methods.  The second will have to
            // be explicitly implemented as it will conflict with the first.  So we need to keep
            // track of what we've actually implemented so that we can check further interface
            // members against both the actual type and that list.
            //
            // Similarly, if you have two interfaces with the same member, then we don't want to
            // implement that member twice.  
            //
            // Note: if we implement a method explicitly then we do *not* add it to this list.
            // That's because later members won't conflict with it even if they have the same
            // signature otherwise.  i.e. if we chose to implement IGoo.Bar explicitly, then we
            // could implement IQuux.Bar implicitly (and vice versa).
            using var _1 = ArrayBuilder<ISymbol>.GetInstance(out var implementedVisibleMembers);
            using var _2 = ArrayBuilder<ISymbol>.GetInstance(out var implementedMembers);
 
            foreach (var (_, unimplementedInterfaceMembers) in unimplementedMembers)
            {
                foreach (var unimplementedInterfaceMember in unimplementedInterfaceMembers)
                {
                    var members = GenerateMembers(
                        compilation, options,
                        unimplementedInterfaceMember, implementedVisibleMembers,
                        propertyGenerationBehavior);
                    foreach (var member in members)
                    {
                        if (member is null)
                            continue;
 
                        implementedMembers.Add(member);
 
                        if (!(member.ExplicitInterfaceImplementations().Any() && Service.HasHiddenExplicitImplementation))
                            implementedVisibleMembers.Add(member);
                    }
                }
            }
 
            return implementedMembers.ToImmutableAndClear();
        }
 
        private bool IsReservedName(string name)
        {
            return
                IdentifiersMatch(State.ClassOrStructType.Name, name) ||
                State.ClassOrStructType.TypeParameters.Any(static (t, arg) => arg.self.IdentifiersMatch(t.Name, arg.name), (self: this, name));
        }
 
        private string DetermineMemberName(ISymbol member, ArrayBuilder<ISymbol> implementedVisibleMembers, out ISymbol? conflictingMember)
        {
            conflictingMember = null;
 
            if (IsReservedName(member.Name) ||
                HasConflictingMember(member, implementedVisibleMembers, out conflictingMember))
            {
                var memberNames = State.ClassOrStructType.GetAccessibleMembersInThisAndBaseTypes<ISymbol>(State.ClassOrStructType).Select(m => m.Name);
 
                return NameGenerator.GenerateUniqueName(
                    string.Format("{0}_{1}", member.ContainingType.Name, member.Name),
                    n => !memberNames.Contains(n) &&
                        !implementedVisibleMembers.Any(m => IdentifiersMatch(m.Name, n)) &&
                        !IsReservedName(n));
            }
 
            return member.Name;
        }
 
        private IEnumerable<ISymbol?> GenerateMembers(
            Compilation compilation,
            ParseOptions options,
            ISymbol member,
            ArrayBuilder<ISymbol> implementedVisibleMembers,
            ImplementTypePropertyGenerationBehavior propertyGenerationBehavior)
        {
            // First check if we already generate a member that matches the member we want to
            // generate.  This can happen in C# when you have interfaces that have the same
            // method, and you are implementing implicitly.  For example:
            //
            // interface IGoo { void Goo(); }
            //
            // interface IBar : IGoo { new void Goo(); }
            //
            // class C : IBar
            //
            // In this case we only want to generate 'Goo' once.
            if (HasMatchingMember(implementedVisibleMembers, member))
                return [];
 
            var memberName = DetermineMemberName(member, implementedVisibleMembers, out var conflictingMember);
 
            // See if we need to generate an invisible member.  If we do, then reset the name
            // back to what then member wants it to be.
            var supportsImplicitImplementationOfNonPublicInterfaceMembers = this.Document
                .GetRequiredLanguageService<ISyntaxFactsService>()
                .SupportsImplicitImplementationOfNonPublicInterfaceMembers(options);
            var generateInvisibleMember = ShouldGenerateInvisibleMember(options, member, memberName, supportsImplicitImplementationOfNonPublicInterfaceMembers);
            memberName = generateInvisibleMember ? member.Name : memberName;
 
            // The language doesn't allow static abstract implementations of interface methods. i.e,
            // Only interface member is declared abstract static, but implementation should be only static.
            var generateAbstractly = !member.IsStatic && !generateInvisibleMember && Abstractly;
 
            // Check if we need to add 'new' to the signature we're adding.  We only need to do this
            // if we're not generating something explicit and we have a naming conflict with
            // something in our base class hierarchy.
            var addNew = !generateInvisibleMember && HasNameConflict(member, memberName, State.ClassOrStructType.GetBaseTypes());
 
            // Check if we need to add 'unsafe' to the signature we're generating.
            var syntaxFacts = Document.GetRequiredLanguageService<ISyntaxFactsService>();
            var addUnsafe = member.RequiresUnsafeModifier() && !syntaxFacts.IsUnsafeContext(State.ContextNode);
 
            return GenerateMembers(
                compilation, member, conflictingMember, memberName, generateInvisibleMember, generateAbstractly,
                addNew, addUnsafe, propertyGenerationBehavior);
        }
 
        public bool ShouldGenerateInvisibleMember(
            ParseOptions options, ISymbol member, string memberName, bool supportsImplementingLessAccessibleMember)
        {
            if (Service.HasHiddenExplicitImplementation)
            {
                // User asked for an explicit (i.e. invisible) member.
                if (Explicitly)
                    return true;
 
                // Have to create an invisible member if we have constraints we can't express
                // with a visible member.
                if (HasUnexpressibleConstraint(options, member))
                    return true;
 
                // If we had a conflict with a member of the same name, then we have to generate
                // as an invisible member.
                if (member.Name != memberName)
                    return true;
 
                // If the member contains a type is less accessible than type, for which we are implementing it, then
                // only explicit implementation is valid.
                if (ContainsTypeLessAccessibleThan(member, State.ClassOrStructType, supportsImplementingLessAccessibleMember))
                    return true;
            }
 
            // Can't generate an invisible member if the language doesn't support it.
            return false;
        }
 
        private bool HasUnexpressibleConstraint(ParseOptions options, ISymbol member)
        {
            // interface IGoo<T> { void Bar<U>() where U : T; }
            //
            // class A : IGoo<int> { }
            //
            // In this case we cannot generate an implement method for Bar.  That's because we'd
            // need to say "where U : int" and that's disallowed by the language.  So we must
            // generate something explicit here.
            if (member is not IMethodSymbol method)
                return false;
 
            var allowDelegateAndEnumConstraints = this.Service.AllowDelegateAndEnumConstraints(options);
            return method.TypeParameters.Any(t => IsUnexpressibleTypeParameter(t, allowDelegateAndEnumConstraints));
        }
 
        private static bool IsUnexpressibleTypeParameter(
            ITypeParameterSymbol typeParameter,
            bool allowDelegateAndEnumConstraints)
        {
            var condition1 = typeParameter.ConstraintTypes.Count(t => t.TypeKind == TypeKind.Class) >= 2;
            var condition2 = typeParameter.ConstraintTypes.Any(static (ts, allowDelegateAndEnumConstraints) => ts.IsUnexpressibleTypeParameterConstraint(allowDelegateAndEnumConstraints), allowDelegateAndEnumConstraints);
            var condition3 = typeParameter.HasReferenceTypeConstraint && typeParameter.ConstraintTypes.Any(static ts => ts.IsReferenceType && ts.SpecialType != SpecialType.System_Object);
 
            return condition1 || condition2 || condition3;
        }
 
        public ImmutableArray<ISymbol> GenerateMembers(
            Compilation compilation,
            ISymbol member,
            ISymbol? conflictingMember,
            string memberName,
            bool generateInvisibly,
            bool generateAbstractly,
            bool addNew,
            bool addUnsafe,
            ImplementTypePropertyGenerationBehavior propertyGenerationBehavior)
        {
            var factory = Document.GetRequiredLanguageService<SyntaxGenerator>();
            var modifiers = new DeclarationModifiers(isStatic: member.IsStatic, isAbstract: generateAbstractly, isNew: addNew, isUnsafe: addUnsafe);
 
            var useExplicitInterfaceSymbol = generateInvisibly || !Service.CanImplementImplicitly;
            var accessibility = member.Name == memberName || generateAbstractly
                ? Accessibility.Public
                : Accessibility.Private;
 
            return member switch
            {
                IMethodSymbol method => [GenerateMethod(compilation, method, conflictingMember as IMethodSymbol, accessibility, modifiers, generateAbstractly, useExplicitInterfaceSymbol, memberName)],
                IPropertySymbol property => GeneratePropertyMembers(compilation, property, conflictingMember as IPropertySymbol, accessibility, modifiers, generateAbstractly, useExplicitInterfaceSymbol, memberName, propertyGenerationBehavior),
                IEventSymbol @event => [GenerateEvent(compilation, memberName, generateInvisibly, factory, modifiers, useExplicitInterfaceSymbol, accessibility, @event)],
                _ => [],
            };
        }
 
        private ISymbol GenerateEvent(Compilation compilation, string memberName, bool generateInvisibly, SyntaxGenerator factory, DeclarationModifiers modifiers, bool useExplicitInterfaceSymbol, Accessibility accessibility, IEventSymbol @event)
        {
            var accessor = CodeGenerationSymbolFactory.CreateAccessorSymbol(
                attributes: default,
                accessibility: Accessibility.NotApplicable,
                statements: factory.CreateThrowNotImplementedStatementBlock(compilation));
 
            return CodeGenerationSymbolFactory.CreateEventSymbol(
                @event,
                accessibility: accessibility,
                modifiers: modifiers,
                explicitInterfaceImplementations: useExplicitInterfaceSymbol ? [@event] : default,
                name: memberName,
                addMethod: GetAddOrRemoveMethod(@event, generateInvisibly, accessor, memberName, factory.AddEventHandler),
                removeMethod: GetAddOrRemoveMethod(@event, generateInvisibly, accessor, memberName, factory.RemoveEventHandler));
        }
 
        private IMethodSymbol? GetAddOrRemoveMethod(
            IEventSymbol @event, bool generateInvisibly, IMethodSymbol accessor, string memberName,
            Func<SyntaxNode, SyntaxNode, SyntaxNode> createAddOrRemoveHandler)
        {
            if (ThroughMember != null)
            {
                var generator = Document.GetRequiredLanguageService<SyntaxGenerator>();
                var throughExpression = generator.CreateDelegateThroughExpression(@event, ThroughMember);
                var statement = generator.ExpressionStatement(createAddOrRemoveHandler(
                    generator.MemberAccessExpression(throughExpression, memberName), generator.IdentifierName("value")));
 
                return CodeGenerationSymbolFactory.CreateAccessorSymbol(
                       attributes: default,
                       accessibility: Accessibility.NotApplicable,
                       statements: [statement]);
            }
 
            return generateInvisibly ? accessor : null;
        }
 
        private bool HasNameConflict(
            ISymbol member,
            string memberName,
            IEnumerable<INamedTypeSymbol> baseTypes)
        {
            // There's a naming conflict if any member in the base types chain is accessible to
            // us, has our name.  Note: a simple name won't conflict with a generic name (and
            // vice versa).  A method only conflicts with another method if they have the same
            // parameter signature (return type is irrelevant). 
            return
                baseTypes.Any(ts => ts.GetMembers(memberName)
                                      .Where(m => m.IsAccessibleWithin(State.ClassOrStructType))
                                      .Any(m => HasNameConflict(member, m)));
        }
 
        private static bool HasNameConflict(ISymbol member, ISymbol baseMember)
        {
            if (member is IMethodSymbol method1 && baseMember is IMethodSymbol method2)
            {
                // A method only conflicts with another method if they have the same parameter
                // signature (return type is irrelevant). 
                return method1.MethodKind == MethodKind.Ordinary &&
                       method2.MethodKind == MethodKind.Ordinary &&
                       method1.TypeParameters.Length == method2.TypeParameters.Length &&
                       method1.Parameters.SequenceEqual(method2.Parameters, SymbolEquivalenceComparer.Instance.ParameterEquivalenceComparer);
            }
 
            // Any non method members with the same name simple name conflict.
            return true;
        }
 
        private bool IdentifiersMatch(string identifier1, string identifier2)
            => IsCaseSensitive
                ? identifier1 == identifier2
                : StringComparer.OrdinalIgnoreCase.Equals(identifier1, identifier2);
 
        private bool IsCaseSensitive => Document.GetRequiredLanguageService<ISyntaxFactsService>().IsCaseSensitive;
 
        private bool HasMatchingMember(ArrayBuilder<ISymbol> implementedVisibleMembers, ISymbol member)
        {
            // If this is a language that doesn't support implicit implementation then no
            // implemented members will ever match.  For example, if you have:
            //
            // Interface IGoo : sub Goo() : End Interface
            //
            // Interface IBar : Inherits IGoo : Shadows Sub Goo() : End Interface
            //
            // Class C : Implements IBar
            //
            // We'll first end up generating:
            //
            // Public Sub Goo() Implements IGoo.Goo
            //
            // However, that same method won't be viable for IBar.Goo (unlike C#) because it
            // explicitly specifies its interface).
            if (!Service.CanImplementImplicitly)
            {
                return false;
            }
 
            return implementedVisibleMembers.Any(m => MembersMatch(m, member));
        }
 
        private bool MembersMatch(ISymbol existingMember, ISymbol memberToAdd)
        {
            if (existingMember.Kind != memberToAdd.Kind)
                return false;
 
            if (existingMember.DeclaredAccessibility != memberToAdd.DeclaredAccessibility ||
                existingMember.IsStatic != memberToAdd.IsStatic)
            {
                return false;
            }
 
            if (existingMember.ExplicitInterfaceImplementations().Any() || memberToAdd.ExplicitInterfaceImplementations().Any())
                return false;
 
            if (!SignatureComparer.Instance.HaveSameSignatureAndConstraintsAndReturnType(existingMember, memberToAdd, IsCaseSensitive))
                return false;
 
            if (existingMember is IPropertySymbol existingProperty && memberToAdd is IPropertySymbol propertyToAdd)
            {
                // Have to make sure the accessors of the properties are complimentary.  Note: it's ok for the new
                // property to have a subset of the accessors of the existing property.
                if (propertyToAdd.GetMethod != null && SignatureComparer.BadPropertyAccessor(propertyToAdd.GetMethod, existingProperty.GetMethod))
                    return false;
 
                if (propertyToAdd.SetMethod != null && SignatureComparer.BadPropertyAccessor(propertyToAdd.SetMethod, existingProperty.SetMethod))
                    return false;
            }
 
            return true;
        }
    }
}