Roslyn Source Generation with multiple asmdef's

I tried to write a source generator that would collect all classes derived from one base class, check enum returned by method and generate one large switch so I could get rid of reflection usage. Everything was fine and I was even able to see my large switch in IDE, but then I found out that it wasn’t working.

Digging deeper I found that Unity provides asmdef’s as dll’s other than as project references, so it is not possible to get syntax data from code in other assembly.

So I am interested if it is not possible to change Unity’s compilation process or may it be changed at the future?

The same scoping rules from analyzers apply to source generators, from the docs:

Though, as usual, Unity’s documentation leaves a lot of room for imagination. Here’s what I’ve found on Unity 2021.3:

It’s important wether the generator/analyzer is inside an asmdef or not:

  • Outside of an asmdef: Runs on all predefined assemblies* as well as all assembly definitions.
  • Inside of an asmdef: Runs on the containing asmdef as well as all asmdefs that directly or indirectly reference the containing asmdef.

Note 1: When inside an asmdef, the asmdef’s “Auto Referenced” option causes all predefined assemblies to reference the asmdef and to be included in the generator/analyzer scope. This also applies to any asmdef referencing the containing assembly – if any of the asmdefs that directly or indirectly depend on the containing asmdef have this option enabled, all predefined assemblies are included. (The DLL itself also has a “Auto Reference” option, this option is irrelevant.)

Note 2: While source files inside packages are required to be inside of an asmdef, generator/analyzer DLLs can be outside of an asmdef. This way they can be inside packages and work on all asmdefs in a project, without being referenced.

Note 3: You cannot just put the generator/analyzer DLL inside a folder with an asmdef and no source files. Having no source files will prevent the asmdef from being compiled and the generators/analyzers will not work either.

Note 4: There are also some important differences between generators and anylzers:

  • Generators will run on the containing assembly, analyzers will not.
  • Outside of an asmdef, generators will run on all packages, analyzers will only run on writeable packages (local or embedded).
  • Predefined assemblies are all the assemblies Unity creates for source files outside of asmdefs, i.e. “Assembly-CSharp”, “Assembly-CSharp-firstpass”, “Assembly-CSharp-Editor” etc.
2 Likes

Hi. Thank’s for your answer)
The thing is I have no problems with generators running on multiple asmdef’s as it is (the title seems misleading now, yeah). This case is a little bit different, the generator is placed outside any asmdef, but when it runs the generator instance is created per compilation instance, meaning per asmdef. So there is one instance that finds the class that needs to be extended and further I need to access other assemblies from that one instance. If such a generator runs on a solution (in IDE for example), other projects would be accessed as source references and syntax information would be provided, but inside Unity’s compilation precess same assemblies are referenced as metadata dll, meaning no syntax info.
Hope the case is more clear now)

Ah, I see, sorry for the misunderstanding.

Unfortunately, Unity uses a custom compilation pipeline and I don’t think this behaviour can be changed. There’s work towards switching Unity from Mono to the .Net runtime as well as changing the compilation pipeline to a more standard MSBuild-based one – but those changes are probably still a few major Unity releases away ( see this long thread for details / progress ).

In the meantime, you have to work around how it currently works. The metadata you get from the DLL references isn’t enough? From what I understand, you should be able to find the derived types and get the return type of one of their methods (e.g. see this SO answer)?

1 Like

The problem is I don’t need a return type, I need a enum value which I can only get from the method body itself. The workaround I’ve come up with is to add attribute with that enum value, cause attribute’s arguments values can be accessed through symbols api