From ccd997cf06d7a02d77f86666a69aacb27db771e3 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Sat, 4 Jan 2025 02:05:16 +0100 Subject: [PATCH] Handle more codegen cases with explicit types --- .../DependencyPropertyGenerator.Execute.cs | 25 ++++++++++-- .../DependencyPropertyGenerator.cs | 6 ++- .../Models/DependencyPropertyDefaultValue.cs | 18 +++++++++ .../Test_DependencyPropertyGenerator.cs | 38 ++++++++++--------- 4 files changed, 64 insertions(+), 23 deletions(-) diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs index d35d8a9c4..ec89759ea 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.Execute.cs @@ -243,13 +243,15 @@ public static bool TryGetAccessibilityModifiers( /// The input that triggered the annotation. /// The type name for the generated property (without nullability annotations). /// The type name for the generated property, including nullability annotations. - /// The type name for the metadata declaration of the property, if explicitly set. + /// The type name for the metadata declaration of the property, if explicitly set. + /// The type symbol for the metadata declaration of the property, if explicitly set. public static void GetPropertyTypes( IPropertySymbol propertySymbol, AttributeData attributeData, out string typeName, out string typeNameWithNullabilityAnnotations, - out string? metadataTypeName) + out string? metadataTypeName, + out ITypeSymbol? metadataTypeSymbol) { // These type names are always present and directly derived from the property type typeName = propertySymbol.Type.GetFullyQualifiedName(); @@ -264,6 +266,7 @@ public static void GetPropertyTypes( if (propertyType is { Kind: TypedConstantKind.Type, IsNull: false, Value: ITypeSymbol typeSymbol }) { metadataTypeName = typeSymbol.GetFullyQualifiedName(); + metadataTypeSymbol = typeSymbol; return; } @@ -271,6 +274,7 @@ public static void GetPropertyTypes( // By default, we'll just match the declared property type metadataTypeName = null; + metadataTypeSymbol = null; } /// @@ -278,6 +282,7 @@ public static void GetPropertyTypes( /// /// The input that triggered the annotation. /// The input instance. + /// The type symbol for the metadata declaration of the property, if explicitly set. /// The for the current compilation. /// Whether to use the UWP XAML or WinUI 3 XAML namespaces. /// The used to cancel the operation, if needed. @@ -285,8 +290,9 @@ public static void GetPropertyTypes( public static DependencyPropertyDefaultValue GetDefaultValue( AttributeData attributeData, IPropertySymbol propertySymbol, + ITypeSymbol? metadataTypeSymbol, SemanticModel semanticModel, - bool useWindowsUIXaml, + bool useWindowsUIXaml, CancellationToken token) { // First, check if we have a callback @@ -355,10 +361,21 @@ public static DependencyPropertyDefaultValue GetDefaultValue( // First we need to special case non nullable values, as for those we need 'default'. if (!propertySymbol.Type.IsDefaultValueNull()) { + // We need special logic to handle cases where the metadata type is different. For instance, + // the XAML initialization won't work if the metadata type on a property is just 'object'. + ITypeSymbol effectiveMetadataTypeSymbol = metadataTypeSymbol ?? propertySymbol.Type; + // For non nullable types, we return 'default(T)', unless we can optimize for projected types return new DependencyPropertyDefaultValue.Default( TypeName: propertySymbol.Type.GetFullyQualifiedName(), - IsProjectedType: propertySymbol.Type.IsWellKnownWinRTProjectedValueType(useWindowsUIXaml)); + IsProjectedType: effectiveMetadataTypeSymbol.IsWellKnownWinRTProjectedValueType(useWindowsUIXaml)); + } + + // If the property type is nullable, but the metadata type is not, and it's a projected WinRT value + // type (meaning that XAML would initialize it to a value), we need to explicitly set it to 'null'. + if (metadataTypeSymbol?.IsWellKnownWinRTProjectedValueType(useWindowsUIXaml) is true) + { + return DependencyPropertyDefaultValue.ExplicitNull.Instance; } // For all other ones, we can just use the 'null' placeholder again diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs index 0b5d6bb11..b2082b826 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/DependencyPropertyGenerator.cs @@ -94,7 +94,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.Attributes[0], out string typeName, out string typeNameWithNullabilityAnnotations, - out string? metadataTypeName); + out string? metadataTypeName, + out ITypeSymbol? metadataTypeSymbol); token.ThrowIfCancellationRequested(); @@ -115,8 +116,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) DependencyPropertyDefaultValue defaultValue = Execute.GetDefaultValue( context.Attributes[0], propertySymbol, + metadataTypeSymbol, context.SemanticModel, - useWindowsUIXaml, + useWindowsUIXaml, token); // The 'UnsetValue' can only be used when local caching is disabled diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs index 993c9d717..108541a2e 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.SourceGenerators/Models/DependencyPropertyDefaultValue.cs @@ -28,6 +28,24 @@ public override string ToString() } } + /// + /// A type representing an explicit value. + /// + /// This is used in some scenarios with mismatched metadata types. + public sealed record ExplicitNull : DependencyPropertyDefaultValue + { + /// + /// The shared instance (the type is stateless). + /// + public static ExplicitNull Instance { get; } = new(); + + /// + public override string ToString() + { + return "null"; + } + } + /// /// A type representing default value for a specific type. /// diff --git a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs index 8f3b276ae..6561a7826 100644 --- a/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs +++ b/components/DependencyPropertyGenerator/CommunityToolkit.DependencyPropertyGenerator.Tests/Test_DependencyPropertyGenerator.cs @@ -4400,21 +4400,25 @@ public partial bool IsSelected } [TestMethod] - [DataRow("bool", "null", "bool", "object")] - [DataRow("bool", "bool", "bool", "object")] - [DataRow("bool", "object", "object", "object")] - [DataRow("bool?", "null", "bool?", "object?")] - [DataRow("bool?", "bool?", "bool?", "object?")] - [DataRow("bool?", "object", "object", "object?")] - [DataRow("bool?", "bool", "bool", "object?")] - [DataRow("string?", "null", "string", "object?")] - [DataRow("string?", "string", "string", "object?")] - [DataRow("string?", "object", "object", "object?")] + [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")] + [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("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( string declaredType, + string generatedDeclaredType, string propertyType, string generatedPropertyType, - string boxedType) + string boxedType, + string propertyMetadata) { string source = $$""" using CommunityToolkit.WinUI; @@ -4449,13 +4453,13 @@ partial class MyControl name: "IsSelected", propertyType: typeof({{generatedPropertyType}}), ownerType: typeof(MyControl), - typeMetadata: null); + typeMetadata: {{propertyMetadata}}); /// [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", )] [global::System.Diagnostics.DebuggerNonUserCode] [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - public partial {{declaredType}} IsSelected + public partial {{generatedDeclaredType}} IsSelected { get { @@ -4463,7 +4467,7 @@ public partial {{declaredType}} IsSelected OnIsSelectedGet(ref __boxedValue); - {{declaredType}} __unboxedValue = ({{declaredType}})__boxedValue; + {{generatedDeclaredType}} __unboxedValue = ({{generatedDeclaredType}})__boxedValue; OnIsSelectedGet(ref __unboxedValue); @@ -4493,7 +4497,7 @@ public partial {{declaredType}} IsSelected /// 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 OnIsSelectedGet(ref {{declaredType}} propertyValue); + partial void OnIsSelectedGet(ref {{generatedDeclaredType}} propertyValue); /// Executes the logic for when the accessor is invoked /// The boxed property value that has been produced before assigning to . @@ -4505,13 +4509,13 @@ public partial {{declaredType}} IsSelected /// 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 OnIsSelectedSet(ref {{declaredType}} propertyValue); + partial void OnIsSelectedSet(ref {{generatedDeclaredType}} 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 OnIsSelectedChanged({{declaredType}} newValue); + partial void OnIsSelectedChanged({{generatedDeclaredType}} 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.