dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
18.71k stars 3.98k forks source link

Don't limit Source Generators to "netstandard2.0" target framework #45162

Closed b-straub closed 4 years ago

b-straub commented 4 years ago

Version Used:

VS 16.7 P2

Steps to Reproduce:

Compile SG with target framework netstandard2.1 or above

Expected Behavior:

SG will be used and generate code

Actual Behavior:

SG is not used

The restriction of SG to netstandard2.0 has an effect on the available language functions. For example, interfaces with default implementations cannot be used. While the generated code is not affected, the SG code itself should not be limited to an outdated standard.

jmarolf commented 4 years ago

because the compiler must run on both .NET Framework and .NET Core all generators must target .NET Standard 2.0

RayKoopa commented 3 years ago

I'm probably missing a big part here, but why exactly does the source generator have to run on both .NET Framework and .NET Core and not the actual target framework?

jmarolf commented 3 years ago

@RayKoopa the "actual target framework" is not applicable to this situation. Your source generator is a plugin that is loaded by the C# compiler. It therefore needs to be able to be loaded and run in all the environments that the C# compiler does.

Lets say at some point in the future the C# compiler runs on only .NET Core. Even in this case you cannot target a newer target framework than what the compiler supports. Meaning that if the compiler is compiled against .NET 5 you cannot target .NET 6 in your source generator.

RayKoopa commented 3 years ago

You're right, I had an error in my thinking. I probably spent too much time realizing why my source generator was "not found" because of its project targeting netstandard2.1 :D (Maybe it should report an exact message that a source generator project must target netstandard2.0 currently, instead of claiming it could not find it's assembly file, but that's another topic.)

jmarolf commented 3 years ago

@RayKoopa I agree we should offer a better message here. I hope to update the tooling soon to also warn about this case

hughesjs commented 1 year ago

@RayKoopa the "actual target framework" is not applicable to this situation. Your source generator is a plugin that is loaded by the C# compiler. It therefore needs to be able to be loaded and run in all the environments that the C# compiler does.

Lets say at some point in the future the C# compiler runs on only .NET Core. Even in this case you cannot target a newer target framework than what the compiler supports. Meaning that if the compiler is compiled against .NET 5 you cannot target .NET 6 in your source generator.

I'm still not sure about this. My source generator generates code that will only compile on net6+ projects. So it's a given that the netcore compiler is going to be being used anywhere my generator is. As such, why would I care if it breaks another environment where the generated code wouldn't work anyway?

ronnyek commented 11 months ago

I know this is closed, but I too am wondering if this is ever going to change. There are language features I want to be able to take advantage of that dont exist until c# 11 (like generic attributes). Will this forever be a limitation that we wont be able to utilize those things in generators, or just for now?

hughesjs commented 11 months ago

@ronnyek - You can actually just target something else and it does seem to work even if it's technically unsupported YMMV though

garcipat commented 9 months ago

You can use some features if you use Polysharp. But for example having things nullable is hard to change even if they say its supported.

hypdeb commented 8 months ago

@ronnyek - You can actually just target something else and it does seem to work even if it's technically unsupported YMMV though

I don't think this is true. Changing the target framework just kills it at least from what I can see. Not only that, but there's absolutely no way of knowing that it didn't work until you realize your generated code is not updated anymore when you try using it.

This is really a shame because it means you're stuck using C# from 5 years ago when developing generators and analyzers. In particular you don't have init-only properties.

From the comments above, it seems this is because of support for .NET Framework. I don't plan on using .NET Framework on any environment, build or runtime, so I don't think I should be limited by it. Is there not a workaround ? Could we get a project file property saying "I don't want to support running that on .NET Framework, please let me use the version of .net I want" ?

hughesjs commented 7 months ago

@ronnyek - You can actually just target something else and it does seem to work even if it's technically unsupported YMMV though

I don't think this is true. Changing the target framework just kills it at least from what I can see. Not only that, but there's absolutely no way of knowing that it didn't work until you realize your generated code is not updated anymore when you try using it.

This is really a shame because it means you're stuck using C# from 5 years ago when developing generators and analyzers. In particular you don't have init-only properties.

From the comments above, it seems this is because of support for .NET Framework. I don't plan on using .NET Framework on any environment, build or runtime, so I don't think I should be limited by it. Is there not a workaround ? Could we get a project file property saying "I don't want to support running that on .NET Framework, please let me use the version of .net I want" ?

I've been using one targetting something else no bother... Are you using VS or Rider? Might be an implementation specific thing that's keeping mine running

hypdeb commented 7 months ago

I'm using Rider. I can try it out tomorrow using a simple dotnet build, that should settle it :)

hughesjs commented 7 months ago

I'm using Rider. I can try it out tomorrow using a simple dotnet build, that should settle it :)

Hmm... I'm on Rider too... Now you've got me doubting myself. I'll pick it up over the weekend and see if I can do an MRE. Could also be a Linux/Windows thing maybe? I work on Linux day-to-day.

maragnus commented 6 months ago

I'm experiencing this issue as well. While my net8.0 solution will build without issue, Rider is unable to internally use the code generator because it and its dependencies are not targeting netstandard. In my case netstandard2.1 works without issue.

See this issue: https://youtrack.jetbrains.com/issue/RIDER-85940 See the applied workaround: https://github.com/maragnus/CodeGeneratorExample/tree/rider-workaround

Note that <LangVersion>12.0</LangVersion> in csproj and a workaround for System.Runtime.CompilerServices.IsExternalInit may be necessary.

sharwell commented 6 months ago

In my case netstandard2.1 works without issue.

netstandard2.1 is not supported for a source generator. The only target framework supported for a source generator implementation is netstandard2.0. It may be possible to trick a specific build into working with a different framework, but in all cases there are known and supported scenarios that will fail if you attempt to use a different target framework.

hughesjs commented 6 months ago

@sharwell - My question to this then is "why?". I understand the principle that it's essential a plugin to the compiler and thus we want it to be able to work regardless of what compiler version is installed but in cases where people are generating code that will only work in say, dotnet 8 environments, why shouldn't they be able to assume that a dotnet 8 compiler is available and thus use that in their source generator? What is actually broken in that case?

sharwell commented 6 months ago

why shouldn't they be able to assume that a dotnet 8 compiler is available and thus use that in their source generator?

The term ".NET 8 compiler" is ambiguous in this context. There are compiler binaries which execute on .NET Framework but are capable of compiling source code to produce output binaries targeting net8.0. At least one case where these compiler binaries are used is inside Visual Studio for the purpose of providing IDE IntelliSense information.

Having a source generator target netstandard2.0 has no impact on the ability of the source generator to produce code that compiles for net8.0.

I implemented a new analyzer to help users recognize this case before it becomes a problem: https://github.com/dotnet/roslyn-analyzers/pull/7116

qub1n commented 5 months ago

Actually I could complain that I cannot use latest C# feature with this limitation.

Unfortunately I cannot write source generator in.NET Standard at all. My old generator which is used as .post build event is quite complex code written in .NET Core 8.

Does mean that I have to rewrite existing code base it back to .NET standard 2? Are you serious?

I believe the limitation of .NET Standard is really annoying, most people write source generators for themselves and do not care about compatibility with old frameworks.

Please think about it again from user/customer perspective and consider reopening the issue. I believe you will find technical solution.

qub1n commented 5 months ago

I will add another reason....

In my source generator, I am looking for Microsoft.AspNetCore.Mvc.ApiVersionAttribute in the code. Microsoft.AspNetCore does not support .NET Standard, so I cannot use typeof(ApiVersionAttribute)

Can I write source generator for ASP.NET Core with this limitation at all?

maragnus commented 5 months ago

@qub1n You are correct. I have had to extensively rewrite two of my code generators for netstandard 2.1 to be supported in an IDE.

When referencing types, I’ve found it sufficiently reliable to refer to types as strings of their full name including namespace. As an added security, I have simple unit tests written against my code generators in .NET 8 that verify that the strings are correct.

sharwell commented 5 months ago

In my source generator, I am looking for Microsoft.AspNetCore.Mvc.ApiVersionAttribute in the code. Microsoft.AspNetCore does not support .NET Standard, so I cannot use typeof(ApiVersionAttribute)

In what exact context are you trying to use typeof(ApiVersionAttribute) in your source generator? If you just need the name of this attribute, you can do so in your source generator as "ApiVersionAttribute" or "Microsoft.AspNetCore.Mvc.ApiVersionAttribute". There is very little risk of getting this wrong when you add unit tests, because the unit tests will fail if there are any compilation errors in the source generator output (e.g. mistyping "ApiVersonAttribute" would result in an immediate test failure saying the type could not be resolved).

qub1n commented 5 months ago

We are parsing C# code and for those methods which contains this attribute, we generate signalR client in C# using NSwag using property attribute.Version.

It is not only that, we need to reference some other models from other project which target .NET 8. It is really difficult to retarget to .NET Standard if it was not designed this way from scratch.

qub1n commented 5 months ago

Let me put it from another view. Every technical problem has a solution, the question is how much time do we want to spent on it and if it makes sense at all.

I did a lot of interviews with software developers. And very often when I asked them "Why do you want to change a job?" I got answers like "In our company we with legacy code hardly learning anything new, we would like to learn and use all those cool features from latest C#". It is very strong motivation for many of them. I promised to them during contract signing that we are switching to latest C# after every .NET release with long term support. I cannot tell to such guys pls go back in time and rewrite it back.

CyrusNajmabadi commented 5 months ago

Can I write source generator for ASP.NET Core with this limitation at all?

There should be no problem there.

The limitations are just in your generator. Your generator can still generate code that leverages the latest c# and .net features.

Think of it this way: a generator is just a to that spits out files you could manually write yourself. Those files are just strings, and are legal if they would have been legal if manually written.

While the final emitted code has no restrictions on it, the actual code that does the emitting does (namely the net standard restriction being discussed).

But this has not been a problem for many teams writing generators, many of whom are targeting modern .net scenarios.

abraluve commented 5 months ago

This is unfortunately ridicolous right now.

Lets assume following project structure:

App (dependency MainLib, SourceGeneratorLib) MainLib (dependency DataLib) DataLib (.net8, dependency EF Core 8) SourceGeneratorLib (dependency MainLib, target .netstandard2.0).. however this fails to compile cause you cant reference .net8 project.

This forces you to create two more projects to hold your basic data structures for mainlib and datalib that can be referenced by SourceGeneratorLib

This is big fail from Microsoft if you have forced dependency to newer version of .NET and also can forces you to use .NET standard 2.0 for source generators.. Well I thought finally would use generators.. I gotta go with reflection once again.

sharwell commented 5 months ago

This forces you to create two more projects to hold your basic data structures for mainlib and datalib that can be referenced by SourceGeneratorLib

While I don't have a copy of your exact code, to date I can't think of a case where this was an impairment. The issue was primarily resolved in one of the following ways:

  1. Remove the dependency from SourceGeneratorLib→MainLib. Most source generators don't need to reference the libraries they generate supporting source for.
  2. Link source files from MainLib into SourceGeneratorLib and/or use a shared project. Most rare source generators that reference something from another library only reference a small portion of code from it.

Other strategies exist as well, but the situations where they occur are so rare I'm not sure it would be helpful to enumerate them without additional context.

If you have more concrete examples with source code, I would be happy to review them and see how it compares to other situations we've seen.

ArgusMagnus commented 2 months ago

My issue with this limitation is the available APIs. I'd like to use System.Text.Json in my SG. It's available as a NuGet package for netstandard2.0 but, as it turns out, referencing (nuget) dependencies is a major pain. At first I thought it would work using the technique shown here: https://github.com/dotnet/roslyn-sdk/blob/0313c80ed950ac4f4eef11bb2e1c6d1009b328c4/samples/CSharp/SourceGenerators/SourceGeneratorSamples/SourceGeneratorSamples.csproj#L13-L30 It seems like I need to manually add all dependencies and their dependencies (and so on) of System.Text.Json this way. But I have hit a wall. For some reason, System.Text.Encodings.Web can not be loaded.

  <ItemGroup>
    <!-- Generator dependencies -->
    <PackageReference Include="System.Text.Json" Version="8.0.3" GeneratePathProperty="true" PrivateAssets="all" />
    <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" GeneratePathProperty="true" PrivateAssets="all" />
    <PackageReference Include="System.Text.Encodings.Web" Version="8.0.0" GeneratePathProperty="true" PrivateAssets="all" />
  </ItemGroup>

  <PropertyGroup>
    <GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
  </PropertyGroup>

  <Target Name="GetDependencyTargetPaths">
    <ItemGroup>
      <TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Json)\lib\netstandard2.0\System.Text.Json.dll" IncludeRuntimeDependency="false" />
      <TargetPathWithTargetPlatformMoniker Include="$(PKGMicrosoft_Bcl_AsyncInterfaces)\lib\netstandard2.0\Microsoft.Bcl.AsyncInterfaces.dll" IncludeRuntimeDependency="false" />
      <TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Encodings_Web)\lib\netstandard2.0\System.Text.Encodings.Web.dll" IncludeRuntimeDependency="false" />
    </ItemGroup>
  </Target>

Long story short: Is there a chance, that you could improve the dependency resolution experience?

CyrusNajmabadi commented 2 months ago

That sounds like an msbuild issue. Which looks external to roslyn. We are just informed of the resolved references and whatnot. :-)

We can def pass on that feedback though.