-
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
All StructId generators are not incremental and extremely inefficient (analyzers too) #60
Comments
Thanks for taking a look at this project @Sergio0694! Very much appreciated. I'll take your analyzer suggestion and see how to refactor that, good point on that front. On moving to attribute for triggering codegen: having to both implement an interface (or provide a ctor) AND adding an attribute seems detrimental to the user experience (but so would be bad IDE perf). I wouldn't want an API designed to satisfy the compiler at the expense of the API's usability itself, but perhaps a faster analyzer + codefix can make the attribute requirement bearable. I'll investigate further. Being just a v1, I didn't spend a lot of time polishing perf on the codegen itself, but rather getting to a feature set I could use myself on another project (where perf isn't an issue -yet?- since it's a green field project). Thanks again for the suggestions and for getting involved. Will keep this issue open and updated (but I'm going on vacation soon-ish so likely it will be some time). UpdateAfter some experimentation, it seems the advise/recommendation is (at least partially) wrong and shouldn't be considered The True Way. See #60 (comment). |
Switch to symbol action rather than syntax, to speed up analysis. Partially fixes #60
Switch to symbol action rather than syntax, to speed up analysis. Partially fixes #60
Switch to symbol action rather than syntax, to speed up analysis. Partially fixes #60
@kzu When I see a new greenfield project that replicates what already exists, I always like to ask why it exists. So: why does this exist when Vogen and StronglyTypedId exist? |
This comment was marked as resolved.
This comment was marked as resolved.
The reality is that the analyzer and codefix performance have absolutely nothing to do with the generator performance. You must address the generator performance issue without any consideration of the performance of analyzers and codefixes.
The reality is that you must satisfy the compiler in this case. It is a requirement. Not doing so will create performance issues for users in IDEs (VS, VS Code, Rider, etc.) on larger codebases.
Why do your types need to inherit an interface? Why not just have the attribute and have the generated code add the interface? |
That's how you provide generics on what type of thing you are, in .NET. You inherit or implement things. It could be a generic attribute instead nowadays, agreed. But other than that, attributes quickly degenerate in "yaml config via attributes", which I'm not too fond of. i.e. who uses assembly-level attributes for anything? Contemplate this use case: https://github.com/devlooped/DependencyInjection?tab=readme-ov-file#convention-based The alternative is doing some weird assembly-level attribute to configure the generator, or worse, adding a .editorconfig. Instead of a simple API in C# itself (which the generator can pick up and act on).
Because I'm free, I enjoy doing it and you can happily use whatever else you want. And I enjoy learning new stuff. So, the feedback from this issue by @Sergio0694 took me down to more learning, so it's a net positive for me. You don't learn much by just blindly using existing stuff via nuget just becase "it already exists". I remember RhinoMocks already existed too 😉 . |
First, who is talking about using assembly-level attributes? Sure, configuration makes sense to do via assembly-level attributes, but there's no reason to assume I'm talking about assembly-level attributet here. In your case, I'd highly recommend you do this instead: [StructId]
public readonly partial record struct UserId with the interface being added in the generated code as so: public readonly partial record struct UserId : IStructId<Guid>; This would be 100x (that's not an exaggeration, that's an actual measured number from the Roslyn team) faster. Second, you should be aware that the Roslyn team is actively discussing removing support for all uses of
Sure, that's a valid reason for another entry into the space. I am just always curious why new entries exist when existing entries exist - usually it's because of some niche that the existing entries do not cover. In many cases, the niche is why I have written several source generator projects this year. I did not see any new purpose for this library, so that's why I was curious. Glad you're learning. Good luck - I hope you're able to transition to an attribute-based model for |
@Sergio0694 I'm puzzled by this:
I disagree. I'm looking at their source and it seems quite possible to write a similarly efficient generator that doesn't need to depend exclusively on attributes: https://github.com/dotnet/roslyn/blob/main/src/Compilers/Core/Portable/SourceGeneration/Nodes/SyntaxValueProvider_ForAttributeWithSimpleName.cs#L55 |
@viceroypenguin my approach to templates is bonkers. Haven't seen anything remotely like it in any of the others. Now I just have to make it performant 😅 . |
You mean using Scriban for generating code? I use it in all of my source generators. 🤷🏼♂️ |
You haven't even read the readme, have you? 😉 https://github.com/devlooped/StructId#customization-via-templates |
I did, I just forgot about that. Yeah, that is unique. The others rely primarily on configuration rather than these templates. I could definitely see some nuanced needs being addressed by this over the others. Cool trick! |
I plan on generalizing it until I can do away with all the other templates that aren't compiled ones (with the exception of the ones that need to iterate all IDs). Then perhaps create a "MetaSharp" package that only does the compiled template expansion alone and perhaps this package just becomes a bunch of templates (plus a couple scribans) and that's it. |
I've been following @andrewlock testing incremental generators post and I have to say that I find the recomendations and explanations (at least partially) wrong. This is a run of an cloned compilation to which I add a new syntax tree, where the pipeline uses the compilation provider and selects just the syntax trees (plus a selector/filter): they are perfectly cacheable and incremental OOB: The generator is perfectly incremental: This is the second run with the added syntax tree, note how the first |
Overview
Disclaimer: I'm not using this package. Just leaving some feedback here to help the ecosystem 🙂
The incremental generators in this project unfortunately have some (big) issues: they are completely not incremental, and they are extremely inefficient. The latter is because they introduce a whole bunch of generator state tables that will never compare as equal anyway (as they're capturing values that cannot be equated), meaning they'll generate source again every single time.
To clarify:
ForAttributeWithMetadataName
If the objection is "but using an attribute is less convenient for users", sure. Maybe. But that's the only way to make this correct and it's a design principle that should not be considered optional for generators. Performance has to come first, because poorly performing generators (like these ones below) impact the entire IDE experience.
You should also enable this property to get the Roslyn analyzer help spot all of these issues (or, most of them):
The offending generators:
StructId/src/StructId.Analyzer/BaseGenerator.cs
Lines 27 to 64 in ff93ea8
StructId/src/StructId.Analyzer/DapperGenerator.cs
Lines 36 to 87 in ff93ea8
StructId/src/StructId.Analyzer/EntityFrameworkGenerator.cs
Lines 50 to 67 in ff93ea8
StructId/src/StructId.Analyzer/NewtonsoftJsonGenerator.cs
Lines 18 to 19 in ff93ea8
StructId/src/StructId.Analyzer/TemplatedGenerator.cs
Lines 81 to 155 in ff93ea8
This model is also completely not incremental:
StructId/src/StructId.Analyzer/KnownTypes.cs
Line 10 in ff93ea8
Analyzers
Performance issues in this project are not exclusive to generators, but analyzers are also not optimal. However, these should be lower priority than fixing the generators, since at least analyzers don't directly block the IDE (though they should still be fixed).
For instance, here's an offending analyzer:
StructId/src/StructId.Analyzer/RecordAnalyzer.cs
Lines 25 to 42 in ff93ea8
Targeting all syntax nodes and then doing
GetDeclaredSymbol
on each of them is not efficient. You should use an appropriate callback method, like one for anINamedTypeSymbol
, and use that. If you need to also compare against well known types, you should gather them in a compilation start action, and then flow them into a nested symbol callback.Solution
Like I mentioned above, all these generators need to be rewritten to use an attribute as trigger, and use
ForAttributeWithMetadataName
. Not wanting to use an attribute for convenience is not a valid argument for having a generator that is outright breaking all design principles of incremental generators, and introducing performance issues that will impact the whole IDE.If it heps, you can check out some other incremental generators and analyzers for reference, such as:
DependencyProperty
generator)The text was updated successfully, but these errors were encountered: