Skip to content

Commit

Permalink
fixes resolution of methods takeing generic parameters defined in the…
Browse files Browse the repository at this point in the history
… declaring type (#240)
  • Loading branch information
adrianoc committed Jun 18, 2023
1 parent 24abc44 commit 0029874
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
using System;
class C<T>
class UsageOfNonGenericMethodOnGenericTypeFromExternalAssembly
{
public void Method(T t) {}
}

class UsageOfNonGenericMethodOnGenericType
{
void M(C<int> c) => c.Method(42);
bool M<T>(System.IEquatable<T> lhs, T rhs) => lhs.Equals(rhs);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class UsageOfNonGenericMethodOnGenericTypeFromExternalAssembly
{
bool M<T>(System.IEquatable<T> lhs, T rhs) => lhs.Equals(rhs);
}
6 changes: 6 additions & 0 deletions Cecilifier.Core.Tests/Tests/Integration/GenericsTestCase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,11 @@ public void TestUsageOfNonGenericMethodOnGenericType()
{
AssertResourceTest("Generics/UsageOfNonGenericMethodOnGenericType");
}

[Test]
public void UsageOfNonGenericMethodOnGenericTypeFromExternalAssembly()
{
AssertResourceTest("Generics/UsageOfNonGenericMethodOnGenericTypeFromExternalAssembly");
}
}
}
15 changes: 14 additions & 1 deletion Cecilifier.Core.Tests/Tests/Unit/GenericTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public void TestForwardReferencedMethodGenericTypeParameter_Issue240()
var code = """
class C
{
bool Run() => M(1, 2);
bool Run() => M(1, 2); // References method 'M<T>()' before its declaration.
bool M<T>(T other, System.IEquatable<T> t) => false;
}
""";
Expand All @@ -171,5 +171,18 @@ class C
Assert.That(cecilifiedCode, Does.Match("""var p_other_\d+ = new ParameterDefinition\("other", ParameterAttributes.None, gp_T_\d+\);"""), "definition of parameter 'other' does not match.");
Assert.That(cecilifiedCode, Does.Match("""var p_t_\d+ = new ParameterDefinition\("t", .+, assembly.MainModule.ImportReference\(typeof\(System.IEquatable<>\)\).MakeGenericInstanceType\(gp_T_\d+\)\);"""), "Generic parameter type does not match.");
}

[Test]
public void TestParameterOfGenericType_DependingOnGenericTypeParameterOfMethod_Issue240()
{
var code = "class C { bool M<T>(T other, System.IEquatable<T> t) => t.Equals(other); }";
var result = RunCecilifier(code);

var cecilifiedCode = result.GeneratedCode.ReadToEnd();

Assert.That(cecilifiedCode, Does.Match("""var l_openEquals_\d+ = assembly.MainModule.ImportReference\(.+ImportReference\(typeof\(System.IEquatable<>\)\)\).Resolve\(\).Methods.First\(m => m.Name == "Equals"\);"""), "method from open generic type not match");
Assert.That(cecilifiedCode, Does.Match("""var r_equals_\d+ = new MethodReference\("Equals", l_openEquals_\d+.ReturnType\)"""), "MethodReference does not match");
Assert.That(cecilifiedCode, Does.Match("""il_M_3.Emit\(OpCodes.Callvirt, r_equals_\d+\);"""), "Call to the target method does not match");
}
}
}
45 changes: 45 additions & 0 deletions Cecilifier.Core/Extensions/MethodExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Reflection;
using System.Text;
using Cecilifier.Core.AST;
using Cecilifier.Core.Naming;
using Cecilifier.Core.Variables;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -55,10 +56,54 @@ public static string MethodResolverExpression(this IMethodSymbol method, IVisito
return variable;
}

if (method.ContainingType.IsGenericType && method.Parameters.Any(p => p.Type.TypeKind == TypeKind.TypeParameter))
{
return ResolveMethodFromGenericType(method, ctx);
}

var declaringTypeName = method.ContainingType.FullyQualifiedName();
return ImportFromMainModule($"TypeHelpers.ResolveMethod(typeof({declaringTypeName}), \"{method.Name}\",{method.ReflectionBindingsFlags()}{method.Parameters.Aggregate("", (acc, curr) => acc + ", \"" + curr.Type.FullyQualifiedName() + "\"")})");
}

private static string ResolveMethodFromGenericType(IMethodSymbol method, IVisitorContext ctx)
{
// resolve declaring type of the method.
var targetTypeVarName = ctx.Naming.SyntheticVariable($"{method.ContainingType.Name}", ElementKind.LocalVariable);
var resolvedTargetTypeExp = ctx.TypeResolver.Resolve(method.ContainingType.OriginalDefinition).MakeGenericInstanceType(method.ContainingType.TypeArguments.Select(t => ctx.TypeResolver.Resolve(t)));
ctx.WriteCecilExpression($"var {targetTypeVarName} = {resolvedTargetTypeExp};");
ctx.WriteNewLine();

// find the original method.
var originalMethodVar = ctx.Naming.SyntheticVariable($"open{method.Name}", ElementKind.LocalVariable);
// TODO: handle overloads
ctx.WriteCecilExpression($"""var {originalMethodVar} = assembly.MainModule.ImportReference({ctx.TypeResolver.Resolve(method.ContainingType.OriginalDefinition)}).Resolve().Methods.First(m => m.Name == "{method.Name}");""");
ctx.WriteNewLine();

// Instantiates a MethodReference representing the called method.
var targetMethodVar = ctx.Naming.SyntheticVariable($"{method.Name}", ElementKind.MemberReference);
ctx.WriteCecilExpression(
$$"""
var {{targetMethodVar}} = new MethodReference("{{method.Name}}", {{originalMethodVar}}.ReturnType)
{
DeclaringType = {{targetTypeVarName}},
HasThis = {{originalMethodVar}}.HasThis,
ExplicitThis = {{originalMethodVar}}.ExplicitThis,
CallingConvention = {{originalMethodVar}}.CallingConvention,
};
""");
ctx.WriteNewLine();

// Add original parameters to the MethodReference
foreach (var parameter in method.Parameters)
{
//TODO: pass the correct ParameterAttributes.None
ctx.WriteCecilExpression($"""{targetMethodVar}.Parameters.Add(new ParameterDefinition("{parameter.Name}", ParameterAttributes.None, {originalMethodVar}.Parameters[{parameter.Ordinal}].ParameterType));""");
ctx.WriteNewLine();
}

return targetMethodVar;
}

public static MethodDefinitionVariable AsMethodDefinitionVariable(this IMethodSymbol method, string variableName = null)
{
return new MethodDefinitionVariable(
Expand Down

0 comments on commit 0029874

Please sign in to comment.