From d31a9c2ae922bef031e75f5975c4791c531341a8 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2026 13:30:41 -0700 Subject: [PATCH 1/9] 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. --- .../Constants/WellKnownTypeNames.cs | 11 ++ .../DependencyPropertyGenerator.Execute.cs | 122 ++++++++++++++++-- .../DependencyPropertyGenerator.cs | 21 +++ .../Models/DependencyPropertyInfo.cs | 2 + 4 files changed, 143 insertions(+), 13 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs index da021cad5..cae9f07bc 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Constants/WellKnownTypeNames.cs @@ -94,4 +94,15 @@ public static string CreateDefaultValueCallback(bool useWindowsUIXaml) ? $"{WindowsUIXamlNamespace}.{nameof(CreateDefaultValueCallback)}" : $"{MicrosoftUIXamlNamespace}.{nameof(CreateDefaultValueCallback)}"; } + + /// + /// Gets the fully qualified type name for the XamlBindingHelper type. + /// + /// + public static string XamlBindingHelper(bool useWindowsUIXaml) + { + return useWindowsUIXaml + ? $"{WindowsUIXamlNamespace}.Markup.{nameof(XamlBindingHelper)}" + : $"{MicrosoftUIXamlNamespace}.Markup.{nameof(XamlBindingHelper)}"; + } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index 7736af4dc..14da98575 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -448,6 +448,60 @@ public static bool IsSharedPropertyChangedCallbackImplemented(IPropertySymbol pr return false; } + /// + /// Checks whether the user has provided an implementation for the On<PROPERTY_NAME>Set(ref object) partial method. + /// + /// The input instance to process. + /// Whether the user has an implementation for the boxed set callback. + public static bool IsObjectSetCallbackImplemented(IPropertySymbol propertySymbol) + { + // Check for any 'OnSet' methods with a single ref 'object' parameter + foreach (ISymbol symbol in propertySymbol.ContainingType.GetMembers($"On{propertySymbol.Name}Set")) + { + if (symbol is IMethodSymbol { IsStatic: false, ReturnsVoid: true, Parameters: [{ RefKind: RefKind.Ref, Type.SpecialType: SpecialType.System_Object }] }) + { + return true; + } + } + + return false; + } + + /// + /// Gets the XamlBindingHelper.SetPropertyFrom* method name for a given property type, if supported. + /// + /// The input to check. + /// The method name to use, or if the type is not supported. + public static string? GetXamlBindingHelperSetMethodName(ITypeSymbol typeSymbol) + { + // Check for well known primitive types first (these are the most common) + switch (typeSymbol.SpecialType) + { + case SpecialType.System_Boolean: return "SetPropertyFromBoolean"; + case SpecialType.System_Byte: return "SetPropertyFromByte"; + case SpecialType.System_Char: return "SetPropertyFromChar16"; + case SpecialType.System_Double: return "SetPropertyFromDouble"; + case SpecialType.System_Int32: return "SetPropertyFromInt32"; + case SpecialType.System_Int64: return "SetPropertyFromInt64"; + case SpecialType.System_Single: return "SetPropertyFromSingle"; + case SpecialType.System_String: return "SetPropertyFromString"; + case SpecialType.System_UInt32: return "SetPropertyFromUInt32"; + case SpecialType.System_UInt64: return "SetPropertyFromUInt64"; + case SpecialType.System_Object: return "SetPropertyFromObject"; + default: break; + } + + // Check for the remaining well known WinRT projected types + if (typeSymbol.HasFullyQualifiedMetadataName("System.DateTimeOffset")) return "SetPropertyFromDateTime"; + if (typeSymbol.HasFullyQualifiedMetadataName("System.TimeSpan")) return "SetPropertyFromTimeSpan"; + if (typeSymbol.HasFullyQualifiedMetadataName("Windows.Foundation.Point")) return "SetPropertyFromPoint"; + if (typeSymbol.HasFullyQualifiedMetadataName("Windows.Foundation.Rect")) return "SetPropertyFromRect"; + if (typeSymbol.HasFullyQualifiedMetadataName("Windows.Foundation.Size")) return "SetPropertyFromSize"; + if (typeSymbol.HasFullyQualifiedMetadataName("System.Uri")) return "SetPropertyFromUri"; + + return null; + } + /// /// Gathers all forwarded attributes for the generated property. /// @@ -708,21 +762,39 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) On{{propertyInfo.PropertyName}}Changing(__oldValue, value); field = value; - - object? __boxedValue = value; """, isMultiline: true); - writer.WriteLineIf(propertyInfo.TypeName != "object", $""" - On{propertyInfo.PropertyName}Set(ref __boxedValue); - """, isMultiline: true); - writer.Write($$""" + // If an optimized 'XamlBindingHelper' method is available, use it directly + if (propertyInfo.XamlBindingHelperSetMethodName is string setMethodName) + { + writer.Write($$""" - SetValue({{propertyInfo.PropertyName}}Property, __boxedValue); + global::{{WellKnownTypeNames.XamlBindingHelper(propertyInfo.UseWindowsUIXaml)}}.{{setMethodName}}(this, {{propertyInfo.PropertyName}}Property, value); - On{{propertyInfo.PropertyName}}Changed(value); - On{{propertyInfo.PropertyName}}Changed(__oldValue, value); - } - """, isMultiline: true); + On{{propertyInfo.PropertyName}}Changed(value); + On{{propertyInfo.PropertyName}}Changed(__oldValue, value); + } + """, isMultiline: true); + } + else + { + writer.Write($$""" + + object? __boxedValue = value; + """, isMultiline: true); + writer.WriteLineIf(propertyInfo.TypeName != "object", $""" + + On{propertyInfo.PropertyName}Set(ref __boxedValue); + """, isMultiline: true); + writer.Write($$""" + + SetValue({{propertyInfo.PropertyName}}Property, __boxedValue); + + On{{propertyInfo.PropertyName}}Changed(value); + On{{propertyInfo.PropertyName}}Changed(__oldValue, value); + } + """, isMultiline: true); + } // If the default value is not what the default field value would be, add an initializer if (propertyInfo.DefaultValue is not (DependencyPropertyDefaultValue.Null or DependencyPropertyDefaultValue.Default or DependencyPropertyDefaultValue.Callback)) @@ -758,9 +830,34 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) } """, isMultiline: true); } - else + else if (propertyInfo.XamlBindingHelperSetMethodName is string setMethodName) { // Same as above but with the extra typed hook for both accessors + writer.WriteLine($$""" + {{GetExpressionWithTrailingSpace(propertyInfo.GetterAccessibility)}}get + { + object? __boxedValue = GetValue({{propertyInfo.PropertyName}}Property); + + On{{propertyInfo.PropertyName}}Get(ref __boxedValue); + + {{propertyInfo.TypeNameWithNullabilityAnnotations}} __unboxedValue = ({{propertyInfo.TypeNameWithNullabilityAnnotations}})__boxedValue; + + On{{propertyInfo.PropertyName}}Get(ref __unboxedValue); + + return __unboxedValue; + } + {{GetExpressionWithTrailingSpace(propertyInfo.SetterAccessibility)}}set + { + On{{propertyInfo.PropertyName}}Set(ref value); + + global::{{WellKnownTypeNames.XamlBindingHelper(propertyInfo.UseWindowsUIXaml)}}.{{setMethodName}}(this, {{propertyInfo.PropertyName}}Property, value); + + On{{propertyInfo.PropertyName}}Changed(value); + } + """, isMultiline: true); + } + else + { writer.WriteLine($$""" {{GetExpressionWithTrailingSpace(propertyInfo.GetterAccessibility)}}get { @@ -787,7 +884,6 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility) On{{propertyInfo.PropertyName}}Changed(value); } """, isMultiline: true); - } } } diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs index b2082b826..3f6219eb4 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs @@ -107,6 +107,26 @@ public void Initialize(IncrementalGeneratorInitializationContext context) token.ThrowIfCancellationRequested(); + // Get the optimized XamlBindingHelper method name for the property type, if applicable. + // This is only used when the property type is not 'object' (which would gain nothing), + // and the user hasn't provided their own 'OnSet(ref object)' implementation. + string? xamlBindingHelperSetMethodName = Execute.GetXamlBindingHelperSetMethodName(propertySymbol.Type); + + if (xamlBindingHelperSetMethodName is not null) + { + // Skip the optimization for the 'object' type (it gains nothing) + if (propertySymbol.Type.SpecialType == SpecialType.System_Object) + { + xamlBindingHelperSetMethodName = null; + } + else if (Execute.IsObjectSetCallbackImplemented(propertySymbol)) + { + xamlBindingHelperSetMethodName = null; + } + } + + token.ThrowIfCancellationRequested(); + // We're using IsValueType here and not IsReferenceType to also cover unconstrained type parameter cases. // 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). @@ -160,6 +180,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) IsSharedPropertyChangedCallbackImplemented: isSharedPropertyChangedCallbackImplemented, IsAdditionalTypesGenerationSupported: isAdditionalTypesGenerationSupported, UseWindowsUIXaml: useWindowsUIXaml, + XamlBindingHelperSetMethodName: xamlBindingHelperSetMethodName, StaticFieldAttributes: staticFieldAttributes); }) .WithTrackingName(WellKnownTrackingNames.Execute) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs index dea504c67..de9af4b32 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyInfo.cs @@ -26,6 +26,7 @@ namespace CommunityToolkit.GeneratedDependencyProperty.Models; /// Indicates whether the WinRT-based shared property changed callback is implemented. /// Indicates whether additional types can be generated. /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. +/// The name of the XamlBindingHelper.SetPropertyFrom* method to use for optimized setters, if available. /// The attributes to emit on the generated static field, if any. internal sealed record DependencyPropertyInfo( HierarchyInfo Hierarchy, @@ -44,4 +45,5 @@ internal sealed record DependencyPropertyInfo( bool IsSharedPropertyChangedCallbackImplemented, bool IsAdditionalTypesGenerationSupported, bool UseWindowsUIXaml, + string? XamlBindingHelperSetMethodName, EquatableArray StaticFieldAttributes); From 83540387ab59601dd5b1a6a8394ad044af903277 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2026 14:08:33 -0700 Subject: [PATCH 2/9] Use XamlBindingHelper setters in tests Replace manual boxing and SetValue calls with global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFrom* calls across the dependency property generator tests. Update DataRow entries to include the target set method name and refactor the test helper to compute the setter body based on an optional setMethodName parameter. Add a new test (SingleProperty_Int32_WithNoCaching_WithObjectSetCallback) covering an int property with an object-based OnNumberSet callback to validate generated code that mixes object set callbacks and optimized XamlBindingHelper setters. Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Test_DependencyPropertyGenerator.cs | 412 +++++++++--------- 1 file changed, 204 insertions(+), 208 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 2dace4e5b..599f02b8b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -70,11 +70,7 @@ public partial int Number field = value; - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); OnNumberChanged(__oldValue, value); @@ -202,11 +198,7 @@ public partial int Number field = value; - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); OnNumberChanged(__oldValue, value); @@ -376,11 +368,7 @@ public partial int Number field = value; - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); OnNumberChanged(__oldValue, value); @@ -501,11 +489,7 @@ public partial int Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); } @@ -617,11 +601,7 @@ public partial int Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); } @@ -739,11 +719,7 @@ public partial int Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); } @@ -903,11 +879,7 @@ public partial int Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); } @@ -1166,11 +1138,7 @@ public partial int Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); } @@ -1336,11 +1304,7 @@ public partial int Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); } @@ -1510,11 +1474,7 @@ public partial int Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); } @@ -1680,11 +1640,7 @@ public partial string? Name field = value; - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); OnNameChanged(__oldValue, value); @@ -1805,11 +1761,7 @@ public partial string? Name { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); } @@ -1921,11 +1873,7 @@ public required partial string Name { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); } @@ -2042,11 +1990,7 @@ partial class MyControl { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); } @@ -2158,11 +2102,7 @@ public virtual partial string Name { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); } @@ -2281,11 +2221,7 @@ partial class MyControl { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); } @@ -2397,11 +2333,7 @@ private set { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); } @@ -2526,11 +2458,7 @@ public partial string? FirstName { OnFirstNameSet(ref value); - object? __boxedValue = value; - - OnFirstNameSet(ref __boxedValue); - - SetValue(FirstNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, FirstNameProperty, value); OnFirstNameChanged(value); } @@ -2558,11 +2486,7 @@ public partial string? LastName { OnLastNameSet(ref value); - object? __boxedValue = value; - - OnLastNameSet(ref __boxedValue); - - SetValue(LastNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, LastNameProperty, value); OnLastNameChanged(value); } @@ -2729,11 +2653,7 @@ public partial string? FirstName { OnFirstNameSet(ref value); - object? __boxedValue = value; - - OnFirstNameSet(ref __boxedValue); - - SetValue(FirstNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, FirstNameProperty, value); OnFirstNameChanged(value); } @@ -2761,11 +2681,7 @@ public partial string? LastName { OnLastNameSet(ref value); - object? __boxedValue = value; - - OnLastNameSet(ref __boxedValue); - - SetValue(LastNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, LastNameProperty, value); OnLastNameChanged(value); } @@ -2982,11 +2898,7 @@ public partial string? FirstName { OnFirstNameSet(ref value); - object? __boxedValue = value; - - OnFirstNameSet(ref __boxedValue); - - SetValue(FirstNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, FirstNameProperty, value); OnFirstNameChanged(value); } @@ -3014,11 +2926,7 @@ public partial string? LastName { OnLastNameSet(ref value); - object? __boxedValue = value; - - OnLastNameSet(ref __boxedValue); - - SetValue(LastNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, LastNameProperty, value); OnLastNameChanged(value); } @@ -3251,11 +3159,7 @@ public partial string? FirstName { OnFirstNameSet(ref value); - object? __boxedValue = value; - - OnFirstNameSet(ref __boxedValue); - - SetValue(FirstNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, FirstNameProperty, value); OnFirstNameChanged(value); } @@ -3283,11 +3187,7 @@ public partial string? LastName { OnLastNameSet(ref value); - object? __boxedValue = value; - - OnLastNameSet(ref __boxedValue); - - SetValue(LastNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, LastNameProperty, value); OnLastNameChanged(value); } @@ -3534,11 +3434,7 @@ public partial string? FirstName { OnFirstNameSet(ref value); - object? __boxedValue = value; - - OnFirstNameSet(ref __boxedValue); - - SetValue(FirstNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, FirstNameProperty, value); OnFirstNameChanged(value); } @@ -3566,11 +3462,7 @@ public partial string? LastName { OnLastNameSet(ref value); - object? __boxedValue = value; - - OnLastNameSet(ref __boxedValue); - - SetValue(LastNameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, LastNameProperty, value); OnLastNameChanged(value); } @@ -3799,11 +3691,7 @@ public partial int Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromInt32(this, NumberProperty, value); OnNumberChanged(value); } @@ -4047,11 +3935,7 @@ public partial string? Name { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); } @@ -4108,21 +3992,21 @@ public partial string? Name [TestMethod] // The 'string' type is special - [DataRow("string", "string", "object", "null")] - [DataRow("string", "string?", "object?", "null")] + [DataRow("string", "string", "object", "null", "", "SetPropertyFromString")] + [DataRow("string", "string?", "object?", "null", "", "SetPropertyFromString")] // Well known WinRT primitive types - [DataRow("int", "int", "object", "null")] - [DataRow("byte", "byte", "object", "null")] + [DataRow("int", "int", "object", "null", "", "SetPropertyFromInt32")] + [DataRow("byte", "byte", "object", "null", "", "SetPropertyFromByte")] [DataRow("sbyte", "sbyte", "object", "null")] [DataRow("short", "short", "object", "null")] [DataRow("ushort", "ushort", "object", "null")] - [DataRow("uint", "uint", "object", "null")] - [DataRow("long", "long", "object", "null")] - [DataRow("ulong", "ulong", "object", "null")] - [DataRow("char", "char", "object", "null")] - [DataRow("float", "float", "object", "null")] - [DataRow("double", "double", "object", "null")] + [DataRow("uint", "uint", "object", "null", "", "SetPropertyFromUInt32")] + [DataRow("long", "long", "object", "null", "", "SetPropertyFromInt64")] + [DataRow("ulong", "ulong", "object", "null", "", "SetPropertyFromUInt64")] + [DataRow("char", "char", "object", "null", "", "SetPropertyFromChar16")] + [DataRow("float", "float", "object", "null", "", "SetPropertyFromSingle")] + [DataRow("double", "double", "object", "null", "", "SetPropertyFromDouble")] // Well known WinRT struct types [DataRow("global::System.Numerics.Matrix3x2", "global::System.Numerics.Matrix3x2", "object", "null")] @@ -4132,11 +4016,11 @@ public partial string? Name [DataRow("global::System.Numerics.Vector2", "global::System.Numerics.Vector2", "object", "null")] [DataRow("global::System.Numerics.Vector3", "global::System.Numerics.Vector3", "object", "null")] [DataRow("global::System.Numerics.Vector4", "global::System.Numerics.Vector4", "object", "null")] - [DataRow("global::Windows.Foundation.Point", "global::Windows.Foundation.Point", "object", "null")] - [DataRow("global::Windows.Foundation.Rect", "global::Windows.Foundation.Rect", "object", "null")] - [DataRow("global::Windows.Foundation.Size", "global::Windows.Foundation.Size", "object", "null")] - [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "object", "null")] - [DataRow("global::System.DateTimeOffset", "global::System.DateTimeOffset", "object", "null")] + [DataRow("global::Windows.Foundation.Point", "global::Windows.Foundation.Point", "object", "null", "", "SetPropertyFromPoint")] + [DataRow("global::Windows.Foundation.Rect", "global::Windows.Foundation.Rect", "object", "null", "", "SetPropertyFromRect")] + [DataRow("global::Windows.Foundation.Size", "global::Windows.Foundation.Size", "object", "null", "", "SetPropertyFromSize")] + [DataRow("global::System.TimeSpan", "global::System.TimeSpan", "object", "null", "", "SetPropertyFromTimeSpan")] + [DataRow("global::System.DateTimeOffset", "global::System.DateTimeOffset", "object", "null", "", "SetPropertyFromDateTime")] // Well known WinRT enum types [DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "object", "null")] @@ -4164,8 +4048,20 @@ public void SingleProperty_MultipleTypes_WithNoCaching_DefaultValueIsOptimized( string propertyType, string defaultValueDefinition, string propertyMetadataExpression, - string? typeDefinition = "") + string? typeDefinition = "", + string? setMethodName = null) { + // Compute the setter body and partial method block based on whether the optimization is used + string setterBody = setMethodName is not null + ? $"global::Windows.UI.Xaml.Markup.XamlBindingHelper.{setMethodName}(this, NameProperty, value)" + : $$""" + object? __boxedValue = value; + + OnNameSet(ref __boxedValue); + + SetValue(NameProperty, __boxedValue) + """; + string source = $$""" using System; using System.Collections.Generic; @@ -4229,11 +4125,7 @@ public partial {{propertyType}} Name { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + {{setterBody}}; OnNameChanged(value); } @@ -4375,11 +4267,7 @@ public partial string? Name { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); } @@ -4491,11 +4379,7 @@ public partial bool IsSelected { OnIsSelectedSet(ref value); - object? __boxedValue = value; - - OnIsSelectedSet(ref __boxedValue); - - SetValue(IsSelectedProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromBoolean(this, IsSelectedProperty, value); OnIsSelectedChanged(value); } @@ -4550,16 +4434,16 @@ public partial bool IsSelected } [TestMethod] - [DataRow("bool", "bool", "null", "bool", "object", "null")] - [DataRow("bool", "bool", "bool", "bool", "object", "null")] - [DataRow("bool", "bool", "object", "object", "object", "new global::Windows.UI.Xaml.PropertyMetadata(default(bool))")] + [DataRow("bool", "bool", "null", "bool", "object", "null", "SetPropertyFromBoolean")] + [DataRow("bool", "bool", "bool", "bool", "object", "null", "SetPropertyFromBoolean")] + [DataRow("bool", "bool", "object", "object", "object", "new global::Windows.UI.Xaml.PropertyMetadata(default(bool))", "SetPropertyFromBoolean")] [DataRow("bool?", "bool?", "null", "bool?", "object?", "null")] [DataRow("bool?", "bool?", "bool?", "bool?", "object?", "null")] [DataRow("bool?", "bool?", "object", "object", "object?", "null")] [DataRow("bool?", "bool?", "bool", "bool", "object?", "new global::Windows.UI.Xaml.PropertyMetadata(null)")] - [DataRow("string?", "string?", "null", "string", "object?", "null")] - [DataRow("string?", "string?", "string", "string", "object?", "null")] - [DataRow("string?", "string?", "object", "object", "object?", "null")] + [DataRow("string?", "string?", "null", "string", "object?", "null", "SetPropertyFromString")] + [DataRow("string?", "string?", "string", "string", "object?", "null", "SetPropertyFromString")] + [DataRow("string?", "string?", "object", "object", "object?", "null", "SetPropertyFromString")] [DataRow("Visibility", "global::Windows.UI.Xaml.Visibility", "object", "object", "object", "new global::Windows.UI.Xaml.PropertyMetadata(default(global::Windows.UI.Xaml.Visibility))")] [DataRow("Visibility?", "global::Windows.UI.Xaml.Visibility?", "object", "object", "object?", "null")] public void SingleProperty_WithCustomMetadataType_WithNoCaching( @@ -4568,8 +4452,20 @@ public void SingleProperty_WithCustomMetadataType_WithNoCaching( string propertyType, string generatedPropertyType, string boxedType, - string propertyMetadata) + string propertyMetadata, + string? setMethodName = null) { + // Compute the setter body and partial method block based on whether the optimization is used + string setterBody = setMethodName is not null + ? $"global::Windows.UI.Xaml.Markup.XamlBindingHelper.{setMethodName}(this, IsSelectedProperty, value)" + : $$""" + object? __boxedValue = value; + + OnIsSelectedSet(ref __boxedValue); + + SetValue(IsSelectedProperty, __boxedValue) + """; + string source = $$""" using CommunityToolkit.WinUI; using Windows.UI.Xaml; @@ -4627,11 +4523,7 @@ public partial {{generatedDeclaredType}} IsSelected { OnIsSelectedSet(ref value); - object? __boxedValue = value; - - OnIsSelectedSet(ref __boxedValue); - - SetValue(IsSelectedProperty, __boxedValue); + {{setterBody}}; OnIsSelectedChanged(value); } @@ -4895,11 +4787,7 @@ public partial string? Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NumberProperty, value); OnNumberChanged(value); } @@ -5022,11 +4910,7 @@ public partial string? Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NumberProperty, value); OnNumberChanged(value); } @@ -5149,11 +5033,7 @@ public partial string? Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NumberProperty, value); OnNumberChanged(value); } @@ -5286,11 +5166,7 @@ public partial string? Number { OnNumberSet(ref value); - object? __boxedValue = value; - - OnNumberSet(ref __boxedValue); - - SetValue(NumberProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NumberProperty, value); OnNumberChanged(value); } @@ -5950,4 +5826,124 @@ public partial T2? Number CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyObject`4.g.cs", result), languageVersion: LanguageVersion.Preview); } + + [TestMethod] + public void SingleProperty_Int32_WithNoCaching_WithObjectSetCallback() + { + const string source = """ + using CommunityToolkit.WinUI; + using Windows.UI.Xaml; + + namespace MyNamespace; + + public partial class MyControl : DependencyObject + { + [GeneratedDependencyProperty] + public partial int Number { get; set; } + + partial void OnNumberSet(ref object propertyValue) + { + } + } + """; + + const string result = """ + // + #pragma warning disable + #nullable enable + + namespace MyNamespace + { + /// + partial class MyControl + { + /// + /// The backing instance for . + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + public static readonly global::Windows.UI.Xaml.DependencyProperty NumberProperty = global::Windows.UI.Xaml.DependencyProperty.Register( + name: "Number", + propertyType: typeof(int), + ownerType: typeof(MyControl), + typeMetadata: null); + + /// + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + [global::System.Diagnostics.DebuggerNonUserCode] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + public partial int Number + { + get + { + object? __boxedValue = GetValue(NumberProperty); + + OnNumberGet(ref __boxedValue); + + int __unboxedValue = (int)__boxedValue; + + OnNumberGet(ref __unboxedValue); + + return __unboxedValue; + } + set + { + OnNumberSet(ref value); + + object? __boxedValue = value; + + OnNumberSet(ref __boxedValue); + + SetValue(NumberProperty, __boxedValue); + + OnNumberChanged(value); + } + } + + /// Executes the logic for when the accessor is invoked + /// The raw property value that has been retrieved from . + /// This method is invoked on the boxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The unboxed property value that has been retrieved from . + /// This method is invoked on the unboxed value retrieved via on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberGet(ref int propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The boxed property value that has been produced before assigning to . + /// This method is invoked on the boxed value that is about to be passed to on . + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref object propertyValue); + + /// Executes the logic for when the accessor is invoked + /// The property value that is being assigned to . + /// This method is invoked on the raw value being assigned to , before is used. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberSet(ref int propertyValue); + + /// 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("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberChanged(int newValue); + + /// Executes the logic for when has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of is changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnNumberPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + + /// Executes the logic for when any dependency property has just changed. + /// Event data that is issued by any event that tracks changes to the effective value of this property. + /// This method is invoked by the infrastructure, after the value of any dependency property has just changed. + [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] + partial void OnPropertyChanged(global::Windows.UI.Xaml.DependencyPropertyChangedEventArgs e); + } + } + """; + + CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyControl.g.cs", result), languageVersion: LanguageVersion.Preview); + } } From f6cda27595c48521bb721695cfc9be974c5f9d61 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Tue, 14 Apr 2026 16:53:01 -0700 Subject: [PATCH 3/9] Use XamlBindingHelper to set dependency property Replace manual boxing and SetValue call in the generated Name property setter with a call to global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value) to avoid temporary object boxing and better align with XAML binding semantics. Also add a missing [TestMethod] attribute to the SingleProperty_Int32_WithNoCaching_WithDefaultValue_WithCallback test so it will be executed. --- .../Test_DependencyPropertyGenerator.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 599f02b8b..c96dc66d2 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -1017,11 +1017,7 @@ public partial string? Name { OnNameSet(ref value); - object? __boxedValue = value; - - OnNameSet(ref __boxedValue); - - SetValue(NameProperty, __boxedValue); + global::Windows.UI.Xaml.Markup.XamlBindingHelper.SetPropertyFromString(this, NameProperty, value); OnNameChanged(value); } @@ -1076,6 +1072,7 @@ public partial string? Name } [TestMethod] + public void SingleProperty_Int32_WithNoCaching_WithDefaultValue_WithCallback() { const string source = """ using CommunityToolkit.WinUI; From cdd2fc39a7a9881c0196e14e8e58ade150c799cb Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2026 10:26:33 -0700 Subject: [PATCH 4/9] Fix escaping in string literal unit test Adjust the expected value in Test_DependencyPropertyGenerator.cs for the verbatim string representing the Windows root path (C: backslash) so the expected literal uses the correct escaped backslash. This corrects an incorrect expected literal and prevents the test from failing due to improper escaping. --- .../Test_DependencyPropertyGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index c96dc66d2..f24ece868 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -958,7 +958,7 @@ public partial int Number [DataRow("@\"She said \"\"hi\"\"\"", "\"She said \\\"hi\\\"\"")] [DataRow("@\"Line1\nLine2\"", "\"Line1\\nLine2\"")] [DataRow("@\"Tab\there\"", "\"Tab\\there\"")] - [DataRow("@\"C:\\"", "\"C:\\\"")] + [DataRow("@\"C:\\\"", "\"C:\\\\\"")] [DataRow("@\"C:\\Program Files\\App\"", "\"C:\\\\Program Files\\\\App\"")] public void SingleProperty_String_WithNoCaching_WithDefaultValue(string inputLiteral, string expectedLiteral) { From 08ca23d7bdc9773153ec5cd0005b7aff32b52769 Mon Sep 17 00:00:00 2001 From: Arlo Date: Wed, 15 Apr 2026 13:28:26 -0500 Subject: [PATCH 5/9] Updated tooling to latest main --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index 587d1106c..394263e10 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 587d1106c959c500527566935c2444cfda0eddcb +Subproject commit 394263e1054cac12c5feb49ae2458f5e13681a68 From 3f7c1d65742d667934a982b3ffab344e0244c5d7 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2026 14:46:44 -0700 Subject: [PATCH 6/9] Add AnalyzerConfigOptionsProvider and use in tests Provide a test AnalyzerConfigOptionsProvider and wire it into the generator tests. Added DependencyPropertyGeneratorAnalyzerConfigOptionsProvider which exposes a singleton AnalyzerConfigOptionsProvider with GlobalOptions setting build_property.DependencyPropertyGeneratorUseWindowsUIXaml=true (plus a small SimpleAnalyzerConfigOptions implementation). Updated CSharpGeneratorTest{TGenerator}.cs to pass this optionsProvider when creating the CSharpGeneratorDriver (and to create generators via AsSourceGenerator where applicable) so the dependency property generator sees the expected MSBuild property during tests. --- .../CSharpGeneratorTest{TGenerator}.cs | 5 +- ...yGeneratorAnalyzerConfigOptionsProvider.cs | 55 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/DependencyPropertyGeneratorAnalyzerConfigOptionsProvider.cs diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs index ca28be27b..1c4b5e10e 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpGeneratorTest{TGenerator}.cs @@ -91,6 +91,7 @@ public static void VerifyIncrementalSteps( GeneratorDriver driver = CSharpGeneratorDriver.Create( generators: [new TGenerator().AsSourceGenerator()], + optionsProvider: DependencyPropertyGeneratorAnalyzerConfigOptionsProvider.Instance, driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true)); // Run the generator on the initial sources @@ -166,7 +167,9 @@ private static void RunGenerator( Compilation originalCompilation = CreateCompilation(source, languageVersion); // Create the generator driver with the specified generator - GeneratorDriver driver = CSharpGeneratorDriver.Create(new TGenerator()).WithUpdatedParseOptions(originalCompilation.SyntaxTrees.First().Options); + GeneratorDriver driver = CSharpGeneratorDriver.Create( + generators: [new TGenerator().AsSourceGenerator()], + optionsProvider: DependencyPropertyGeneratorAnalyzerConfigOptionsProvider.Instance).WithUpdatedParseOptions(originalCompilation.SyntaxTrees.First().Options); // Run all source generators on the input source code _ = driver.RunGeneratorsAndUpdateCompilation(originalCompilation, out compilation, out diagnostics); diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/DependencyPropertyGeneratorAnalyzerConfigOptionsProvider.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/DependencyPropertyGeneratorAnalyzerConfigOptionsProvider.cs new file mode 100644 index 000000000..bf0f7458b --- /dev/null +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/DependencyPropertyGeneratorAnalyzerConfigOptionsProvider.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace CommunityToolkit.GeneratedDependencyProperty.Tests.Helpers; + +/// +/// A custom providing the MSBuild properties needed by the dependency property generator. +/// +internal sealed class DependencyPropertyGeneratorAnalyzerConfigOptionsProvider : AnalyzerConfigOptionsProvider +{ + /// + /// The singleton instance. + /// + public static DependencyPropertyGeneratorAnalyzerConfigOptionsProvider Instance { get; } = new(); + + /// + public override AnalyzerConfigOptions GlobalOptions { get; } = new SimpleAnalyzerConfigOptions( + ImmutableDictionary.Empty.Add("build_property.DependencyPropertyGeneratorUseWindowsUIXaml", "true")); + + /// + public override AnalyzerConfigOptions GetOptions(SyntaxTree tree) + { + return SimpleAnalyzerConfigOptions.Empty; + } + + /// + public override AnalyzerConfigOptions GetOptions(AdditionalText textFile) + { + return SimpleAnalyzerConfigOptions.Empty; + } + + /// + /// A simple implementation backed by an immutable dictionary. + /// + /// The dictionary of options. + private sealed class SimpleAnalyzerConfigOptions(ImmutableDictionary options) : AnalyzerConfigOptions + { + /// + /// An empty instance. + /// + public static SimpleAnalyzerConfigOptions Empty { get; } = new(ImmutableDictionary.Empty); + + /// + public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) + { + return options.TryGetValue(key, out value); + } + } +} From 87ae0401562d9feaa8cad60aa8c06d9819698423 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2026 14:51:18 -0700 Subject: [PATCH 7/9] Format setterBody raw strings in tests Reformat the computed setterBody strings in Test_DependencyPropertyGenerator to use multi-line raw string literals and adjust the indentation where they are injected (for Name and IsSelected cases). This is a whitespace/formatting change to improve readability and consistency; no functional behavior changes are intended. --- .../Test_DependencyPropertyGenerator.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index f24ece868..de5209666 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -4050,8 +4050,10 @@ public void SingleProperty_MultipleTypes_WithNoCaching_DefaultValueIsOptimized( { // Compute the setter body and partial method block based on whether the optimization is used string setterBody = setMethodName is not null - ? $"global::Windows.UI.Xaml.Markup.XamlBindingHelper.{setMethodName}(this, NameProperty, value)" - : $$""" + ? $""" + global::Windows.UI.Xaml.Markup.XamlBindingHelper.{setMethodName}(this, NameProperty, value) + """ + : """ object? __boxedValue = value; OnNameSet(ref __boxedValue); @@ -4122,7 +4124,7 @@ public partial {{propertyType}} Name { OnNameSet(ref value); - {{setterBody}}; + {{setterBody}}; OnNameChanged(value); } @@ -4454,8 +4456,10 @@ public void SingleProperty_WithCustomMetadataType_WithNoCaching( { // Compute the setter body and partial method block based on whether the optimization is used string setterBody = setMethodName is not null - ? $"global::Windows.UI.Xaml.Markup.XamlBindingHelper.{setMethodName}(this, IsSelectedProperty, value)" - : $$""" + ? $""" + global::Windows.UI.Xaml.Markup.XamlBindingHelper.{setMethodName}(this, IsSelectedProperty, value) + """ + : """ object? __boxedValue = value; OnIsSelectedSet(ref __boxedValue); @@ -4520,7 +4524,7 @@ public partial {{generatedDeclaredType}} IsSelected { OnIsSelectedSet(ref value); - {{setterBody}}; + {{setterBody}}; OnIsSelectedChanged(value); } From 2e945d4199c335f317002f56de0e53932d83d8a9 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Wed, 15 Apr 2026 14:58:59 -0700 Subject: [PATCH 8/9] Add .globalconfig enabling Windows UI XAML Add a /.globalconfig analyzer config to test helper classes to enable Windows UI XAML for the DependencyPropertyGenerator tests. Sets build_property.DependencyPropertyGeneratorUseWindowsUIXaml = true in CSharpAnalyzerTest, CSharpCodeFixTest and CSharpSuppressorTest so the generator uses Windows.UI.Xaml APIs during tests. --- .../Helpers/CSharpAnalyzerTest{TAnalyzer}.cs | 5 +++++ .../Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs | 4 ++++ .../Helpers/CSharpSuppressorTest{TSuppressor}.cs | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs index 071190a59..0d76a6c5b 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs @@ -39,6 +39,11 @@ internal sealed class CSharpAnalyzerTest : CSharpAnalyzerTest diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs index 16ba5a5d5..bef0ef05c 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpCodeFixerTest{TAnalyzer,TCodeFixer}.cs @@ -43,6 +43,10 @@ public CSharpCodeFixTest(LanguageVersion languageVersion) TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location)); TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location)); TestState.AnalyzerConfigFiles.Add(("/.editorconfig", "[*]\nend_of_line = lf")); + TestState.AnalyzerConfigFiles.Add(("/.globalconfig", """ + is_global = true + build_property.DependencyPropertyGeneratorUseWindowsUIXaml = true + """)); } /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpSuppressorTest{TSuppressor}.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpSuppressorTest{TSuppressor}.cs index bae55d74b..1b179cc72 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpSuppressorTest{TSuppressor}.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Helpers/CSharpSuppressorTest{TSuppressor}.cs @@ -63,6 +63,10 @@ public CSharpSuppressorTest( TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location)); TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location)); TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location)); + TestState.AnalyzerConfigFiles.Add(("/.globalconfig", """ + is_global = true + build_property.DependencyPropertyGeneratorUseWindowsUIXaml = true + """)); } /// From 0975aac3e036e92477c7c38e0beb6bdc566e83d1 Mon Sep 17 00:00:00 2001 From: Arlo Date: Wed, 15 Apr 2026 17:56:09 -0500 Subject: [PATCH 9/9] Bumped tooling to latest main --- tooling | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tooling b/tooling index 394263e10..48ba90f5f 160000 --- a/tooling +++ b/tooling @@ -1 +1 @@ -Subproject commit 394263e1054cac12c5feb49ae2458f5e13681a68 +Subproject commit 48ba90f5fd02aaf755725c1896189e2166572042