Skip to content

Commit

Permalink
Merge pull request #879 from Sergio0694/dev/minor-tweaks
Browse files Browse the repository at this point in the history
Minor [CanvasEffectProperty] generator tweaks
  • Loading branch information
Sergio0694 authored Dec 3, 2024
2 parents 0fbe1a6 + e80b110 commit 36b0c25
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -128,6 +134,11 @@ public static CanvasEffectInvalidationType GetCanvasEffectInvalidationType(Attri
return CanvasEffectInvalidationType.Update;
}

/// <summary>
/// Writes all implementations of partial effect property declarations.
/// </summary>
/// <param name="properties">The input set of declared effect properties.</param>
/// <param name="writer">The <see cref="IndentedTextWriter"/> instance to write into.</param>
public static void WritePropertyDeclarations(EquatableArray<CanvasEffectPropertyInfo> properties, IndentedTextWriter writer)
{
// Helper to get the nullable type name for the initial property value
Expand Down Expand Up @@ -163,8 +174,10 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility)

writer.WriteLine("/// <inheritdoc/>");
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -115,6 +116,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
SetterAccessibility: setterAccessibility,
TypeNameWithNullabilityAnnotations: typeNameWithNullabilityAnnotations,
IsReferenceTypeOrUnconstraindTypeParameter: isReferenceTypeOrUnconstraindTypeParameter,
IsRequired: isRequired,
InvalidationType: invalidationType);
})
.WithTrackingName(WellKnownTrackingNames.Execute)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ internal static class DiagnosticDescriptors
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a CanvasEffect property with invalid accessors.
/// Gets a <see cref="DiagnosticDescriptor"/> for a CanvasEffect property that is not an incomplete partial property definition.
/// <para>
/// Format: <c>"The property "{0}" is not an incomplete partial definition ([GeneratedCanvasEffectProperty] must be used on partial property definitions with no implementation part)"</c>.
/// </para>
Expand All @@ -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");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a CanvasEffect property with invalid accessors.
/// Gets a <see cref="DiagnosticDescriptor"/> for a CanvasEffect property that returns a ref value.
/// <para>
/// Format: <c>"The property "{0}" returns a value by reference ([GeneratedCanvasEffectProperty] must be used on properties returning a type by value)"</c>.
/// </para>
Expand All @@ -112,7 +112,7 @@ internal static class DiagnosticDescriptors
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a CanvasEffect property with invalid accessors.
/// Gets a <see cref="DiagnosticDescriptor"/> for a CanvasEffect property that returns a byref-like value.
/// <para>
/// Format: <c>"The property "{0}" returns a ref struct value ([GeneratedCanvasEffectProperty] must be used on properties with a type that is not a ref struct)"</c>.
/// </para>
Expand All @@ -128,7 +128,7 @@ internal static class DiagnosticDescriptors
helpLinkUri: "https://github.com/Sergio0694/ComputeSharp");

/// <summary>
/// Gets a <see cref="DiagnosticDescriptor"/> for a CanvasEffect property with invalid accessors.
/// Gets a <see cref="DiagnosticDescriptor"/> for when C# is not set to 'preview'.
/// <para>
/// Format: <c>"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 &lt;LangVersion&gt;preview&lt;/LangVersion&gt; to your .csproj/.props file)"</c>.
/// </para>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace ComputeSharp.D2D1.WinUI.SourceGenerators.Models;
/// <param name="SetterAccessibility">The accessibility of the <see langword="set"/> accessor, if available.</param>
/// <param name="TypeNameWithNullabilityAnnotations">The type name for the generated property, including nullability annotations.</param>
/// <param name="IsReferenceTypeOrUnconstraindTypeParameter">Indicates whether the property is of a reference type or an unconstrained type parameter.</param>
/// <param name="IsRequired">Whether or not the generated property should be marked as required.</param>
/// <param name="InvalidationType">The invalidation type to request.</param>
internal sealed record CanvasEffectPropertyInfo(
HierarchyInfo Hierarchy,
Expand All @@ -26,4 +27,5 @@ internal sealed record CanvasEffectPropertyInfo(
Accessibility SetterAccessibility,
string TypeNameWithNullabilityAnnotations,
bool IsReferenceTypeOrUnconstraindTypeParameter,
bool IsRequired,
CanvasEffectInvalidationType InvalidationType);
Original file line number Diff line number Diff line change
Expand Up @@ -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 = """"
// <auto-generated/>
#pragma warning disable
namespace MyNamespace
{
/// <inheritdoc cref="MyEffect"/>
partial class MyEffect
{
/// <inheritdoc/>
[global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", <ASSEMBLY_VERSION>)]
[global::System.Diagnostics.DebuggerNonUserCode]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public required partial int Number
{
get => field;
set
{
if (global::System.Collections.Generic.EqualityComparer<int>.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);
}
}
/// <summary>Executes the logic for when <see cref="Number"/> is changing.</summary>
/// <param name="value">The new property value being set.</param>
/// <remarks>This method is invoked right before the value of <see cref="Number"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", <ASSEMBLY_VERSION>)]
partial void OnNumberChanging(int newValue);
/// <summary>Executes the logic for when <see cref="Number"/> is changing.</summary>
/// <param name="oldValue">The previous property value that is being replaced.</param>
/// <param name="newValue">The new property value being set.</param>
/// <remarks>This method is invoked right before the value of <see cref="Number"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", <ASSEMBLY_VERSION>)]
partial void OnNumberChanging(int oldValue, int newValue);
/// <summary>Executes the logic for when <see cref="Number"/> has just changed.</summary>
/// <param name="value">The new property value that has been set.</param>
/// <remarks>This method is invoked right after the value of <see cref="Number"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", <ASSEMBLY_VERSION>)]
partial void OnNumberChanged(int newValue);
/// <summary>Executes the logic for when <see cref="Number"/> has just changed.</summary>
/// <param name="oldValue">The previous property value that has been replaced.</param>
/// <param name="newValue">The new property value that has been set.</param>
/// <remarks>This method is invoked right after the value of <see cref="Number"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("ComputeSharp.D2D1.WinUI.CanvasEffectPropertyGenerator", <ASSEMBLY_VERSION>)]
partial void OnNumberChanged(int oldValue, int newValue);
}
}
"""";

VerifyGeneratedDiagnostics(source, ("MyNamespace.MyEffect.g.cs", result));
}

[TestMethod]
public void MultipleProperties()
{
Expand Down

0 comments on commit 36b0c25

Please sign in to comment.