In my recent work on the dotnet/roslyn repository, I addressed an issue related to the handling of expression-bodied methods in public interface implementations. The Roslyn repository is the home of the .NET Compiler Platform, which provides open-source C# and Visual Basic compilers with rich code analysis APIs.
The problem was identified in issue #65984, titled "As Public Interface Implementation doesn't handle expression-bodied correctly".
The existing code resulted in this:
public interface IGeneral
{
object DoSomething();
}
public class C : IGeneral
{
object IGeneral.DoSomething() => new();
}
The issue was that As Public Interface Implementation did not correctly handle the expression-bodied method in the explicit implementation above.
The solution involves modifying the WithBodies
method to correctly handle expression-bodied methods and properties. The WithBodies
method is responsible for ensuring that a method, property, or accessor has a body. If it doesn't, a new body is created and added.
In the original code, the WithBodies
method only checked if the Body
property was null
. However, this approach didn't account for expression-bodied methods and properties, which use the ExpressionBody
property instead of Body
.
Here's the updated WithBodies
method for MethodDeclaration
:
private static SyntaxNode WithInterfaceSpecifier(SyntaxNode declaration, ExplicitInterfaceSpecifierSyntax? specifier)
=> declaration.Kind() switch
{
SyntaxKind.MethodDeclaration => ((MethodDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
SyntaxKind.PropertyDeclaration => ((PropertyDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
SyntaxKind.IndexerDeclaration => ((IndexerDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
SyntaxKind.EventDeclaration => ((EventDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
_ => declaration,
};
private SyntaxNode AsImplementation(SyntaxNode declaration, Accessibility requiredAccess)
{
declaration = this.WithAccessibility(declaration, requiredAccess);
declaration = this.WithModifiers(declaration, this.GetModifiers(declaration) - DeclarationModifiers.Abstract);
declaration = WithBodies(declaration);
return declaration;
}
private static SyntaxNode WithBodies(SyntaxNode declaration)
{
switch (declaration.Kind())
{
case SyntaxKind.MethodDeclaration:
var method = (MethodDeclarationSyntax)declaration;
return (method.Body == null) ? method.WithSemicolonToken(default).WithBody(CreateBlock()) : metho
case SyntaxKind.OperatorDeclaration:
var op = (OperatorDeclarationSyntax)declaration;
return (op.Body == null) ? op.WithSemicolonToken(default).WithBody(CreateBlock()) : o
case SyntaxKind.ConversionOperatorDeclaration:
var cop = (ConversionOperatorDeclarationSyntax)declaration;
return (cop.Body == null) ? cop.WithSemicolonToken(default).WithBody(CreateBlock()) : cop;
case SyntaxKind.OperatorDeclaration:
var method = (BaseMethodDeclarationSyntax)declaration;
return (method.Body == null && method.ExpressionBody == null) ? method.WithSemicolonToken(default).WithBody(CreateBlock()) : metho
case SyntaxKind.PropertyDeclaration:
var prop = (PropertyDeclarationSyntax)declaration;
return (prop.AccessorList != null) ? prop.WithAccessorList(WithBodies(prop.AccessorList)) : prop;
case SyntaxKind.IndexerDeclaration:
var ind = (IndexerDeclarationSyntax)declaration;
return (ind.AccessorList != null) ? ind.WithAccessorList(WithBodies(ind.AccessorList)) : ind;
case SyntaxKind.EventDeclaration:
var ev = (EventDeclarationSyntax)declaration;
return (ev.AccessorList != null) ? ev.WithAccessorList(WithBodies(ev.AccessorList)) : ev;
}
return declaration;
}
private static AccessorListSyntax WithBodies(AccessorListSyntax accessorList)
=> accessorList.WithAccessors(SyntaxFactory.List(accessorList.Accessors.Select(x => WithBody(x)))
private static AccessorDeclarationSyntax WithBody(AccessorDeclarationSyntax accessor)
{
if (accessor.Body == null)
if (accessor.Body == null && accessor.ExpressionBody == null)
{
return accessor.WithSemicolonToken(default).WithBody(CreateBlock(null));
}
else
{
return accessor;
}
}
private static SyntaxNode WithInterfaceSpecifier(SyntaxNode declaration, ExplicitInterfaceSpecifierSyntax? specifier)
=> declaration.Kind() switch
{
SyntaxKind.MethodDeclaration => ((MethodDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
SyntaxKind.PropertyDeclaration => ((PropertyDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
SyntaxKind.OperatorDeclaration => ((OperatorDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
SyntaxKind.ConversionOperatorDeclaration => ((ConversionOperatorDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
SyntaxKind.IndexerDeclaration => ((IndexerDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
SyntaxKind.EventDeclaration => ((EventDeclarationSyntax)declaration).WithExplicitInterfaceSpecifier(specifier),
_ => declaration,
};
private SyntaxNode AsImplementation(SyntaxNode declaration, Accessibility requiredAccess)
{
declaration = this.WithAccessibility(declaration, requiredAccess);
declaration = this.WithModifiers(declaration, this.GetModifiers(declaration) - DeclarationModifiers.Abstract);
declaration = WithBodies(declaration);
return declaration;
}
private static SyntaxNode WithBodies(SyntaxNode declaration)
{
switch (declaration.Kind())
{
case SyntaxKind.MethodDeclarati
case SyntaxKind.ConversionOperatorDeclarati
case SyntaxKind.OperatorDeclaration:
var method = (BaseMethodDeclarationSyntax)declaration;
return (method.Body == null && method.ExpressionBody == null) ? method.WithSemicolonToken(default).WithBody(CreateBlock()) : meth
case SyntaxKind.PropertyDeclaration:
var prop = (PropertyDeclarationSyntax)declaration;
return (prop.AccessorList != null) ? prop.WithAccessorList(WithBodies(prop.AccessorList)) : prop;
case SyntaxKind.IndexerDeclaration:
var ind = (IndexerDeclarationSyntax)declaration;
return (ind.AccessorList != null) ? ind.WithAccessorList(WithBodies(ind.AccessorList)) : ind;
case SyntaxKind.EventDeclaration:
var ev = (EventDeclarationSyntax)declaration;
return (ev.AccessorList != null) ? ev.WithAccessorList(WithBodies(ev.AccessorList)) : ev;
}
return declaration;
}
private static AccessorListSyntax WithBodies(AccessorListSyntax accessorList)
=> accessorList.WithAccessors(SyntaxFactory.List(accessorList.Accessors.Select(x => WithBody(x))
private static AccessorDeclarationSyntax WithBody(AccessorDeclarationSyntax accessor)
{
if (accessor.Body == null && accessor.ExpressionBody == null)
{
return accessor.WithSemicolonToken(default).WithBody(CreateBlock(null));
}
else
{
return accessor;
}
}
This change ensures that the expression-bodied methods are correctly handled in public interface implementations, improving the accuracy of the code generation.