Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support generating records in syntax generator #67337

Merged
merged 5 commits into from
Mar 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
*REMOVED*Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.Name.get -> Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax!
*REMOVED*static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RecordDeclaration(Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax!> attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax! typeParameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax! parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BaseListSyntax! baseList, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax!> constraintClauses, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax!> members) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax!
static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.RecordDeclaration(Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.AttributeListSyntax!> attributeLists, Microsoft.CodeAnalysis.SyntaxTokenList modifiers, Microsoft.CodeAnalysis.SyntaxToken keyword, Microsoft.CodeAnalysis.SyntaxToken identifier, Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterListSyntax? typeParameterList, Microsoft.CodeAnalysis.CSharp.Syntax.ParameterListSyntax? parameterList, Microsoft.CodeAnalysis.CSharp.Syntax.BaseListSyntax? baseList, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.TypeParameterConstraintClauseSyntax!> constraintClauses, Microsoft.CodeAnalysis.SyntaxList<Microsoft.CodeAnalysis.CSharp.Syntax.MemberDeclarationSyntax!> members) -> Microsoft.CodeAnalysis.CSharp.Syntax.RecordDeclarationSyntax!
static Microsoft.CodeAnalysis.CSharpExtensions.ContainsDirective(this Microsoft.CodeAnalysis.SyntaxNode! node, Microsoft.CodeAnalysis.CSharp.SyntaxKind kind) -> bool
CyrusNajmabadi marked this conversation as resolved.
Show resolved Hide resolved
Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.Name.get -> Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax?
Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax.NamespaceOrType.get -> Microsoft.CodeAnalysis.CSharp.Syntax.TypeSyntax!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using Roslyn.Utilities;


namespace Microsoft.CodeAnalysis.CSharp.Syntax
{
Expand Down Expand Up @@ -32,15 +33,19 @@ public static RecordDeclarationSyntax RecordDeclaration(SyntaxList<AttributeList
}

public static RecordDeclarationSyntax RecordDeclaration(SyntaxList<AttributeListSyntax> attributeLists, SyntaxTokenList modifiers, SyntaxToken keyword, SyntaxToken identifier,
TypeParameterListSyntax typeParameterList, ParameterListSyntax parameterList, BaseListSyntax baseList, SyntaxList<TypeParameterConstraintClauseSyntax> constraintClauses, SyntaxList<MemberDeclarationSyntax> members)
TypeParameterListSyntax? typeParameterList, ParameterListSyntax? parameterList, BaseListSyntax? baseList, SyntaxList<TypeParameterConstraintClauseSyntax> constraintClauses, SyntaxList<MemberDeclarationSyntax> members)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the api change is just making these three members optional. Obviously, and correctly, the type-params/param-list and base list of a record are all optional. This also matches all the other overloads in this file. This appears to just be an errant mistake that does not impact anyone to fix.

@333fred for confirmation.

{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was a bridge method for back compat. But actually trying to use it produces terrible results. For example, if you specify any members, you get: record C public string ToString(); (without braces).

Copy link
Member

@cston cston Mar 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a test to SyntaxFactoryTests.cs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do. thanks :)

var semicolonToken = members.Count == 0 ? Token(SyntaxKind.SemicolonToken) : default;
var openBraceToken = members.Count == 0 ? default : Token(SyntaxKind.OpenBraceToken);
var closeBraceToken = members.Count == 0 ? default : Token(SyntaxKind.CloseBraceToken);

return RecordDeclaration(SyntaxKind.RecordDeclaration, attributeLists, modifiers, keyword, classOrStructKeyword: default, identifier,
typeParameterList, parameterList, baseList, constraintClauses, openBraceToken: default, members, closeBraceToken: default, semicolonToken: default);
typeParameterList, parameterList, baseList, constraintClauses, openBraceToken, members, closeBraceToken, semicolonToken);
}

public static RecordDeclarationSyntax RecordDeclaration(SyntaxToken keyword, string identifier)
{
return RecordDeclaration(keyword, SyntaxFactory.Identifier(identifier));
return RecordDeclaration(keyword, Identifier(identifier));
}

public static RecordDeclarationSyntax RecordDeclaration(SyntaxToken keyword, SyntaxToken identifier)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,7 @@ private static AccessorDeclarationSyntax WithoutBody(AccessorDeclarationSyntax a
=> accessor.Body != null ? accessor.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)).WithBody(null) : accessor;

private protected override SyntaxNode ClassDeclaration(
bool isRecord,
string name,
IEnumerable<SyntaxNode>? typeParameters,
Accessibility accessibility,
Expand All @@ -678,35 +679,26 @@ private protected override SyntaxNode ClassDeclaration(
IEnumerable<SyntaxNode>? interfaceTypes,
IEnumerable<SyntaxNode>? members)
{
List<BaseTypeSyntax>? baseTypes = null;
using var _ = ArrayBuilder<BaseTypeSyntax>.GetInstance(out var baseTypes);
if (baseType != null || interfaceTypes != null)
{
baseTypes = new List<BaseTypeSyntax>();

if (baseType != null)
{
baseTypes.Add(SyntaxFactory.SimpleBaseType((TypeSyntax)baseType));
}

if (interfaceTypes != null)
{
baseTypes.AddRange(interfaceTypes.Select(i => SyntaxFactory.SimpleBaseType((TypeSyntax)i)));
}

if (baseTypes.Count == 0)
{
baseTypes = null;
}
}

return SyntaxFactory.ClassDeclaration(
default,
AsModifierList(accessibility, modifiers, SyntaxKind.ClassDeclaration),
name.ToIdentifierToken(),
AsTypeParameterList(typeParameters),
baseTypes != null ? SyntaxFactory.BaseList(SyntaxFactory.SeparatedList(baseTypes)) : null,
default,
this.AsClassMembers(name, members));
var kind = isRecord ? SyntaxKind.RecordDeclaration : SyntaxKind.ClassDeclaration;
var modifierList = AsModifierList(accessibility, modifiers, kind);
var nameToken = name.ToIdentifierToken();
var typeParameterList = AsTypeParameterList(typeParameters);
var baseTypeList = baseTypes.Count > 0 ? SyntaxFactory.BaseList(SyntaxFactory.SeparatedList(baseTypes)) : null;
var typeMembers = this.AsClassMembers(name, members);

return isRecord
? SyntaxFactory.RecordDeclaration(default, modifierList, SyntaxFactory.Token(SyntaxKind.RecordKeyword), nameToken, typeParameterList, null, baseTypeList, default, typeMembers)
: SyntaxFactory.ClassDeclaration(default, modifierList, nameToken, typeParameterList, baseTypeList, default, typeMembers);
}

private SyntaxList<MemberDeclarationSyntax> AsClassMembers(string className, IEnumerable<SyntaxNode>? members)
Expand Down Expand Up @@ -734,6 +726,7 @@ private SyntaxList<MemberDeclarationSyntax> AsClassMembers(string className, IEn
}

private protected override SyntaxNode StructDeclaration(
bool isRecord,
string name,
IEnumerable<SyntaxNode>? typeParameters,
Accessibility accessibility,
Expand All @@ -742,19 +735,17 @@ private protected override SyntaxNode StructDeclaration(
IEnumerable<SyntaxNode>? members)
{
var itypes = interfaceTypes?.Select(i => (BaseTypeSyntax)SyntaxFactory.SimpleBaseType((TypeSyntax)i)).ToList();
if (itypes?.Count == 0)
{
itypes = null;
}

return SyntaxFactory.StructDeclaration(
default,
AsModifierList(accessibility, modifiers, SyntaxKind.StructDeclaration),
name.ToIdentifierToken(),
AsTypeParameterList(typeParameters),
itypes != null ? SyntaxFactory.BaseList(SyntaxFactory.SeparatedList(itypes)) : null,
default,
this.AsClassMembers(name, members));
var kind = isRecord ? SyntaxKind.RecordStructDeclaration : SyntaxKind.StructDeclaration;
var modifierList = AsModifierList(accessibility, modifiers, kind);
var nameToken = name.ToIdentifierToken();
var typeParameterList = AsTypeParameterList(typeParameters);
var baseTypeList = itypes?.Count > 0 ? SyntaxFactory.BaseList(SyntaxFactory.SeparatedList(itypes)) : null;
var structMembers = this.AsClassMembers(name, members);

return isRecord
? SyntaxFactory.RecordDeclaration(default, modifierList, SyntaxFactory.Token(SyntaxKind.RecordKeyword), nameToken, typeParameterList, null, baseTypeList, default, structMembers).WithClassOrStructKeyword(SyntaxFactory.Token(SyntaxKind.StructKeyword))
: SyntaxFactory.StructDeclaration(default, modifierList, nameToken, typeParameterList, baseTypeList, default, structMembers);
}

private protected override SyntaxNode InterfaceDeclaration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4482,6 +4482,82 @@ public class C : IDisposable
Assert.Equal(expected, elasticOnlyFormatted);
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67335")]
public void TestGenerateRecordClass1()
{
var comp = Compile(
@"public record class R;");

var symbolR = (INamedTypeSymbol)comp.GlobalNamespace.GetMembers("R").First();

VerifySyntax<RecordDeclarationSyntax>(
Generator.Declaration(symbolR),
"""
public record R : global::System.Object, global::System.IEquatable<global::R>;
""");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67335")]
public void TestGenerateRecordClass2()
{
var comp = Compile(
@"public record class R(int i) { public int I => i; }");

var symbolR = (INamedTypeSymbol)comp.GlobalNamespace.GetMembers("R").First();

VerifySyntax<RecordDeclarationSyntax>(
Generator.Declaration(symbolR),
"""
public record R : global::System.Object, global::System.IEquatable<global::R>
{
public R(global::System.Int32 i)
{
}

public global::System.Int32 i { get; set; }
public global::System.Int32 I { get; }
}
""");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67335")]
public void TestGenerateRecordStruct1()
{
var comp = Compile(
@"public record struct R;");

var symbolR = (INamedTypeSymbol)comp.GlobalNamespace.GetMembers("R").First();

VerifySyntax<RecordDeclarationSyntax>(
Generator.Declaration(symbolR),
"""
public record struct R : global::System.IEquatable<global::R>;
""");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/67335")]
public void TestGenerateRecordStruct2()
{
var comp = Compile(
@"public readonly record struct R(int i) { public int I => i; }");

var symbolR = (INamedTypeSymbol)comp.GlobalNamespace.GetMembers("R").First();

VerifySyntax<RecordDeclarationSyntax>(
Generator.Declaration(symbolR),
"""
public readonly record struct R : global::System.IEquatable<global::R>
{
public R(global::System.Int32 i)
{
}

public global::System.Int32 i { get; set; }
public global::System.Int32 I { get; }
}
""");
}

#endregion

#region DeclarationModifiers
Expand Down
Loading