Logging Source Generator fails to compile due to CS0246 and CS0265 errors if type for generic constraint is in a different namespace
martincostello opened this issue · 11 comments
Description
If a containing class for a class used to provide the partial methods for the Logging Source Generator uses as class as a type constraint which is defined in another namespace, the project fails to compile with errors similar to the below.
Repro\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(6,47): error CS0246: The type or namespace name 'Message' could not be found (are you missing a using directive or an assembly reference?) [Repro.csproj]
Repro\MessagePrinter.cs(6,26): error CS0265: Partial declarations of 'MessagePrinter<T>' have inconsistent constraints for type parameter 'T' [Repro.csproj]
If the type which is used for the type constraint of the class is in the same namespace, then project compiles and runs as expected.
The compilation errors can be worked around by using global using statements, such as:
<ItemGroup>
<Using Include="Repro.AnotherNamespace" />
</ItemGroup>Minimal repro
Repro.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<RootNamespace>Repro</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0-rc.1.21424.15" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0-rc.1.21424.15" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0-rc.1.21424.15" />
</ItemGroup>
</Project>Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Repro;
using var serviceProvider = new ServiceCollection()
.AddLogging(builder => builder.AddConsole())
.BuildServiceProvider();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<MessagePrinter<Message>>();
var printer = new MessagePrinter<Message>(logger);
printer.Print(new() { Text = "Hello" });Message.cs
namespace Repro.AnotherNamespace
{
public class Message
{
public string? Text { get; set; }
}
}MessagePrinter.cs
using Microsoft.Extensions.Logging;
using Repro.AnotherNamespace;
namespace Repro
{
public partial class MessagePrinter<T>
where T : Message
{
private readonly ILogger _logger;
public MessagePrinter(ILogger<MessagePrinter<T>> logger)
{
_logger = logger;
}
public void Print(T message)
{
Console.WriteLine(message);
Log.Message(_logger, message.Text);
}
internal static partial class Log
{
[LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "The message is {Text}.")]
internal static partial void Message(ILogger logger, string? text);
}
}
}Expected Output
Repro> dotnet run
Repro.Message
info: Repro.MessagePrinter[1]
The message is Hello.Configuration
.NET SDK 6.0.100-rc.1.21424.38
Microsoft.Extensions.Logging 6.0.0-rc.1.21424.15
Regression?
No.
Tagging subscribers to this area: @maryamariyan
See info in area-owners.md if you want to be subscribed.
Issue Details
Description
If a containing class for a class used to provide the partial methods for the Logging Source Generator uses as class as a type constraint which is defined in another namespace, the project fails to compile with errors similar to the below.
Repro\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(6,47): error CS0246: The type or namespace name 'Message' could not be found (are you missing a using directive or an assembly reference?) [Repro.csproj]
Repro\MessagePrinter.cs(6,26): error CS0265: Partial declarations of 'MessagePrinter<T>' have inconsistent constraints for type parameter 'T' [Repro.csproj]
If the type which is used for the type constraint of the class is in the same namespace, then project compiles and runs as expected.
Minimal repro
Repro.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<RootNamespace>Repro</RootNamespace>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0-rc.1.21424.15" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0-rc.1.21424.15" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0-rc.1.21424.15" />
</ItemGroup>
</Project>Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Repro;
using var serviceProvider = new ServiceCollection()
.AddLogging(builder => builder.AddConsole())
.BuildServiceProvider();
var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<MessagePrinter<Message>>();
var printer = new MessagePrinter<Message>(logger);
printer.Print(new() { Text = "Hello" });Message.cs
namespace Repro.AnotherNamespace
{
public class Message
{
public string? Text { get; set; }
}
}MessagePrinter.cs
using Microsoft.Extensions.Logging;
using Repro.AnotherNamespace;
namespace Repro
{
public partial class MessagePrinter<T>
where T : Message
{
private readonly ILogger _logger;
public MessagePrinter(ILogger<MessagePrinter<T>> logger)
{
_logger = logger;
}
public void Print(T message)
{
Console.WriteLine(message);
Log.Message(_logger, message.Text);
}
internal static partial class Log
{
[LoggerMessage(EventId = 1, Level = LogLevel.Information, Message = "The message is {Text}.")]
internal static partial void Message(ILogger logger, string? text);
}
}
}Expected Output
Repro> dotnet run
Repro.Message
info: Repro.MessagePrinter[1]
The message is Hello.Configuration
.NET SDK 6.0.100-rc.1.21424.38
Microsoft.Extensions.Logging 6.0.0-rc.1.21424.15
Regression?
No.
| Author: | martincostello |
|---|---|
| Assignees: | - |
| Labels: |
|
| Milestone: | - |
Do you really want to punt that to 7.0? Most, if not all, source gen errors for the JSON source generator were classified as blocking.
My reasoning:
This problem also occurs if MessagePrinter is an abstract base class with constraints, I looked through my code where a class gets the non-generic ILogger interface in the constructor (a cursory glance verifies that these are abstract), i.e. an injected logger is used in a shared base class somewhere and ~25% of those types use some kind of generic constraint. That's a few dozen types as that specific project is using MediatR quite heavily.
Many of the source gen compiler errors are sufficiently obscure that without looking at the g.cs, they are not easy to understand.
Having said that, if you can't fix it for 6.0 you want to make sure that you put that bug fairly prominently into the release notes/documentation including the workaround by @martincostello
For what it's worth, I found this in a library code base converting the logging which was using the nested private Log class pattern to the source generators and hit this on three classes, two of which were abstract.
I went with non-abstract for writing up the repro as it was easier to follow.
Another workaround is to use full qualified name of class in another namespace, that is being used as constraint, e.g.
// code
public partial class MessagePrinter<T> where T : Repro.AnotherNamespace.Message
{}
// codeRunning into this as well trying to convert lots of my code to use this.. happens in 5 files for my MediatR extensions.
Example:
using MediatR;
using Microsoft.Extensions.Logging;
namespace LoggingGeneratorErrorExample;
internal partial class LoggingNotificationHandlerWrapper<TInner, TNotification> : INotificationHandler<TNotification>
where TNotification : INotification
where TInner : INotificationHandler<TNotification>
{
private readonly ILogger<TInner> logger = null!;
public Task Handle(TNotification notification, CancellationToken cancellationToken)
=> Task.CompletedTask;
[LoggerMessage(Level = LogLevel.Debug, Message = "Test Message")]
private partial void LogNotification();
}<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>True</EmitCompilerGeneratedFiles>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MediatR" Version="9.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
</ItemGroup>
</Project>Fails with:
C:\demos\LoggingGeneratorErrorExample\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(6,98): error CS0246: The type or namespace name 'INotification' could not be found (are you missing a using directive or an assembly reference?) [C:\demos\LoggingGeneratorErrorExample\LoggingGeneratorErrorExample.csproj]
C:\demos\LoggingGeneratorErrorExample\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(7,20): error CS0246: The type or namespace name 'INotificationHandler<>' could not be found (are you missing a using directive or an assembly reference?) [C:\demos\LoggingGeneratorErrorExample\LoggingGeneratorErrorExample.csproj]
C:\demos\LoggingGeneratorErrorExample\LoggingNotificationHandlerWrapper.cs(6,24): error CS0265: Partial declarations of 'LoggingNotificationHandlerWrapper<TInner, TNotification>' have inconsistent constraints for type parameter 'TInner' [C:\demos\LoggingGeneratorErrorExample\LoggingGeneratorErrorExample.csproj]
C:\demos\LoggingGeneratorErrorExample\LoggingNotificationHandlerWrapper.cs(6,24): error CS0265: Partial declarations of 'LoggingNotificationHandlerWrapper<TInner, TNotification>' have inconsistent constraints for type parameter 'TNotification' [C:\demos\LoggingGeneratorErrorExample\LoggingGeneratorErrorExample.csproj]
Build FAILED.
C:\demos\LoggingGeneratorErrorExample\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(6,98): error CS0246: The type or namespace name 'INotification' could not be found (are you missing a using directive or an assembly reference?) [C:\demos\LoggingGeneratorErrorExample\LoggingGeneratorErrorExample.csproj]
C:\demos\LoggingGeneratorErrorExample\Microsoft.Extensions.Logging.Generators\Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator\LoggerMessage.g.cs(7,20): error CS0246: The type or namespace name 'INotificationHandler<>' could not be found (are you missing a using directive or an assembly reference?) [C:\demos\LoggingGeneratorErrorExample\LoggingGeneratorErrorExample.csproj]
C:\demos\LoggingGeneratorErrorExample\LoggingNotificationHandlerWrapper.cs(6,24): error CS0265: Partial declarations of 'LoggingNotificationHandlerWrapper<TInner, TNotification>' have inconsistent constraints for type parameter 'TInner' [C:\demos\LoggingGeneratorErrorExample\LoggingGeneratorErrorExample.csproj]
C:\demos\LoggingGeneratorErrorExample\LoggingNotificationHandlerWrapper.cs(6,24): error CS0265: Partial declarations of 'LoggingNotificationHandlerWrapper<TInner, TNotification>' have inconsistent constraints for type parameter 'TNotification' [C:\demos\LoggingGeneratorErrorExample\LoggingGeneratorErrorExample.csproj]
0 Warning(s)
4 Error(s)
Generated code emitted by the generator:
// <auto-generated/>
#nullable enable
namespace LoggingGeneratorErrorExample
{
partial class LoggingNotificationHandlerWrapper<TInner, TNotification> where TNotification : INotification
where TInner : INotificationHandler<TNotification>
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.5.2210")]
private static readonly global::System.Action<global::Microsoft.Extensions.Logging.ILogger, global::System.Exception?> __LogNotificationCallback =
global::Microsoft.Extensions.Logging.LoggerMessage.Define(global::Microsoft.Extensions.Logging.LogLevel.Debug, new global::Microsoft.Extensions.Logging.EventId(-1, nameof(LogNotification)), "Test Message", new global::Microsoft.Extensions.Logging.LogDefineOptions() { SkipEnabledCheck = true });
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Logging.Generators", "6.0.5.2210")]
private partial void LogNotification()
{
if (logger.IsEnabled(global::Microsoft.Extensions.Logging.LogLevel.Debug))
{
__LogNotificationCallback(logger, null);
}
}
}
}
Fwiw, I moved the conversion of my code base to the logger src gen to 7.0. I had way too many build errors for complex examples (mostly this case) that I stopped my attempts.
I was just hoping this can be fixed sooner not just to get the feature working in non-trivial setups but also b/c the in-box generators are a good source of reference (and documentation.. especially for the newer Roslyn 4 incremental generator APIs) for other people writing generators
I was just hoping this can be fixed sooner not just to get the feature working in non-trivial
Yeah I second that, but as there was no feedback from MS when I specifically asked if they want to punt that. There are quite a few logger src gen issues, which either point to missing test coverage, or more likely, that the developer experience is still bad (as the json src gen had similar problems).
Yeah I second that, but as there was no feedback from MS when I specifically asked if they want to punt that. There are quite a few logger src gen issues, which either point to missing test coverage, or more likely, that the developer experience is still bad (as the json src gen had similar problems).
Thanks for the feedback. Will take a look and make improvements here.
A couple small observations around possible fixes. I don't think partial files need to restate all the derived types and generic parameter constraints, so it may be possible to just omit things in the generated file. Another approach we've often taken in generated files is to fully qualify all type references, so if we aren't doing that already that might be another thing to consider.
cc @layomia @stephentoub @elinor-fung who've built other generators and may have an opinion here or wish to address a similar bug in other generators.
+1 to both @ericstj's suggestions.
For this issue, I think the simplest and correct thing to do would be to just remove the constraints when emitting code for the parent classes and logger class.
For the p/invoke generator, we don't have this specific problem with generic constraints (since DllImports can't be in generic types), but in general we try to both avoid unnecessarily re-specifying things on partial class/method definitions and use fully qualified names in the generated code.