|
// 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.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Extensions
{
internal partial class ITypeSymbolExtensions
{
private class TypeSyntaxGeneratorVisitor : SymbolVisitor<TypeSyntax>
{
private readonly bool _nameOnly;
private static readonly TypeSyntaxGeneratorVisitor NameOnlyInstance = new(nameOnly: true);
private static readonly TypeSyntaxGeneratorVisitor NotNameOnlyInstance = new(nameOnly: false);
private TypeSyntaxGeneratorVisitor(bool nameOnly)
=> _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(SyntaxFactory.ElasticMarker).WithAppendedTrailingTrivia(SyntaxFactory.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(SyntaxFactory.ArrayRankSpecifier(
SyntaxFactory.SeparatedList(Enumerable.Repeat<ExpressionSyntax>(SyntaxFactory.OmittedArraySizeExpression(), arrayType.Rank))));
arrayType = arrayType.ElementType as IArrayTypeSymbol;
}
TypeSyntax arrayTypeSyntax = SyntaxFactory.ArrayType(elementTypeSyntax, ranks.ToSyntaxList());
if (symbol.NullableAnnotation == NullableAnnotation.Annotated)
{
arrayTypeSyntax = SyntaxFactory.NullableType(arrayTypeSyntax);
}
return AddInformationTo(arrayTypeSyntax, symbol);
}
public override TypeSyntax VisitDynamicType(IDynamicTypeSymbol symbol)
=> AddInformationTo(SyntaxFactory.IdentifierName("dynamic"), symbol);
public static bool TryCreateNativeIntegerType(INamedTypeSymbol symbol, [NotNullWhen(true)] out TypeSyntax? syntax)
{
if (symbol.IsNativeIntegerType)
{
syntax = SyntaxFactory.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 => new[] { GetConventionForString("Cdecl") },
System.Reflection.Metadata.SignatureCallingConvention.StdCall => new[] { GetConventionForString("Stdcall") },
System.Reflection.Metadata.SignatureCallingConvention.ThisCall => new[] { GetConventionForString("Thiscall") },
System.Reflection.Metadata.SignatureCallingConvention.FastCall => new[] { 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 = SyntaxFactory.FunctionPointerCallingConvention(
SyntaxFactory.Token(SyntaxKind.UnmanagedKeyword),
conventionsList is object
? SyntaxFactory.FunctionPointerUnmanagedCallingConventionList(SyntaxFactory.SeparatedList(conventionsList))
: null);
static FunctionPointerUnmanagedCallingConventionSyntax GetConventionForString(string identifier)
=> SyntaxFactory.FunctionPointerUnmanagedCallingConvention(SyntaxFactory.Identifier(identifier));
}
var parameters = symbol.Signature.Parameters.Select(p => (p.Type, RefKindModifiers: CSharpSyntaxGeneratorInternal.GetParameterModifiers(p.RefKind)))
.Concat(SpecializedCollections.SingletonEnumerable((
Type: symbol.Signature.ReturnType,
RefKindModifiers: CSharpSyntaxGeneratorInternal.GetParameterModifiers(symbol.Signature.RefKind, forFunctionPointerReturnParameter: true))))
.SelectAsArray(t => SyntaxFactory.FunctionPointerParameter(t.Type.GenerateTypeSyntax()).WithModifiers(t.RefKindModifiers));
return AddInformationTo(
SyntaxFactory.FunctionPointerType(callingConventionSyntax, SyntaxFactory.FunctionPointerParameterList(SyntaxFactory.SeparatedList(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 CreateSystemObject();
}
if (symbol.TypeParameters.Length == 0)
{
if (symbol.TypeKind == TypeKind.Error && symbol.Name == "var")
{
return CreateSystemObject();
}
return symbol.Name.ToIdentifierName();
}
var typeArguments = symbol.IsUnboundGenericType
? Enumerable.Repeat((TypeSyntax)SyntaxFactory.OmittedTypeArgument(), symbol.TypeArguments.Length)
: symbol.TypeArguments.SelectAsArray(t => t.GenerateTypeSyntax());
return SyntaxFactory.GenericName(
symbol.Name.ToIdentifierToken(),
SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList(typeArguments)));
}
public static QualifiedNameSyntax CreateSystemObject()
{
return SyntaxFactory.QualifiedName(
SyntaxFactory.AliasQualifiedName(
CreateGlobalIdentifier(),
SyntaxFactory.IdentifierName("System")),
SyntaxFactory.IdentifierName("Object"));
}
private static IdentifierNameSyntax CreateGlobalIdentifier()
=> SyntaxFactory.IdentifierName(SyntaxFactory.Token(SyntaxKind.GlobalKeyword));
private static TypeSyntax? TryCreateSpecializedNamedTypeSyntax(INamedTypeSymbol symbol)
{
if (symbol.SpecialType == SpecialType.System_Void)
{
return SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.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(
SyntaxFactory.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 : SyntaxFactory.Identifier(element.Name);
list = list.Add(SyntaxFactory.TupleElement(element.Type.GenerateTypeSyntax(), name));
}
return AddInformationTo(SyntaxFactory.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(
SyntaxFactory.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(SyntaxFactory.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(SyntaxFactory.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(SyntaxFactory.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(
SyntaxFactory.AliasQualifiedName(
CreateGlobalIdentifier(),
syntax), symbol);
}
public override TypeSyntax VisitPointerType(IPointerTypeSymbol symbol)
{
ThrowIfNameOnly();
return AddInformationTo(
SyntaxFactory.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(SyntaxFactory.NullableType(typeSyntax), symbol);
}
return typeSyntax;
}
}
}
}
|