Skip to content

Commit

Permalink
Handle supported default value types
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio0694 committed Dec 13, 2024
1 parent be37f5f commit 26d8f03
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,44 @@ public static DependencyPropertyDefaultValue GetDefaultValue(
// First we need to special case non nullable values, as for those we need 'default'.
if (propertySymbol.Type is { IsValueType: true } and not INamedTypeSymbol { IsGenericType: true, ConstructedFrom.SpecialType: SpecialType.System_Nullable_T })
{
return new DependencyPropertyDefaultValue.Default(propertySymbol.Type.GetFullyQualifiedName());
string fullyQualifiedTypeName = propertySymbol.Type.GetFullyQualifiedName();

// There is a special case for this: if the type of the property is a built-in WinRT
// projected enum type or struct type (ie. some projected value type in general, except
// for 'Nullable<T>' values), then we can just use 'null' and bypass creating the property
// metadata. The WinRT runtime will automatically instantiate a default value for us.
if (propertySymbol.Type.IsContainedInNamespace(WellKnownTypeNames.XamlNamespace(useWindowsUIXaml)) ||
propertySymbol.Type.IsContainedInNamespace("Windows.Foundation.Numerics"))
{
return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true);
}

// Special case a few more well known value types that are mapped for WinRT
if (propertySymbol.Type.Name is "Point" or "Rect" or "Size" &&
propertySymbol.Type.IsContainedInNamespace("Windows.Foundation"))
{
return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true);
}

// Lastly, special case the well known primitive types
if (propertySymbol.Type.SpecialType is
SpecialType.System_Int32 or
SpecialType.System_Byte or
SpecialType.System_SByte or
SpecialType.System_Int16 or
SpecialType.System_UInt16 or
SpecialType.System_UInt32 or
SpecialType.System_Int64 or
SpecialType.System_UInt64 or
SpecialType.System_Char or
SpecialType.System_Single or
SpecialType.System_Double)
{
return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: true);
}

// In all other cases, just use 'default(T)' here
return new DependencyPropertyDefaultValue.Default(fullyQualifiedTypeName, IsProjectedType: false);
}

// For all other ones, we can just use the 'null' placeholder again
Expand Down Expand Up @@ -436,7 +473,7 @@ static string GetExpressionWithTrailingSpace(Accessibility accessibility)
string typeMetadata = propertyInfo switch
{
// Shared codegen
{ DefaultValue: DependencyPropertyDefaultValue.Null or DependencyPropertyDefaultValue.Default, IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false }
{ DefaultValue: DependencyPropertyDefaultValue.Null or DependencyPropertyDefaultValue.Default(_, true), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false }
=> "null",
{ DefaultValue: DependencyPropertyDefaultValue.Callback(string methodName), IsPropertyChangedCallbackImplemented: false, IsSharedPropertyChangedCallbackImplemented: false }
=> $"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,51 @@ static void BuildFrom(ISymbol? symbol, ref readonly ImmutableArrayBuilder<char>

BuildFrom(symbol, in builder);
}

/// <summary>
/// Checks whether a given type is contained in a namespace with a specified name.
/// </summary>
/// <param name="symbol">The input <see cref="ITypeSymbol"/> instance.</param>
/// <param name="namespaceName">The namespace to check.</param>
/// <returns>Whether <paramref name="symbol"/> is contained within <paramref name="namespaceName"/>.</returns>
public static bool IsContainedInNamespace(this ITypeSymbol symbol, string? namespaceName)
{
static void BuildFrom(INamespaceSymbol? symbol, ref readonly ImmutableArrayBuilder<char> builder)
{
switch (symbol)
{
// Namespaces that are nested also append a leading '.'
case INamespaceSymbol { ContainingNamespace.IsGlobalNamespace: false }:
BuildFrom(symbol.ContainingNamespace, in builder);
builder.Add('.');
builder.AddRange(symbol.MetadataName.AsSpan());
break;

// Other namespaces (i.e. the one right before global) skip the leading '.'
case INamespaceSymbol { IsGlobalNamespace: false }:
builder.AddRange(symbol.MetadataName.AsSpan());
break;
default:
break;
}
}

// Special case for no containing namespace
if (symbol.ContainingNamespace is not { } containingNamespace)
{
return namespaceName is null;
}

// Special case if the type is directly in the global namespace
if (containingNamespace.IsGlobalNamespace)
{
return containingNamespace.MetadataName == namespaceName;
}

using ImmutableArrayBuilder<char> builder = new();

BuildFrom(containingNamespace, in builder);

return builder.WrittenSpan.SequenceEqual(namespaceName.AsSpan());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public override string ToString()
/// A <see cref="DependencyPropertyDefaultValue"/> type representing default value for a specific type.
/// </summary>
/// <param name="TypeName">The input type name.</param>
public sealed record Default(string TypeName) : DependencyPropertyDefaultValue
/// <param name="IsProjectedType">Indicates whether the type is projected, meaning WinRT can default initialize it automatically if needed.</param>
public sealed record Default(string TypeName, bool IsProjectedType) : DependencyPropertyDefaultValue
{
/// <inheritdoc/>
public override string ToString()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.Foundation;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;

Expand Down Expand Up @@ -178,6 +179,7 @@ private static CSharpCompilation CreateCompilation(string source, LanguageVersio
IEnumerable<MetadataReference> metadataReferences =
[
.. Net80.References.All,
MetadataReference.CreateFromFile(typeof(Point).Assembly.Location),
MetadataReference.CreateFromFile(typeof(ApplicationView).Assembly.Location),
MetadataReference.CreateFromFile(typeof(DependencyProperty).Assembly.Location),
MetadataReference.CreateFromFile(typeof(GeneratedDependencyPropertyAttribute).Assembly.Location)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2702,64 +2702,68 @@ public partial string? Name

[TestMethod]

// The 'string' and 'object' types are special
[DataRow("string", "string", "null")]
[DataRow("string", "string?", "null")]
[DataRow("object", "object", "null")]
[DataRow("object", "object?", "null")]
// The 'string' type is special
[DataRow("string", "string", "object", "null")]
[DataRow("string", "string?", "object?", "null")]

// Well known WinRT primitive types
[DataRow("int", "int", "null")]
[DataRow("byte", "byte", "null")]
[DataRow("sbyte", "sbyte", "null")]
[DataRow("short", "short", "null")]
[DataRow("ushort", "ushort", "null")]
[DataRow("uint", "uint", "null")]
[DataRow("long", "long", "null")]
[DataRow("ulong", "ulong", "null")]
[DataRow("char", "char", "null")]
[DataRow("float", "float", "null")]
[DataRow("double", "double", "null")]
[DataRow("int", "int", "object", "null")]
[DataRow("byte", "byte", "object", "null")]
[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")]

// Well known WinRT struct types
[DataRow("Matrix3x2", "Matrix3x2", "null")]
[DataRow("Matrix4x4", "Matrix4x4", "null")]
[DataRow("Plane", "Plane", "null")]
[DataRow("Quaternion", "Quaternion", "null")]
[DataRow("Vector2", "Vector2", "null")]
[DataRow("Vector3", "Vector3", "null")]
[DataRow("Vector4", "Vector4", "null")]
[DataRow("Point", "Point", "null")]
[DataRow("Rect", "Rect", "null")]
[DataRow("global::Windows.Foundation.Numerics.Matrix3x2", "global::Windows.Foundation.Numerics.Matrix3x2", "object", "null")]
[DataRow("global::Windows.Foundation.Numerics.Matrix4x4", "global::Windows.Foundation.Numerics.Matrix4x4", "object", "null")]
[DataRow("global::Windows.Foundation.Numerics.Plane", "global::Windows.Foundation.Numerics.Plane", "object", "null")]
[DataRow("global::Windows.Foundation.Numerics.Quaternion", "global::Windows.Foundation.Numerics.Quaternion", "object", "null")]
[DataRow("global::Windows.Foundation.Numerics.Vector2", "global::Windows.Foundation.Numerics.Vector2", "object", "null")]
[DataRow("global::Windows.Foundation.Numerics.Vector3", "global::Windows.Foundation.Numerics.Vector3", "object", "null")]
[DataRow("global::Windows.Foundation.Numerics.Vector4", "global::Windows.Foundation.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")]

// Well known WinRT enum types
[DataRow("Visibility", "Visibility", "null")]
[DataRow("global::Windows.UI.Xaml.Visibility", "global::Windows.UI.Xaml.Visibility", "object", "null")]

// Nullable types, they're always just 'null'
[DataRow("int?", "int?", "null")]
[DataRow("byte?", "byte?", "null")]
[DataRow("char?", "char?", "null")]
[DataRow("long?", "long?", "null")]
[DataRow("float?", "float?", "null")]
[DataRow("double?", "double?", "null")]
[DataRow("DateTimeOffset?", "DateTimeOffset?", "null")]
[DataRow("TimeSpan?", "TimeSpan?", "null")]
[DataRow("Guid?", "Guid?", "null")]
[DataRow("KeyValuePair<int, float>?", "KeyValuePair<int, float>?", "null")]
[DataRow("int?", "int?", "object?", "null")]
[DataRow("byte?", "byte?", "object?", "null")]
[DataRow("char?", "char?", "object?", "null")]
[DataRow("long?", "long?", "object?", "null")]
[DataRow("float?", "float?", "object?", "null")]
[DataRow("double?", "double?", "object?", "null")]
[DataRow("global::System.DateTimeOffset?", "global::System.DateTimeOffset?", "object?", "null")]
[DataRow("global::System.TimeSpan?", "global::System.TimeSpan?", "object?", "null")]
[DataRow("global::System.Guid?", "global::System.Guid?", "object?", "null")]
[DataRow("global::System.Collections.Generic.KeyValuePair<int, float>?", "global::System.Collections.Generic.KeyValuePair<int, float>?", "object?", "null")]

// Custom struct types
[DataRow("MyStruct", "MyStruct", "new PropertyMetadata(default(MyStruct))", "public struct MyStruct { public int X; }")]
[DataRow("MyStruct", "MyStruct", "new PropertyMetadata(default(MyStruct))", "public struct MyStruct { public string X { get; set; }")]
[DataRow("global::MyNamespace.MyStruct", "global::MyNamespace.MyStruct", "object", "new global::Windows.UI.Xaml.PropertyMetadata(default(global::MyNamespace.MyStruct))", "public struct MyStruct { public int X; }")]
[DataRow("global::MyNamespace.MyStruct", "global::MyNamespace.MyStruct", "object", "new global::Windows.UI.Xaml.PropertyMetadata(default(global::MyNamespace.MyStruct))", "public struct MyStruct { public string X { get; set; } }")]

// Custom enum types
[DataRow("MyEnum", "MyEnum", "new PropertyMetadata(default(MyEnum))", "public enum MyEnum { A, B, C }")]
[DataRow("global::MyNamespace.MyEnum", "global::MyNamespace.MyEnum", "object", "new global::Windows.UI.Xaml.PropertyMetadata(default(global::MyNamespace.MyEnum))", "public enum MyEnum { A, B, C }")]
public void SingleProperty_MultipleTypes_WithNoCaching_DefaultValueIsOptimized(
string dependencyPropertyType,
string propertyType,
string defaultValueDefinition,
string propertyMetadataExpression,
string? typeDefinition = "")
{
string source = $$"""
using System;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.Foundation.Numerics;
using Windows.UI.Xaml;
using CommunityToolkit.WinUI;
Expand Down Expand Up @@ -2832,7 +2836,7 @@ public partial {{propertyType}} Name
/// <param name="propertyValue">The raw property value that has been retrieved from <see cref="NameProperty"/>.</param>
/// <remarks>This method is invoked on the boxed value retrieved via <see cref="GetValue"/> on <see cref="NameProperty"/>.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", <ASSEMBLY_VERSION>)]
partial void OnNameGet(ref object? propertyValue);
partial void OnNameGet(ref {{defaultValueDefinition}} propertyValue);
/// <summary>Executes the logic for when the <see langword="get"/> accessor <see cref="Name"/> is invoked</summary>
/// <param name="propertyValue">The unboxed property value that has been retrieved from <see cref="NameProperty"/>.</param>
Expand All @@ -2844,7 +2848,7 @@ public partial {{propertyType}} Name
/// <param name="propertyValue">The boxed property value that has been produced before assigning to <see cref="NameProperty"/>.</param>
/// <remarks>This method is invoked on the boxed value that is about to be passed to <see cref="SetValue"/> on <see cref="NameProperty"/>.</remarks>
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.WinUI.DependencyPropertyGenerator", <ASSEMBLY_VERSION>)]
partial void OnNameSet(ref object? propertyValue);
partial void OnNameSet(ref {{defaultValueDefinition}} propertyValue);
/// <summary>Executes the logic for when the <see langword="set"/> accessor <see cref="Name"/> is invoked</summary>
/// <param name="propertyValue">The property value that is being assigned to <see cref="Name"/>.</param>
Expand Down

0 comments on commit 26d8f03

Please sign in to comment.