From 8ef543d371950b221999fc20cc46e036c0a9866a Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Tue, 31 Mar 2026 16:05:41 +1100 Subject: [PATCH] fix TUnit0073 for when type from from another assembly Fixed the analyzer to iterate over individual referenced assemblies when GetTypeByMetadataName returns null (which happens due to ambiguity when multiple assemblies embed the same type via Polyfill). --- .../MissingPolyfillAnalyzerTests.cs | 76 +++++++++++++++++++ TUnit.Analyzers/MissingPolyfillAnalyzer.cs | 25 +++++- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/TUnit.Analyzers.Tests/MissingPolyfillAnalyzerTests.cs b/TUnit.Analyzers.Tests/MissingPolyfillAnalyzerTests.cs index 3ec986a8e5..0cd8e13854 100644 --- a/TUnit.Analyzers.Tests/MissingPolyfillAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/MissingPolyfillAnalyzerTests.cs @@ -1,3 +1,5 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Testing; using Verifier = TUnit.Analyzers.Tests.Verifiers.CSharpAnalyzerVerifier; @@ -48,4 +50,78 @@ public class MyClass .WithArguments("System.Runtime.CompilerServices.ModuleInitializerAttribute") ); } + + [Test] + public async Task No_Error_When_ModuleInitializerAttribute_Provided_By_Polyfill() + { + var net48References = new ReferenceAssemblies( + "net48", + new PackageIdentity("Microsoft.NETFramework.ReferenceAssemblies.net48", "1.0.3"), + System.IO.Path.Combine("ref", "net48")); + + await Verifier + .VerifyAnalyzerAsync( + """ + namespace System.Runtime.CompilerServices + { + internal sealed class ModuleInitializerAttribute : Attribute + { + } + } + + public class MyClass + { + } + """, + test => + { + test.ReferenceAssemblies = net48References; + test.TestState.AdditionalReferences.Clear(); + test.CompilerDiagnostics = CompilerDiagnostics.None; + } + ); + } + + [Test] + public async Task No_Error_When_ModuleInitializerAttribute_In_Multiple_Referenced_Assemblies() + { + // On modern TFMs, ModuleInitializerAttribute is in the runtime. + // When a referenced library also embeds it via Polyfill, + // GetTypeByMetadataName returns null due to ambiguity. + // The analyzer should not report TUnit0073 in this case. + var refs = await ReferenceAssemblies.Net.Net90.ResolveAsync(LanguageNames.CSharp, CancellationToken.None); + + var attributeSource = CSharpSyntaxTree.ParseText(""" + namespace System.Runtime.CompilerServices + { + internal sealed class ModuleInitializerAttribute : Attribute + { + } + } + """); + + using var stream = new MemoryStream(); + var result = CSharpCompilation.Create("PolyfillLibrary", + [attributeSource], + refs, + new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) + .Emit(stream); + await TUnit.Assertions.Assert.That(result.Success).IsTrue(); + + await Verifier + .VerifyAnalyzerAsync( + """ + public class MyClass + { + } + """, + test => + { + test.TestState.AdditionalReferences.Clear(); + test.TestState.AdditionalReferences.Add( + MetadataReference.CreateFromImage(stream.ToArray())); + test.CompilerDiagnostics = CompilerDiagnostics.None; + } + ); + } } diff --git a/TUnit.Analyzers/MissingPolyfillAnalyzer.cs b/TUnit.Analyzers/MissingPolyfillAnalyzer.cs index 3d84eb10c4..4bd69e4ffc 100644 --- a/TUnit.Analyzers/MissingPolyfillAnalyzer.cs +++ b/TUnit.Analyzers/MissingPolyfillAnalyzer.cs @@ -22,11 +22,34 @@ private static void AnalyzeCompilation(CompilationAnalysisContext context) { foreach (var typeName in RequiredTypes) { - if (context.Compilation.GetTypeByMetadataName(typeName) is null) + if (!HasType(context.Compilation, typeName)) { context.ReportDiagnostic( Diagnostic.Create(Rules.MissingPolyfillPackage, Location.None, typeName)); } } } + + private static bool HasType(Compilation compilation, string typeName) + { + // GetTypeByMetadataName returns null when the type exists in multiple + // referenced assemblies (e.g. when multiple libraries embed + // ModuleInitializerAttribute via Polyfill). Check each assembly + // individually to avoid this ambiguity problem. + if (compilation.GetTypeByMetadataName(typeName) is not null) + { + return true; + } + + foreach (var reference in compilation.References) + { + if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assembly && + assembly.GetTypeByMetadataName(typeName) is not null) + { + return true; + } + } + + return false; + } }