diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.Execute.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.Execute.cs index 8568b84a3..169f8e974 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.Execute.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.Execute.cs @@ -46,6 +46,12 @@ public static bool IsCandidatePropertyDeclaration(SyntaxNode node, CancellationT return false; } + // Static properties are not supported + if (property.Modifiers.Any(SyntaxKind.StaticKeyword)) + { + return false; + } + // The accessors must be a get and a set (with any accessibility) if (accessors[0].Kind() is not (SyntaxKind.GetAccessorDeclaration or SyntaxKind.SetAccessorDeclaration) || accessors[1].Kind() is not (SyntaxKind.GetAccessorDeclaration or SyntaxKind.SetAccessorDeclaration)) @@ -128,6 +134,11 @@ public static CanvasEffectInvalidationType GetCanvasEffectInvalidationType(Attri return CanvasEffectInvalidationType.Update; } + /// + /// Writes all implementations of partial effect property declarations. + /// + /// The input set of declared effect properties. + /// The instance to write into. public static void WritePropertyDeclarations(EquatableArray properties, IndentedTextWriter writer) { // Helper to get the nullable type name for the initial property value @@ -163,8 +174,10 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) writer.WriteLine("/// "); writer.WriteGeneratedAttributes(GeneratorName); - writer.WriteLine($$""" - {{GetExpressionWithTrailingSpace(propertyInfo.DeclaredAccessibility)}}partial {{propertyInfo.TypeNameWithNullabilityAnnotations}} {{propertyInfo.PropertyName}} + writer.Write(GetExpressionWithTrailingSpace(propertyInfo.DeclaredAccessibility)); + writer.WriteIf(propertyInfo.IsRequired, "required "); + writer.WriteLine($"partial {propertyInfo.TypeNameWithNullabilityAnnotations} {propertyInfo.PropertyName}"); + writer.WriteLine($$""" { {{GetExpressionWithTrailingSpace(propertyInfo.GetterAccessibility)}}get => field; {{GetExpressionWithTrailingSpace(propertyInfo.SetterAccessibility)}}set diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.cs index 269650a13..3bf7d0698 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/CanvasEffectPropertyGenerator.cs @@ -63,7 +63,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } // Also ignore all properties that have an invalid declaration - if (propertySymbol.IsStatic || propertySymbol.ReturnsByRef || propertySymbol.ReturnsByRefReadonly || propertySymbol.Type.IsRefLikeType) + if (propertySymbol.ReturnsByRef || propertySymbol.ReturnsByRefReadonly || propertySymbol.Type.IsRefLikeType) { return default; } @@ -101,6 +101,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) // This will cover both reference types as well T when the constraints are not struct or unmanaged. // If this is true, it means the field storage can potentially be in a null state (even if not annotated). bool isReferenceTypeOrUnconstraindTypeParameter = !propertySymbol.Type.IsValueType; + bool isRequired = propertySymbol.IsRequired; // Finally, get the hierarchy too HierarchyInfo hierarchyInfo = HierarchyInfo.From(typeSymbol); @@ -115,6 +116,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) SetterAccessibility: setterAccessibility, TypeNameWithNullabilityAnnotations: typeNameWithNullabilityAnnotations, IsReferenceTypeOrUnconstraindTypeParameter: isReferenceTypeOrUnconstraindTypeParameter, + IsRequired: isRequired, InvalidationType: invalidationType); }) .WithTrackingName(WellKnownTrackingNames.Execute) diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs index 64e1c2f0c..4a745df32 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/Diagnostics/DiagnosticDescriptors.cs @@ -80,7 +80,7 @@ internal static class DiagnosticDescriptors helpLinkUri: "https://github.com/Sergio0694/ComputeSharp"); /// - /// Gets a for a CanvasEffect property with invalid accessors. + /// Gets a for a CanvasEffect property that is not an incomplete partial property definition. /// /// Format: "The property "{0}" is not an incomplete partial definition ([GeneratedCanvasEffectProperty] must be used on partial property definitions with no implementation part)". /// @@ -92,11 +92,11 @@ internal static class DiagnosticDescriptors category: DiagnosticCategory, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true, - description: "A property using [GeneratedCanvasEffectProperty] is either not partial, or a partial implementation part ([GeneratedCanvasEffectProperty] must be used on partial property definitions with no implementation par).", + description: "A property using [GeneratedCanvasEffectProperty] is either not partial, or a partial implementation part ([GeneratedCanvasEffectProperty] must be used on partial property definitions with no implementation part).", helpLinkUri: "https://github.com/Sergio0694/ComputeSharp"); /// - /// Gets a for a CanvasEffect property with invalid accessors. + /// Gets a for a CanvasEffect property that returns a ref value. /// /// Format: "The property "{0}" returns a value by reference ([GeneratedCanvasEffectProperty] must be used on properties returning a type by value)". /// @@ -112,7 +112,7 @@ internal static class DiagnosticDescriptors helpLinkUri: "https://github.com/Sergio0694/ComputeSharp"); /// - /// Gets a for a CanvasEffect property with invalid accessors. + /// Gets a for a CanvasEffect property that returns a byref-like value. /// /// Format: "The property "{0}" returns a ref struct value ([GeneratedCanvasEffectProperty] must be used on properties with a type that is not a ref struct)". /// @@ -128,7 +128,7 @@ internal static class DiagnosticDescriptors helpLinkUri: "https://github.com/Sergio0694/ComputeSharp"); /// - /// Gets a for a CanvasEffect property with invalid accessors. + /// Gets a for when C# is not set to 'preview'. /// /// Format: "Using [GeneratedCanvasEffectProperty] requires the C# language version to be set to 'preview', as support for the 'field' keyword is needed by the source generators to emit valid code (add <LangVersion>preview</LangVersion> to your .csproj/.props file)". /// diff --git a/src/ComputeSharp.D2D1.UI.SourceGenerators/Models/CanvasEffectPropertyInfo.cs b/src/ComputeSharp.D2D1.UI.SourceGenerators/Models/CanvasEffectPropertyInfo.cs index 3116543b8..f8eef81a3 100644 --- a/src/ComputeSharp.D2D1.UI.SourceGenerators/Models/CanvasEffectPropertyInfo.cs +++ b/src/ComputeSharp.D2D1.UI.SourceGenerators/Models/CanvasEffectPropertyInfo.cs @@ -17,6 +17,7 @@ namespace ComputeSharp.D2D1.WinUI.SourceGenerators.Models; /// The accessibility of the accessor, if available. /// The type name for the generated property, including nullability annotations. /// Indicates whether the property is of a reference type or an unconstrained type parameter. +/// Whether or not the generated property should be marked as required. /// The invalidation type to request. internal sealed record CanvasEffectPropertyInfo( HierarchyInfo Hierarchy, @@ -26,4 +27,5 @@ internal sealed record CanvasEffectPropertyInfo( Accessibility SetterAccessibility, string TypeNameWithNullabilityAnnotations, bool IsReferenceTypeOrUnconstraindTypeParameter, + bool IsRequired, CanvasEffectInvalidationType InvalidationType); diff --git a/tests/ComputeSharp.D2D1.WinUI.Tests.SourceGenerators/Test_CanvasEffectPropertyGenerator.cs b/tests/ComputeSharp.D2D1.WinUI.Tests.SourceGenerators/Test_CanvasEffectPropertyGenerator.cs index 6597d5ac5..7a98b1712 100644 --- a/tests/ComputeSharp.D2D1.WinUI.Tests.SourceGenerators/Test_CanvasEffectPropertyGenerator.cs +++ b/tests/ComputeSharp.D2D1.WinUI.Tests.SourceGenerators/Test_CanvasEffectPropertyGenerator.cs @@ -344,6 +344,90 @@ private set VerifyGeneratedDiagnostics(source, ("MyNamespace.MyEffect.g.cs", result)); } + [TestMethod] + public void SingleProperty_Required() + { + const string source = """ + using ComputeSharp.D2D1.WinUI; + + namespace MyNamespace; + + public abstract partial class MyEffect : CanvasEffect + { + [GeneratedCanvasEffectProperty] + public required partial int Number { get; set; } + } + """; + + const string result = """" + // + #pragma warning disable + + namespace MyNamespace + { + /// + partial class MyEffect + { + /// + [global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public required partial int Number + { + get => field; + set + { + if (global::System.Collections.Generic.EqualityComparer.Default.Equals(field, value)) + { + return; + } + + int oldValue = field; + + OnNumberChanging(value); + OnNumberChanging(oldValue, value); + + field = value; + + OnNumberChanged(value); + OnNumberChanged(oldValue, value); + + InvalidateEffectGraph(global::ComputeSharp.D2D1.WinUI.CanvasEffectInvalidationType.Update); + } + } + + /// Executes the logic for when is changing. + /// The new property value being set. + /// This method is invoked right before the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", )] + partial void OnNumberChanging(int newValue); + + /// Executes the logic for when is changing. + /// The previous property value that is being replaced. + /// The new property value being set. + /// This method is invoked right before the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", )] + partial void OnNumberChanging(int oldValue, int newValue); + + /// Executes the logic for when has just changed. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// The previous property value that has been replaced. + /// The new property value that has been set. + /// This method is invoked right after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", )] + partial void OnNumberChanged(int oldValue, int newValue); + } + } + """"; + + VerifyGeneratedDiagnostics(source, ("MyNamespace.MyEffect.g.cs", result)); + } + [TestMethod] public void MultipleProperties() {