File: src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\Extensions\SyntaxGeneratorExtensions.cs
Web Access
Project: src\src\CodeStyle\Core\CodeFixes\Microsoft.CodeAnalysis.CodeStyle.Fixes.csproj (Microsoft.CodeAnalysis.CodeStyle.Fixes)
// 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.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
 
#if CODE_STYLE
using DeclarationModifiers = Microsoft.CodeAnalysis.Internal.Editing.DeclarationModifiers;
#else
using DeclarationModifiers = Microsoft.CodeAnalysis.Editing.DeclarationModifiers;
#endif
 
namespace Microsoft.CodeAnalysis.Shared.Extensions;
 
internal static partial class SyntaxGeneratorExtensions
{
    private const string EqualsName = "Equals";
    private const string DefaultName = "Default";
    private const string ObjName = "obj";
    public const string OtherName = "other";
 
    public static SyntaxNode CreateThrowNotImplementedStatement(
        this SyntaxGenerator codeDefinitionFactory, Compilation compilation)
    {
        return codeDefinitionFactory.ThrowStatement(
           CreateNewNotImplementedException(codeDefinitionFactory, compilation));
    }
 
    public static SyntaxNode CreateThrowNotImplementedExpression(
        this SyntaxGenerator codeDefinitionFactory, Compilation compilation)
    {
        return codeDefinitionFactory.ThrowExpression(
           CreateNewNotImplementedException(codeDefinitionFactory, compilation));
    }
 
    private static SyntaxNode CreateNewNotImplementedException(SyntaxGenerator codeDefinitionFactory, Compilation compilation)
    {
        var notImplementedExceptionTypeSyntax = compilation.NotImplementedExceptionType() is INamedTypeSymbol symbol
            ? codeDefinitionFactory.TypeExpression(symbol, addImport: false)
            : codeDefinitionFactory.QualifiedName(codeDefinitionFactory.IdentifierName(nameof(System)), codeDefinitionFactory.IdentifierName(nameof(NotImplementedException)));
 
        return codeDefinitionFactory.ObjectCreationExpression(
            notImplementedExceptionTypeSyntax,
            arguments: []);
    }
 
    public static ImmutableArray<SyntaxNode> CreateThrowNotImplementedStatementBlock(
        this SyntaxGenerator codeDefinitionFactory, Compilation compilation)
        => [CreateThrowNotImplementedStatement(codeDefinitionFactory, compilation)];
 
    public static ImmutableArray<SyntaxNode> CreateArguments(
        this SyntaxGenerator factory,
        ImmutableArray<IParameterSymbol> parameters)
    {
        return parameters.SelectAsArray(p => CreateArgument(factory, p));
    }
 
    private static SyntaxNode CreateArgument(
        this SyntaxGenerator factory,
        IParameterSymbol parameter)
    {
        return factory.Argument(parameter.RefKind, factory.IdentifierName(parameter.Name));
    }
 
    public static SyntaxNode GetDefaultEqualityComparer(
        this SyntaxGenerator factory,
        SyntaxGeneratorInternal generatorInternal,
        Compilation compilation,
        ITypeSymbol type)
    {
        var equalityComparerType = compilation.EqualityComparerOfTType();
        var typeExpression = equalityComparerType == null
            ? factory.GenericName(nameof(EqualityComparer<int>), type)
            : generatorInternal.Type(equalityComparerType.Construct(type), typeContext: false);
 
        return factory.MemberAccessExpression(typeExpression, factory.IdentifierName(DefaultName));
    }
 
    private static ITypeSymbol GetType(Compilation compilation, ISymbol symbol)
        => symbol switch
        {
            IFieldSymbol field => field.Type,
            IPropertySymbol property => property.Type,
            _ => compilation.GetSpecialType(SpecialType.System_Object),
        };
 
    public static SyntaxNode IsPatternExpression(this SyntaxGeneratorInternal generator, SyntaxNode expression, SyntaxNode pattern)
        => generator.IsPatternExpression(expression, isToken: default, pattern);
 
    /// <summary>
    /// Generates a call to a method *through* an existing field or property symbol.
    /// </summary>
    /// <returns></returns>
    public static SyntaxNode GenerateDelegateThroughMemberStatement(
        this SyntaxGenerator generator, IMethodSymbol method, ISymbol throughMember)
    {
        var through = generator.MemberAccessExpression(
            CreateDelegateThroughExpression(generator, method, throughMember),
            method.IsGenericMethod
                ? generator.GenericName(method.Name, method.TypeArguments)
                : generator.IdentifierName(method.Name));
 
        var invocationExpression = generator.InvocationExpression(through, generator.CreateArguments(method.Parameters));
        return method.ReturnsVoid
            ? generator.ExpressionStatement(invocationExpression)
            : generator.ReturnStatement(invocationExpression);
    }
 
    public static SyntaxNode CreateDelegateThroughExpression(
        this SyntaxGenerator generator, ISymbol member, ISymbol throughMember)
    {
        var name = generator.IdentifierName(throughMember.Name);
        var through = throughMember.IsStatic
            ? GenerateContainerName(generator, throughMember)
            // If we're delegating through a primary constructor parameter, we cannot qualify the name at all.
            : throughMember is IParameterSymbol
                ? null
                : generator.ThisExpression();
 
        through = through is null ? name : generator.MemberAccessExpression(through, name);
 
        var throughMemberType = throughMember.GetMemberType();
        if (throughMemberType != null &&
            member.ContainingType is { TypeKind: TypeKind.Interface } interfaceBeingImplemented)
        {
            // In the case of 'implement interface through field / property', we need to know what
            // interface we are implementing so that we can insert casts to this interface on every
            // usage of the field in the generated code. Without these casts we would end up generating
            // code that fails compilation in certain situations.
            // 
            // For example consider the following code.
            //      class C : IReadOnlyList<int> { int[] field; }
            // When applying the 'implement interface through field' code fix in the above example,
            // we need to generate the following code to implement the Count property on IReadOnlyList<int>
            //      class C : IReadOnlyList<int> { int[] field; int Count { get { ((IReadOnlyList<int>)field).Count; } ...}
            // as opposed to the following code which will fail to compile (because the array field
            // doesn't have a property named .Count) -
            //      class C : IReadOnlyList<int> { int[] field; int Count { get { field.Count; } ...}
            //
            // The 'InterfaceTypes' property on the state object always contains only one item
            // in the case of C# i.e. it will contain exactly the interface we are trying to implement.
            // This is also the case most of the time in the case of VB, except in certain error conditions
            // (recursive / circular cases) where the span of the squiggle for the corresponding 
            // diagnostic (BC30149) changes and 'InterfaceTypes' ends up including all interfaces
            // in the Implements clause. For the purposes of inserting the above cast, we ignore the
            // uncommon case and optimize for the common one - in other words, we only apply the cast
            // in cases where we can unambiguously figure out which interface we are trying to implement.
            if (!throughMemberType.Equals(interfaceBeingImplemented))
            {
                through = generator.CastExpression(interfaceBeingImplemented,
                    through.WithAdditionalAnnotations(Simplifier.Annotation));
            }
            else if (throughMember is IPropertySymbol { IsStatic: false, ExplicitInterfaceImplementations: [var explicitlyImplementedProperty, ..] })
            {
                // If we are implementing through an explicitly implemented property, we need to cast 'this' to
                // the explicitly implemented interface type before calling the member, as in:
                //       ((IA)this).Prop.Member();
                //
                var explicitImplementationCast = generator.CastExpression(
                    explicitlyImplementedProperty.ContainingType,
                    generator.ThisExpression());
 
                through = generator.MemberAccessExpression(explicitImplementationCast,
                    generator.IdentifierName(explicitlyImplementedProperty.Name));
 
                through = through.WithAdditionalAnnotations(Simplifier.Annotation);
            }
        }
 
        return through.WithAdditionalAnnotations(Simplifier.Annotation);
 
        // local functions
 
        static SyntaxNode GenerateContainerName(SyntaxGenerator factory, ISymbol throughMember)
        {
            var classOrStructType = throughMember.ContainingType;
            return classOrStructType.IsGenericType
                ? factory.GenericName(classOrStructType.Name, classOrStructType.TypeArguments)
                : factory.IdentifierName(classOrStructType.Name);
        }
    }
 
    public static ImmutableArray<SyntaxNode> GetGetAccessorStatements(
        this SyntaxGenerator generator,
        Compilation compilation,
        IPropertySymbol property,
        IPropertySymbol? conflictingProperty,
        ISymbol? throughMember,
        bool preferAutoProperties)
    {
        if (throughMember != null)
        {
            var throughExpression = CreateDelegateThroughExpression(generator, property, throughMember);
            var expression = property.IsIndexer
                ? throughExpression
                : generator.MemberAccessExpression(
                    throughExpression, generator.IdentifierName(property.Name));
 
            if (property.Parameters.Length > 0)
            {
                var arguments = generator.CreateArguments(property.Parameters);
                expression = generator.ElementAccessExpression(expression, arguments);
            }
 
            return [generator.ReturnStatement(expression)];
        }
 
        if (preferAutoProperties)
            return default;
 
        // Forward from the explicit property we're creating to the existing property it conflicts with if possible.
        if (conflictingProperty is { GetMethod: not null, Parameters.Length: 0 } &&
            property is { GetMethod: not null, Parameters.Length: 0 })
        {
            if (compilation.ClassifyCommonConversion(conflictingProperty.Type, property.Type) is { Exists: true, IsImplicit: true })
                return [generator.ReturnStatement(generator.MemberAccessExpression(generator.ThisExpression(), property.Name))];
        }
 
        return generator.CreateThrowNotImplementedStatementBlock(compilation);
    }
 
    public static ImmutableArray<SyntaxNode> GetSetAccessorStatements(
        this SyntaxGenerator generator,
        Compilation compilation,
        IPropertySymbol property,
        IPropertySymbol? conflictingProperty,
        ISymbol? throughMember,
        bool preferAutoProperties)
    {
        if (throughMember != null)
        {
            var throughExpression = CreateDelegateThroughExpression(generator, property, throughMember);
            var expression = property.IsIndexer
                ? throughExpression
                : generator.MemberAccessExpression(
                    throughExpression, generator.IdentifierName(property.Name));
 
            if (property.Parameters.Length > 0)
            {
                var arguments = generator.CreateArguments(property.Parameters);
                expression = generator.ElementAccessExpression(expression, arguments);
            }
 
            expression = generator.AssignmentStatement(expression, generator.IdentifierName("value"));
 
            return [generator.ExpressionStatement(expression)];
        }
 
        if (preferAutoProperties)
            return default;
 
        // Forward from the explicit property we're creating to the existing property it conflicts with if possible.
        if (conflictingProperty is { SetMethod.Parameters.Length: 1 } &&
            property is { SetMethod.Parameters: [var parameter] })
        {
            if (compilation.ClassifyCommonConversion(property.Type, conflictingProperty.Type) is { Exists: true, IsImplicit: true })
                return [generator.ExpressionStatement(generator.AssignmentStatement(generator.MemberAccessExpression(generator.ThisExpression(), property.Name), generator.IdentifierName(parameter.Name)))];
        }
 
        return generator.CreateThrowNotImplementedStatementBlock(compilation);
    }
 
    private static bool TryGetValue(IDictionary<string, string>? dictionary, string key, [NotNullWhen(true)] out string? value)
    {
        value = null;
        return
            dictionary != null &&
            dictionary.TryGetValue(key, out value);
    }
 
    private static bool TryGetValue(IDictionary<string, ISymbol>? dictionary, string key, [NotNullWhen(true)] out string? value)
    {
        value = null;
        if (dictionary != null && dictionary.TryGetValue(key, out var symbol))
        {
            value = symbol.Name;
            return true;
        }
 
        return false;
    }
 
    public static ImmutableArray<ISymbol> CreateFieldsForParameters(
        ImmutableArray<IParameterSymbol> parameters, ImmutableDictionary<string, string>? parameterToNewFieldMap, bool isContainedInUnsafeType)
    {
        using var _ = ArrayBuilder<ISymbol>.GetInstance(out var result);
        foreach (var parameter in parameters)
        {
            // For non-out parameters, create a field and assign the parameter to it.
            if (parameter.RefKind != RefKind.Out &&
                TryGetValue(parameterToNewFieldMap, parameter.Name, out var fieldName))
            {
                result.Add(CodeGenerationSymbolFactory.CreateFieldSymbol(
                    attributes: default,
                    accessibility: Accessibility.Private,
                    modifiers: new DeclarationModifiers(isUnsafe: !isContainedInUnsafeType && parameter.RequiresUnsafeModifier()),
                    type: parameter.Type,
                    name: fieldName));
            }
        }
 
        return result.ToImmutableAndClear();
    }
 
    public static ImmutableArray<ISymbol> CreatePropertiesForParameters(
        ImmutableArray<IParameterSymbol> parameters, ImmutableDictionary<string, string>? parameterToNewPropertyMap, bool isContainedInUnsafeType)
    {
        using var _ = ArrayBuilder<ISymbol>.GetInstance(out var result);
        foreach (var parameter in parameters)
        {
            // For non-out parameters, create a property and assign the parameter to it.
            if (parameter.RefKind != RefKind.Out &&
                TryGetValue(parameterToNewPropertyMap, parameter.Name, out var propertyName))
            {
                result.Add(CodeGenerationSymbolFactory.CreatePropertySymbol(
                    attributes: default,
                    accessibility: Accessibility.Public,
                    modifiers: new DeclarationModifiers(isUnsafe: !isContainedInUnsafeType && parameter.RequiresUnsafeModifier()),
                    type: parameter.Type,
                    refKind: RefKind.None,
                    explicitInterfaceImplementations: [],
                    name: propertyName,
                    parameters: [],
                    getMethod: CodeGenerationSymbolFactory.CreateAccessorSymbol(
                        attributes: default,
                        accessibility: default,
                        statements: default),
                    setMethod: null));
            }
        }
 
        return result.ToImmutableAndClear();
    }
 
    public static ImmutableArray<SyntaxNode> CreateAssignmentStatements(
        this SyntaxGenerator factory,
        SyntaxGeneratorInternal generatorInternal,
        SemanticModel semanticModel,
        ImmutableArray<IParameterSymbol> parameters,
        IDictionary<string, ISymbol>? parameterToExistingFieldMap,
        IDictionary<string, string>? parameterToNewFieldMap,
        bool addNullChecks,
        bool preferThrowExpression)
    {
        using var _1 = ArrayBuilder<SyntaxNode>.GetInstance(out var nullCheckStatements);
        using var _2 = ArrayBuilder<SyntaxNode>.GetInstance(out var assignStatements);
 
        foreach (var parameter in parameters)
        {
            var refKind = parameter.RefKind;
            var parameterType = parameter.Type;
            var parameterName = parameter.Name;
 
            if (refKind == RefKind.Out)
            {
                // If it's an out param, then don't create a field for it.  Instead, assign
                // the default value for that type (i.e. "default(...)") to it.
                var assignExpression = factory.AssignmentStatement(
                    factory.IdentifierName(parameterName),
                    factory.DefaultExpression(parameterType));
                var statement = factory.ExpressionStatement(assignExpression);
                assignStatements.Add(statement);
            }
            else
            {
                // For non-out parameters, create a field and assign the parameter to it.
                // TODO: I'm not sure that's what we really want for ref parameters.
                if (TryGetValue(parameterToExistingFieldMap, parameterName, out var fieldName) ||
                    TryGetValue(parameterToNewFieldMap, parameterName, out fieldName))
                {
                    var fieldAccess = factory.MemberAccessExpression(factory.ThisExpression(), factory.IdentifierName(fieldName))
                                             .WithAdditionalAnnotations(Simplifier.Annotation);
 
                    factory.AddAssignmentStatements(
                        generatorInternal,
                        semanticModel, parameter, fieldAccess,
                        addNullChecks, preferThrowExpression,
                        nullCheckStatements, assignStatements);
                }
            }
        }
 
        return [.. nullCheckStatements, .. assignStatements];
    }
 
    public static void AddAssignmentStatements(
         this SyntaxGenerator factory,
         SyntaxGeneratorInternal generatorInternal,
         SemanticModel semanticModel,
         IParameterSymbol parameter,
         SyntaxNode fieldAccess,
         bool addNullChecks,
         bool preferThrowExpression,
         ArrayBuilder<SyntaxNode> nullCheckStatements,
         ArrayBuilder<SyntaxNode> assignStatements)
    {
        // Don't want to add a null check for something of the form `int?`.  The type was
        // already declared as nullable to indicate that null is ok.  Adding a null check
        // just disallows something that should be allowed.
        var shouldAddNullCheck = addNullChecks && parameter.Type.CanAddNullCheck() && !parameter.Type.IsNullable();
 
        if (shouldAddNullCheck && preferThrowExpression && generatorInternal.SupportsThrowExpression())
        {
            // Generate: this.x = x ?? throw ...
            assignStatements.Add(CreateAssignWithNullCheckStatement(
                factory, semanticModel.Compilation, parameter, fieldAccess));
        }
        else
        {
            if (shouldAddNullCheck)
            {
                // generate: if (x == null) throw ...
                nullCheckStatements.Add(
                    factory.CreateNullCheckAndThrowStatement(generatorInternal, semanticModel, parameter));
            }
 
            // generate: this.x = x;
            assignStatements.Add(
                factory.ExpressionStatement(
                    factory.AssignmentStatement(
                        fieldAccess,
                        factory.IdentifierName(parameter.Name))));
        }
    }
 
    public static SyntaxNode CreateAssignWithNullCheckStatement(
        this SyntaxGenerator factory, Compilation compilation, IParameterSymbol parameter, SyntaxNode fieldAccess)
    {
        return factory.ExpressionStatement(factory.AssignmentStatement(
            fieldAccess,
            factory.CoalesceExpression(
                factory.IdentifierName(parameter.Name),
                factory.CreateThrowArgumentNullExpression(compilation, parameter))));
    }
 
    public static SyntaxNode CreateThrowArgumentNullExpression(this SyntaxGenerator factory, Compilation compilation, IParameterSymbol parameter)
        => factory.ThrowExpression(CreateNewArgumentNullException(factory, compilation, parameter));
 
    private static SyntaxNode CreateNewArgumentNullException(SyntaxGenerator factory, Compilation compilation, IParameterSymbol parameter)
    {
        var type = compilation.GetTypeByMetadataName(typeof(ArgumentNullException).FullName!);
        Contract.ThrowIfNull(type);
        return factory.ObjectCreationExpression(type,
            factory.NameOfExpression(
                factory.IdentifierName(parameter.Name))).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation);
    }
 
    public static SyntaxNode CreateNullCheckAndThrowStatement(
        this SyntaxGenerator factory,
        SyntaxGeneratorInternal generatorInternal,
        SemanticModel semanticModel,
        IParameterSymbol parameter)
    {
        var condition = factory.CreateNullCheckExpression(generatorInternal, semanticModel, parameter.Name);
        var throwStatement = factory.CreateThrowArgumentNullExceptionStatement(semanticModel.Compilation, parameter);
 
        // generates: if (s is null) { throw new ArgumentNullException(nameof(s)); }
        return factory.IfStatement(condition, [throwStatement]);
    }
    public static SyntaxNode CreateNullCheckExpression(
        this SyntaxGenerator factory, SyntaxGeneratorInternal generatorInternal, SemanticModel semanticModel, string identifierName)
    {
        var identifier = factory.IdentifierName(identifierName);
        var nullExpr = factory.NullLiteralExpression();
        var condition = generatorInternal.SupportsPatterns(semanticModel.SyntaxTree.Options)
            ? generatorInternal.IsPatternExpression(identifier, generatorInternal.ConstantPattern(nullExpr))
            : factory.ReferenceEqualsExpression(identifier, nullExpr);
        return condition;
    }
 
    public static SyntaxNode CreateThrowArgumentNullExceptionStatement(this SyntaxGenerator factory, Compilation compilation, IParameterSymbol parameter)
        => factory.ThrowStatement(CreateNewArgumentNullException(factory, compilation, parameter));
}