diff --git a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs index 6a64948f3828a2..c86d7f2e00ebc4 100644 --- a/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs +++ b/src/libraries/Common/src/Roslyn/GetBestTypeByMetadataName.cs @@ -3,9 +3,9 @@ using Microsoft.CodeAnalysis; -namespace System.Text.Json.Reflection +namespace Microsoft.CodeAnalysis.DotnetRuntime.Extensions { - internal static partial class RoslynExtensions + internal static class RoslynExtensions { // Copied from: https://github.com/dotnet/roslyn/blob/main/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs /// diff --git a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs index fa45f5511f157e..23b1d5959aced5 100644 --- a/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs +++ b/src/libraries/Common/tests/SourceGenerators/RoslynTestUtils.cs @@ -48,10 +48,10 @@ public static Project CreateTestProject(IEnumerable? references, bool } return new AdhocWorkspace() - .AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create())) - .AddProject("Test", "test.dll", "C#") - .WithMetadataReferences(refs) - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Enable)); + .AddSolution(SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create())) + .AddProject("Test", "test.dll", "C#") + .WithMetadataReferences(refs) + .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithNullableContextOptions(NullableContextOptions.Enable)); } public static Task CommitChanges(this Project proj, params string[] ignorables) @@ -152,13 +152,24 @@ public static TextSpan MakeSpan(string text, int spanNum) CancellationToken cancellationToken = default) { Project proj = CreateTestProject(references, includeBaseReferences); - proj = proj.WithDocuments(sources); - Assert.True(proj.Solution.Workspace.TryApplyChanges(proj.Solution)); - Compilation? comp = await proj!.GetCompilationAsync(CancellationToken.None).ConfigureAwait(false); + return RunGenerator(comp!, generator, cancellationToken); + } + /// + /// Runs a Roslyn generator given a Compilation. + /// + public static (ImmutableArray, ImmutableArray) RunGenerator( + Compilation compilation, +#if ROSLYN4_0_OR_GREATER + IIncrementalGenerator generator, +#else + ISourceGenerator generator, +#endif + CancellationToken cancellationToken = default) + { #if ROSLYN4_0_OR_GREATER // workaround https://github.com/dotnet/roslyn/pull/55866. We can remove "LangVersion=Preview" when we get a Roslyn build with that change. CSharpParseOptions options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview); @@ -167,7 +178,7 @@ public static TextSpan MakeSpan(string text, int spanNum) CSharpGeneratorDriver cgd = CSharpGeneratorDriver.Create(new[] { generator }); #endif - GeneratorDriver gd = cgd.RunGenerators(comp!, cancellationToken); + GeneratorDriver gd = cgd.RunGenerators(compilation, cancellationToken); GeneratorDriverRunResult r = gd.GetRunResult(); return (r.Results[0].Diagnostics, r.Results[0].GeneratedSources); diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs index 93d7590059b310..f33f506a213787 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Collections.Immutable; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; namespace Microsoft.Extensions.Logging.Generators { @@ -65,28 +66,28 @@ internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => /// public IReadOnlyList GetLogClasses(IEnumerable classes) { - INamedTypeSymbol loggerMessageAttribute = _compilation.GetTypeByMetadataName(LoggerMessageAttribute); + INamedTypeSymbol loggerMessageAttribute = _compilation.GetBestTypeByMetadataName(LoggerMessageAttribute); if (loggerMessageAttribute == null) { // nothing to do if this type isn't available return Array.Empty(); } - INamedTypeSymbol loggerSymbol = _compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.ILogger"); + INamedTypeSymbol loggerSymbol = _compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.ILogger"); if (loggerSymbol == null) { // nothing to do if this type isn't available return Array.Empty(); } - INamedTypeSymbol logLevelSymbol = _compilation.GetTypeByMetadataName("Microsoft.Extensions.Logging.LogLevel"); + INamedTypeSymbol logLevelSymbol = _compilation.GetBestTypeByMetadataName("Microsoft.Extensions.Logging.LogLevel"); if (logLevelSymbol == null) { // nothing to do if this type isn't available return Array.Empty(); } - INamedTypeSymbol exceptionSymbol = _compilation.GetTypeByMetadataName("System.Exception"); + INamedTypeSymbol exceptionSymbol = _compilation.GetBestTypeByMetadataName("System.Exception"); if (exceptionSymbol == null) { Diag(DiagnosticDescriptors.MissingRequiredType, null, "System.Exception"); diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.targets b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.targets index 2f3db63183ecdc..77a182fa4649aa 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.targets +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/Microsoft.Extensions.Logging.Generators.targets @@ -20,4 +20,8 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/CompilationHelper.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/CompilationHelper.cs new file mode 100644 index 00000000000000..59c82a5437147f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/CompilationHelper.cs @@ -0,0 +1,64 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.Extensions.Logging.Generators.Tests +{ + public class CompilationHelper + { + public static Compilation CreateCompilation( + string source, + MetadataReference[]? additionalReferences = null, + string assemblyName = "TestAssembly") + { + string corelib = Assembly.GetAssembly(typeof(object))!.Location; + string runtimeDir = Path.GetDirectoryName(corelib)!; + + var refs = new List(); + refs.Add(MetadataReference.CreateFromFile(corelib)); + refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "netstandard.dll"))); + refs.Add(MetadataReference.CreateFromFile(Path.Combine(runtimeDir, "System.Runtime.dll"))); + refs.Add(MetadataReference.CreateFromFile(typeof(ILogger).Assembly.Location)); + refs.Add(MetadataReference.CreateFromFile(typeof(LoggerMessageAttribute).Assembly.Location)); + + if (additionalReferences != null) + { + foreach (MetadataReference reference in additionalReferences) + { + refs.Add(reference); + } + } + + CSharpParseOptions options = CSharpParseOptions.Default; + +#if ROSLYN4_0_OR_GREATER + // workaround https://github.com/dotnet/roslyn/pull/55866. We can remove "LangVersion=Preview" when we get a Roslyn build with that change. + options = options.WithLanguageVersion(LanguageVersion.Preview); +#endif + + return CSharpCompilation.Create( + assemblyName, + syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, options) }, + references: refs.ToArray(), + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + ); + } + + public static byte[] CreateAssemblyImage(Compilation compilation) + { + MemoryStream ms = new MemoryStream(); + var emitResult = compilation.Emit(ms); + if (!emitResult.Success) + { + throw new InvalidOperationException(); + } + return ms.ToArray(); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs index 4ccb289f3aba7e..09b93e633335c7 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -677,6 +678,55 @@ static partial class C Assert.Empty(diagnostics); // should fail quietly on broken code } + [Fact] + internal void MultipleTypeDefinitions() + { + // Adding a dependency to an assembly that has internal definitions of public types + // should not result in a collision and break generation. + // Verify usage of the extension GetBestTypeByMetadataName(this Compilation) instead of Compilation.GetTypeByMetadataName(). + var referencedSource = @" + namespace Microsoft.Extensions.Logging + { + internal class LoggerMessageAttribute { } + } + namespace Microsoft.Extensions.Logging + { + internal interface ILogger { } + internal enum LogLevel { } + }"; + + // Compile the referenced assembly first. + Compilation referencedCompilation = CompilationHelper.CreateCompilation(referencedSource); + + // Obtain the image of the referenced assembly. + byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation); + + // Generate the code + string source = @" + namespace Test + { + using Microsoft.Extensions.Logging; + + partial class C + { + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1"")] + static partial void M1(ILogger logger); + } + }"; + + MetadataReference[] additionalReferences = { MetadataReference.CreateFromImage(referencedImage) }; + Compilation compilation = CompilationHelper.CreateCompilation(source, additionalReferences); + LoggerMessageGenerator generator = new LoggerMessageGenerator(); + + (ImmutableArray diagnostics, ImmutableArray generatedSources) = + RoslynTestUtils.RunGenerator(compilation, generator); + + // Make sure compilation was successful. + Assert.Empty(diagnostics); + Assert.Equal(1, generatedSources.Length); + Assert.Equal(21, generatedSources[0].SourceText.Lines.Count); + } + private static async Task> RunGenerator( string code, bool wrap = true, diff --git a/src/libraries/System.Private.CoreLib/generators/EventSourceGenerator.Parser.cs b/src/libraries/System.Private.CoreLib/generators/EventSourceGenerator.Parser.cs index be971bb185f0b8..2b28b46e03335b 100644 --- a/src/libraries/System.Private.CoreLib/generators/EventSourceGenerator.Parser.cs +++ b/src/libraries/System.Private.CoreLib/generators/EventSourceGenerator.Parser.cs @@ -7,10 +7,10 @@ using System.Security.Cryptography; using System.Text; using System.Threading; - using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; namespace Generators { @@ -31,14 +31,14 @@ public Parser(Compilation compilation, Action reportDiagnostic, Canc public EventSourceClass[] GetEventSourceClasses(List classDeclarations) { - INamedTypeSymbol? autogenerateAttribute = _compilation.GetTypeByMetadataName("System.Diagnostics.Tracing.EventSourceAutoGenerateAttribute"); + INamedTypeSymbol? autogenerateAttribute = _compilation.GetBestTypeByMetadataName("System.Diagnostics.Tracing.EventSourceAutoGenerateAttribute"); if (autogenerateAttribute is null) { // No EventSourceAutoGenerateAttribute return Array.Empty(); } - INamedTypeSymbol? eventSourceAttribute = _compilation.GetTypeByMetadataName("System.Diagnostics.Tracing.EventSourceAttribute"); + INamedTypeSymbol? eventSourceAttribute = _compilation.GetBestTypeByMetadataName("System.Diagnostics.Tracing.EventSourceAttribute"); if (eventSourceAttribute is null) { // No EventSourceAttribute diff --git a/src/libraries/System.Private.CoreLib/generators/System.Private.CoreLib.Generators.csproj b/src/libraries/System.Private.CoreLib/generators/System.Private.CoreLib.Generators.csproj index bf6c663b56432f..451bb520f66ead 100644 --- a/src/libraries/System.Private.CoreLib/generators/System.Private.CoreLib.Generators.csproj +++ b/src/libraries/System.Private.CoreLib/generators/System.Private.CoreLib.Generators.csproj @@ -15,6 +15,7 @@ + diff --git a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs index e5cb105b39d6e4..e985f1c3c38a7e 100644 --- a/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs +++ b/src/libraries/System.Text.Json/gen/JsonSourceGenerator.Parser.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; using Microsoft.CodeAnalysis.Text; namespace System.Text.Json.SourceGeneration diff --git a/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs index 189a842f44f5c6..15020ec6ea6217 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/MetadataLoadContextInternal.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.DotnetRuntime.Extensions; namespace System.Text.Json.Reflection { diff --git a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs index aa3f431fced8d0..62afc237624a15 100644 --- a/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs +++ b/src/libraries/System.Text.Json/gen/Reflection/RoslynExtensions.cs @@ -7,7 +7,7 @@ namespace System.Text.Json.Reflection { - internal static partial class RoslynExtensions + internal static class RoslynExtensions { public static Type AsType(this ITypeSymbol typeSymbol, MetadataLoadContextInternal metadataLoadContext) { diff --git a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs index 0aa811d64fcce4..105dc91af17b38 100644 --- a/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs +++ b/src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Unit.Tests/JsonSourceGeneratorTests.cs @@ -474,7 +474,7 @@ public void TestMultipleDefinitions() { // Adding a dependency to an assembly that has internal definitions of public types // should not result in a collision and break generation. - // This verifies the usage of GetBestTypeByMetadataName() instead of GetTypeByMetadataName(). + // Verify usage of the extension GetBestTypeByMetadataName(this Compilation) instead of Compilation.GetTypeByMetadataName(). var referencedSource = @" namespace System.Text.Json.Serialization { @@ -487,16 +487,7 @@ internal class JsonSourceGenerationOptionsAttribute { } Compilation referencedCompilation = CompilationHelper.CreateCompilation(referencedSource); // Obtain the image of the referenced assembly. - byte[] referencedImage; - using (MemoryStream ms = new MemoryStream()) - { - var emitResult = referencedCompilation.Emit(ms); - if (!emitResult.Success) - { - throw new InvalidOperationException(); - } - referencedImage = ms.ToArray(); - } + byte[] referencedImage = CompilationHelper.CreateAssemblyImage(referencedCompilation); // Generate the code string source = @" diff --git a/src/libraries/tests.proj b/src/libraries/tests.proj index 69f0e7bbed025c..af4c31639bb4e3 100644 --- a/src/libraries/tests.proj +++ b/src/libraries/tests.proj @@ -15,8 +15,14 @@ - - + + + + + + + +