|
// 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;
}
}
}
|