Skip to content

Commit

Permalink
Handle more codegen cases with explicit types
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio0694 committed Jan 4, 2025
1 parent e73eb24 commit ccd997c
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -243,13 +243,15 @@ public static bool TryGetAccessibilityModifiers(
/// <param name="attributeData">The input <see cref="AttributeData"/> that triggered the annotation.</param>
/// <param name="typeName">The type name for the generated property (without nullability annotations).</param>
/// <param name="typeNameWithNullabilityAnnotations">The type name for the generated property, including nullability annotations.</param>
/// <param name=",etadataTypeName">The type name for the metadata declaration of the property, if explicitly set.</param>
/// <param name="metadataTypeName">The type name for the metadata declaration of the property, if explicitly set.</param>
/// <param name="metadataTypeSymbol">The type symbol for the metadata declaration of the property, if explicitly set.</param>
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();
Expand All @@ -264,29 +266,33 @@ public static void GetPropertyTypes(
if (propertyType is { Kind: TypedConstantKind.Type, IsNull: false, Value: ITypeSymbol typeSymbol })
{
metadataTypeName = typeSymbol.GetFullyQualifiedName();
metadataTypeSymbol = typeSymbol;

return;
}
}

// By default, we'll just match the declared property type
metadataTypeName = null;
metadataTypeSymbol = null;
}

/// <summary>
/// Gets the default value to use to initialize the generated property, if explicitly specified.
/// </summary>
/// <param name="attributeData">The input <see cref="AttributeData"/> that triggered the annotation.</param>
/// <param name="propertySymbol">The input <see cref="IPropertySymbol"/> instance.</param>
/// <param name="metadataTypeSymbol">The type symbol for the metadata declaration of the property, if explicitly set.</param>
/// <param name="semanticModel">The <see cref="SemanticModel"/> for the current compilation.</param>
/// <param name="useWindowsUIXaml">Whether to use the UWP XAML or WinUI 3 XAML namespaces.</param>
/// <param name="token">The <see cref="CancellationToken"/> used to cancel the operation, if needed.</param>
/// <returns>The default value to use to initialize the generated property.</returns>
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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ public override string ToString()
}
}

/// <summary>
/// A <see cref="DependencyPropertyDefaultValue"/> type representing an explicit <see langword="null"/> value.
/// </summary>
/// <remarks>This is used in some scenarios with mismatched metadata types.</remarks>
public sealed record ExplicitNull : DependencyPropertyDefaultValue
{
/// <summary>
/// The shared <see cref="ExplicitNull"/> instance (the type is stateless).
/// </summary>
public static ExplicitNull Instance { get; } = new();

/// <inheritdoc/>
public override string ToString()
{
return "null";
}
}

/// <summary>
/// A <see cref="DependencyPropertyDefaultValue"/> type representing default value for a specific type.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -4449,21 +4453,21 @@ partial class MyControl
name: "IsSelected",
propertyType: typeof({{generatedPropertyType}}),
ownerType: typeof(MyControl),
typeMetadata: null);
typeMetadata: {{propertyMetadata}});
/// <inheritdoc/>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", <ASSEMBLY_VERSION>)]
[global::System.Diagnostics.DebuggerNonUserCode]
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
public partial {{declaredType}} IsSelected
public partial {{generatedDeclaredType}} IsSelected
{
get
{
object? __boxedValue = GetValue(IsSelectedProperty);
OnIsSelectedGet(ref __boxedValue);
{{declaredType}} __unboxedValue = ({{declaredType}})__boxedValue;
{{generatedDeclaredType}} __unboxedValue = ({{generatedDeclaredType}})__boxedValue;
OnIsSelectedGet(ref __unboxedValue);
Expand Down Expand Up @@ -4493,7 +4497,7 @@ public partial {{declaredType}} IsSelected
/// <param name="propertyValue">The unboxed property value that has been retrieved from <see cref="IsSelectedProperty"/>.</param>
/// <remarks>This method is invoked on the unboxed value retrieved via <see cref="GetValue"/> on <see cref="IsSelectedProperty"/>.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", <ASSEMBLY_VERSION>)]
partial void OnIsSelectedGet(ref {{declaredType}} propertyValue);
partial void OnIsSelectedGet(ref {{generatedDeclaredType}} propertyValue);
/// <summary>Executes the logic for when the <see langword="set"/> accessor <see cref="IsSelected"/> is invoked</summary>
/// <param name="propertyValue">The boxed property value that has been produced before assigning to <see cref="IsSelectedProperty"/>.</param>
Expand All @@ -4505,13 +4509,13 @@ public partial {{declaredType}} IsSelected
/// <param name="propertyValue">The property value that is being assigned to <see cref="IsSelected"/>.</param>
/// <remarks>This method is invoked on the raw value being assigned to <see cref="IsSelected"/>, before <see cref="SetValue"/> is used.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", <ASSEMBLY_VERSION>)]
partial void OnIsSelectedSet(ref {{declaredType}} propertyValue);
partial void OnIsSelectedSet(ref {{generatedDeclaredType}} propertyValue);
/// <summary>Executes the logic for when <see cref="IsSelected"/> 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="IsSelected"/> is changed.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", <ASSEMBLY_VERSION>)]
partial void OnIsSelectedChanged({{declaredType}} newValue);
partial void OnIsSelectedChanged({{generatedDeclaredType}} newValue);
/// <summary>Executes the logic for when <see cref="IsSelected"/> has just changed.</summary>
/// <param name="e">Event data that is issued by any event that tracks changes to the effective value of this property.</param>
Expand Down

0 comments on commit ccd997c

Please sign in to comment.