|
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using static CodeGenerationHelpers;
using static CSharpCodeGenerationHelpers;
using static CSharpSyntaxTokens;
using static SyntaxFactory;
internal static class NamedTypeGenerator
{
public static TypeDeclarationSyntax AddNamedTypeTo(
ICodeGenerationService service,
TypeDeclarationSyntax destination,
INamedTypeSymbol namedType,
CSharpCodeGenerationContextInfo info,
IList<bool>? availableIndices,
CancellationToken cancellationToken)
{
var declaration = GenerateNamedTypeDeclaration(service, namedType, GetDestination(destination), info, cancellationToken);
var members = Insert(destination.Members, declaration, info, availableIndices);
return AddMembersTo(destination, members, cancellationToken);
}
public static BaseNamespaceDeclarationSyntax AddNamedTypeTo(
ICodeGenerationService service,
BaseNamespaceDeclarationSyntax destination,
INamedTypeSymbol namedType,
CSharpCodeGenerationContextInfo info,
IList<bool>? availableIndices,
CancellationToken cancellationToken)
{
var declaration = GenerateNamedTypeDeclaration(service, namedType, CodeGenerationDestination.Namespace, info, cancellationToken);
var members = Insert(destination.Members, declaration, info, availableIndices);
return ConditionallyAddFormattingAnnotationTo(
destination.WithMembers(members),
members);
}
public static CompilationUnitSyntax AddNamedTypeTo(
ICodeGenerationService service,
CompilationUnitSyntax destination,
INamedTypeSymbol namedType,
CSharpCodeGenerationContextInfo info,
IList<bool>? availableIndices,
CancellationToken cancellationToken)
{
var declaration = GenerateNamedTypeDeclaration(service, namedType, CodeGenerationDestination.CompilationUnit, info, cancellationToken);
var members = Insert(
destination.Members, declaration, info, availableIndices,
// We're adding a named type to a compilation unit. If there are any global statements, we must place it after them.
after: static members => members.LastOrDefault(m => m is GlobalStatementSyntax),
canPlaceAtIndex: static (members, index) => index >= members.Count || members[index] is not GlobalStatementSyntax);
return destination.WithMembers(members);
}
public static MemberDeclarationSyntax GenerateNamedTypeDeclaration(
ICodeGenerationService service,
INamedTypeSymbol namedType,
CodeGenerationDestination destination,
CSharpCodeGenerationContextInfo info,
CancellationToken cancellationToken)
{
var declaration = GetDeclarationSyntaxWithoutMembers(namedType, destination, info);
// If we are generating members then make sure to exclude properties that cannot be generated.
// Reason: Calling AddProperty on a propertysymbol that can't be generated (like one with params) causes
// the getter and setter to get generated instead. Since the list of members is going to include
// the method symbols for the getter and setter, we don't want to generate them twice.
var members = GetMembers(namedType).WhereAsArray(s => s.Kind != SymbolKind.Property || PropertyGenerator.CanBeGenerated((IPropertySymbol)s));
if (namedType.IsRecord)
{
declaration = GenerateRecordMembers(service, info, (RecordDeclarationSyntax)declaration, members, cancellationToken);
}
else
{
// If we're generating a ComImport type, then do not attempt to do any
// reordering of members.
if (namedType.IsComImport)
info = info.WithContext(info.Context.With(autoInsertionLocation: false, sortMembers: false));
if (info.Context.GenerateMembers && namedType.TypeKind != TypeKind.Delegate)
declaration = service.AddMembers(declaration, members, info, cancellationToken);
}
return AddFormatterAndCodeGeneratorAnnotationsTo(ConditionallyAddDocumentationCommentTo(declaration, namedType, info, cancellationToken));
}
private static RecordDeclarationSyntax GenerateRecordMembers(
ICodeGenerationService service,
CSharpCodeGenerationContextInfo info,
RecordDeclarationSyntax recordDeclaration,
ImmutableArray<ISymbol> members,
CancellationToken cancellationToken)
{
if (!info.Context.GenerateMembers)
members = [];
// For a record, add record parameters if we have a primary constructor.
var primaryConstructor = members.OfType<IMethodSymbol>().FirstOrDefault(m => CodeGenerationConstructorInfo.GetIsPrimaryConstructor(m));
if (primaryConstructor != null)
{
var parameterList = ParameterGenerator.GenerateParameterList(primaryConstructor.Parameters, isExplicit: false, info);
recordDeclaration = recordDeclaration.WithParameterList(parameterList);
// remove the primary constructor from the list of members to generate.
members = members.Remove(primaryConstructor);
// remove any fields/properties that were created by the primary constructor
members = members.WhereAsArray(m => m is not IPropertySymbol and not IFieldSymbol || !primaryConstructor.Parameters.Any(static (p, m) => p.Name == m.Name, m));
}
// remove any implicit overrides to generate.
members = members.WhereAsArray(m => !m.IsImplicitlyDeclared);
// If there are no members, just make a simple record with no body
if (members.Length == 0)
return recordDeclaration.WithSemicolonToken(SemicolonToken);
// Otherwise, give the record a body and add the members to it.
recordDeclaration = recordDeclaration.WithOpenBraceToken(OpenBraceToken)
.WithCloseBraceToken(CloseBraceToken)
.WithSemicolonToken(default);
return service.AddMembers(recordDeclaration, members, info, cancellationToken);
}
public static MemberDeclarationSyntax UpdateNamedTypeDeclaration(
ICodeGenerationService service,
MemberDeclarationSyntax declaration,
IList<ISymbol> newMembers,
CSharpCodeGenerationContextInfo info,
CancellationToken cancellationToken)
{
declaration = RemoveAllMembers(declaration);
declaration = service.AddMembers(declaration, newMembers, info, cancellationToken);
return AddFormatterAndCodeGeneratorAnnotationsTo(declaration);
}
private static MemberDeclarationSyntax GetDeclarationSyntaxWithoutMembers(
INamedTypeSymbol namedType,
CodeGenerationDestination destination,
CSharpCodeGenerationContextInfo info)
{
var reusableDeclarationSyntax = GetReuseableSyntaxNodeForSymbol<MemberDeclarationSyntax>(namedType, info);
return reusableDeclarationSyntax == null
? GetDeclarationSyntaxWithoutMembersWorker(namedType, destination, info)
: RemoveAllMembers(reusableDeclarationSyntax);
}
private static MemberDeclarationSyntax RemoveAllMembers(MemberDeclarationSyntax declaration)
{
switch (declaration.Kind())
{
case SyntaxKind.EnumDeclaration:
return ((EnumDeclarationSyntax)declaration).WithMembers(default);
case SyntaxKind.StructDeclaration:
case SyntaxKind.RecordStructDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.RecordDeclaration:
return ((TypeDeclarationSyntax)declaration).WithMembers(default);
default:
return declaration;
}
}
private static MemberDeclarationSyntax GetDeclarationSyntaxWithoutMembersWorker(
INamedTypeSymbol namedType,
CodeGenerationDestination destination,
CSharpCodeGenerationContextInfo info)
{
if (namedType.TypeKind == TypeKind.Enum)
{
return GenerateEnumDeclaration(namedType, destination, info);
}
else if (namedType.TypeKind == TypeKind.Delegate)
{
return GenerateDelegateDeclaration(namedType, destination, info);
}
TypeDeclarationSyntax typeDeclaration;
if (namedType.IsRecord)
{
var isRecordClass = namedType.TypeKind is TypeKind.Class;
var declarationKind = isRecordClass ? SyntaxKind.RecordDeclaration : SyntaxKind.RecordStructDeclaration;
var classOrStructKeyword = Token(isRecordClass ? default : SyntaxKind.StructKeyword);
typeDeclaration = RecordDeclaration(kind: declarationKind, attributeLists: default, modifiers: default,
RecordKeyword, classOrStructKeyword, namedType.Name.ToIdentifierToken(),
typeParameterList: null, parameterList: null, baseList: null, constraintClauses: default, openBraceToken: default, members: default, closeBraceToken: default,
SemicolonToken);
}
else
{
var kind = namedType.TypeKind == TypeKind.Struct ? SyntaxKind.StructDeclaration :
namedType.TypeKind == TypeKind.Interface ? SyntaxKind.InterfaceDeclaration : SyntaxKind.ClassDeclaration;
typeDeclaration = TypeDeclaration(kind, namedType.Name.ToIdentifierToken());
}
var result = typeDeclaration
.WithAttributeLists(GenerateAttributeDeclarations(namedType, info))
.WithModifiers(GenerateModifiers(namedType, destination, info))
.WithTypeParameterList(GenerateTypeParameterList(namedType, info))
.WithBaseList(GenerateBaseList(namedType))
.WithConstraintClauses(GenerateConstraintClauses(namedType));
return result;
}
private static DelegateDeclarationSyntax GenerateDelegateDeclaration(
INamedTypeSymbol namedType,
CodeGenerationDestination destination,
CSharpCodeGenerationContextInfo info)
{
var invokeMethod = namedType.DelegateInvokeMethod;
Contract.ThrowIfNull(invokeMethod);
return DelegateDeclaration(
GenerateAttributeDeclarations(namedType, info),
GenerateModifiers(namedType, destination, info),
invokeMethod.ReturnType.GenerateTypeSyntax(),
namedType.Name.ToIdentifierToken(),
TypeParameterGenerator.GenerateTypeParameterList(namedType.TypeParameters, info),
ParameterGenerator.GenerateParameterList(invokeMethod.Parameters, isExplicit: false, info: info),
namedType.TypeParameters.GenerateConstraintClauses());
}
private static EnumDeclarationSyntax GenerateEnumDeclaration(
INamedTypeSymbol namedType,
CodeGenerationDestination destination,
CSharpCodeGenerationContextInfo info)
{
var baseList = namedType.EnumUnderlyingType != null && namedType.EnumUnderlyingType.SpecialType != SpecialType.System_Int32
? BaseList([SimpleBaseType(namedType.EnumUnderlyingType.GenerateTypeSyntax())])
: null;
return EnumDeclaration(
GenerateAttributeDeclarations(namedType, info),
GenerateModifiers(namedType, destination, info),
namedType.Name.ToIdentifierToken(),
baseList: baseList,
members: default);
}
private static SyntaxList<AttributeListSyntax> GenerateAttributeDeclarations(
INamedTypeSymbol namedType, CSharpCodeGenerationContextInfo info)
{
return AttributeGenerator.GenerateAttributeLists(namedType.GetAttributes(), info);
}
private static SyntaxTokenList GenerateModifiers(
INamedTypeSymbol namedType,
CodeGenerationDestination destination,
CSharpCodeGenerationContextInfo info)
{
using var _ = ArrayBuilder<SyntaxToken>.GetInstance(out var tokens);
if (!namedType.IsFileLocal)
{
var defaultAccessibility = destination is CodeGenerationDestination.CompilationUnit or CodeGenerationDestination.Namespace
? Accessibility.Internal
: Accessibility.Private;
AddAccessibilityModifiers(namedType.DeclaredAccessibility, tokens, info, defaultAccessibility);
}
else
{
tokens.Add(FileKeyword);
}
if (namedType.IsStatic)
{
tokens.Add(StaticKeyword);
}
else
{
if (namedType.TypeKind == TypeKind.Class)
{
if (namedType.IsAbstract)
tokens.Add(AbstractKeyword);
if (namedType.IsSealed)
tokens.Add(SealedKeyword);
}
}
if (namedType.IsReadOnly)
tokens.Add(ReadOnlyKeyword);
if (namedType.IsRefLikeType)
tokens.Add(RefKeyword);
return [.. tokens];
}
private static TypeParameterListSyntax? GenerateTypeParameterList(
INamedTypeSymbol namedType, CSharpCodeGenerationContextInfo info)
{
return TypeParameterGenerator.GenerateTypeParameterList(namedType.TypeParameters, info);
}
private static BaseListSyntax? GenerateBaseList(INamedTypeSymbol namedType)
{
var types = new List<BaseTypeSyntax>();
if (namedType.TypeKind == TypeKind.Class && namedType.BaseType != null && namedType.BaseType.SpecialType != Microsoft.CodeAnalysis.SpecialType.System_Object)
types.Add(SimpleBaseType(namedType.BaseType.GenerateTypeSyntax()));
foreach (var type in namedType.Interfaces)
types.Add(SimpleBaseType(type.GenerateTypeSyntax()));
if (types.Count == 0)
return null;
return BaseList([.. types]);
}
private static SyntaxList<TypeParameterConstraintClauseSyntax> GenerateConstraintClauses(INamedTypeSymbol namedType)
=> namedType.TypeParameters.GenerateConstraintClauses();
}
|