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

Minor [CanvasEffectProperty] generator tweaks #879

Merged
merged 2 commits into from
Dec 3, 2024
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 @@ -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