File: src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\Extensions\ITypeSymbolExtensions.TypeSyntaxGeneratorVisitor.cs
Web Access
Project: src\src\RoslynAnalyzers\Roslyn.Diagnostics.Analyzers\CSharp\Roslyn.Diagnostics.CSharp.Analyzers.csproj (Roslyn.Diagnostics.CSharp.Analyzers)
// 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.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Extensions;
 
using static CSharpSyntaxTokens;
using static SyntaxFactory;
 
internal partial class ITypeSymbolExtensions
{
    private sealed class TypeSyntaxGeneratorVisitor(bool nameOnly) : SymbolVisitor<TypeSyntax>
    {
        private static readonly TypeSyntaxGeneratorVisitor NameOnlyInstance = new(nameOnly: true);
        private static readonly TypeSyntaxGeneratorVisitor NotNameOnlyInstance = new(nameOnly: false);
 
        private static readonly QualifiedNameSyntax SystemObjectType =
            QualifiedName(
                AliasQualifiedName(
                    CreateGlobalIdentifier(),
                    IdentifierName("System")),
                IdentifierName("Object"));
 
        private readonly bool _nameOnly = nameOnly;
 
        public static TypeSyntaxGeneratorVisitor Create(bool nameOnly = false)
            => nameOnly ? NameOnlyInstance : NotNameOnlyInstance;
 
        public override TypeSyntax DefaultVisit(ISymbol node)
            => throw new NotImplementedException();
 
        private static TTypeSyntax AddInformationTo<TTypeSyntax>(TTypeSyntax syntax, ISymbol symbol)
            where TTypeSyntax : TypeSyntax
        {
            syntax = syntax.WithPrependedLeadingTrivia(ElasticMarker).WithAppendedTrailingTrivia(ElasticMarker);
            syntax = syntax.WithAdditionalAnnotations(SymbolAnnotation.Create(symbol));
 
            return syntax;
        }
 
        public override TypeSyntax VisitAlias(IAliasSymbol symbol)
            => AddInformationTo(symbol.Name.ToIdentifierName(), symbol);
 
        private void ThrowIfNameOnly()
        {
            if (_nameOnly)
            {
                throw new InvalidOperationException("This symbol cannot be converted into a NameSyntax");
            }
        }
 
        public override TypeSyntax VisitArrayType(IArrayTypeSymbol symbol)
        {
            ThrowIfNameOnly();
 
            ITypeSymbol underlyingType = symbol;
 
            while (underlyingType is IArrayTypeSymbol innerArray)
            {
                underlyingType = innerArray.ElementType;
 
                if (underlyingType.NullableAnnotation == NullableAnnotation.Annotated)
                {
                    // If the inner array we just moved to is also nullable, then
                    // we must terminate the digging now so we produce the syntax for that,
                    // and then append the ranks we passed through at the end. This is because
                    // nullability annotations acts as a "barrier" where we won't reorder array
                    // through. So whereas:
                    //
                    //     string[][,]
                    //
                    // is really an array of rank 1 that has an element of rank 2,
                    //
                    //     string[]?[,]
                    //
                    // is really an array of rank 2 that has nullable elements of rank 1.
 
                    break;
                }
            }
 
            var elementTypeSyntax = underlyingType.GenerateTypeSyntax();
            using var _ = ArrayBuilder<ArrayRankSpecifierSyntax>.GetInstance(out var ranks);
 
            var arrayType = symbol;
            while (arrayType != null && !arrayType.Equals(underlyingType))
            {
                ranks.Add(ArrayRankSpecifier(
                    [.. Enumerable.Repeat<ExpressionSyntax>(OmittedArraySizeExpression(), arrayType.Rank)]));
 
                arrayType = arrayType.ElementType as IArrayTypeSymbol;
            }
 
            TypeSyntax arrayTypeSyntax = ArrayType(elementTypeSyntax, [.. ranks]);
 
            if (symbol.NullableAnnotation == NullableAnnotation.Annotated)
            {
                arrayTypeSyntax = NullableType(arrayTypeSyntax);
            }
 
            return AddInformationTo(arrayTypeSyntax, symbol);
        }
 
        public override TypeSyntax VisitDynamicType(IDynamicTypeSymbol symbol)
        {
            var typeSyntax = IdentifierName("dynamic");
            return symbol.NullableAnnotation is NullableAnnotation.Annotated
                ? AddInformationTo(NullableType(typeSyntax), symbol)
                : AddInformationTo(typeSyntax, symbol);
        }
 
        public static bool TryCreateNativeIntegerType(INamedTypeSymbol symbol, [NotNullWhen(true)] out TypeSyntax? syntax)
        {
            if (symbol.IsNativeIntegerType)
            {
                syntax = IdentifierName(symbol.SpecialType == SpecialType.System_IntPtr ? "nint" : "nuint");
                return true;
            }
 
            syntax = null;
            return false;
        }
 
        public override TypeSyntax VisitFunctionPointerType(IFunctionPointerTypeSymbol symbol)
        {
            FunctionPointerCallingConventionSyntax? callingConventionSyntax = null;
            // For varargs there is no C# syntax. You get a use-site diagnostic if you attempt to use it, and just
            // making a default-convention symbol is likely good enough. This is only observable through metadata
            // that always be uncompilable in C# anyway.
            if (symbol.Signature.CallingConvention is not System.Reflection.Metadata.SignatureCallingConvention.Default
                and not System.Reflection.Metadata.SignatureCallingConvention.VarArgs)
            {
                var conventionsList = symbol.Signature.CallingConvention switch
                {
                    System.Reflection.Metadata.SignatureCallingConvention.CDecl => [GetConventionForString("Cdecl")],
                    System.Reflection.Metadata.SignatureCallingConvention.StdCall => [GetConventionForString("Stdcall")],
                    System.Reflection.Metadata.SignatureCallingConvention.ThisCall => [GetConventionForString("Thiscall")],
                    System.Reflection.Metadata.SignatureCallingConvention.FastCall => [GetConventionForString("Fastcall")],
                    System.Reflection.Metadata.SignatureCallingConvention.Unmanaged =>
                        // All types that come from CallingConventionTypes start with "CallConv". We don't want the prefix for the actual
                        // syntax, so strip it off
                        symbol.Signature.UnmanagedCallingConventionTypes.IsEmpty
                            ? null : symbol.Signature.UnmanagedCallingConventionTypes.Select(type => GetConventionForString(type.Name["CallConv".Length..])),
 
                    _ => throw ExceptionUtilities.UnexpectedValue(symbol.Signature.CallingConvention),
                };
 
                callingConventionSyntax = FunctionPointerCallingConvention(
                    UnmanagedKeyword,
                    conventionsList is object
                        ? FunctionPointerUnmanagedCallingConventionList([.. conventionsList])
                        : null);
 
                static FunctionPointerUnmanagedCallingConventionSyntax GetConventionForString(string identifier)
                    => FunctionPointerUnmanagedCallingConvention(Identifier(identifier));
            }
 
            var parameters = symbol.Signature.Parameters.Select(p => (p.Type, RefKindModifiers: CSharpSyntaxGeneratorInternal.GetParameterModifiers(p)))
                .Concat([(
                    Type: symbol.Signature.ReturnType,
                    RefKindModifiers: CSharpSyntaxGeneratorInternal.GetParameterModifiers(isScoped: false, symbol.Signature.RefKind, isParams: false, forFunctionPointerReturnParameter: true))])
                .SelectAsArray(t => FunctionPointerParameter(t.Type.GenerateTypeSyntax()).WithModifiers(t.RefKindModifiers));
 
            return AddInformationTo(
                FunctionPointerType(callingConventionSyntax, FunctionPointerParameterList([.. parameters])), symbol);
        }
 
        public TypeSyntax CreateSimpleTypeSyntax(INamedTypeSymbol symbol)
        {
            if (!_nameOnly)
            {
                var syntax = TryCreateSpecializedNamedTypeSyntax(symbol);
                if (syntax != null)
                    return syntax;
            }
 
            if (symbol.IsTupleType && symbol.TupleUnderlyingType != null && !symbol.Equals(symbol.TupleUnderlyingType))
            {
                return CreateSimpleTypeSyntax(symbol.TupleUnderlyingType);
            }
 
            if (symbol.Name == string.Empty || symbol.IsAnonymousType)
                return SystemObjectType;
 
            if (symbol.TypeParameters.Length == 0)
            {
                if (symbol is { TypeKind: TypeKind.Error, Name: "var" })
                    return SystemObjectType;
 
                return symbol.Name.ToIdentifierName();
            }
 
            var typeArguments = symbol.IsUnboundGenericType
                ? Enumerable.Repeat((TypeSyntax)OmittedTypeArgument(), symbol.TypeArguments.Length)
                : symbol.TypeArguments.SelectAsArray(t => t.GenerateTypeSyntax());
 
            return GenericName(
                symbol.Name.ToIdentifierToken(),
                TypeArgumentList([.. typeArguments]));
        }
 
        public static QualifiedNameSyntax CreateSystemObject()
            => SystemObjectType;
 
        private static IdentifierNameSyntax CreateGlobalIdentifier()
            => IdentifierName(GlobalKeyword);
 
        private static TypeSyntax? TryCreateSpecializedNamedTypeSyntax(INamedTypeSymbol symbol)
        {
            if (symbol.SpecialType == SpecialType.System_Void)
            {
                return PredefinedType(VoidKeyword);
            }
 
            if (symbol.IsTupleType && symbol.TupleElements.Length >= 2)
            {
                return CreateTupleTypeSyntax(symbol);
            }
 
            if (symbol.IsNullable())
            {
                // Can't have a nullable of a pointer type.  i.e. "int*?" is illegal.
                var innerType = symbol.TypeArguments.First();
                if (innerType.TypeKind != TypeKind.Pointer)
                {
                    return AddInformationTo(
                        NullableType(innerType.GenerateTypeSyntax()), symbol);
                }
            }
 
            return null;
        }
 
        private static TupleTypeSyntax CreateTupleTypeSyntax(INamedTypeSymbol symbol)
        {
            var list = new SeparatedSyntaxList<TupleElementSyntax>();
 
            foreach (var element in symbol.TupleElements)
            {
                var name = element.IsImplicitlyDeclared ? default : element.Name.ToIdentifierToken();
                list = list.Add(TupleElement(element.Type.GenerateTypeSyntax(), name));
            }
 
            return AddInformationTo(TupleType(list), symbol);
        }
 
        public override TypeSyntax VisitNamedType(INamedTypeSymbol symbol)
        {
            if (TryCreateNativeIntegerType(symbol, out var typeSyntax))
                return typeSyntax;
 
            typeSyntax = CreateSimpleTypeSyntax(symbol);
            if (typeSyntax is not SimpleNameSyntax)
                return typeSyntax;
 
            var simpleNameSyntax = (SimpleNameSyntax)typeSyntax;
            if (symbol.ContainingType != null)
            {
                if (symbol.ContainingType.TypeKind != TypeKind.Submission)
                {
                    var containingTypeSyntax = symbol.ContainingType.Accept(this);
                    if (containingTypeSyntax is NameSyntax name)
                    {
                        typeSyntax = AddInformationTo(
                            QualifiedName(name, simpleNameSyntax),
                            symbol);
                    }
                    else
                    {
                        typeSyntax = AddInformationTo(simpleNameSyntax, symbol);
                    }
                }
            }
            else if (symbol.ContainingNamespace != null)
            {
                if (symbol.ContainingNamespace.IsGlobalNamespace)
                {
                    if (symbol.TypeKind != TypeKind.Error)
                    {
                        typeSyntax = AddGlobalAlias(symbol, simpleNameSyntax);
                    }
                }
                else
                {
                    var container = symbol.ContainingNamespace.Accept(this)!;
                    typeSyntax = AddInformationTo(QualifiedName(
                        (NameSyntax)container,
                        simpleNameSyntax), symbol);
                }
            }
 
            if (symbol is { IsValueType: false, NullableAnnotation: NullableAnnotation.Annotated })
            {
                // value type with nullable annotation may be composed from unconstrained nullable generic
                // doesn't mean nullable value type in this case
                typeSyntax = AddInformationTo(NullableType(typeSyntax), symbol);
            }
 
            return typeSyntax;
        }
 
        public override TypeSyntax VisitNamespace(INamespaceSymbol symbol)
        {
            var syntax = AddInformationTo(symbol.Name.ToIdentifierName(), symbol);
            if (symbol.ContainingNamespace == null)
            {
                return syntax;
            }
 
            if (symbol.ContainingNamespace.IsGlobalNamespace)
            {
                return AddGlobalAlias(symbol, syntax);
            }
            else
            {
                var container = symbol.ContainingNamespace.Accept(this)!;
                return AddInformationTo(QualifiedName(
                    (NameSyntax)container,
                    syntax), symbol);
            }
        }
 
        /// <summary>
        /// We always unilaterally add "global::" to all named types/namespaces.  This
        /// will then be trimmed off if possible by the simplifier.
        /// </summary>
        private static TypeSyntax AddGlobalAlias(INamespaceOrTypeSymbol symbol, SimpleNameSyntax syntax)
        {
            return AddInformationTo(
                AliasQualifiedName(
                    CreateGlobalIdentifier(),
                    syntax), symbol);
        }
 
        public override TypeSyntax VisitPointerType(IPointerTypeSymbol symbol)
        {
            ThrowIfNameOnly();
 
            return AddInformationTo(
                PointerType(symbol.PointedAtType.GenerateTypeSyntax()),
                symbol);
        }
 
        public override TypeSyntax VisitTypeParameter(ITypeParameterSymbol symbol)
        {
            TypeSyntax typeSyntax = AddInformationTo(symbol.Name.ToIdentifierName(), symbol);
            if (symbol is { IsValueType: false, NullableAnnotation: NullableAnnotation.Annotated })
            {
                // value type with nullable annotation may be composed from unconstrained nullable generic
                // doesn't mean nullable value type in this case
                typeSyntax = AddInformationTo(NullableType(typeSyntax), symbol);
            }
 
            return typeSyntax;
        }
    }
}