Skip to content

Commit

Permalink
adds support for abstract static interface methods (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
adrianoc committed Jan 21, 2023
1 parent 534cc66 commit f204c58
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 11 deletions.
45 changes: 45 additions & 0 deletions Cecilifier.Core.Tests/Tests/Unit/InterfaceTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,55 @@
using System;
using System.Text.RegularExpressions;
using Cecilifier.Core.Extensions;
using NUnit.Framework;

namespace Cecilifier.Core.Tests.Tests.Unit;

[TestFixture]
public class InterfaceTests : CecilifierUnitTestBase
{
[TestCase("public interface IFoo<T> { abstract static T M(); }", "Void", TestName = "With Generic")]
[TestCase("public interface IFoo { abstract static int M(); }", "Int32", TestName = "Simple")]
public void AbstractStaticMethodDefinitionTest(string code, string expectedReturnTypeInDeclaration)
{
var result = RunCecilifier(code);
Assert.That(result.GeneratedCode.ReadToEnd(), Contains.Substring(
$"""
new MethodDefinition("M", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual | MethodAttributes.Abstract, assembly.MainModule.TypeSystem.{expectedReturnTypeInDeclaration});
"""));
}

[TestCase("public interface IFoo<T> { abstract static T M(); } class Foo : IFoo<Foo> { public static Foo M() => null; }", "cls_foo_3", TestName = "With Generic1")]
[TestCase("public interface IFoo { abstract static int M(); } class Foo : IFoo { public static int M() => 0; }", "assembly.MainModule.TypeSystem.Int32", TestName = "Simple1")]
public void AbstractStaticMethodImplementationTest(string code, string expectedReturnTypeInImplementation)
{
var result = RunCecilifier(code);
var cecilified = result.GeneratedCode.ReadToEnd();
Assert.That(cecilified, Does.Match(
$$"""
\s+var (m_M_\d+) = new MethodDefinition\("M", MethodAttributes.Public \| MethodAttributes.Static \| MethodAttributes.HideBySig, {{expectedReturnTypeInImplementation}}\);
\s+(cls_foo_\d+).Methods.Add\(\1\);
"""));

var itfMethodDefVar = Regex.Match(cecilified, @"itf_iFoo_\d+.Methods.Add\((m_M_\d+)\);").Groups[1].Value;
Assert.That(
cecilified, Does.Match(
$$"""
m_M_\d+.Overrides.Add\({{itfMethodDefVar}}|new MethodReference\({{itfMethodDefVar}}.Name, {{itfMethodDefVar}}.ReturnType\) {.+DeclaringType = itf_iFoo_\d+.MakeGenericInstanceType\(cls_foo_\d+\).+}\);
"""));
}

[TestCase("public interface IFoo<T> { abstract static T M(); } class C { T M<T>() where T : class, IFoo<T> { return T.M(); } }", "cls_foo_3", TestName = "With Generic")]
public void AbstractStaticMethodReferenceTest(string code, string expectedReturnTypeInImplementation)
{
var result = RunCecilifier(code);
Assert.That(result.GeneratedCode.ReadToEnd(), Does.Match(
"""
(il_M_\d+\.Emit\(OpCodes\.)Constrained, (gp_T_\d+)\);
\s+\1Call, new MethodReference\(m_M_2.Name, m_M_2.ReturnType\) { HasThis = m_M_2.HasThis, ExplicitThis = m_M_2.ExplicitThis, DeclaringType = itf_iFoo_0.MakeGenericInstanceType\(\2\), CallingConvention = m_M_2.CallingConvention,}\);
"""));
}

[TestCase(
"using System; class Foo : IDisposable { void IDisposable.Dispose() {} }",
"m_dispose_\\d+.Overrides.Add\\(.+System.IDisposable.+, \"Dispose\".+\\);",
Expand Down
4 changes: 4 additions & 0 deletions Cecilifier.Core/AST/ExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,10 @@ private void HandleIdentifier(SimpleNameSyntax node)
ProcessProperty(node, member.Symbol as IPropertySymbol);
break;

case SymbolKind.TypeParameter:
AddCilInstruction(ilVar, OpCodes.Constrained, (ITypeSymbol) member.Symbol);
break;

default:
trackIfNotPartOfTypeName.Discard();
break;
Expand Down
2 changes: 1 addition & 1 deletion Cecilifier.Core/AST/MethodDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private void ProcessMethodDeclarationInternal(
HandleAttributesInMemberDeclaration(attributes, TargetDoesNotMatch, SyntaxKind.ReturnKeyword, methodVar); // Normal method attrs.
HandleAttributesInMemberDeclaration(attributes, TargetMatches, SyntaxKind.ReturnKeyword, $"{methodVar}.MethodReturnType"); // [return:Attr]

ProcessExplicitInterfaceImplementation(methodVar, methodSymbol);
ProcessExplicitInterfaceImplementationAndStaticAbstractMethods(methodVar, methodSymbol);

if (modifiersTokens.IndexOf(SyntaxKind.ExternKeyword) == -1)
{
Expand Down
9 changes: 5 additions & 4 deletions Cecilifier.Core/AST/PropertyDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private bool PropertyAlreadyProcessed(BasePropertyDeclarationSyntax node)
return false;

// check the methods of the property because we do not register the property itself, only its methods.
var methodToCheck = propInfo?.GetMethod ?? propInfo?.SetMethod;
var methodToCheck = propInfo.GetMethod ?? propInfo.SetMethod;
var found = Context.DefinitionVariables.GetMethodVariable(methodToCheck.AsMethodDefinitionVariable());
return found.IsValid;
}
Expand Down Expand Up @@ -180,7 +180,7 @@ void AddSetterMethod(IPropertySymbol property, string setterReturnType, Accessor
//TODO : NEXT : try to use CecilDefinitionsFactory.Method()
AddCecilExpression($"var {setMethodVar} = new MethodDefinition(\"set_{propName}\", {accessorModifiers}, {setterReturnType});");
parameters.ForEach(p => AddCecilExpression($"{setMethodVar}.Parameters.Add({p.VariableName});"));
ProcessExplicitInterfaceImplementation(setMethodVar, property.SetMethod);
ProcessExplicitInterfaceImplementationAndStaticAbstractMethods(setMethodVar, property.SetMethod);
AddCecilExpression($"{propertyDeclaringTypeVar}.Methods.Add({setMethodVar});");

AddCecilExpression($"{setMethodVar}.Body = new MethodBody({setMethodVar});");
Expand Down Expand Up @@ -218,12 +218,13 @@ void AddSetterMethod(IPropertySymbol property, string setterReturnType, Accessor

ScopedDefinitionVariable AddGetterMethodGuts(IPropertySymbol property, out string ilVar)
{
Context.WriteComment(" Getter");
Context.WriteComment("Getter");

var getMethodVar = Context.Naming.SyntheticVariable("get", ElementKind.Method);
var definitionVariable = Context.DefinitionVariables.WithCurrentMethod(declaringType.Identifier.Text, $"get_{propName}", parameters.Select(p => p.Type).ToArray(), getMethodVar);

AddCecilExpression($"var {getMethodVar} = new MethodDefinition(\"get_{propName}\", {accessorModifiers}, {propertyType});");
ProcessExplicitInterfaceImplementation(getMethodVar, property.GetMethod);
ProcessExplicitInterfaceImplementationAndStaticAbstractMethods(getMethodVar, property.GetMethod);
if (property.HasCovariantGetter())
AddCecilExpression($"{getMethodVar}.CustomAttributes.Add(new CustomAttribute(assembly.MainModule.Import(typeof(System.Runtime.CompilerServices.PreserveBaseOverridesAttribute).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[0], null))));");

Expand Down
12 changes: 10 additions & 2 deletions Cecilifier.Core/AST/SyntaxWalkerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -939,11 +939,19 @@ protected void LogUnsupportedSyntax(SyntaxNode node)
AddCecilExpression($"/* Syntax '{node.Kind()}' is not supported in {lineSpan.Path} ({lineSpan.Span.Start.Line + 1},{lineSpan.Span.Start.Character + 1}):\n------\n{node}\n----*/");
}

protected void ProcessExplicitInterfaceImplementation(string methodVar, IMethodSymbol method)
protected void ProcessExplicitInterfaceImplementationAndStaticAbstractMethods(string methodVar, IMethodSymbol method)
{
// first check explicit interface implementation...
var explicitImplement = method?.ExplicitInterfaceImplementations.FirstOrDefault();
if (explicitImplement == null)
return;
{
// if it is not an explicit interface implementation check for abstract static method from interfaces
var lastDeclared = method.FindLastDefinition(method.ContainingType.Interfaces);
if (lastDeclared == null || SymbolEqualityComparer.Default.Equals(lastDeclared, method) || lastDeclared.ContainingType.TypeKind != TypeKind.Interface || method?.IsStatic == false)
return;

explicitImplement = lastDeclared;
}

WriteCecilExpression(Context, $"{methodVar}.Overrides.Add({explicitImplement.MethodResolverExpression(Context)});");
}
Expand Down
4 changes: 1 addition & 3 deletions Cecilifier.Core/Extensions/CecilifierExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,13 @@ private static IMethodSymbol FindLastDefinition(IMethodSymbol method, INamedType
return found;
}

private static IMethodSymbol FindLastDefinition(IMethodSymbol method, ImmutableArray<INamedTypeSymbol> implementedItfs)
public static IMethodSymbol FindLastDefinition(this IMethodSymbol method, ImmutableArray<INamedTypeSymbol> implementedItfs)
{
foreach (var itf in implementedItfs)
{
var found = FindLastDefinition(method, itf);
if (found != null)
{
return found;
}
}

return null;
Expand Down
6 changes: 5 additions & 1 deletion Cecilifier.Core/Extensions/MethodExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,17 @@ public static string MethodModifiersToCecil(this SyntaxTokenList modifiers, stri
{
modifiersStr = Constants.Cecil.InterfaceMethodDefinitionAttributes.AppendModifier("MethodAttributes.Final");
}
else if (lastDeclaredIn.ContainingType.TypeKind == TypeKind.Interface)
else if (lastDeclaredIn.ContainingType.TypeKind == TypeKind.Interface && !methodSymbol.IsStatic)
{
modifiersStr = Constants.Cecil.InterfaceMethodDefinitionAttributes.AppendModifier(
SymbolEqualityComparer.Default.Equals(lastDeclaredIn.ContainingType, methodSymbol.ContainingType)
? "MethodAttributes.Abstract"
: "MethodAttributes.Final");
}
else if (lastDeclaredIn.ContainingType.TypeKind == TypeKind.Interface && methodSymbol.IsStatic)
{
modifiersStr = string.Empty;
}
}

var validModifiers = RemoveSourceModifiersWithNoILEquivalent(modifiers);
Expand Down

0 comments on commit f204c58

Please sign in to comment.