Skip to content
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

MVVM Toolkit vNext: source generators #3873

Merged

Conversation

Sergio0694
Copy link
Member

@Sergio0694 Sergio0694 commented Mar 22, 2021

(🦙) Testing something for Discord Preview, please ignore this text at the top for now while we test it. Thanks! This is going to be great feature in the Toolkit, can't wait! (🦙)


Contributes to #3872

PR Type

What kind of change does this PR introduce?

  • Feature
  • Performance improvements

Changelog

Changes discussed/tracked in the linked issue.

PR Checklist

Please check if your PR fulfills the following requirements:

  • Tested code with current supported SDKs
  • Pull Request has been submitted to the documentation repository instructions. Link:
  • Sample in sample app has been added / updated (for bug fixes / features)
  • New major technical changes in the toolkit have or will be added to the Wiki e.g. build changes, source generators, testing infrastructure, sample creation changes, etc...
  • Tests for the changes have been added (for bug fixes / features) (if applicable)
  • Header has been added to all new source files (run build/UpdateHeaders.bat)
  • Contains NO breaking changes

@Sergio0694 Sergio0694 added feature 💡 nuget 📦 optimization ☄ Performance or memory usage improvements .NET Components which are .NET based (non UWP specific) mvvm-toolkit 🧰 Issues/PRs for the Microsoft.Toolkit.Mvvm package labels Mar 22, 2021
@ghost
Copy link

ghost commented Mar 22, 2021

Thanks Sergio0694 for opening a Pull Request! The reviewers will test the PR and highlight if there is any conflict or changes required. If the PR is approved we will proceed to merge the pull request 🙌


if (index != -1)
{
return memberDeclaration.WithModifiers(memberDeclaration.Modifiers.RemoveAt(index));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not preserve trivia if the removed modifier has any. No idea if that's okay, but wanted to note.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good catch. Yeah that's fine, the generation step doesn't care about trivia at all (also to make things simpler). When we actually generate code at the end we just call NormalizeWhitespace() anyway that will fix everything 🙂

{
string propertyName = methodSymbol.Name;

if (SymbolEqualityComparer.Default.Equals(methodSymbol.ReturnType, context.Compilation.GetTypeByMetadataName("System.Threading.Tasks.Task")!) &&

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a suppression is needed? Equals parameters are null annotated

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're right, thanks! Fixed in e97a60b.
I think I had originally added that suppression due to missing annotations with some older preview version of the Roslyn NuGet packages, and tghen forgot to go back and remove that here after updating the references 😄

@michael-hawker
Copy link
Member

Thanks @Sergio0694 looks good to me. I've been playing with them already, never going to go back! 🦙❤

@XAML-Knight think you'll find this interesting too. Feel free to give it a quick go around.

Copy link
Contributor

@XAML-Knight XAML-Knight left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive check-in - I found a couple of small items.

[NotNullWhen(false)] out DiagnosticDescriptor? descriptor)
{
INamedTypeSymbol
observableRecipientSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableRecipient")!,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd recommend continuing to pass in the FullName of the Type, versus relying upon magic strings, where possible

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"where possible" is the key phrase here, of course :)

}
}

INamedTypeSymbol observableValidatorSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableValidator")!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, recommend sending in FullName of the Type

{
if (context.Node is ClassDeclarationSyntax classDeclaration &&
context.SemanticModel.GetDeclaredSymbol(classDeclaration) is INamedTypeSymbol { IsGenericType: false } classSymbol &&
context.SemanticModel.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableValidator") is INamedTypeSymbol validatorSymbol &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, recommend sending in FullName of the Type

}

// Get the symbol for the ValidationAttribute type
INamedTypeSymbol validationSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.ValidationAttribute")!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, recommend sending in FullName of the Type

{
if (context.Node is ClassDeclarationSyntax classDeclaration &&
context.SemanticModel.GetDeclaredSymbol(classDeclaration) is INamedTypeSymbol { IsGenericType: false } classSymbol &&
context.SemanticModel.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.Messaging.IRecipient`1") is INamedTypeSymbol iRecipientSymbol &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see why you're just passing in a string value, here

@ghost
Copy link

ghost commented Jul 23, 2021

This PR has been marked as "needs attention 👋" and awaiting a response from the team.

Adding the type name of the generator is not necessary since Visual Studio already groups generated sources for each individual generator, which also automatically avoids conflicts
@Sergio0694
Copy link
Member Author

Hey @XAML-Knight, thank you for having a look, glad you liked the PR! 😄

Let me give some context as to why I was using those hardcoded strings instead of using typeof(T) before, and in fact in 9283f2c I've removed those usages altogether and just switched to hardcoded strings everywhere for consistency. I've also talked with a Roslyn engineer to confirm this was a commonly used pattern on their end as well, for good measure 🙂

The short version of why those usages you've highlighted in your review comments weren't using typeof(T) is that those types were simply not available, so I couldn't have used typeof(T) even if I had wanted to (which I did, initially). Note that the MVVM Toolkit generator does not have a dependency on the MVVM Toolkit, so it can't see any of those types directly. The ones I was using typeof(T) for were types that I had manually linked to the generator project to enable that, which I've also removed now. The reason why typeof(T) is technically not entirely correct is that you're essentially loading a type that's not the same type as the one actually used in consuming projects. Source generators run at build time and they're a totally separate assembly that's only loaded by Roslyn when building a project (so they're not referenced by consuming projects directly either), and because of this they can't access runtime information from consuming projects such as Type instances - the consuming project is just not an assembly that's loaded into memory and that is being executed at all at that point. This is the reason why generators only work with syntax trees and semantic information (symbols), as those are the only type of information that is available at that point in the toolchain. Even when we were using typeof(T) before to simplify things, we were actually not referencing the same type that consumers would've used, but just loading a Type instance from a source copy of that types that happened to be in the compiled in the generator assembly, which also happened to match the one actually present in the MVVM Toolkit and therefore using the same fully qualified name when final consumers would actually use it at runtime. But it was not technically the same type. Now instead we're just matching the actual well known type name, which is what Roslyn also does frequently as well (eg. see here).

Hope this helps! 😄

[NotNullWhen(false)] out DiagnosticDescriptor? descriptor)
{
INamedTypeSymbol
observableRecipientSymbol = context.Compilation.GetTypeByMetadataName("Microsoft.Toolkit.Mvvm.ComponentModel.ObservableRecipient")!,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"where possible" is the key phrase here, of course :)

@XAML-Knight
Copy link
Contributor

Thanks for the detailed explanation, @Sergio0694

PR Approved

@ghost ghost removed the needs attention 👋 label Jul 23, 2021
@Sergio0694
Copy link
Member Author

@XAML-Knight No worries, glad I could help! And yeah for sure, I did see that "where possible", just figured it would be useful to share some additional context in here instead of just saying "yeah it's not possible here", either for you in case you weren't super familiar with source generators yet (as they're pretty new, or more like, still in preview themselves ahah), as well as others possibly reading through this PR. Glad everything makes sense, thank you for the review! 🙌

@michael-hawker michael-hawker merged commit 9444484 into CommunityToolkit:main Jul 23, 2021
@Sergio0694 Sergio0694 deleted the feature/mvvm-toolkit-sg branch July 23, 2021 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature 💡 mvvm-toolkit 🧰 Issues/PRs for the Microsoft.Toolkit.Mvvm package .NET Components which are .NET based (non UWP specific) next preview ✈️ Label for marking what we want to include in the next preview release for developers to try. nuget 📦 optimization ☄ Performance or memory usage improvements priority 🚩
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MVVM: Clearing the event handlers for ObservableObject
4 participants