diff --git a/src/.gitignore b/src/.gitignore index 6a74613..b4d8c30 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1,4 @@ +# Claude code +.claude/ + *.dll diff --git a/src/Refasmer/Importer/ImportLogic.cs b/src/Refasmer/Importer/ImportLogic.cs index d8b0b60..7fb7022 100644 --- a/src/Refasmer/Importer/ImportLogic.cs +++ b/src/Refasmer/Importer/ImportLogic.cs @@ -178,9 +178,21 @@ private void ImportTypeDefinitionAccessories( TypeDefinitionHandle srcHandle, Ty srcImpl => { var body = Import(srcImpl.MethodBody); + + // If the method body wasn't imported (e.g., private explicit interface implementation), + // skip importing the declaration. For generic interfaces, the declaration is a + // MemberReference whose import triggers signature processing. If the signature contains + // internal types that weren't preserved, this would throw UnknownTypeInSignature. + // See: https://github.com/JetBrains/Refasmer/issues/54 + // + // Note: a non-generic interface method implementation is not a MemberReference, but a + // MethodDefinition which doesn't trigger signature processing (should be already in the method + // definition cache). + if (body.IsNil) + return default; var decl = Import(srcImpl.MethodDeclaration); - return body.IsNil || decl.IsNil + return decl.IsNil ? default : _builder.AddMethodImplementation(dstHandle, body, decl); }, diff --git a/tests/Refasmer.Tests/ExitCodeTests.cs b/tests/Refasmer.Tests/ExitCodeTests.cs index ee847f8..de52b7f 100644 --- a/tests/Refasmer.Tests/ExitCodeTests.cs +++ b/tests/Refasmer.Tests/ExitCodeTests.cs @@ -20,10 +20,13 @@ private async Task DoTest(int expectedCode, params string[] additionalArgs) var outputPath = Path.GetTempFileName(); using var collector = CollectConsoleOutput(); var exitCode = ExecuteRefasmAndGetExitCode(assemblyPath, outputPath, additionalArgs); + if (exitCode != expectedCode) + { + await TestContext.Out.WriteLineAsync($"StdOut:\n{collector.StdOut}\nStdErr: {collector.StdErr}"); + } Assert.That( exitCode, Is.EqualTo(expectedCode), - $"Refasmer returned exit code {exitCode}, while {expectedCode} was expected." + - $" StdOut:\n{collector.StdOut}\nStdErr: {collector.StdErr}"); + $"Refasmer returned exit code {exitCode}, while {expectedCode} was expected."); } } diff --git a/tests/Refasmer.Tests/IntegrationTestBase.cs b/tests/Refasmer.Tests/IntegrationTestBase.cs index 91a4b7e..c1ad42b 100644 --- a/tests/Refasmer.Tests/IntegrationTestBase.cs +++ b/tests/Refasmer.Tests/IntegrationTestBase.cs @@ -16,10 +16,15 @@ protected static async Task BuildTestAssembly() Console.WriteLine($"Building project {testProject}…"); var result = await Command.Run("dotnet", "build", testProject, "--configuration", "Release").Task; + if (result.ExitCode != 0) + { + await TestContext.Out.WriteLineAsync($"StdOut:\n{result.StandardOutput}\nStdErr: {result.StandardError}"); + } + Assert.That( result.ExitCode, Is.EqualTo(0), - $"Failed to build test assembly, exit code {result.ExitCode}. StdOut:\n{result.StandardOutput}\nStdErr: {result.StandardError}"); + $"Failed to build test assembly, exit code {result.ExitCode}."); return Path.Combine( root, @@ -67,10 +72,11 @@ protected static string RefasmTestAssembly(string assemblyPath, bool omitNonApiM var options = new[]{ "--omit-non-api-members", omitNonApiMembers.ToString(CultureInfo.InvariantCulture) }; using var collector = CollectConsoleOutput(); var exitCode = ExecuteRefasmAndGetExitCode(assemblyPath, outputPath, options); - Assert.That( - exitCode, - Is.EqualTo(0), - $"Refasmer returned exit code {exitCode}. StdOut:\n{collector.StdOut}\nStdErr: {collector.StdErr}"); + if (exitCode != 0) + { + TestContext.Out.WriteLine($"Refasmer returned exit code {exitCode}. StdOut:\n{collector.StdOut}\nStdErr: {collector.StdErr}"); + } + Assert.That(exitCode, Is.EqualTo(0)); return outputPath; } diff --git a/tests/Refasmer.Tests/IntegrationTests.cs b/tests/Refasmer.Tests/IntegrationTests.cs index 1b826d3..0755406 100644 --- a/tests/Refasmer.Tests/IntegrationTests.cs +++ b/tests/Refasmer.Tests/IntegrationTests.cs @@ -57,6 +57,7 @@ await VerifyTypeContents( [TestCase("PublicClassImplementingInternal", "IInterface1ToBeMarkedInternal")] [TestCase("PublicClassWithInternalInterfaceImpl", "Class3ToBeMarkedInternal,IInterface2ToBeMarkedInternal`1")] [TestCase("PublicClassWithInternalTypeInExplicitImpl", "IInterface3")] + [TestCase("ExplicitImplOfInternalInterface", "IInternalInterface`1")] public async Task InternalTypeInPublicApi(string mainClassName, string auxiliaryClassNames) { var assemblyPath = await BuildTestAssemblyWithInternalTypeInPublicApi(); diff --git a/tests/Refasmer.Tests/data/IntegrationTests.InternalTypeInPublicApi_mainClassName=ExplicitImplOfInternalInterface.verified.txt b/tests/Refasmer.Tests/data/IntegrationTests.InternalTypeInPublicApi_mainClassName=ExplicitImplOfInternalInterface.verified.txt new file mode 100644 index 0000000..33e06a4 --- /dev/null +++ b/tests/Refasmer.Tests/data/IntegrationTests.InternalTypeInPublicApi_mainClassName=ExplicitImplOfInternalInterface.verified.txt @@ -0,0 +1,5 @@ +public class: RefasmerTestAssembly.ExplicitImplOfInternalInterface + - interface impl: RefasmerTestAssembly.IInternalInterface`1 +methods: +- .ctor(): System.Void: +internal interface: RefasmerTestAssembly.IInternalInterface`1 diff --git a/tests/RefasmerTestAssembly/InternalInteraceImplBug.cs b/tests/RefasmerTestAssembly/InternalInteraceImplBug.cs new file mode 100644 index 0000000..d113e86 --- /dev/null +++ b/tests/RefasmerTestAssembly/InternalInteraceImplBug.cs @@ -0,0 +1,16 @@ +namespace RefasmerTestAssembly; + +// See #53 and #54. +public class ExplicitImplOfInternalInterface : IInternalInterface +{ + void IInternalInterface.Method(string x, Internal2 y) + { + } +} + +internal class Internal2{} + +internal interface IInternalInterface +{ + void Method(T x, Internal2 y); +}