Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,4 @@ MVVMTK0047 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator
MVVMTK0048 | CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0048
MVVMTK0049 | CommunityToolkit.Mvvm.SourceGenerators.INotifyPropertyChangedGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0049
MVVMTK0050 | CommunityToolkit.Mvvm.SourceGenerators.ObservableObjectGenerator | Warning | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0050
MVVMTK0051 | CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator | Info | See https://aka.ms/mvvmtoolkit/errors/mvvmtk0050
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
Expand All @@ -20,7 +21,9 @@ namespace CommunityToolkit.Mvvm.SourceGenerators;
public sealed class WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer : DiagnosticAnalyzer
{
/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(WinRTObservablePropertyOnFieldsIsNotAotCompatible);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
WinRTObservablePropertyOnFieldsIsNotAotCompatible,
WinRTObservablePropertyOnFieldsIsNotAotCompatibleCompilationEndInfo);

/// <inheritdoc/>
public override void Initialize(AnalysisContext context)
Expand All @@ -42,6 +45,9 @@ public override void Initialize(AnalysisContext context)
return;
}

// Track whether we produced any diagnostics, for the compilation end scenario
AttributeData? firstObservablePropertyAttribute = null;

context.RegisterSymbolAction(context =>
{
// Ensure we do have a valid field
Expand All @@ -51,7 +57,7 @@ public override void Initialize(AnalysisContext context)
}

// Emit a diagnostic if the field is using the [ObservableProperty] attribute
if (fieldSymbol.HasAttributeWithType(observablePropertySymbol))
if (fieldSymbol.TryGetAttributeWithType(observablePropertySymbol, out AttributeData? observablePropertyAttribute))
{
context.ReportDiagnostic(Diagnostic.Create(
WinRTObservablePropertyOnFieldsIsNotAotCompatible,
Expand All @@ -61,8 +67,32 @@ public override void Initialize(AnalysisContext context)
.Add(FieldReferenceForObservablePropertyFieldAnalyzer.PropertyNameKey, ObservablePropertyGenerator.Execute.GetGeneratedPropertyName(fieldSymbol)),
fieldSymbol.ContainingType,
fieldSymbol.Name));

// Notify that we did produce at least one diagnostic. Note: callbacks can run in parallel, so the order
// is not guaranteed. As such, there's no point in using an interlocked compare exchange operation here,
// since we couldn't rely on the value being written actually being the "first" occurrence anyway.
// So we can just do a normal volatile read for better performance.
Volatile.Write(ref firstObservablePropertyAttribute, observablePropertyAttribute);
}
}, SymbolKind.Field);

// If C# preview is already in use, we can stop here. The last diagnostic is only needed when partial properties
// cannot be used, to inform developers that they'll need to bump the language version to enable the code fixer.
if (context.Compilation.IsLanguageVersionPreview())
{
return;
}

context.RegisterCompilationEndAction(context =>
{
// If we have produced at least one diagnostic, also emit the info message
if (Volatile.Read(ref firstObservablePropertyAttribute) is { } observablePropertyAttribute)
{
context.ReportDiagnostic(Diagnostic.Create(
WinRTObservablePropertyOnFieldsIsNotAotCompatibleCompilationEndInfo,
observablePropertyAttribute.GetLocation()));
}
});
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,4 +844,21 @@ internal static class DiagnosticDescriptors
isEnabledByDefault: true,
description: "Using the [ObservableObject] attribute on types is not AOT compatible in WinRT scenarios (such as UWP XAML and WinUI 3 apps), and they should derive from ObservableObject instead (as it allows the CsWinRT generators to correctly produce the necessary WinRT marshalling code).",
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0050");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for when <c>[ObservableProperty]</c> is used on a field in WinRT scenarios.
/// <para>
/// Format: <c>"This project produced one or more 'MVVMTK0045' warnings due to [ObservableProperty] being used on fields, which is not AOT compatible in WinRT scenarios, but it can't enable partial properties and the associated code fixer because 'LangVersion' is not set to 'preview' (setting 'LangVersion=preview' is required to use [ObservableProperty] on partial properties and address these warnings)"</c>.
/// </para>
/// </summary>
public static readonly DiagnosticDescriptor WinRTObservablePropertyOnFieldsIsNotAotCompatibleCompilationEndInfo = new(
id: "MVVMTK0051",
title: "Using [ObservableProperty] with WinRT and AOT requires 'LangVersion=preview'",
messageFormat: """This project produced one or more 'MVVMTK0045' warnings due to [ObservableProperty] being used on fields, which is not AOT compatible in WinRT scenarios, but it can't enable partial properties and the associated code fixer because 'LangVersion' is not set to 'preview' (setting 'LangVersion=preview' is required to use [ObservableProperty] on partial properties and address these warnings)""",
category: typeof(ObservablePropertyGenerator).FullName,
defaultSeverity: DiagnosticSeverity.Info,
isEnabledByDefault: true,
description: "Project producing one or more 'MVVMTK0045' warnings due to [ObservableProperty] being used on fields, which is not AOT compatible in WinRT scenarios, should set 'LangVersion' to 'preview' to enable partial properties and the associated code fixer because (setting 'LangVersion=preview' is required to use [ObservableProperty] on partial properties and address these warnings).",
helpLinkUri: "https://aka.ms/mvvmtoolkit/errors/mvvmtk0051",
customTags: WellKnownDiagnosticTags.CompilationEnd);
}
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,28 @@ await CSharpAnalyzerWithLanguageVersionTest<WinRTObservablePropertyOnFieldsIsNot
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
}

[TestMethod]
public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_Auto_NotCSharpPreview_Warns_WithCompilationWarning()
{
const string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[{|MVVMTK0051:ObservableProperty|}]
private string {|MVVMTK0045:name|};
}
}
""";

await CSharpAnalyzerWithLanguageVersionTest<WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp12,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
}

[TestMethod]
public async Task WinRTObservablePropertyOnFieldsIsNotAotCompatibleAnalyzer_TargetingWindows_CsWinRTAotOptimizerEnabled_True_NoXaml_Level1_DoesNotWarn()
{
Expand Down