Skip to content

Commit e2641ea

Browse files
committed
Optimize generated setters via XamlBindingHelper
Add support for using XamlBindingHelper.SetPropertyFrom* methods to optimize generated dependency property setters. Introduces WellKnownTypeNames.XamlBindingHelper to resolve the fully-qualified type name, adds Execute.GetXamlBindingHelperSetMethodName and Execute.IsObjectSetCallbackImplemented to detect applicable types and user overrides, and wires the chosen method into the generator pipeline. Generation now emits direct XamlBindingHelper calls when available and falls back to boxed SetValue path otherwise. Also update DependencyPropertyInfo to carry the XamlBindingHelperSetMethodName.
1 parent 26b8b64 commit e2641ea

File tree

4 files changed

+155
-22
lines changed

4 files changed

+155
-22
lines changed

components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,4 +94,15 @@ public static string CreateDefaultValueCallback(bool useWindowsUIXaml)
9494
? $"{WindowsUIXamlNamespace}.{nameof(CreateDefaultValueCallback)}"
9595
: $"{MicrosoftUIXamlNamespace}.{nameof(CreateDefaultValueCallback)}";
9696
}
97+
98+
/// <summary>
99+
/// Gets the fully qualified type name for the <c>XamlBindingHelper</c> type.
100+
/// </summary>
101+
/// <param name="useWindowsUIXaml"><inheritdoc cref="XamlNamespace(bool)" path="/param[@name='useWindowsUIXaml']/text()"/></param>
102+
public static string XamlBindingHelper(bool useWindowsUIXaml)
103+
{
104+
return useWindowsUIXaml
105+
? $"{WindowsUIXamlNamespace}.Markup.{nameof(XamlBindingHelper)}"
106+
: $"{MicrosoftUIXamlNamespace}.Markup.{nameof(XamlBindingHelper)}";
107+
}
97108
}

components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs

Lines changed: 121 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,60 @@ public static bool IsSharedPropertyChangedCallbackImplemented(IPropertySymbol pr
448448
return false;
449449
}
450450

451+
/// <summary>
452+
/// Checks whether the user has provided an implementation for the <c>On&lt;PROPERTY_NAME&gt;Set(ref object)</c> partial method.
453+
/// </summary>
454+
/// <param name="propertySymbol">The input <see cref="IPropertySymbol"/> instance to process.</param>
455+
/// <returns>Whether the user has an implementation for the boxed set callback.</returns>
456+
public static bool IsObjectSetCallbackImplemented(IPropertySymbol propertySymbol)
457+
{
458+
// Check for any 'On<PROPERTY_NAME>Set' methods with a single ref 'object' parameter
459+
foreach (ISymbol symbol in propertySymbol.ContainingType.GetMembers($"On{propertySymbol.Name}Set"))
460+
{
461+
if (symbol is IMethodSymbol { IsStatic: false, ReturnsVoid: true, Parameters: [{ RefKind: RefKind.Ref, Type.SpecialType: SpecialType.System_Object }] })
462+
{
463+
return true;
464+
}
465+
}
466+
467+
return false;
468+
}
469+
470+
/// <summary>
471+
/// Gets the <c>XamlBindingHelper.SetPropertyFrom*</c> method name for a given property type, if supported.
472+
/// </summary>
473+
/// <param name="typeSymbol">The input <see cref="ITypeSymbol"/> to check.</param>
474+
/// <returns>The method name to use, or <see langword="null"/> if the type is not supported.</returns>
475+
public static string? GetXamlBindingHelperSetMethodName(ITypeSymbol typeSymbol)
476+
{
477+
// Check for well known primitive types first (these are the most common)
478+
switch (typeSymbol.SpecialType)
479+
{
480+
case SpecialType.System_Boolean: return "SetPropertyFromBoolean";
481+
case SpecialType.System_Byte: return "SetPropertyFromByte";
482+
case SpecialType.System_Char: return "SetPropertyFromChar16";
483+
case SpecialType.System_Double: return "SetPropertyFromDouble";
484+
case SpecialType.System_Int32: return "SetPropertyFromInt32";
485+
case SpecialType.System_Int64: return "SetPropertyFromInt64";
486+
case SpecialType.System_Single: return "SetPropertyFromSingle";
487+
case SpecialType.System_String: return "SetPropertyFromString";
488+
case SpecialType.System_UInt32: return "SetPropertyFromUInt32";
489+
case SpecialType.System_UInt64: return "SetPropertyFromUInt64";
490+
case SpecialType.System_Object: return "SetPropertyFromObject";
491+
default: break;
492+
}
493+
494+
// Check for the remaining well known WinRT projected types
495+
if (typeSymbol.HasFullyQualifiedMetadataName("System.DateTimeOffset")) return "SetPropertyFromDateTime";
496+
if (typeSymbol.HasFullyQualifiedMetadataName("System.TimeSpan")) return "SetPropertyFromTimeSpan";
497+
if (typeSymbol.HasFullyQualifiedMetadataName("Windows.Foundation.Point")) return "SetPropertyFromPoint";
498+
if (typeSymbol.HasFullyQualifiedMetadataName("Windows.Foundation.Rect")) return "SetPropertyFromRect";
499+
if (typeSymbol.HasFullyQualifiedMetadataName("Windows.Foundation.Size")) return "SetPropertyFromSize";
500+
if (typeSymbol.HasFullyQualifiedMetadataName("System.Uri")) return "SetPropertyFromUri";
501+
502+
return null;
503+
}
504+
451505
/// <summary>
452506
/// Gathers all forwarded attributes for the generated property.
453507
/// </summary>
@@ -708,21 +762,39 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility)
708762
On{{propertyInfo.PropertyName}}Changing(__oldValue, value);
709763
710764
field = value;
711-
712-
object? __boxedValue = value;
713765
""", isMultiline: true);
714-
writer.WriteLineIf(propertyInfo.TypeName != "object", $"""
715766

716-
On{propertyInfo.PropertyName}Set(ref __boxedValue);
717-
""", isMultiline: true);
718-
writer.Write($$"""
767+
// If an optimized 'XamlBindingHelper' method is available, use it directly
768+
if (propertyInfo.XamlBindingHelperSetMethodName is string setMethodName)
769+
{
770+
writer.Write($$"""
719771
720-
SetValue({{propertyInfo.PropertyName}}Property, __boxedValue);
772+
global::{{WellKnownTypeNames.XamlBindingHelper(propertyInfo.UseWindowsUIXaml)}}.{{setMethodName}}(this, {{propertyInfo.PropertyName}}Property, value);
721773
722-
On{{propertyInfo.PropertyName}}Changed(value);
723-
On{{propertyInfo.PropertyName}}Changed(__oldValue, value);
724-
}
725-
""", isMultiline: true);
774+
On{{propertyInfo.PropertyName}}Changed(value);
775+
On{{propertyInfo.PropertyName}}Changed(__oldValue, value);
776+
}
777+
""", isMultiline: true);
778+
}
779+
else
780+
{
781+
writer.Write($$"""
782+
783+
object? __boxedValue = value;
784+
""", isMultiline: true);
785+
writer.WriteLineIf(propertyInfo.TypeName != "object", $"""
786+
787+
On{propertyInfo.PropertyName}Set(ref __boxedValue);
788+
""", isMultiline: true);
789+
writer.Write($$"""
790+
791+
SetValue({{propertyInfo.PropertyName}}Property, __boxedValue);
792+
793+
On{{propertyInfo.PropertyName}}Changed(value);
794+
On{{propertyInfo.PropertyName}}Changed(__oldValue, value);
795+
}
796+
""", isMultiline: true);
797+
}
726798

727799
// If the default value is not what the default field value would be, add an initializer
728800
if (propertyInfo.DefaultValue is not (DependencyPropertyDefaultValue.Null or DependencyPropertyDefaultValue.Default or DependencyPropertyDefaultValue.Callback))
@@ -758,9 +830,34 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility)
758830
}
759831
""", isMultiline: true);
760832
}
761-
else
833+
else if (propertyInfo.XamlBindingHelperSetMethodName is string setMethodName)
762834
{
763835
// Same as above but with the extra typed hook for both accessors
836+
writer.WriteLine($$"""
837+
{{GetExpressionWithTrailingSpace(propertyInfo.GetterAccessibility)}}get
838+
{
839+
object? __boxedValue = GetValue({{propertyInfo.PropertyName}}Property);
840+
841+
On{{propertyInfo.PropertyName}}Get(ref __boxedValue);
842+
843+
{{propertyInfo.TypeNameWithNullabilityAnnotations}} __unboxedValue = ({{propertyInfo.TypeNameWithNullabilityAnnotations}})__boxedValue;
844+
845+
On{{propertyInfo.PropertyName}}Get(ref __unboxedValue);
846+
847+
return __unboxedValue;
848+
}
849+
{{GetExpressionWithTrailingSpace(propertyInfo.SetterAccessibility)}}set
850+
{
851+
On{{propertyInfo.PropertyName}}Set(ref value);
852+
853+
global::{{WellKnownTypeNames.XamlBindingHelper(propertyInfo.UseWindowsUIXaml)}}.{{setMethodName}}(this, {{propertyInfo.PropertyName}}Property, value);
854+
855+
On{{propertyInfo.PropertyName}}Changed(value);
856+
}
857+
""", isMultiline: true);
858+
}
859+
else
860+
{
764861
writer.WriteLine($$"""
765862
{{GetExpressionWithTrailingSpace(propertyInfo.GetterAccessibility)}}get
766863
{
@@ -787,7 +884,6 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility)
787884
On{{propertyInfo.PropertyName}}Changed(value);
788885
}
789886
""", isMultiline: true);
790-
791887
}
792888
}
793889
}
@@ -824,15 +920,18 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility)
824920
}
825921
}
826922

827-
// On<PROPERTY_NAME>Set 'object' overload
828-
writer.WriteLine(skipIfPresent: true);
829-
writer.WriteLine($"""
830-
/// <summary>Executes the logic for when the <see langword="set"/> accessor <see cref="{propertyInfo.PropertyName}"/> is invoked</summary>
831-
/// <param name="propertyValue">The boxed property value that has been produced before assigning to <see cref="{propertyInfo.PropertyName}Property"/>.</param>
832-
/// <remarks>This method is invoked on the boxed value that is about to be passed to <see cref="SetValue"/> on <see cref="{propertyInfo.PropertyName}Property"/>.</remarks>
833-
""", isMultiline: true);
834-
writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false);
835-
writer.WriteLine($"partial void On{propertyInfo.PropertyName}Set(ref {objectTypeNameWithNullabilityAnnotation} propertyValue);");
923+
// On<PROPERTY_NAME>Set 'object' overload (only when the optimized XamlBindingHelper path is not used)
924+
if (propertyInfo.XamlBindingHelperSetMethodName is null)
925+
{
926+
writer.WriteLine(skipIfPresent: true);
927+
writer.WriteLine($"""
928+
/// <summary>Executes the logic for when the <see langword="set"/> accessor <see cref="{propertyInfo.PropertyName}"/> is invoked</summary>
929+
/// <param name="propertyValue">The boxed property value that has been produced before assigning to <see cref="{propertyInfo.PropertyName}Property"/>.</param>
930+
/// <remarks>This method is invoked on the boxed value that is about to be passed to <see cref="SetValue"/> on <see cref="{propertyInfo.PropertyName}Property"/>.</remarks>
931+
""", isMultiline: true);
932+
writer.WriteGeneratedAttributes(GeneratorName, includeNonUserCodeAttributes: false);
933+
writer.WriteLine($"partial void On{propertyInfo.PropertyName}Set(ref {objectTypeNameWithNullabilityAnnotation} propertyValue);");
934+
}
836935

837936
if (propertyInfo.TypeName != "object")
838937
{

components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,26 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
107107

108108
token.ThrowIfCancellationRequested();
109109

110+
// Get the optimized XamlBindingHelper method name for the property type, if applicable.
111+
// This is only used when the property type is not 'object' (which would gain nothing),
112+
// and the user hasn't provided their own 'On<PROPERTY_NAME>Set(ref object)' implementation.
113+
string? xamlBindingHelperSetMethodName = Execute.GetXamlBindingHelperSetMethodName(propertySymbol.Type);
114+
115+
if (xamlBindingHelperSetMethodName is not null)
116+
{
117+
// Skip the optimization for the 'object' type (it gains nothing)
118+
if (propertySymbol.Type.SpecialType == SpecialType.System_Object)
119+
{
120+
xamlBindingHelperSetMethodName = null;
121+
}
122+
else if (Execute.IsObjectSetCallbackImplemented(propertySymbol))
123+
{
124+
xamlBindingHelperSetMethodName = null;
125+
}
126+
}
127+
128+
token.ThrowIfCancellationRequested();
129+
110130
// We're using IsValueType here and not IsReferenceType to also cover unconstrained type parameter cases.
111131
// This will cover both reference types as well T when the constraints are not struct or unmanaged.
112132
// If this is true, it means the field storage can potentially be in a null state (even if not annotated).
@@ -160,6 +180,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
160180
IsSharedPropertyChangedCallbackImplemented: isSharedPropertyChangedCallbackImplemented,
161181
IsAdditionalTypesGenerationSupported: isAdditionalTypesGenerationSupported,
162182
UseWindowsUIXaml: useWindowsUIXaml,
183+
XamlBindingHelperSetMethodName: xamlBindingHelperSetMethodName,
163184
StaticFieldAttributes: staticFieldAttributes);
164185
})
165186
.WithTrackingName(WellKnownTrackingNames.Execute)

components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Models;
2626
/// <param name="IsSharedPropertyChangedCallbackImplemented">Indicates whether the WinRT-based shared property changed callback is implemented.</param>
2727
/// <param name="IsAdditionalTypesGenerationSupported">Indicates whether additional types can be generated.</param>
2828
/// <param name="UseWindowsUIXaml">Whether to use the UWP XAML or WinUI 3 XAML namespaces.</param>
29+
/// <param name="XamlBindingHelperSetMethodName">The name of the <c>XamlBindingHelper.SetPropertyFrom*</c> method to use for optimized setters, if available.</param>
2930
/// <param name="StaticFieldAttributes">The attributes to emit on the generated static field, if any.</param>
3031
internal sealed record DependencyPropertyInfo(
3132
HierarchyInfo Hierarchy,
@@ -44,4 +45,5 @@ internal sealed record DependencyPropertyInfo(
4445
bool IsSharedPropertyChangedCallbackImplemented,
4546
bool IsAdditionalTypesGenerationSupported,
4647
bool UseWindowsUIXaml,
48+
string? XamlBindingHelperSetMethodName,
4749
EquatableArray<AttributeInfo> StaticFieldAttributes);

0 commit comments

Comments
 (0)