diff --git a/build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml b/build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml
index c89ba88e43..a9b2308f8a 100644
--- a/build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml
+++ b/build/AzurePipelineTemplates/CsWinRT-Test-Steps.yml
@@ -22,6 +22,18 @@ steps:
--no-build
testRunTitle: Unit Tests
+# Run Source Generator 2 Tests
+ - task: DotNetCoreCLI@2
+ displayName: Run Source Generator 2 Tests
+ condition: and(succeeded(), or(eq(variables['BuildPlatform'], 'x86'), eq(variables['BuildPlatform'], 'x64')))
+ inputs:
+ command: test
+ projects: 'src/Tests/SourceGenerator2Test/SourceGenerator2Test.csproj'
+ arguments: >
+ /p:platform=$(BuildPlatform);configuration=$(BuildConfiguration)
+ --no-build
+ testRunTitle: Source Generator 2 Tests
+
# Run Host Tests
- task: CmdLine@2
displayName: Run Host Tests
diff --git a/src/Authoring/WinRT.SourceGenerator2/AnalyzerReleases.Shipped.md b/src/Authoring/WinRT.SourceGenerator2/AnalyzerReleases.Shipped.md
new file mode 100644
index 0000000000..d6343c4c03
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/AnalyzerReleases.Shipped.md
@@ -0,0 +1,17 @@
+; Shipped analyzer releases
+; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
+## Release 3.0.0
+
+### New Rules
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
+CSWINRT2000 | WindowsRuntime.SourceGenerator | Error | Invalid '[GeneratedCustomPropertyProvider]' target type
+CSWINRT2001 | WindowsRuntime.SourceGenerator | Error | Missing 'partial' for '[GeneratedCustomPropertyProvider]' target type
+CSWINRT2002 | WindowsRuntime.SourceGenerator | Error | 'ICustomPropertyProvider' interface type not available
+CSWINRT2003 | WindowsRuntime.SourceGenerator | Error | Existing 'ICustomPropertyProvider' member implementation
+CSWINRT2004 | WindowsRuntime.SourceGenerator | Error | Null property name in '[GeneratedCustomPropertyProvider]'
+CSWINRT2005 | WindowsRuntime.SourceGenerator | Error | Null indexer type in '[GeneratedCustomPropertyProvider]'
+CSWINRT2006 | WindowsRuntime.SourceGenerator | Error | Property name not found for '[GeneratedCustomPropertyProvider]'
+CSWINRT2007 | WindowsRuntime.SourceGenerator | Error | Indexer type not found for '[GeneratedCustomPropertyProvider]'
+CSWINRT2008 | WindowsRuntime.SourceGenerator | Error | Static indexer for '[GeneratedCustomPropertyProvider]'
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/AnalyzerReleases.Unshipped.md b/src/Authoring/WinRT.SourceGenerator2/AnalyzerReleases.Unshipped.md
new file mode 100644
index 0000000000..7634792eb7
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/AnalyzerReleases.Unshipped.md
@@ -0,0 +1,6 @@
+; Unshipped analyzer release
+; https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
+
+### New Rules
+Rule ID | Category | Severity | Notes
+--------|----------|----------|-------
diff --git a/src/Authoring/WinRT.SourceGenerator2/AuthoringExportTypesGenerator.Execute.cs b/src/Authoring/WinRT.SourceGenerator2/AuthoringExportTypesGenerator.Execute.cs
index 0eb151d997..a2b215bf52 100644
--- a/src/Authoring/WinRT.SourceGenerator2/AuthoringExportTypesGenerator.Execute.cs
+++ b/src/Authoring/WinRT.SourceGenerator2/AuthoringExportTypesGenerator.Execute.cs
@@ -112,7 +112,7 @@ public static void EmitManagedExports(SourceProductionContext context, Authoring
return;
}
- IndentedTextWriter writer = new(literalLength: 0, formattedCount: 0);
+ IndentedTextWriter writer = new(literalLength: 0, formattedCount: 0); // TODO: adjust the literal length
// Emit the '[WindowsRuntimeComponentAssemblyExportsType]' attribute so other tooling (including this same generator)
// can reliably find the generated export types from other assemblies, which is needed when merging activation factories.
@@ -134,14 +134,14 @@ namespace ABI.{{info.AssemblyName.EscapeIdentifierName()}};
///
/// Contains the managed exports for activating types from the current project in an authoring scenario.
///
- """);
+ """, isMultiline: true);
// Emit the standard generated attributes, and also mark the type as hidden, since it's generated as a public type
writer.WriteGeneratedAttributes(nameof(AuthoringExportTypesGenerator), useFullyQualifiedTypeNames: false);
writer.WriteLine($$"""
[EditorBrowsable(EditorBrowsableState.Never)]
public static unsafe class ManagedExports
- """);
+ """, isMultiline: true);
// Indent via a block, as we'll also need to emit custom logic for the activation factory
using (writer.WriteBlock())
@@ -155,7 +155,7 @@ public static unsafe class ManagedExports
/// The class identifier that is associated with an activatable runtime class.
/// The resulting pointer to the activation factory that corresponds with the class specified by .
public static void* GetActivationFactory(ReadOnlySpan activatableClassId)
- """);
+ """, isMultiline: true);
using (writer.WriteBlock())
{
@@ -167,7 +167,7 @@ public static unsafe class ManagedExports
static extern void* AuthoringGetActivationFactory(
[UnsafeAccessorType("ABI.{info.AssemblyName.EscapeIdentifierName()}.ManagedExports, WinRT.Authoring")] object? _,
ReadOnlySpan activatableClassId);
- """);
+ """, isMultiline: true);
}
// Emit the specialized code to redirect the activation
@@ -181,7 +181,7 @@ public static unsafe class ManagedExports
void* activationFactory = AuthoringGetActivationFactory(null, activatableClassId);
return activationFactory ?? ReferencedManagedExports.GetActivationFactory(activatableClassId);
- """);
+ """, isMultiline: true);
}
else if (info.Options.MergeReferencedActivationFactories)
{
@@ -198,7 +198,7 @@ public static nint GetActivationFactory(string activatableClassId)
{
return (nint)GetActivationFactory(activatableClassId.AsSpan());
}
- """);
+ """, isMultiline: true);
}
// Emit a helper type with the logic for merging activaton factories, if needed
@@ -209,7 +209,7 @@ public static nint GetActivationFactory(string activatableClassId)
///
/// Contains the logic for activating types from transitively referenced Windows Runtime components.
///
- """);
+ """, isMultiline: true);
writer.WriteGeneratedAttributes(nameof(AuthoringExportTypesGenerator), useFullyQualifiedTypeNames: false);
writer.WriteLine("file static unsafe class ReferencedManagedExports");
@@ -222,7 +222,7 @@ public static nint GetActivationFactory(string activatableClassId)
/// The class identifier that is associated with an activatable runtime class.
/// The resulting pointer to the activation factory that corresponds with the class specified by .
public static void* GetActivationFactory(ReadOnlySpan activatableClassId)
- """);
+ """, isMultiline: true);
using (writer.WriteBlock())
{
@@ -239,7 +239,7 @@ public static nint GetActivationFactory(string activatableClassId)
{
return activationFactory;
}
- """);
+ """, isMultiline: true);
}
// No match across the referenced factories, we can't do anything else
@@ -290,7 +290,7 @@ namespace ABI.{info.AssemblyName.EscapeIdentifierName()};
///
/// Contains the native exports for activating types from the current project in an authoring scenario.
///
- """);
+ """, isMultiline: true);
// Emit the attributes to mark the code as generated, and to exclude it from code coverage as well. We also use a
// file-scoped P/Invoke, so we don't take a dependency on private implementation detail types from 'WinRT.Runtime.dll'.
@@ -383,7 +383,7 @@ file static class WindowsRuntimeImports
[SupportedOSPlatform("windows6.2")]
public static extern char* WindowsGetStringRawBuffer(HSTRING @string, uint* length);
}
- """);
+ """, isMultiline: true);
context.AddSource("NativeExports.g.cs", writer.ToStringAndClear());
}
diff --git a/src/Authoring/WinRT.SourceGenerator2/CustomPropertyProviderGenerator.Emit.cs b/src/Authoring/WinRT.SourceGenerator2/CustomPropertyProviderGenerator.Emit.cs
new file mode 100644
index 0000000000..c63a1979e9
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/CustomPropertyProviderGenerator.Emit.cs
@@ -0,0 +1,463 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using WindowsRuntime.SourceGenerator.Models;
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+public partial class CustomPropertyProviderGenerator
+{
+ ///
+ /// Generation methods for .
+ ///
+ private static class Emit
+ {
+ ///
+ /// Emits the ICustomPropertyProvider implementation for a given annotated type.
+ ///
+ /// The value to use.
+ /// The input state to use.
+ public static void WriteCustomPropertyProviderImplementation(SourceProductionContext context, CustomPropertyProviderInfo info)
+ {
+ const int ApproximateTypeDeclarationLength = 2048;
+
+ // Approximate a close enough starting length to reduce copies
+ int approximateLiteralLength = ApproximateTypeDeclarationLength + (ApproximateTypeDeclarationLength * info.CustomProperties.Length);
+
+ IndentedTextWriter writer = new(literalLength: approximateLiteralLength, formattedCount: 0);
+
+ // Emit the implementation on the annotated type
+ info.TypeHierarchy.WriteSyntax(
+ state: info,
+ writer: ref writer,
+ baseTypes: [info.FullyQualifiedCustomPropertyProviderInterfaceName],
+ memberCallbacks: [
+ WriteCustomPropertyProviderType,
+ WriteCustomPropertyProviderGetCustomProperty,
+ WriteCustomPropertyProviderGetIndexedProperty,
+ WriteCustomPropertyProviderGetStringRepresentation]);
+
+ // Emit the additional property implementation types, if needed
+ WriteCustomPropertyImplementationTypes(info, ref writer);
+
+ // Add the source file for the annotated type
+ context.AddSource($"{info.TypeHierarchy.FullyQualifiedMetadataName}.g.cs", writer.ToStringAndClear());
+ }
+
+ ///
+ /// Writes the ICustomPropertyProvider.Type implementation.
+ ///
+ ///
+ ///
+ private static void WriteCustomPropertyProviderType(CustomPropertyProviderInfo info, ref IndentedTextWriter writer)
+ {
+ writer.WriteLine("/// ");
+ writer.WriteGeneratedAttributes(nameof(CustomPropertyProviderGenerator), includeNonUserCodeAttributes: false);
+ writer.WriteLine($"global::System.Type {info.FullyQualifiedCustomPropertyProviderInterfaceName}.Type => typeof({info.TypeHierarchy.Hierarchy[0].QualifiedName});");
+ }
+
+ ///
+ /// Writes the ICustomPropertyProvider.GetCustomProperty implementation.
+ ///
+ ///
+ ///
+ private static void WriteCustomPropertyProviderGetCustomProperty(CustomPropertyProviderInfo info, ref IndentedTextWriter writer)
+ {
+ writer.WriteLine("/// ");
+ writer.WriteGeneratedAttributes(nameof(CustomPropertyProviderGenerator), includeNonUserCodeAttributes: false);
+ writer.WriteLine($"""
+ {info.FullyQualifiedCustomPropertyInterfaceName} {info.FullyQualifiedCustomPropertyProviderInterfaceName}.GetCustomProperty(string name)
+ """, isMultiline: true);
+
+ using (writer.WriteBlock())
+ {
+ // Fast-path if there are no non-indexer custom properties
+ if (!info.CustomProperties.Any(static info => !info.IsIndexer))
+ {
+ writer.WriteLine("return null;");
+
+ return;
+ }
+
+ writer.WriteLine("return name switch");
+ writer.WriteLine("{");
+ writer.IncreaseIndent();
+
+ // Emit a switch case for each available property
+ foreach (CustomPropertyInfo propertyInfo in info.CustomProperties)
+ {
+ if (propertyInfo.IsIndexer)
+ {
+ continue;
+ }
+
+ // Return the cached property implementation for the current custom property
+ writer.WriteLine($"nameof({propertyInfo.Name}) => global::WindowsRuntime.Xaml.Generated.{info.TypeHierarchy.Hierarchy[0].QualifiedName}_{propertyInfo.Name}.Instance,");
+ }
+
+ // If there's no matching property, just return 'null'
+ writer.WriteLine("_ => null");
+ writer.DecreaseIndent();
+ writer.WriteLine("};");
+ }
+ }
+
+ ///
+ /// Writes the ICustomPropertyProvider.GetIndexedProperty implementation.
+ ///
+ ///
+ ///
+ private static void WriteCustomPropertyProviderGetIndexedProperty(CustomPropertyProviderInfo info, ref IndentedTextWriter writer)
+ {
+ writer.WriteLine("/// ");
+ writer.WriteGeneratedAttributes(nameof(CustomPropertyProviderGenerator), includeNonUserCodeAttributes: false);
+ writer.WriteLine($"""
+ {info.FullyQualifiedCustomPropertyInterfaceName} {info.FullyQualifiedCustomPropertyProviderInterfaceName}.GetIndexedProperty(string name, global::System.Type type)
+ """, isMultiline: true);
+
+ using (writer.WriteBlock())
+ {
+ // Fast-path if there are no indexer custom properties
+ if (!info.CustomProperties.Any(static info => info.IsIndexer))
+ {
+ writer.WriteLine("return null;");
+
+ return;
+ }
+
+ // Switch over the type of all available indexer properties
+ foreach (CustomPropertyInfo propertyInfo in info.CustomProperties)
+ {
+ if (!propertyInfo.IsIndexer)
+ {
+ continue;
+ }
+
+ string implementationTypeName = $"{info.TypeHierarchy.Hierarchy[0].QualifiedName}_this_{propertyInfo.FullyQualifiedIndexerTypeName.Replace("global::", "").EscapeIdentifierName()}";
+
+ // If we have a match, return the cached property implementation for the current indexer
+ writer.WriteLine(skipIfPresent: true);
+ writer.WriteLine($$"""
+ if (type == typeof({{propertyInfo.FullyQualifiedIndexerTypeName}}))
+ {
+ return global::WindowsRuntime.Xaml.Generated.{{implementationTypeName}}.Instance;
+ }
+ """, isMultiline: true);
+ }
+
+ // If there's no matching property, just return 'null'
+ writer.WriteLine(skipIfPresent: true);
+ writer.WriteLine("return null;");
+ }
+ }
+
+ ///
+ /// Writes the ICustomPropertyProvider.GetStringRepresentation implementation.
+ ///
+ ///
+ ///
+ private static void WriteCustomPropertyProviderGetStringRepresentation(CustomPropertyProviderInfo info, ref IndentedTextWriter writer)
+ {
+ writer.WriteLine("/// ");
+ writer.WriteGeneratedAttributes(nameof(CustomPropertyProviderGenerator), includeNonUserCodeAttributes: false);
+ writer.WriteLine($$"""
+ string {{info.FullyQualifiedCustomPropertyProviderInterfaceName}}.GetStringRepresentation()
+ {
+ return ToString();
+ }
+ """, isMultiline: true);
+ }
+
+ ///
+ /// Writes the ICustomProperty implementation types.
+ ///
+ ///
+ ///
+ private static void WriteCustomPropertyImplementationTypes(CustomPropertyProviderInfo info, ref IndentedTextWriter writer)
+ {
+ // If we have no custom properties, we don't need to emit any additional code
+ if (info.CustomProperties.IsEmpty)
+ {
+ return;
+ }
+
+ // All generated types go in this well-known namespace
+ writer.WriteLine();
+ writer.WriteLine("namespace WindowsRuntime.Xaml.Generated");
+
+ using (writer.WriteBlock())
+ {
+ // Using declarations for well-known namespaces we can use with simple names
+ writer.WriteLine("using global::System;");
+ writer.WriteLine("using global::System.CodeDom.Compiler;");
+ writer.WriteLine("using global::System.Diagnostics;");
+ writer.WriteLine("using global::System.Diagnostics.CodeAnalysis;");
+ writer.WriteLine($"using global::{info.FullyQualifiedCustomPropertyProviderInterfaceName.Replace(".ICustomPropertyProvider", "")};");
+ writer.WriteLine();
+
+ // Write all custom property implementation types
+ for (int i = 0; i < info.CustomProperties.Length; i++)
+ {
+ // Ensure members are correctly separated by one line
+ if (i > 0)
+ {
+ writer.WriteLine();
+ }
+
+ CustomPropertyInfo propertyInfo = info.CustomProperties[i];
+
+ // Generate the correct implementation types for normal properties or indexer properties
+ if (propertyInfo.IsIndexer)
+ {
+ WriteIndexedCustomPropertyImplementationType(info, propertyInfo, ref writer);
+ }
+ else
+ {
+ WriteNonIndexedCustomPropertyImplementationType(info, propertyInfo, ref writer);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Writes a single non indexed ICustomProperty implementation type.
+ ///
+ ///
+ /// The input instance for the property to generate the implementation type for.
+ ///
+ private static void WriteNonIndexedCustomPropertyImplementationType(CustomPropertyProviderInfo info, CustomPropertyInfo propertyInfo, ref IndentedTextWriter writer)
+ {
+ string userTypeName = info.TypeHierarchy.GetFullyQualifiedTypeName().Replace("global::", "");
+ string implementationTypeName = $"{info.TypeHierarchy.Hierarchy[0].QualifiedName}_{propertyInfo.Name}";
+
+ // Emit a type as follows:
+ //
+ // file sealed class : ICustomProperty
+ writer.WriteLine($"""
+ ///
+ /// The implementation for .
+ ///
+ """, isMultiline: true);
+ writer.WriteGeneratedAttributes(nameof(CustomPropertyProviderGenerator), useFullyQualifiedTypeNames: false);
+ writer.WriteLine($"file sealed class {implementationTypeName} : ICustomProperty");
+
+ using (writer.WriteBlock())
+ {
+ // Emit all 'ICustomProperty' members for an indexer proprty, and the singleton field
+ writer.WriteLine($$"""
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly {{implementationTypeName}} Instance = new();
+
+ ///
+ public bool CanRead => {{propertyInfo.CanRead.ToString().ToLowerInvariant()}};
+
+ ///
+ public bool CanWrite => {{propertyInfo.CanWrite.ToString().ToLowerInvariant()}};
+
+ ///
+ public string Name => "{{propertyInfo.Name}}";
+
+ ///
+ public Type Type => typeof({{propertyInfo.FullyQualifiedTypeName}});
+ """, isMultiline: true);
+
+ writer.WriteLine();
+
+ // Emit 'GetValue' depending on whether the property is readable and whether it's static
+ if (propertyInfo.CanRead && propertyInfo.IsStatic)
+ {
+ writer.WriteLine($$"""
+ ///
+ public object GetValue(object target)
+ {
+ return {{info.TypeHierarchy.GetFullyQualifiedTypeName()}}.{{propertyInfo.Name}};
+ }
+ """, isMultiline: true);
+ }
+ else if (propertyInfo.CanRead)
+ {
+ writer.WriteLine($$"""
+ ///
+ public object GetValue(object target)
+ {
+ return (({{info.TypeHierarchy.GetFullyQualifiedTypeName()}})target).{{propertyInfo.Name}};
+ }
+ """, isMultiline: true);
+ }
+ else
+ {
+ writer.WriteLine("""
+ ///
+ public object GetValue(object target)
+ {
+ throw new NotSupportedException();
+ }
+ """, isMultiline: true);
+ }
+
+ writer.WriteLine();
+
+ // Emit 'SetValue' depending on whether the property is writable and whether it's static
+ if (propertyInfo.CanWrite && propertyInfo.IsStatic)
+ {
+ writer.WriteLine($$"""
+ ///
+ public void SetValue(object target, object value)
+ {
+ {{info.TypeHierarchy.GetFullyQualifiedTypeName()}}.{{propertyInfo.Name}} = ({{propertyInfo.FullyQualifiedTypeName}})value;
+ }
+ """, isMultiline: true);
+ }
+ else if (propertyInfo.CanWrite)
+ {
+ writer.WriteLine($$"""
+ ///
+ public void SetValue(object target, object value)
+ {
+ (({{info.TypeHierarchy.GetFullyQualifiedTypeName()}})target).{{propertyInfo.Name}} = ({{propertyInfo.FullyQualifiedTypeName}})value;
+ }
+ """, isMultiline: true);
+ }
+ else
+ {
+ writer.WriteLine("""
+ ///
+ public void SetValue(object target, object value)
+ {
+ throw new NotSupportedException();
+ }
+ """, isMultiline: true);
+ }
+
+ // Emit the property accessors (indexer properties can only be instance properties)
+ writer.WriteLine();
+ writer.WriteLine("""
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ """, isMultiline: true);
+ }
+ }
+
+ ///
+ /// Writes a single indexed ICustomProperty implementation type.
+ ///
+ ///
+ /// The input instance for the property to generate the implementation type for.
+ ///
+ private static void WriteIndexedCustomPropertyImplementationType(CustomPropertyProviderInfo info, CustomPropertyInfo propertyInfo, ref IndentedTextWriter writer)
+ {
+ string userTypeName = info.TypeHierarchy.GetFullyQualifiedTypeName().Replace("global::", "");
+ string indexerTypeName = propertyInfo.FullyQualifiedIndexerTypeName!.Replace("global::", "");
+ string implementationTypeName = $"{info.TypeHierarchy.Hierarchy[0].QualifiedName}_this_{indexerTypeName.EscapeIdentifierName()}";
+
+ // Emit the implementation type, same as above
+ writer.WriteLine($"""
+ ///
+ /// The implementation for 's indexer.
+ ///
+ """, isMultiline: true);
+ writer.WriteGeneratedAttributes(nameof(CustomPropertyProviderGenerator), useFullyQualifiedTypeNames: false);
+ writer.WriteLine($"file sealed class {implementationTypeName} : ICustomProperty");
+
+ using (writer.WriteBlock())
+ {
+ // Emit all 'ICustomProperty' members for a normal property, and the singleton field
+ writer.WriteLine($$"""
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly {{implementationTypeName}} Instance = new();
+
+ ///
+ public bool CanRead => {{propertyInfo.CanRead.ToString().ToLowerInvariant()}};
+
+ ///
+ public bool CanWrite => {{propertyInfo.CanWrite.ToString().ToLowerInvariant()}};
+
+ ///
+ public string Name => "this";
+
+ ///
+ public Type Type => typeof({{propertyInfo.FullyQualifiedTypeName}});
+ """, isMultiline: true);
+
+ // This is an indexed property, so non indexed ones will always throw
+ writer.WriteLine();
+ writer.WriteLine($$"""
+ ///
+ public object GetValue(object target)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ throw new NotSupportedException();
+ }
+ """, isMultiline: true);
+
+ // Emit the indexer property accessors, conditionally based on CanRead/CanWrite
+ writer.WriteLine();
+
+ if (propertyInfo.CanRead)
+ {
+ writer.WriteLine($$"""
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ return (({{info.TypeHierarchy.GetFullyQualifiedTypeName()}})target)[({{propertyInfo.FullyQualifiedIndexerTypeName}})index];
+ }
+ """, isMultiline: true);
+ }
+ else
+ {
+ writer.WriteLine("""
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+ """, isMultiline: true);
+ }
+
+ writer.WriteLine();
+
+ if (propertyInfo.CanWrite)
+ {
+ writer.WriteLine($$"""
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ (({{info.TypeHierarchy.GetFullyQualifiedTypeName()}})target)[({{propertyInfo.FullyQualifiedIndexerTypeName}})index] = ({{propertyInfo.FullyQualifiedTypeName}})value;
+ }
+ """, isMultiline: true);
+ }
+ else
+ {
+ writer.WriteLine("""
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ """, isMultiline: true);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/CustomPropertyProviderGenerator.Execute.cs b/src/Authoring/WinRT.SourceGenerator2/CustomPropertyProviderGenerator.Execute.cs
new file mode 100644
index 0000000000..1063c06976
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/CustomPropertyProviderGenerator.Execute.cs
@@ -0,0 +1,197 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using WindowsRuntime.SourceGenerator.Models;
+
+#pragma warning disable CS8620, IDE0046 // TODO: remove 'CS8620' suppression when compiler warning is fixed
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+public partial class CustomPropertyProviderGenerator
+{
+ ///
+ /// Generation methods for .
+ ///
+ private static class Execute
+ {
+ ///
+ /// Checks whether a target node needs the ICustomPropertyProvider implementation.
+ ///
+ /// The target instance to check.
+ /// The cancellation token for the operation.
+ /// Whether is a valid target for the ICustomPropertyProvider implementation.
+ [SuppressMessage("Style", "IDE0060", Justification = "The cancellation token is supplied by Roslyn.")]
+ public static bool IsTargetNodeValid(SyntaxNode node, CancellationToken token)
+ {
+ // We only care about class and struct types, all other types are not valid targets
+ if (!node.IsAnyKind(SyntaxKind.ClassDeclaration, SyntaxKind.RecordDeclaration, SyntaxKind.StructDeclaration, SyntaxKind.RecordStructDeclaration))
+ {
+ return false;
+ }
+
+ // If the type is static, abstract, or 'ref', we cannot implement 'ICustomPropertyProvider' on it
+ if (((MemberDeclarationSyntax)node).Modifiers.ContainsAny(SyntaxKind.StaticKeyword, SyntaxKind.AbstractKeyword, SyntaxKind.RefKeyword))
+ {
+ return false;
+ }
+
+ // We can only generate the 'ICustomPropertyProvider' implementation if the type is 'partial'.
+ // Additionally, all parent type declarations must also be 'partial', for generation to work.
+ if (!((MemberDeclarationSyntax)node).IsPartialAndWithinPartialTypeHierarchy)
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Tries to get the instance for a given annotated symbol.
+ ///
+ /// The value to use.
+ /// The cancellation token for the operation.
+ /// The resulting instance, if processed successfully.
+ public static CustomPropertyProviderInfo? GetCustomPropertyProviderInfo(GeneratorAttributeSyntaxContextWithOptions context, CancellationToken token)
+ {
+ bool useWindowsUIXamlProjections = context.GlobalOptions.GetCsWinRTUseWindowsUIXamlProjections();
+
+ token.ThrowIfCancellationRequested();
+
+ string customPropertyProviderMetadataName = useWindowsUIXamlProjections
+ ? "Windows.UI.Xaml.Data.ICustomPropertyProvider"
+ : "Microsoft.UI.Xaml.Data.ICustomPropertyProvider";
+
+ // Make sure that the target interface types are available. This is mostly because when UWP XAML projections
+ // are not used, the target project must be referencing the WinUI package to get the right interface type.
+ // If we can't find it, we just stop here. A separate diagnostic analyzer will emit the right diagnostic.
+ if (context.SemanticModel.Compilation.GetTypeByMetadataName(customPropertyProviderMetadataName) is not { } customPropertyProviderType)
+ {
+ return null;
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ // Ensure we have a valid named type symbol for the annotated type
+ if (context.TargetSymbol is not INamedTypeSymbol typeSymbol)
+ {
+ return null;
+ }
+
+ // If the annotated type already implements 'ICustomPropertyProvider' and has or inherits any
+ // member implementations, we can't generate the implementation. A separate diagnostic analyzer
+ // will emit the right diagnostic for this case.
+ if (typeSymbol.HasAnyImplementedMembersForInterface(customPropertyProviderType))
+ {
+ return null;
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ // Get the type hierarchy (needed to correctly generate sources for nested types too)
+ HierarchyInfo typeHierarchy = HierarchyInfo.From(typeSymbol);
+
+ token.ThrowIfCancellationRequested();
+
+ // Gather all custom properties, depending on how the attribute was used
+ EquatableArray customProperties = GetCustomPropertyInfo(typeSymbol, context.Attributes[0], token);
+
+ token.ThrowIfCancellationRequested();
+
+ return new(
+ TypeHierarchy: typeHierarchy,
+ CustomProperties: customProperties,
+ UseWindowsUIXamlProjections: useWindowsUIXamlProjections);
+ }
+
+ ///
+ /// Gets the values for all applicable properties of a target type.
+ ///
+ /// The annotated type.
+ /// The attribute to trigger generation.
+ /// The cancellation token for the operation.
+ /// The resulting values for .
+ private static EquatableArray GetCustomPropertyInfo(INamedTypeSymbol typeSymbol, AttributeData attribute, CancellationToken token)
+ {
+ string?[]? propertyNames = null;
+ ITypeSymbol?[]? indexerTypes = null;
+
+ token.ThrowIfCancellationRequested();
+
+ // If using the attribute constructor taking explicit property names and indexer
+ // types, get those names to filter the properties. We'll validate them later.
+ if (attribute.ConstructorArguments is [
+ { Kind: TypedConstantKind.Array, Values: var typedPropertyNames },
+ { Kind: TypedConstantKind.Array, Values: var typedIndexerTypes }])
+ {
+ propertyNames = [.. typedPropertyNames.Select(tc => tc.Value as string)];
+ indexerTypes = [.. typedIndexerTypes.Select(tc => tc.Value as ITypeSymbol)];
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ using PooledArrayBuilder customPropertyInfo = new();
+
+ // Enumerate all members of the annotated type to discover all properties
+ foreach (ISymbol symbol in typeSymbol.EnumerateAllMembers())
+ {
+ token.ThrowIfCancellationRequested();
+
+ // Only gather public properties, and ignore overrides (we'll find the base definition instead).
+ // We also ignore partial property implementations, as we only care about the partial definitions.
+ if (symbol is not IPropertySymbol { DeclaredAccessibility: Accessibility.Public, IsOverride: false, PartialDefinitionPart: null } propertySymbol)
+ {
+ continue;
+ }
+
+ // Indexer properties must be instance properties
+ if (propertySymbol.IsIndexer && propertySymbol.IsStatic)
+ {
+ continue;
+ }
+
+ // We can only support indexers with a single parameter.
+ // If there's more, an analyzer will emit a warning.
+ if (propertySymbol.Parameters.Length > 1)
+ {
+ continue;
+ }
+
+ ITypeSymbol? indexerType = propertySymbol.Parameters.FirstOrDefault()?.Type;
+
+ // Ignore the current property if we have explicit filters and the property doesn't match
+ if ((propertySymbol.IsIndexer && indexerTypes?.Contains(indexerType, SymbolEqualityComparer.Default) is false) ||
+ (!propertySymbol.IsIndexer && propertyNames?.Contains(propertySymbol.Name, StringComparer.Ordinal) is false))
+ {
+ continue;
+ }
+
+ // If any types in the property signature cannot be boxed, we have to skip the property
+ if (!propertySymbol.Type.CanBeBoxed || indexerType?.CanBeBoxed is false)
+ {
+ continue;
+ }
+
+ // Gather all the info for the current property
+ customPropertyInfo.Add(new CustomPropertyInfo(
+ Name: propertySymbol.Name,
+ FullyQualifiedTypeName: propertySymbol.Type.GetFullyQualifiedNameWithNullabilityAnnotations(),
+ FullyQualifiedIndexerTypeName: indexerType?.GetFullyQualifiedNameWithNullabilityAnnotations(),
+ CanRead: propertySymbol.GetMethod is { DeclaredAccessibility: Accessibility.Public },
+ CanWrite: propertySymbol.SetMethod is { DeclaredAccessibility: Accessibility.Public },
+ IsStatic: propertySymbol.IsStatic));
+ }
+
+ token.ThrowIfCancellationRequested();
+
+ return customPropertyInfo.ToImmutable();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/CustomPropertyProviderGenerator.cs b/src/Authoring/WinRT.SourceGenerator2/CustomPropertyProviderGenerator.cs
new file mode 100644
index 0000000000..e342f16129
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/CustomPropertyProviderGenerator.cs
@@ -0,0 +1,29 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis;
+using WindowsRuntime.SourceGenerator.Models;
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+/// A generator to emit ICustomPropertyProvider implementations for annotated types.
+///
+[Generator]
+public sealed partial class CustomPropertyProviderGenerator : IIncrementalGenerator
+{
+ ///
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ // Gather the info on all types annotated with '[GeneratedCustomPropertyProvider]'.
+ IncrementalValuesProvider providerInfo = context.ForAttributeWithMetadataNameAndOptions(
+ fullyQualifiedMetadataName: "WindowsRuntime.Xaml.GeneratedCustomPropertyProviderAttribute",
+ predicate: Execute.IsTargetNodeValid,
+ transform: Execute.GetCustomPropertyProviderInfo)
+ .WithTrackingName("CustomPropertyProviderInfo")
+ .SkipNullValues();
+
+ // Write the implementation for all annotated types
+ context.RegisterSourceOutput(providerInfo, Emit.WriteCustomPropertyProviderImplementation);
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderAttributeArgumentAnalyzer.cs b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderAttributeArgumentAnalyzer.cs
new file mode 100644
index 0000000000..c9e8c04693
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderAttributeArgumentAnalyzer.cs
@@ -0,0 +1,203 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace WindowsRuntime.SourceGenerator.Diagnostics;
+
+///
+/// A diagnostic analyzer that validates the arguments of [GeneratedCustomPropertyProvider] when
+/// using the constructor taking explicit property names and indexer types.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class GeneratedCustomPropertyProviderAttributeArgumentAnalyzer : DiagnosticAnalyzer
+{
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } = [
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderNullPropertyName,
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderNullIndexerType,
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderPropertyNameNotFound,
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderIndexerTypeNotFound,
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderStaticIndexer];
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(static context =>
+ {
+ // Get the '[GeneratedCustomPropertyProvider]' symbol
+ if (context.Compilation.GetTypeByMetadataName("WindowsRuntime.Xaml.GeneratedCustomPropertyProviderAttribute") is not { } attributeType)
+ {
+ return;
+ }
+
+ context.RegisterSymbolAction(context =>
+ {
+ // Only classes and structs can be targets of the attribute
+ if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct } typeSymbol)
+ {
+ return;
+ }
+
+ // Get the attribute instance, if present
+ if (!typeSymbol.TryGetAttributeWithType(attributeType, out AttributeData? attribute))
+ {
+ return;
+ }
+
+ // Only validate when using the constructor with explicit property names and indexer types
+ if (attribute.ConstructorArguments is not [
+ { Kind: TypedConstantKind.Array, Values: var typedPropertyNames },
+ { Kind: TypedConstantKind.Array, Values: var typedIndexerTypes }])
+ {
+ return;
+ }
+
+ // Validate all property name arguments
+ foreach (TypedConstant typedName in typedPropertyNames)
+ {
+ // Check that we have a valid 'string' value
+ if (typedName.Value is not string propertyName)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderNullPropertyName,
+ typeSymbol.Locations.FirstOrDefault(),
+ typeSymbol));
+
+ continue;
+ }
+
+ // Check whether any public, non-override, non-partial-impl, non-indexer property has this name
+ if (!HasAccessiblePropertyWithName(typeSymbol, propertyName))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderPropertyNameNotFound,
+ typeSymbol.Locations.FirstOrDefault(),
+ propertyName,
+ typeSymbol));
+ }
+ }
+
+ // Validate all indexer type arguments
+ foreach (TypedConstant typedType in typedIndexerTypes)
+ {
+ // Check that we have a valid 'Type' value (the parameter type of the target indexer)
+ if (typedType.Value is not ITypeSymbol indexerType)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderNullIndexerType,
+ typeSymbol.Locations.FirstOrDefault(),
+ typeSymbol));
+
+ continue;
+ }
+
+ // Check whether there's a static indexer with a matching parameter type (which wouldn't be usable)
+ if (HasStaticIndexerWithParameterType(typeSymbol, indexerType))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderStaticIndexer,
+ typeSymbol.Locations.FirstOrDefault(),
+ indexerType,
+ typeSymbol));
+ }
+ else if (!HasAccessibleIndexerWithParameterType(typeSymbol, indexerType))
+ {
+ // No matching instance or static indexer at all
+ context.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderIndexerTypeNotFound,
+ typeSymbol.Locations.FirstOrDefault(),
+ indexerType,
+ typeSymbol));
+ }
+ }
+ }, SymbolKind.NamedType);
+ });
+ }
+
+ ///
+ /// Checks whether a type has an accessible (public, non-override, non-indexer) property with a given name.
+ ///
+ /// The type to inspect.
+ /// The property name to look for.
+ /// Whether a matching property exists.
+ private static bool HasAccessiblePropertyWithName(INamedTypeSymbol typeSymbol, string propertyName)
+ {
+ foreach (ISymbol symbol in typeSymbol.EnumerateAllMembers())
+ {
+ // Filter to public properties that we might care about (we ignore indexers here)
+ if (symbol is not IPropertySymbol { DeclaredAccessibility: Accessibility.Public, IsOverride: false, PartialDefinitionPart: null, IsIndexer: false } property)
+ {
+ continue;
+ }
+
+ if (property.Name.Equals(propertyName, StringComparison.Ordinal))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Checks whether a type has an accessible (public, non-override, non-static, single-parameter) indexer
+ /// with a parameter matching a given type.
+ ///
+ /// The type to inspect.
+ /// The indexer parameter type to look for.
+ /// Whether a matching indexer exists.
+ private static bool HasAccessibleIndexerWithParameterType(INamedTypeSymbol typeSymbol, ITypeSymbol indexerType)
+ {
+ foreach (ISymbol symbol in typeSymbol.EnumerateAllMembers())
+ {
+ // Same filtering as above, but we also exclude static members, and we only look for indexers
+ if (symbol is not IPropertySymbol { DeclaredAccessibility: Accessibility.Public, IsOverride: false, PartialDefinitionPart: null, IsIndexer: true, IsStatic: false } property)
+ {
+ continue;
+ }
+
+ // Check that we have a single parameter, that matches theone we're looking for
+ if (property.Parameters is [{ } parameter] && SymbolEqualityComparer.Default.Equals(parameter.Type, indexerType))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Checks whether a type has a static indexer with a parameter matching a given type.
+ /// This is used to provide a more specific diagnostic when the indexer exists but is static.
+ ///
+ /// The type to inspect.
+ /// The indexer parameter type to look for.
+ /// Whether a matching static indexer exists.
+ private static bool HasStaticIndexerWithParameterType(INamedTypeSymbol typeSymbol, ITypeSymbol indexerType)
+ {
+ foreach (ISymbol symbol in typeSymbol.EnumerateAllMembers())
+ {
+ // Same filtering as above, but this time including static properties
+ if (symbol is not IPropertySymbol { DeclaredAccessibility: Accessibility.Public, IsOverride: false, PartialDefinitionPart: null, IsIndexer: true, IsStatic: true } property)
+ {
+ continue;
+ }
+
+ // Validate the parameter type (same as above)
+ if (property.Parameters is [{ } parameter] && SymbolEqualityComparer.Default.Equals(parameter.Type, indexerType))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderExistingMemberImplementationAnalyzer.cs b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderExistingMemberImplementationAnalyzer.cs
new file mode 100644
index 0000000000..7b2cfaefc8
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderExistingMemberImplementationAnalyzer.cs
@@ -0,0 +1,94 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace WindowsRuntime.SourceGenerator.Diagnostics;
+
+///
+/// A diagnostic analyzer that validates when [GeneratedCustomPropertyProvider] is used on a type
+/// that already has or inherits implementations for one or more ICustomPropertyProvider members.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class GeneratedCustomPropertyProviderExistingMemberImplementationAnalyzer : DiagnosticAnalyzer
+{
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } = [DiagnosticDescriptors.GeneratedCustomPropertyProviderExistingMemberImplementation];
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(static context =>
+ {
+ // Get the '[GeneratedCustomPropertyProvider]' symbol
+ if (context.Compilation.GetTypeByMetadataName("WindowsRuntime.Xaml.GeneratedCustomPropertyProviderAttribute") is not { } attributeType)
+ {
+ return;
+ }
+
+ // Try to get any 'ICustomPropertyProvider' symbol
+ INamedTypeSymbol? windowsUIXamlCustomPropertyProviderType = context.Compilation.GetTypeByMetadataName("Windows.UI.Xaml.Data.ICustomPropertyProvider");
+ INamedTypeSymbol? microsoftUIXamlCustomPropertyProviderType = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Data.ICustomPropertyProvider");
+
+ // If we have neither, a different analyzer will handle that
+ if (windowsUIXamlCustomPropertyProviderType is null && microsoftUIXamlCustomPropertyProviderType is null)
+ {
+ return;
+ }
+
+ // Resolve the '[GeneratedCode]' attribute type, used to skip generated implementations
+ INamedTypeSymbol generatedCodeAttributeType = context.Compilation.GetTypeByMetadataName("System.CodeDom.Compiler.GeneratedCodeAttribute")!;
+
+ context.RegisterSymbolAction(context =>
+ {
+ // Only classes and structs can be targets of the attribute
+ if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct } typeSymbol)
+ {
+ return;
+ }
+
+ // Immediately bail if the type doesn't have the attribute
+ if (!typeSymbol.HasAttributeWithType(attributeType))
+ {
+ return;
+ }
+
+ // Helper to check whether a symbol has any generated interface members that are not emitted by this generator. his covers
+ // both manually implemented members and those produced by other generators, and explicitly interface implementations too.
+ static bool HasNonGeneratedImplementedMembers(
+ INamedTypeSymbol typeSymbol,
+ INamedTypeSymbol? interfaceType,
+ INamedTypeSymbol generatedCodeAttributeType)
+ {
+ if (interfaceType is null)
+ {
+ return false;
+ }
+
+ ISymbol[] implementedMembers = [.. typeSymbol.EnumerateImplementedMembersForInterface(interfaceType)];
+
+ // Check that we have at least one implemented member, and that not all of them are produced by our generator
+ return implementedMembers.Length > 0 && !implementedMembers.AreAllImplementedByGenerator(generatedCodeAttributeType, nameof(CustomPropertyProviderGenerator));
+ }
+
+ // Check whether the type has or inherits any 'ICustomPropertyProvider' member implementations.
+ // We don't warn if all implementations are generated by 'CustomPropertyProviderGenerator' itself,
+ // so that only user-authored implementations trigger this diagnostic.
+ if (HasNonGeneratedImplementedMembers(typeSymbol, windowsUIXamlCustomPropertyProviderType, generatedCodeAttributeType) ||
+ HasNonGeneratedImplementedMembers(typeSymbol, microsoftUIXamlCustomPropertyProviderType, generatedCodeAttributeType))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderExistingMemberImplementation,
+ typeSymbol.Locations.FirstOrDefault(),
+ typeSymbol));
+ }
+ }, SymbolKind.NamedType);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderNoAvailableInterfaceTypeAnalyzer.cs b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderNoAvailableInterfaceTypeAnalyzer.cs
new file mode 100644
index 0000000000..e9b2327bfb
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderNoAvailableInterfaceTypeAnalyzer.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace WindowsRuntime.SourceGenerator.Diagnostics;
+
+///
+/// A diagnostic analyzer that validates when [GeneratedCustomPropertyProvider] is used but no interface is available.
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class GeneratedCustomPropertyProviderNoAvailableInterfaceTypeAnalyzer : DiagnosticAnalyzer
+{
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } = [DiagnosticDescriptors.GeneratedCustomPropertyProviderNoAvailableInterfaceType];
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(static context =>
+ {
+ // Get the '[GeneratedCustomPropertyProvider]' symbol
+ if (context.Compilation.GetTypeByMetadataName("WindowsRuntime.Xaml.GeneratedCustomPropertyProviderAttribute") is not { } attributeType)
+ {
+ return;
+ }
+
+ // Try to get any 'ICustomPropertyProvider' symbol
+ INamedTypeSymbol? windowsUIXamlCustomPropertyProviderType = context.Compilation.GetTypeByMetadataName("Windows.UI.Xaml.Data.ICustomPropertyProvider");
+ INamedTypeSymbol? microsoftUIXamlCustomPropertyProviderType = context.Compilation.GetTypeByMetadataName("Microsoft.UI.Xaml.Data.ICustomPropertyProvider");
+
+ // If we have either of them, we'll never need to report any diagnostics
+ if (windowsUIXamlCustomPropertyProviderType is not null || microsoftUIXamlCustomPropertyProviderType is not null)
+ {
+ return;
+ }
+
+ context.RegisterSymbolAction(context =>
+ {
+ // Only classes and structs can be targets of the attribute
+ if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct } typeSymbol)
+ {
+ return;
+ }
+
+ // Emit a diagnostic if the type has the attribute, as it can't be used now
+ if (typeSymbol.HasAttributeWithType(attributeType))
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderNoAvailableInterfaceType,
+ typeSymbol.Locations.FirstOrDefault(),
+ typeSymbol));
+ }
+ }, SymbolKind.NamedType);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderTargetTypeAnalyzer.cs b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderTargetTypeAnalyzer.cs
new file mode 100644
index 0000000000..38e3078fbc
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/Analyzers/GeneratedCustomPropertyProviderTargetTypeAnalyzer.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Immutable;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace WindowsRuntime.SourceGenerator.Diagnostics;
+
+///
+/// A diagnostic analyzer that validates target types for [GeneratedCustomPropertyProvider].
+///
+[DiagnosticAnalyzer(LanguageNames.CSharp)]
+public sealed class GeneratedCustomPropertyProviderTargetTypeAnalyzer : DiagnosticAnalyzer
+{
+ ///
+ public override ImmutableArray SupportedDiagnostics { get; } = [
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderInvalidTargetType,
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderMissingPartialModifier];
+
+ ///
+ public override void Initialize(AnalysisContext context)
+ {
+ context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
+ context.EnableConcurrentExecution();
+
+ context.RegisterCompilationStartAction(static context =>
+ {
+ // Get the '[GeneratedCustomPropertyProvider]' symbol
+ if (context.Compilation.GetTypeByMetadataName("WindowsRuntime.Xaml.GeneratedCustomPropertyProviderAttribute") is not { } attributeType)
+ {
+ return;
+ }
+
+ context.RegisterSymbolAction(context =>
+ {
+ // Only classes and structs can be targets of the attribute
+ if (context.Symbol is not INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct } typeSymbol)
+ {
+ return;
+ }
+
+ // Immediately bail if the type doesn't have the attribute
+ if (!typeSymbol.HasAttributeWithType(attributeType))
+ {
+ return;
+ }
+
+ // If the type is static, abstract, or 'ref', it isn't valid
+ if (typeSymbol.IsAbstract || typeSymbol.IsStatic || typeSymbol.IsRefLikeType)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderInvalidTargetType,
+ typeSymbol.Locations.FirstOrDefault(),
+ typeSymbol));
+ }
+
+ // Try to get a syntax reference for the symbol, to resolve the syntax node for it
+ if (typeSymbol.DeclaringSyntaxReferences.FirstOrDefault() is SyntaxReference syntaxReference)
+ {
+ SyntaxNode typeNode = syntaxReference.GetSyntax(context.CancellationToken);
+
+ // If there's no 'partial' modifier in the type hierarchy, the target type isn't valid
+ if (!((MemberDeclarationSyntax)typeNode).IsPartialAndWithinPartialTypeHierarchy)
+ {
+ context.ReportDiagnostic(Diagnostic.Create(
+ DiagnosticDescriptors.GeneratedCustomPropertyProviderMissingPartialModifier,
+ typeSymbol.Locations.FirstOrDefault(),
+ typeSymbol));
+ }
+ }
+ }, SymbolKind.NamedType);
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Diagnostics/DiagnosticDescriptors.cs b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/DiagnosticDescriptors.cs
new file mode 100644
index 0000000000..a5f73dd6ad
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Diagnostics/DiagnosticDescriptors.cs
@@ -0,0 +1,129 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis;
+
+namespace WindowsRuntime.SourceGenerator.Diagnostics;
+
+///
+/// A container for all instances for errors reported by analyzers in this project.
+///
+internal static partial class DiagnosticDescriptors
+{
+ ///
+ /// Gets a for an invalid target type for [GeneratedCustomPropertyProvider].
+ ///
+ public static readonly DiagnosticDescriptor GeneratedCustomPropertyProviderInvalidTargetType = new(
+ id: "CSWINRT2000",
+ title: "Invalid '[GeneratedCustomPropertyProvider]' target type",
+ messageFormat: """The type '{0}' is not a valid target for '[GeneratedCustomPropertyProvider]': it must be a 'class' or 'struct' type, and it can't be 'static', 'abstract', or 'ref'""",
+ category: "WindowsRuntime.SourceGenerator",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Types annotated with '[GeneratedCustomPropertyProvider]' must be 'class' or 'struct' types, and they can't be 'static', 'abstract', or 'ref'.",
+ helpLinkUri: "https://github.com/microsoft/CsWinRT");
+
+ ///
+ /// Gets a for a target type for [GeneratedCustomPropertyProvider] missing .
+ ///
+ public static readonly DiagnosticDescriptor GeneratedCustomPropertyProviderMissingPartialModifier = new(
+ id: "CSWINRT2001",
+ title: "Missing 'partial' for '[GeneratedCustomPropertyProvider]' target type",
+ messageFormat: """The type '{0}' (or one of its containing types) is missing the 'partial' modifier, which is required to be used as a target for '[GeneratedCustomPropertyProvider]'""",
+ category: "WindowsRuntime.SourceGenerator",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Types annotated with '[GeneratedCustomPropertyProvider]' must be marked as 'partial' across their whole type hierarchy.",
+ helpLinkUri: "https://github.com/microsoft/CsWinRT");
+
+ ///
+ /// Gets a for when [GeneratedCustomPropertyProvider] can't resolve the interface type.
+ ///
+ public static readonly DiagnosticDescriptor GeneratedCustomPropertyProviderNoAvailableInterfaceType = new(
+ id: "CSWINRT2002",
+ title: "'ICustomPropertyProvider' interface type not available",
+ messageFormat: """The 'ICustomPropertyProvider' interface is not available in the compilation, but it is required to use '[GeneratedCustomPropertyProvider]' (make sure to either reference 'Microsoft.WindowsAppSDK.WinUI' or set the 'UseUwp' property in your .csproj file)""",
+ category: "WindowsRuntime.SourceGenerator",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Using '[GeneratedCustomPropertyProvider]' requires the 'ICustomPropertyProvider' interface type to be available in the compilation, which can be done by either referencing 'WindowsAppSDK.WinUI' or by setting the 'UseUwp' property in the .csproj file.",
+ helpLinkUri: "https://github.com/microsoft/CsWinRT");
+
+ ///
+ /// Gets a for when [GeneratedCustomPropertyProvider] is used on a type that already implements ICustomPropertyProvider members.
+ ///
+ public static readonly DiagnosticDescriptor GeneratedCustomPropertyProviderExistingMemberImplementation = new(
+ id: "CSWINRT2003",
+ title: "Existing 'ICustomPropertyProvider' member implementation",
+ messageFormat: """The type '{0}' cannot use '[GeneratedCustomPropertyProvider]' because it already has or inherits implementations for one or more 'ICustomPropertyProvider' members""",
+ category: "WindowsRuntime.SourceGenerator",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Types annotated with '[GeneratedCustomPropertyProvider]' must not already have or inherit implementations for any 'ICustomPropertyProvider' members, as the generator will provide them.",
+ helpLinkUri: "https://github.com/microsoft/CsWinRT");
+
+ ///
+ /// Gets a for when a property name is specified in [GeneratedCustomPropertyProvider].
+ ///
+ public static readonly DiagnosticDescriptor GeneratedCustomPropertyProviderNullPropertyName = new(
+ id: "CSWINRT2004",
+ title: "Null property name in '[GeneratedCustomPropertyProvider]'",
+ messageFormat: """A null property name was specified in '[GeneratedCustomPropertyProvider]' on type '{0}'""",
+ category: "WindowsRuntime.SourceGenerator",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Property names specified in '[GeneratedCustomPropertyProvider]' must not be null.",
+ helpLinkUri: "https://github.com/microsoft/CsWinRT");
+
+ ///
+ /// Gets a for when a indexer type is specified in [GeneratedCustomPropertyProvider].
+ ///
+ public static readonly DiagnosticDescriptor GeneratedCustomPropertyProviderNullIndexerType = new(
+ id: "CSWINRT2005",
+ title: "Null indexer type in '[GeneratedCustomPropertyProvider]'",
+ messageFormat: """A null indexer type was specified in '[GeneratedCustomPropertyProvider]' on type '{0}'""",
+ category: "WindowsRuntime.SourceGenerator",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Indexer types specified in '[GeneratedCustomPropertyProvider]' must not be null.",
+ helpLinkUri: "https://github.com/microsoft/CsWinRT");
+
+ ///
+ /// Gets a for when a property name in [GeneratedCustomPropertyProvider] doesn't match any accessible property.
+ ///
+ public static readonly DiagnosticDescriptor GeneratedCustomPropertyProviderPropertyNameNotFound = new(
+ id: "CSWINRT2006",
+ title: "Property name not found for '[GeneratedCustomPropertyProvider]'",
+ messageFormat: """The property name '{0}' specified in '[GeneratedCustomPropertyProvider]' on type '{1}' does not match any accessible property""",
+ category: "WindowsRuntime.SourceGenerator",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Property names specified in '[GeneratedCustomPropertyProvider]' must match the name of a public, non-override, non-indexer property on the annotated type.",
+ helpLinkUri: "https://github.com/microsoft/CsWinRT");
+
+ ///
+ /// Gets a for when an indexer type in [GeneratedCustomPropertyProvider] doesn't match any accessible indexer.
+ ///
+ public static readonly DiagnosticDescriptor GeneratedCustomPropertyProviderIndexerTypeNotFound = new(
+ id: "CSWINRT2007",
+ title: "Indexer type not found for '[GeneratedCustomPropertyProvider]'",
+ messageFormat: """The indexer type '{0}' specified in '[GeneratedCustomPropertyProvider]' on type '{1}' does not match any accessible indexer""",
+ category: "WindowsRuntime.SourceGenerator",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Indexer types specified in '[GeneratedCustomPropertyProvider]' must match the parameter type of a public, non-override, non-static, single-parameter indexer on the annotated type.",
+ helpLinkUri: "https://github.com/microsoft/CsWinRT");
+
+ ///
+ /// Gets a for when an indexer type in [GeneratedCustomPropertyProvider] matches a static indexer.
+ ///
+ public static readonly DiagnosticDescriptor GeneratedCustomPropertyProviderStaticIndexer = new(
+ id: "CSWINRT2008",
+ title: "Static indexer for '[GeneratedCustomPropertyProvider]'",
+ messageFormat: """The indexer type '{0}' specified in '[GeneratedCustomPropertyProvider]' on type '{1}' matches a static indexer, which is not supported""",
+ category: "WindowsRuntime.SourceGenerator",
+ defaultSeverity: DiagnosticSeverity.Error,
+ isEnabledByDefault: true,
+ description: "Indexers used with '[GeneratedCustomPropertyProvider]' must be instance indexers, not static indexers.",
+ helpLinkUri: "https://github.com/microsoft/CsWinRT");
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Extensions/ISymbolExtensions.cs b/src/Authoring/WinRT.SourceGenerator2/Extensions/ISymbolExtensions.cs
index 117fcd2cdf..59441b65cb 100644
--- a/src/Authoring/WinRT.SourceGenerator2/Extensions/ISymbolExtensions.cs
+++ b/src/Authoring/WinRT.SourceGenerator2/Extensions/ISymbolExtensions.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
@@ -16,6 +17,24 @@ internal static class ISymbolExtensions
/// The input instance.
extension(ISymbol symbol)
{
+ ///
+ /// Gets the fully qualified name for a given symbol.
+ ///
+ /// The fully qualified name for .
+ public string GetFullyQualifiedName()
+ {
+ return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
+ }
+
+ ///
+ /// Gets the fully qualified name for a given symbol, including nullability annotations
+ ///
+ /// The fully qualified name for .
+ public string GetFullyQualifiedNameWithNullabilityAnnotations()
+ {
+ return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier));
+ }
+
///
/// Checks whether a type has an attribute with a specified type.
///
@@ -59,4 +78,56 @@ public bool IsAccessibleFromCompilationAssembly(Compilation compilation)
return compilation.IsSymbolAccessibleWithin(symbol, compilation.Assembly);
}
}
+
+ /// The input sequence of instances.
+ extension(IEnumerable symbols)
+ {
+ ///
+ /// Checks whether all symbols in the sequence are generated by a specific generator.
+ ///
+ /// The for [GeneratedCode].
+ /// The generator name to match against the first constructor argument of [GeneratedCode].
+ /// Whether all symbols in have a [GeneratedCode] attribute matching .
+ public bool AreAllImplementedByGenerator(INamedTypeSymbol generatedCodeAttributeType, string generatorName)
+ {
+ bool hasAtLeastOneImplementedMember = false;
+
+ foreach (ISymbol symbol in symbols)
+ {
+ hasAtLeastOneImplementedMember = true;
+
+ ISymbol currentSymbol = symbol;
+
+ // First, try to get the attribute directly on the symbol (this should work in most cases)
+ if (!currentSymbol.TryGetAttributeWithType(generatedCodeAttributeType, out AttributeData? attributeData))
+ {
+ // If the current symbol is some property accessor, move to the associated property instead.
+ // This is also needed because usually '[GeneratedCode]' would only be on the property.
+ if (currentSymbol is IMethodSymbol { AssociatedSymbol: { } associatedSymbol })
+ {
+ currentSymbol = associatedSymbol;
+ }
+
+ // Stop if the symbol doesn't have '[GeneratedCode]' on it. Note, we want to traverse the symbol
+ // hierarchy and find the attribute on the closest parent (so we can also handle e.g. accessors).
+ for (ISymbol? parentSymbol = currentSymbol; parentSymbol is not null; parentSymbol = parentSymbol.ContainingSymbol)
+ {
+ if (parentSymbol.TryGetAttributeWithType(generatedCodeAttributeType, out attributeData))
+ {
+ break;
+ }
+ }
+ }
+
+ // Check that the symbol was specifically generated by the target generator
+ if (attributeData is not { ConstructorArguments: [{ Value: string name }, ..] } || name != generatorName)
+ {
+ return false;
+ }
+ }
+
+ // If the input set is empty, we want to return 'false' to indicate that there are no implemented members
+ return hasAtLeastOneImplementedMember;
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Extensions/ITypeSymbolExtensions.cs b/src/Authoring/WinRT.SourceGenerator2/Extensions/ITypeSymbolExtensions.cs
new file mode 100644
index 0000000000..30cd258ea2
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Extensions/ITypeSymbolExtensions.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+
+#pragma warning disable CS1734, IDE0046
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+/// Extensions for .
+///
+internal static class ITypeSymbolExtensions
+{
+ extension(ITypeSymbol symbol)
+ {
+ ///
+ /// Gets a value indicating whether the given can be boxed.
+ ///
+ public bool CanBeBoxed
+ {
+ get
+ {
+ // Byref-like types can't be boxed, and same for all kinds of pointers
+ if (symbol.IsRefLikeType || symbol.TypeKind is TypeKind.Pointer or TypeKind.FunctionPointer)
+ {
+ return false;
+ }
+
+ // Type parameters with 'allows ref struct' also can't be boxed
+ if (symbol is ITypeParameterSymbol { AllowsRefLikeType: true })
+ {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ ///
+ /// Enumerates all members of a given instance, including inherited ones.
+ ///
+ /// The sequence of all member symbols for .
+ public IEnumerable EnumerateAllMembers()
+ {
+ for (ITypeSymbol? currentSymbol = symbol;
+ currentSymbol is not (null or { SpecialType: SpecialType.System_ValueType or SpecialType.System_Object });
+ currentSymbol = currentSymbol.BaseType)
+ {
+ foreach (ISymbol currentMember in currentSymbol.GetMembers())
+ {
+ yield return currentMember;
+ }
+ }
+ }
+
+ ///
+ /// Enumerates all members of a specified interface that have implementations on the given .
+ ///
+ /// The interface type to enumerate member implementations for.
+ /// The sequence of all implemented members for on .
+ public IEnumerable EnumerateImplementedMembersForInterface(INamedTypeSymbol interfaceType)
+ {
+ if (!symbol.AllInterfaces.Contains(interfaceType, SymbolEqualityComparer.Default))
+ {
+ yield break;
+ }
+
+ foreach (ISymbol member in interfaceType.GetMembers())
+ {
+ if (symbol.FindImplementationForInterfaceMember(member) is { } implementation)
+ {
+ yield return implementation;
+ }
+ }
+ }
+
+ ///
+ /// Checks whether the given implements a specified interface
+ /// and has or inherits implementations for any of its members.
+ ///
+ /// The interface type to check for member implementations.
+ /// Whether has any implemented members for .
+ public bool HasAnyImplementedMembersForInterface(INamedTypeSymbol interfaceType)
+ {
+ return symbol.EnumerateImplementedMembersForInterface(interfaceType).Any();
+ }
+
+ ///
+ /// Gets the fully qualified metadata name for a given instance.
+ ///
+ /// The fully qualified metadata name for .
+ public string GetFullyQualifiedMetadataName()
+ {
+ using PooledArrayBuilder builder = new();
+
+ symbol.AppendFullyQualifiedMetadataName(in builder);
+
+ return builder.ToString();
+ }
+
+ ///
+ /// Appends the fully qualified metadata name for a given symbol to a target builder.
+ ///
+ /// The target instance.
+ public void AppendFullyQualifiedMetadataName(ref readonly PooledArrayBuilder builder)
+ {
+ static void BuildFrom(ISymbol? symbol, ref readonly PooledArrayBuilder 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 (ie. the one right before global) skip the leading '.'
+ case INamespaceSymbol { IsGlobalNamespace: false }:
+ builder.AddRange(symbol.MetadataName.AsSpan());
+ break;
+
+ // Types with no namespace just have their metadata name directly written
+ case ITypeSymbol { ContainingSymbol: INamespaceSymbol { IsGlobalNamespace: true } }:
+ builder.AddRange(symbol.MetadataName.AsSpan());
+ break;
+
+ // Types with a containing non-global namespace also append a leading '.'
+ case ITypeSymbol { ContainingSymbol: INamespaceSymbol namespaceSymbol }:
+ BuildFrom(namespaceSymbol, in builder);
+ builder.Add('.');
+ builder.AddRange(symbol.MetadataName.AsSpan());
+ break;
+
+ // Nested types append a leading '+'
+ case ITypeSymbol { ContainingSymbol: ITypeSymbol typeSymbol }:
+ BuildFrom(typeSymbol, in builder);
+ builder.Add('+');
+ builder.AddRange(symbol.MetadataName.AsSpan());
+ break;
+ default:
+ break;
+ }
+ }
+
+ BuildFrom(symbol, in builder);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Extensions/IncrementalGeneratorInitializationContextExtensions.cs b/src/Authoring/WinRT.SourceGenerator2/Extensions/IncrementalGeneratorInitializationContextExtensions.cs
new file mode 100644
index 0000000000..3f94b3183c
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Extensions/IncrementalGeneratorInitializationContextExtensions.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Immutable;
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Diagnostics;
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+/// Extension methods for .
+///
+internal static class IncrementalGeneratorInitializationContextExtensions
+{
+ ///
+ public static IncrementalValuesProvider ForAttributeWithMetadataNameAndOptions(
+ this IncrementalGeneratorInitializationContext context,
+ string fullyQualifiedMetadataName,
+ Func predicate,
+ Func transform)
+ {
+ // Invoke 'ForAttributeWithMetadataName' normally, but just return the context directly
+ IncrementalValuesProvider syntaxContext = context.SyntaxProvider.ForAttributeWithMetadataName(
+ fullyQualifiedMetadataName,
+ predicate,
+ static (context, token) => context);
+
+ // Do the same for the analyzer config options
+ IncrementalValueProvider configOptions = context.AnalyzerConfigOptionsProvider.Select(static (provider, token) => provider.GlobalOptions);
+
+ // Merge the two and invoke the provided transform on these two values. Neither value
+ // is equatable, meaning the pipeline will always re-run until this point. This is
+ // intentional: we don't want any symbols or other expensive objects to be kept alive
+ // across incremental steps, especially if they could cause entire compilations to be
+ // rooted, which would significantly increase memory use and introduce more GC pauses.
+ // In this specific case, flowing non equatable values in a pipeline is therefore fine.
+ return syntaxContext.Combine(configOptions).Select((input, token) => transform(new GeneratorAttributeSyntaxContextWithOptions(input.Left, input.Right), token));
+ }
+}
+
+///
+///
+///
+/// The original value.
+/// The original value.
+internal readonly struct GeneratorAttributeSyntaxContextWithOptions(
+ GeneratorAttributeSyntaxContext syntaxContext,
+ AnalyzerConfigOptions globalOptions)
+{
+ ///
+ public SyntaxNode TargetNode { get; } = syntaxContext.TargetNode;
+
+ ///
+ public ISymbol TargetSymbol { get; } = syntaxContext.TargetSymbol;
+
+ ///
+ public SemanticModel SemanticModel { get; } = syntaxContext.SemanticModel;
+
+ ///
+ public ImmutableArray Attributes { get; } = syntaxContext.Attributes;
+
+ ///
+ public AnalyzerConfigOptions GlobalOptions { get; } = globalOptions;
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Extensions/IncrementalValuesProviderExtensions.cs b/src/Authoring/WinRT.SourceGenerator2/Extensions/IncrementalValuesProviderExtensions.cs
new file mode 100644
index 0000000000..3c49e1f14a
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Extensions/IncrementalValuesProviderExtensions.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis;
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+/// Extensions for .
+///
+internal static class IncrementalValuesProviderExtensions
+{
+ ///
+ /// Skips all values from a given provider.
+ ///
+ /// The type of values being produced.
+ /// The input instance.
+ /// The resulting instance.
+ public static IncrementalValuesProvider SkipNullValues(this IncrementalValuesProvider provider)
+ where T : class
+ {
+ return provider.Where(static value => value is not null)!;
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Extensions/IndentedTextWriterExtensions.cs b/src/Authoring/WinRT.SourceGenerator2/Extensions/IndentedTextWriterExtensions.cs
index 21cfeef592..a0deebb335 100644
--- a/src/Authoring/WinRT.SourceGenerator2/Extensions/IndentedTextWriterExtensions.cs
+++ b/src/Authoring/WinRT.SourceGenerator2/Extensions/IndentedTextWriterExtensions.cs
@@ -5,6 +5,8 @@
// See: https://github.com/Sergio0694/ComputeSharp/blob/main/src/ComputeSharp.SourceGeneration/Helpers/IndentedTextWriter.cs.
using System;
+using System.Collections.Generic;
+using System.Linq;
namespace WindowsRuntime.SourceGenerator;
@@ -56,4 +58,74 @@ public static void WriteGeneratedAttributes(
}
}
}
+
+ ///
+ /// Writes a sequence of using directives, sorted correctly.
+ ///
+ /// The instance to write into.
+ /// The sequence of using directives to write.
+ public static void WriteSortedUsingDirectives(this ref IndentedTextWriter writer, IEnumerable usingDirectives)
+ {
+ // Add the System directives first, in the correct order
+ foreach (string usingDirective in usingDirectives.Where(static name => name.StartsWith("global::System", StringComparison.InvariantCulture)).OrderBy(static name => name))
+ {
+ writer.WriteLine($"using {usingDirective};");
+ }
+
+ // Add the other directives, also sorted in the correct order
+ foreach (string usingDirective in usingDirectives.Where(static name => !name.StartsWith("global::System", StringComparison.InvariantCulture)).OrderBy(static name => name))
+ {
+ writer.WriteLine($"using {usingDirective};");
+ }
+
+ // Leave a trailing blank line if at least one using directive has been written.
+ // This is so that any members will correctly have a leading blank line before.
+ writer.WriteLineIf(usingDirectives.Any());
+ }
+
+ ///
+ /// Writes a series of members separated by one line between each of them.
+ ///
+ /// The type of input items to process.
+ /// The instance to write into.
+ /// The input items to process.
+ /// The instance to invoke for each item.
+ public static void WriteLineSeparatedMembers(
+ this scoped ref IndentedTextWriter writer,
+ scoped ReadOnlySpan items,
+ IndentedTextWriter.Callback callback)
+ {
+ for (int i = 0; i < items.Length; i++)
+ {
+ if (i > 0)
+ {
+ writer.WriteLine();
+ }
+
+ callback(items[i], ref writer);
+ }
+ }
+
+ ///
+ /// Writes a series of initialization expressions separated by a comma between each of them.
+ ///
+ /// The type of input items to process.
+ /// The instance to write into.
+ /// The input items to process.
+ /// The instance to invoke for each item.
+ public static void WriteInitializationExpressions(
+ this scoped ref IndentedTextWriter writer,
+ scoped ReadOnlySpan items,
+ IndentedTextWriter.Callback callback)
+ {
+ for (int i = 0; i < items.Length; i++)
+ {
+ callback(items[i], ref writer);
+
+ if (i < items.Length - 1)
+ {
+ writer.WriteLine(",");
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Extensions/MemberDeclarationSyntaxExtensions.cs b/src/Authoring/WinRT.SourceGenerator2/Extensions/MemberDeclarationSyntaxExtensions.cs
new file mode 100644
index 0000000000..cc510ce9d3
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Extensions/MemberDeclarationSyntaxExtensions.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+/// Extensions for member declaration syntax types.
+///
+internal static class MemberDeclarationSyntaxExtensions
+{
+ extension(MemberDeclarationSyntax node)
+ {
+ ///
+ /// Gets whether the input member declaration is partial.
+ ///
+ public bool IsPartial => node.Modifiers.Any(SyntaxKind.PartialKeyword);
+
+ ///
+ /// Gets whether the input member declaration is partial and
+ /// all of its parent type declarations are also partial.
+ ///
+ public bool IsPartialAndWithinPartialTypeHierarchy
+ {
+ get
+ {
+ // If the target node is not partial, stop immediately
+ if (!node.IsPartial)
+ {
+ return false;
+ }
+
+ // Walk all parent type declarations, stop if any of them is not partial
+ foreach (SyntaxNode ancestor in node.Ancestors())
+ {
+ if (ancestor is BaseTypeDeclarationSyntax { IsPartial: false })
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Extensions/SyntaxExtensions.cs b/src/Authoring/WinRT.SourceGenerator2/Extensions/SyntaxExtensions.cs
new file mode 100644
index 0000000000..4af55419b0
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Extensions/SyntaxExtensions.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+/// Extensions for syntax types.
+///
+internal static class SyntaxExtensions
+{
+ extension(SyntaxNode node)
+ {
+ ///
+ /// Determines if is of any of the specified kinds.
+ ///
+ /// The syntax kinds to test for.
+ /// Whether the input node is of any of the specified kinds.
+ public bool IsAnyKind(params ReadOnlySpan kinds)
+ {
+ foreach (SyntaxKind kind in kinds)
+ {
+ if (node.IsKind(kind))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ extension(SyntaxTokenList list)
+ {
+ ///
+ /// Tests whether a list contains any token of particular kinds.
+ ///
+ /// The syntax kinds to test for.
+ /// Whether the input list contains any of the specified kinds.
+ public bool ContainsAny(params ReadOnlySpan kinds)
+ {
+ foreach (SyntaxKind kind in kinds)
+ {
+ if (list.IndexOf(kind) >= 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Helpers/EquatableArray{T}.cs b/src/Authoring/WinRT.SourceGenerator2/Helpers/EquatableArray{T}.cs
index 223d8a6b54..384a904e1f 100644
--- a/src/Authoring/WinRT.SourceGenerator2/Helpers/EquatableArray{T}.cs
+++ b/src/Authoring/WinRT.SourceGenerator2/Helpers/EquatableArray{T}.cs
@@ -75,19 +75,37 @@ public bool IsEmpty
get => AsImmutableArray().IsEmpty;
}
- ///
+ ///
+ /// Gets a value indicating whether the current array is default or empty.
+ ///
+ public bool IsDefaultOrEmpty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => AsImmutableArray().IsDefaultOrEmpty;
+ }
+
+ ///
+ /// Gets the length of the current array.
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => AsImmutableArray().Length;
+ }
+
+ ///
public bool Equals(EquatableArray array)
{
return AsSpan().SequenceEqual(array.AsSpan());
}
- ///
+ ///
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj is EquatableArray array && Equals(array);
}
- ///
+ ///
public override int GetHashCode()
{
if (_array is not T[] array)
@@ -152,13 +170,13 @@ public ImmutableArray.Enumerator GetEnumerator()
return AsImmutableArray().GetEnumerator();
}
- ///
+ ///
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)AsImmutableArray()).GetEnumerator();
}
- ///
+ ///
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)AsImmutableArray()).GetEnumerator();
diff --git a/src/Authoring/WinRT.SourceGenerator2/Helpers/IndentedTextWriter.cs b/src/Authoring/WinRT.SourceGenerator2/Helpers/IndentedTextWriter.cs
index 64ef7c1046..840dd22cde 100644
--- a/src/Authoring/WinRT.SourceGenerator2/Helpers/IndentedTextWriter.cs
+++ b/src/Authoring/WinRT.SourceGenerator2/Helpers/IndentedTextWriter.cs
@@ -100,12 +100,13 @@ public void DecreaseIndent()
/// Writes a block to the underlying buffer.
///
/// A value to close the open block with.
+ [UnscopedRef]
public Block WriteBlock()
{
WriteLine("{");
IncreaseIndent();
- return new(this);
+ return new(ref this);
}
///
@@ -142,6 +143,14 @@ public void Write(scoped ReadOnlySpan content, bool isMultiline = false)
{
ReadOnlySpan line = content[..newLineIndex];
+ // Remove the trailing 'CR' character, if present. This ensures that the resulting
+ // text will be correctly normalized with 'LF' newlines, regardless of the line
+ // endings used in source files. In fact, this whole repo uses 'CRLF' line endings.
+ if (line is [.. var trim, '\r'])
+ {
+ line = trim;
+ }
+
// Write the current line (if it's empty, we can skip writing the text entirely).
// This ensures that raw multiline string literals with blank lines don't have
// extra whitespace at the start of those lines, which would otherwise happen.
@@ -225,9 +234,14 @@ public readonly void WriteIf(bool condition, [InterpolatedStringHandlerArgument(
/// Indicates whether to skip adding the line if there already is one.
public void WriteLine(bool skipIfPresent = false)
{
- if (skipIfPresent && _handler.Text is [.., '\n', '\n'])
+ if (skipIfPresent)
{
- return;
+ ReadOnlySpan trimmedText = _handler.Text.TrimEnd(' ');
+
+ if (trimmedText is [.., '\n', '\n'] or [.., '{', '\n'])
+ {
+ return;
+ }
}
_handler.AppendFormatted(DefaultNewLine);
@@ -366,33 +380,31 @@ private void WriteRawText(scoped ReadOnlySpan content)
/// The type of data to use.
/// The input data to use to write into .
/// The instance to write into.
- public delegate void Callback(T value, IndentedTextWriter writer);
+ public delegate void Callback(T value, ref IndentedTextWriter writer);
///
/// Represents an indented block that needs to be closed.
///
/// The input instance to wrap.
- public ref struct Block(IndentedTextWriter writer) : IDisposable
+ public unsafe ref struct Block(ref IndentedTextWriter writer) : IDisposable
{
///
/// The instance to write to.
///
- private IndentedTextWriter _writer = writer;
+ private IndentedTextWriter* _writer = (IndentedTextWriter*)Unsafe.AsPointer(ref writer);
///
public void Dispose()
{
- IndentedTextWriter writer = _writer;
+ IndentedTextWriter* writer = _writer;
- _writer = default;
+ _writer = null;
- // We check the indentation as a way of knowing if we have reset the field before.
- // The field itself can't be 'null', but if we have assigned 'default' to it, then
- // that field will be 'null' even though it's always set from the constructor.
- if (writer._currentIndentation is not null)
+ // Make sure to only close the block the first time this value is disposed
+ if (writer is not null)
{
- writer.DecreaseIndent();
- writer.WriteLine("}");
+ writer->DecreaseIndent();
+ writer->WriteLine("}");
}
}
}
@@ -402,26 +414,26 @@ public void Dispose()
///
[EditorBrowsable(EditorBrowsableState.Never)]
[InterpolatedStringHandler]
- public readonly ref struct WriteInterpolatedStringHandler
+ public readonly unsafe ref struct WriteInterpolatedStringHandler
{
/// The associated to which to append.
- private readonly IndentedTextWriter _writer;
+ private readonly IndentedTextWriter* _writer;
/// Creates a handler used to append an interpolated string into a .
/// The number of constant characters outside of interpolation expressions in the interpolated string.
/// The number of interpolation expressions in the interpolated string.
/// The associated to which to append.
/// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.
- public WriteInterpolatedStringHandler(int literalLength, int formattedCount, IndentedTextWriter writer)
+ public WriteInterpolatedStringHandler(int literalLength, int formattedCount, in IndentedTextWriter writer)
{
- _writer = writer;
+ _writer = (IndentedTextWriter*)Unsafe.AsPointer(ref Unsafe.AsRef(in writer));
}
/// Writes the specified string to the handler.
/// The string to write.
public void AppendLiteral(string value)
{
- _writer.Write(value);
+ _writer->Write(value);
}
/// Writes the specified value to the handler.
@@ -435,7 +447,7 @@ public void AppendFormatted(string? value)
/// The span to write.
public void AppendFormatted(scoped ReadOnlySpan value)
{
- _writer.Write(value);
+ _writer->Write(value);
}
/// Writes the specified value to the handler.
@@ -445,7 +457,7 @@ public void AppendFormatted(T? value)
{
if (value is not null)
{
- _writer.Write(value.ToString()!);
+ _writer->Write(value.ToString()!);
}
}
@@ -458,11 +470,11 @@ public void AppendFormatted(T? value, string? format)
{
if (value is IFormattable)
{
- _writer.Write(((IFormattable)value).ToString(format, CultureInfo.InvariantCulture));
+ _writer->Write(((IFormattable)value).ToString(format, CultureInfo.InvariantCulture));
}
else if (value is not null)
{
- _writer.Write(value.ToString()!);
+ _writer->Write(value.ToString()!);
}
}
}
@@ -484,11 +496,11 @@ public readonly ref struct WriteIfInterpolatedStringHandler
/// The condition to use to decide whether or not to write content.
/// A value indicating whether formatting should proceed.
/// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.
- public WriteIfInterpolatedStringHandler(int literalLength, int formattedCount, IndentedTextWriter writer, bool condition, out bool shouldAppend)
+ public WriteIfInterpolatedStringHandler(int literalLength, int formattedCount, in IndentedTextWriter writer, bool condition, out bool shouldAppend)
{
if (condition)
{
- _writer = new WriteInterpolatedStringHandler(literalLength, formattedCount, writer);
+ _writer = new WriteInterpolatedStringHandler(literalLength, formattedCount, in writer);
shouldAppend = true;
}
diff --git a/src/Authoring/WinRT.SourceGenerator2/Helpers/ObjectPool{T}.cs b/src/Authoring/WinRT.SourceGenerator2/Helpers/ObjectPool{T}.cs
new file mode 100644
index 0000000000..04f60a0894
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Helpers/ObjectPool{T}.cs
@@ -0,0 +1,154 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ported from Roslyn.
+// See: https://github.com/dotnet/roslyn/blob/main/src/Dependencies/PooledObjects/ObjectPool%601.cs.
+
+using System;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+#pragma warning disable RS1035
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+///
+/// Generic implementation of object pooling pattern with predefined pool size limit. The main purpose
+/// is that limited number of frequently used objects can be kept in the pool for further recycling.
+///
+///
+/// Notes:
+///
+/// -
+/// It is not the goal to keep all returned objects. Pool is not meant for storage. If there
+/// is no space in the pool, extra returned objects will be dropped.
+///
+/// -
+/// It is implied that if object was obtained from a pool, the caller will return it back in
+/// a relatively short time. Keeping checked out objects for long durations is ok, but
+/// reduces usefulness of pooling. Just new up your own.
+///
+///
+///
+///
+/// Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice.
+/// Rationale: if there is no intent for reusing the object, do not use pool - just use "new".
+///
+///
+/// The type of objects to pool.
+/// The input factory to produce items.
+///
+/// The factory is stored for the lifetime of the pool. We will call this only when pool needs to
+/// expand. compared to "new T()", Func gives more flexibility to implementers and faster than "new T()".
+///
+/// The pool size to use.
+internal sealed class ObjectPool(Func factory, int size)
+ where T : class
+{
+ ///
+ /// The array of cached items.
+ ///
+ private readonly Element[] _items = new Element[size - 1];
+
+ ///
+ /// Storage for the pool objects. The first item is stored in a dedicated field
+ /// because we expect to be able to satisfy most requests from it.
+ ///
+ private T? _firstItem;
+
+ ///
+ /// Creates a new instance with the specified parameters.
+ ///
+ /// The input factory to produce items.
+ public ObjectPool(Func factory)
+ : this(factory, Environment.ProcessorCount * 2)
+ {
+ }
+
+ ///
+ /// Produces a instance.
+ ///
+ /// The returned item to use.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public T Allocate()
+ {
+ T? item = _firstItem;
+
+ if (item is null || item != Interlocked.CompareExchange(ref _firstItem, null, item))
+ {
+ item = AllocateSlow();
+ }
+
+ return item;
+ }
+
+ ///
+ /// Returns a given instance to the pool.
+ ///
+ /// The instance to return.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Free(T obj)
+ {
+ if (_firstItem is null)
+ {
+ _firstItem = obj;
+ }
+ else
+ {
+ FreeSlow(obj);
+ }
+ }
+
+ ///
+ /// Allocates a new item.
+ ///
+ /// The returned item to use.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private T AllocateSlow()
+ {
+ foreach (ref Element element in _items.AsSpan())
+ {
+ T? instance = element.Value;
+
+ if (instance is not null)
+ {
+ if (instance == Interlocked.CompareExchange(ref element.Value, null, instance))
+ {
+ return instance;
+ }
+ }
+ }
+
+ return factory();
+ }
+
+ ///
+ /// Frees a given item.
+ ///
+ /// The item to return to the pool.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void FreeSlow(T obj)
+ {
+ foreach (ref Element element in _items.AsSpan())
+ {
+ if (element.Value is null)
+ {
+ element.Value = obj;
+
+ break;
+ }
+ }
+ }
+
+ ///
+ /// A container for a produced item (using a wrapper to avoid covariance checks).
+ ///
+ private struct Element
+ {
+ ///
+ /// The value held at the current element.
+ ///
+ internal T? Value;
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Helpers/PooledArrayBuilder{T}.cs b/src/Authoring/WinRT.SourceGenerator2/Helpers/PooledArrayBuilder{T}.cs
new file mode 100644
index 0000000000..2fe6d5cbb8
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Helpers/PooledArrayBuilder{T}.cs
@@ -0,0 +1,355 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ported from ComputeSharp.
+// See: https://github.com/Sergio0694/ComputeSharp/blob/main/src/ComputeSharp.SourceGeneration/Helpers/ImmutableArrayBuilder%7BT%7D.cs.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Runtime.CompilerServices;
+
+#pragma warning disable IDE0032
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+/// A helper type to build sequences of values with pooled buffers.
+///
+/// The type of items to create sequences for.
+internal struct PooledArrayBuilder : IDisposable
+{
+ ///
+ /// The shared instance to share objects.
+ ///
+ private static readonly ObjectPool SharedObjectPool = new(static () => new Writer());
+
+ ///
+ /// The pooled instance to use.
+ ///
+ private Writer? _writer;
+
+ ///
+ /// Creates a new object.
+ ///
+ public PooledArrayBuilder()
+ {
+ _writer = SharedObjectPool.Allocate();
+ }
+
+ ///
+ /// Gets the number of elements currently written in the current instance.
+ ///
+ public readonly int Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _writer!.Count;
+ }
+
+ ///
+ /// Gets the data written to the underlying buffer so far, as a .
+ ///
+ public readonly ReadOnlySpan WrittenSpan
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _writer!.WrittenSpan;
+ }
+
+ ///
+ /// Advances the current writer and gets a to the requested memory area.
+ ///
+ /// The requested size to advance by.
+ /// A to the requested memory area.
+ ///
+ /// No other data should be written to the builder while the returned
+ /// is in use, as it could invalidate the memory area wrapped by it, if resizing occurs.
+ ///
+ public readonly Span Advance(int requestedSize)
+ {
+ return _writer!.Advance(requestedSize);
+ }
+
+ ///
+ public readonly void Add(T item)
+ {
+ _writer!.Add(item);
+ }
+
+ ///
+ /// Adds the specified items to the end of the array.
+ ///
+ /// The items to add at the end of the array.
+ public readonly void AddRange(ReadOnlySpan items)
+ {
+ _writer!.AddRange(items);
+ }
+
+ ///
+ public readonly void Clear()
+ {
+ _writer!.Clear();
+ }
+
+ ///
+ /// Inserts an item to the builder at the specified index.
+ ///
+ /// The zero-based index at which should be inserted.
+ /// The object to insert into the current instance.
+ public readonly void Insert(int index, T item)
+ {
+ _writer!.Insert(index, item);
+ }
+
+ ///
+ /// Gets an instance for the current builder.
+ ///
+ /// An instance for the current builder.
+ ///
+ /// The builder should not be mutated while an enumerator is in use.
+ ///
+ public readonly IEnumerable AsEnumerable()
+ {
+ return _writer!;
+ }
+
+ ///
+ public readonly ImmutableArray ToImmutable()
+ {
+ return _writer!.WrittenSpan.ToImmutableArray();
+ }
+
+ ///
+ public readonly T[] ToArray()
+ {
+ return _writer!.WrittenSpan.ToArray();
+ }
+
+ ///
+ public override readonly string ToString()
+ {
+ return _writer!.WrittenSpan.ToString();
+ }
+
+ ///
+ public void Dispose()
+ {
+ Writer? writer = _writer;
+
+ _writer = null;
+
+ if (writer is not null)
+ {
+ writer.Clear();
+
+ SharedObjectPool.Free(writer);
+ }
+ }
+
+ ///
+ /// A class handling the actual buffer writing.
+ ///
+ private sealed class Writer : IList, IReadOnlyList
+ {
+ ///
+ /// The underlying array.
+ ///
+ private T[] _array;
+
+ ///
+ /// The starting offset within .
+ ///
+ private int _index;
+
+ ///
+ /// Creates a new instance with the specified parameters.
+ ///
+ public Writer()
+ {
+ _array = typeof(T) == typeof(char)
+ ? new T[1024]
+ : new T[8];
+
+ _index = 0;
+ }
+
+ ///
+ public int Count
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _index;
+ }
+
+ ///
+ public ReadOnlySpan WrittenSpan
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new(_array, 0, _index);
+ }
+
+ ///
+ bool ICollection.IsReadOnly => true;
+
+ ///
+ T IReadOnlyList.this[int index] => WrittenSpan[index];
+
+ ///
+ T IList.this[int index]
+ {
+ get => WrittenSpan[index];
+ set => throw new NotSupportedException();
+ }
+
+ ///
+ public Span Advance(int requestedSize)
+ {
+ EnsureCapacity(requestedSize);
+
+ Span span = _array.AsSpan(_index, requestedSize);
+
+ _index += requestedSize;
+
+ return span;
+ }
+
+ ///
+ public void Add(T value)
+ {
+ EnsureCapacity(1);
+
+ _array[_index++] = value;
+ }
+
+ ///
+ public void AddRange(ReadOnlySpan items)
+ {
+ EnsureCapacity(items.Length);
+
+ items.CopyTo(_array.AsSpan(_index));
+
+ _index += items.Length;
+ }
+
+ ///
+ public void Insert(int index, T item)
+ {
+ if (index < 0 || index > _index)
+ {
+ PooledArrayBuilder.ThrowArgumentOutOfRangeExceptionForIndex();
+ }
+
+ EnsureCapacity(1);
+
+ if (index < _index)
+ {
+ Array.Copy(_array, index, _array, index + 1, _index - index);
+ }
+
+ _array[index] = item;
+ _index++;
+ }
+
+ ///
+ public void Clear()
+ {
+ if (RuntimeHelpers.IsReferenceOrContainsReferences())
+ {
+ _array.AsSpan(0, _index).Clear();
+ }
+
+ _index = 0;
+ }
+
+ ///
+ /// Ensures that has enough free space to contain a given number of new items.
+ ///
+ /// The minimum number of items to ensure space for in .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void EnsureCapacity(int requestedSize)
+ {
+ if (requestedSize > _array.Length - _index)
+ {
+ ResizeBuffer(requestedSize);
+ }
+ }
+
+ ///
+ /// Resizes to ensure it can fit the specified number of new items.
+ ///
+ /// The minimum number of items to ensure space for in .
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void ResizeBuffer(int sizeHint)
+ {
+ int minimumSize = _index + sizeHint;
+ int requestedSize = Math.Max(_array.Length * 2, minimumSize);
+
+ T[] newArray = new T[requestedSize];
+
+ Array.Copy(_array, newArray, _index);
+
+ _array = newArray;
+ }
+
+ ///
+ int IList.IndexOf(T item)
+ {
+ return Array.IndexOf(_array, item, 0, _index);
+ }
+
+ ///
+ void IList.RemoveAt(int index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ bool ICollection.Contains(T item)
+ {
+ return Array.IndexOf(_array, item, 0, _index) >= 0;
+ }
+
+ ///
+ void ICollection.CopyTo(T[] array, int arrayIndex)
+ {
+ Array.Copy(_array, 0, array, arrayIndex, _index);
+ }
+
+ ///
+ bool ICollection.Remove(T item)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ T?[] array = _array!;
+ int length = _index;
+
+ for (int i = 0; i < length; i++)
+ {
+ yield return array[i]!;
+ }
+ }
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return ((IEnumerable)this).GetEnumerator();
+ }
+ }
+}
+
+///
+/// Private helpers for the type.
+///
+file static class PooledArrayBuilder
+{
+ ///
+ /// Throws an for "index".
+ ///
+ public static void ThrowArgumentOutOfRangeExceptionForIndex()
+ {
+ throw new ArgumentOutOfRangeException("index");
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Models/CustomPropertyInfo.cs b/src/Authoring/WinRT.SourceGenerator2/Models/CustomPropertyInfo.cs
new file mode 100644
index 0000000000..fd58c9cdc5
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Models/CustomPropertyInfo.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Diagnostics.CodeAnalysis;
+
+namespace WindowsRuntime.SourceGenerator.Models;
+
+///
+/// A model representing a specific ICustomProperty to generate code for.
+///
+/// The property name.
+/// The fully qualified type name of the property.
+/// The fully qualified type name of the indexer parameter, if applicable.
+/// Whether the property can be read.
+/// Whether the property can be written to.
+/// Whether the property is static.
+internal sealed record CustomPropertyInfo(
+ string Name,
+ string FullyQualifiedTypeName,
+ string? FullyQualifiedIndexerTypeName,
+ bool CanRead,
+ bool CanWrite,
+ bool IsStatic)
+{
+ ///
+ /// Gets whether the current property is an indexer property.
+ ///
+ [MemberNotNullWhen(true, nameof(FullyQualifiedIndexerTypeName))]
+ public bool IsIndexer => FullyQualifiedIndexerTypeName is not null;
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Models/CustomPropertyProviderInfo.cs b/src/Authoring/WinRT.SourceGenerator2/Models/CustomPropertyProviderInfo.cs
new file mode 100644
index 0000000000..118422c955
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Models/CustomPropertyProviderInfo.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace WindowsRuntime.SourceGenerator.Models;
+
+///
+/// A model describing a type that implements ICustomPropertyProvider.
+///
+/// The type hierarchy info for the annotated type.
+/// The custom properties to generate code for on the annotated type.
+/// Whether to use Windows.UI.Xaml projections.
+internal sealed record CustomPropertyProviderInfo(
+ HierarchyInfo TypeHierarchy,
+ EquatableArray CustomProperties,
+ bool UseWindowsUIXamlProjections)
+{
+ ///
+ /// Gets the fully qualified name of the ICustomPropertyProvider interface to use.
+ ///
+ public string FullyQualifiedCustomPropertyProviderInterfaceName => UseWindowsUIXamlProjections
+ ? "Windows.UI.Xaml.Data.ICustomPropertyProvider"
+ : "Microsoft.UI.Xaml.Data.ICustomPropertyProvider";
+
+ ///
+ /// Gets the fully qualified name of the ICustomProperty interface to use.
+ ///
+ public string FullyQualifiedCustomPropertyInterfaceName => UseWindowsUIXamlProjections
+ ? "Windows.UI.Xaml.Data.ICustomProperty"
+ : "Microsoft.UI.Xaml.Data.ICustomProperty";
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Models/HierarchyInfo.cs b/src/Authoring/WinRT.SourceGenerator2/Models/HierarchyInfo.cs
new file mode 100644
index 0000000000..e044ca0b97
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Models/HierarchyInfo.cs
@@ -0,0 +1,139 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ported from ComputeSharp.
+// See: https://github.com/Sergio0694/ComputeSharp/blob/main/src/ComputeSharp.SourceGeneration/Models/HierarchyInfo.cs.
+
+using System;
+using Microsoft.CodeAnalysis;
+using static Microsoft.CodeAnalysis.SymbolDisplayTypeQualificationStyle;
+
+namespace WindowsRuntime.SourceGenerator.Models;
+
+///
+/// A model describing the hierarchy info for a specific type.
+///
+/// The fully qualified metadata name for the current type.
+/// Gets the namespace for the current type.
+/// Gets the sequence of type definitions containing the current type.
+internal sealed partial record HierarchyInfo(string FullyQualifiedMetadataName, string Namespace, EquatableArray Hierarchy)
+{
+ ///
+ /// Creates a new instance from a given .
+ ///
+ /// The input instance to gather info for.
+ /// A instance describing .
+ public static HierarchyInfo From(INamedTypeSymbol typeSymbol)
+ {
+ using PooledArrayBuilder hierarchy = new();
+
+ for (INamedTypeSymbol? parent = typeSymbol;
+ parent is not null;
+ parent = parent.ContainingType)
+ {
+ hierarchy.Add(new TypeInfo(
+ parent.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
+ parent.TypeKind,
+ parent.IsRecord));
+ }
+
+ return new(
+ typeSymbol.GetFullyQualifiedMetadataName(),
+ typeSymbol.ContainingNamespace.ToDisplayString(new(typeQualificationStyle: NameAndContainingTypesAndNamespaces)),
+ hierarchy.ToImmutable());
+ }
+
+ ///
+ /// Writes syntax for the current hierarchy into a target writer.
+ ///
+ /// The type of state to pass to callbacks.
+ /// The input state to pass to callbacks.
+ /// The target instance to write text to.
+ /// A list of base types to add to the generated type, if any.
+ /// The callbacks to use to write members into the declared type.
+ public void WriteSyntax(
+ T state,
+ scoped ref IndentedTextWriter writer,
+ scoped ReadOnlySpan baseTypes,
+ scoped ReadOnlySpan> memberCallbacks)
+ {
+ // Write the generated file header
+ writer.WriteLine("// ");
+ writer.WriteLine("#pragma warning disable");
+ writer.WriteLine();
+
+ // Declare the namespace, if needed
+ if (Namespace.Length > 0)
+ {
+ writer.WriteLine($"namespace {Namespace}");
+ writer.WriteLine("{");
+ writer.IncreaseIndent();
+ }
+
+ // Declare all the opening types until the inner-most one
+ for (int i = Hierarchy.Length - 1; i >= 0; i--)
+ {
+ writer.WriteLine($$"""/// """);
+ writer.Write($$"""partial {{Hierarchy[i].GetTypeKeyword()}} {{Hierarchy[i].QualifiedName}}""");
+
+ // Add any base types, if needed
+ if (i == 0 && !baseTypes.IsEmpty)
+ {
+ writer.Write(" : ");
+ writer.WriteInitializationExpressions(baseTypes, static (item, ref writer) => writer.Write(item));
+ writer.WriteLine();
+ }
+ else
+ {
+ writer.WriteLine();
+ }
+
+ writer.WriteLine($$"""{""");
+ writer.IncreaseIndent();
+ }
+
+ // Generate all nested members
+ writer.WriteLineSeparatedMembers(memberCallbacks, (callback, ref writer) => callback(state, ref writer));
+
+ // Close all scopes and reduce the indentation
+ for (int i = 0; i < Hierarchy.Length; i++)
+ {
+ writer.DecreaseIndent();
+ writer.WriteLine("}");
+ }
+
+ // Close the namespace scope as well, if needed
+ if (Namespace.Length > 0)
+ {
+ writer.DecreaseIndent();
+ writer.WriteLine("}");
+ }
+ }
+
+ ///
+ /// Gets the fully qualified type name for the current instance.
+ ///
+ /// The fully qualified type name for the current instance.
+ public string GetFullyQualifiedTypeName()
+ {
+ using PooledArrayBuilder fullyQualifiedTypeName = new();
+
+ fullyQualifiedTypeName.AddRange("global::".AsSpan());
+
+ if (Namespace.Length > 0)
+ {
+ fullyQualifiedTypeName.AddRange(Namespace.AsSpan());
+ fullyQualifiedTypeName.Add('.');
+ }
+
+ fullyQualifiedTypeName.AddRange(Hierarchy[^1].QualifiedName.AsSpan());
+
+ for (int i = Hierarchy.Length - 2; i >= 0; i--)
+ {
+ fullyQualifiedTypeName.Add('.');
+ fullyQualifiedTypeName.AddRange(Hierarchy[i].QualifiedName.AsSpan());
+ }
+
+ return fullyQualifiedTypeName.ToString();
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/Models/TypeInfo.cs b/src/Authoring/WinRT.SourceGenerator2/Models/TypeInfo.cs
new file mode 100644
index 0000000000..b7575e9990
--- /dev/null
+++ b/src/Authoring/WinRT.SourceGenerator2/Models/TypeInfo.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+// Ported from ComputeSharp.
+// See: https://github.com/Sergio0694/ComputeSharp/blob/main/src/ComputeSharp.SourceGeneration/Models/TypeInfo.cs.
+
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+
+namespace WindowsRuntime.SourceGenerator.Models;
+
+///
+/// A model describing a type info in a type hierarchy.
+///
+/// The qualified name for the type.
+/// The type of the type in the hierarchy.
+/// Whether the type is a record type.
+internal sealed record TypeInfo(string QualifiedName, TypeKind Kind, bool IsRecord)
+{
+ ///
+ /// Gets the keyword for the current type kind.
+ ///
+ /// The keyword for the current type kind.
+ [SuppressMessage("Style", "IDE0072", Justification = "These are the only relevant cases for type hierarchies.")]
+ public string GetTypeKeyword()
+ {
+ return Kind switch
+ {
+ TypeKind.Struct when IsRecord => "record struct",
+ TypeKind.Struct => "struct",
+ TypeKind.Interface => "interface",
+ TypeKind.Class when IsRecord => "record",
+ _ => "class"
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.Execute.cs b/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.Execute.cs
index cee19ae3e5..c93be3bf56 100644
--- a/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.Execute.cs
+++ b/src/Authoring/WinRT.SourceGenerator2/TypeMapAssemblyTargetGenerator.Execute.cs
@@ -2,7 +2,6 @@
// Licensed under the MIT License.
using System.Collections.Immutable;
-using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis;
@@ -91,25 +90,32 @@ public static void EmitPrivateProjectionsTypeMapAssemblyTargetAttributes(SourceP
return;
}
- StringBuilder builder = new();
+ const int ApproximateHeaderLength = 64;
+ const int ApproximateAttributesLength = 512;
- _ = builder.AppendLine($"""
+ // Approximate a close enough starting length to reduce copies
+ int approximateLiteralLength = ApproximateHeaderLength + (ApproximateAttributesLength * assemblyNames.Length);
+
+ IndentedTextWriter builder = new(literalLength: approximateLiteralLength, formattedCount: 0);
+
+ // Add the auto-generated header and disable warnings for the generated file
+ builder.Write($"""
//
#pragma warning disable
-
- """);
+ """, isMultiline: true);
// Add a '[TypeMapAssemblyTarget]' entry for each assembly
foreach (string assemblyName in assemblyNames)
{
- _ = builder.AppendLine($"""
+ builder.WriteLine();
+ builder.Write($"""
[assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("{assemblyName}")]
[assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("{assemblyName}")]
[assembly: global::System.Runtime.InteropServices.TypeMapAssemblyTarget("{assemblyName}")]
- """);
+ """, isMultiline: true);
}
- context.AddSource("TypeMapAssemblyTarget.PrivateProjections.g.cs", builder.ToString());
+ context.AddSource("TypeMapAssemblyTarget.PrivateProjections.g.cs", builder.ToStringAndClear());
}
///
diff --git a/src/Authoring/WinRT.SourceGenerator2/WinRT.SourceGenerator2.csproj b/src/Authoring/WinRT.SourceGenerator2/WinRT.SourceGenerator2.csproj
index 556e838190..01151f646d 100644
--- a/src/Authoring/WinRT.SourceGenerator2/WinRT.SourceGenerator2.csproj
+++ b/src/Authoring/WinRT.SourceGenerator2/WinRT.SourceGenerator2.csproj
@@ -6,11 +6,11 @@
true
+ Suppress the following warning:
+ "RS1041: This compiler extension should not be implemented in an assembly with target framework
+ '.NET 10.0'. References to other target frameworks will cause the compiler to behave unpredictably."
+ Using .NET 10 is fine for the scenarios we need to support.
+ -->
$(NoWarn);RS1041
@@ -41,6 +41,9 @@
true
+
+ $(NoWarn);CS8500
+
strict
@@ -69,6 +72,12 @@
..\..\WinRT.Runtime2\key.snk
+
+
+
+
+
+
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index fc050eeb09..995432db31 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -17,8 +17,8 @@
-
-
+
+
diff --git a/src/Projections/WinAppSDK/WinAppSDK.csproj b/src/Projections/WinAppSDK/WinAppSDK.csproj
index 44d39464f9..188de263f2 100644
--- a/src/Projections/WinAppSDK/WinAppSDK.csproj
+++ b/src/Projections/WinAppSDK/WinAppSDK.csproj
@@ -21,14 +21,9 @@
-include Microsoft
# The current WinUI nuget incorrectly references several Windows.* types that should be
# Microsoft.* types instead. Temporarily include these to enable the build
- -include Windows.UI.Xaml.Interop.Type
- -include Windows.UI.Xaml.Interop.NotifyCollectionChangedAction
- -include Windows.UI.Xaml.Markup.ContentPropertyAttribute
-include Windows.UI.Xaml.StyleTypedPropertyAttribute
-include Windows.UI.Xaml.TemplatePartAttribute
-include Windows.UI.Xaml.TemplateVisualStateAttribute
- -include Windows.UI.Xaml.Data.BindableAttribute
- -include Windows.UI.Xaml.Markup.ContentPropertyAttribute
-include Windows.UI.Xaml.Markup.FullXamlMetadataProviderAttribute
-include Windows.UI.Xaml.Markup.MarkupExtensionReturnTypeAttribute
-include Windows.UI.Xaml.Media.Animation.ConditionallyIndependentlyAnimatableAttribute
@@ -53,4 +48,4 @@
-
\ No newline at end of file
+
diff --git a/src/Projections/Windows.UI.Xaml/Windows.UI.Xaml.csproj b/src/Projections/Windows.UI.Xaml/Windows.UI.Xaml.csproj
index 64204b7ff3..1ade586088 100644
--- a/src/Projections/Windows.UI.Xaml/Windows.UI.Xaml.csproj
+++ b/src/Projections/Windows.UI.Xaml/Windows.UI.Xaml.csproj
@@ -19,6 +19,17 @@
-exclude Windows
-include Windows.UI.Xaml
+-include Windows.UI.Colors
+-include Windows.UI.ColorHelper
+-include Windows.UI.IColorHelper
+-include Windows.UI.IColors
+-include Windows.UI.Text.FontWeights
+-include Windows.UI.Text.IFontWeights
+-include Windows.ApplicationModel.Store.Preview.WebAuthenticationCoreManagerHelper
+-include Windows.ApplicationModel.Store.Preview.IWebAuthenticationCoreManagerHelper
+-exclude Windows.UI.Xaml.Interop
+-exclude Windows.UI.Xaml.Data.BindableAttribute
+-exclude Windows.UI.Xaml.Markup.ContentPropertyAttribute
diff --git a/src/Projections/Windows/Windows.csproj b/src/Projections/Windows/Windows.csproj
index 2dc1c974cb..ee46556aa7 100644
--- a/src/Projections/Windows/Windows.csproj
+++ b/src/Projections/Windows/Windows.csproj
@@ -14,26 +14,22 @@
-
-include Windows
-# Exclude Windows.UI, Windows.UI.Text, Windows.UI.Xaml per Microsoft.Windows.SDK.WinUI.Contracts NuGet
--include Windows.UI.Popups
-exclude Windows.UI.Colors
-exclude Windows.UI.IColors
-exclude Windows.UI.ColorHelper
-exclude Windows.UI.IColorHelper
-exclude Windows.UI.IColorHelperStatics
-exclude Windows.UI.IColorHelperStatics2
-#-exclude Windows.UI.Text (must include Windows.UI.Text to work around WinUI nuget issues)
+-exclude Windows.UI.Text.FontWeights
+-exclude Windows.UI.Text.IFontWeights
-exclude Windows.UI.Xaml
--exclude Windows.ApplicationModel.Store.Preview
-# Allow Windows.UI.Text, Windows.UI.Xaml types used in other namespaces
--include Windows.UI.Text.FontStretch
--include Windows.UI.Text.FontStyle
--include Windows.UI.Text.FontWeight
--include Windows.UI.Text.IFontWeights
--include Windows.UI.Text.UnderlineType
+-exclude Windows.ApplicationModel.Store.Preview.WebAuthenticationCoreManagerHelper
+-exclude Windows.ApplicationModel.Store.Preview.IWebAuthenticationCoreManagerHelper
+-include Windows.UI.Xaml.Interop
+-include Windows.UI.Xaml.Data.BindableAttribute
+-include Windows.UI.Xaml.Markup.ContentPropertyAttribute
diff --git a/src/Tests/AuthoringWuxTest/AuthoringWuxTest.csproj b/src/Tests/AuthoringWuxTest/AuthoringWuxTest.csproj
index 1f131e8256..24e964724b 100644
--- a/src/Tests/AuthoringWuxTest/AuthoringWuxTest.csproj
+++ b/src/Tests/AuthoringWuxTest/AuthoringWuxTest.csproj
@@ -10,10 +10,10 @@
true
-
+
-
+
diff --git a/src/Tests/FunctionalTests/ClassActivation/ClassActivation.csproj b/src/Tests/FunctionalTests/ClassActivation/ClassActivation.csproj
index f9e366e402..72b523a375 100644
--- a/src/Tests/FunctionalTests/ClassActivation/ClassActivation.csproj
+++ b/src/Tests/FunctionalTests/ClassActivation/ClassActivation.csproj
@@ -6,12 +6,14 @@
x86;x64
win-x86;win-x64
$(MSBuildProjectDirectory)\..\PublishProfiles\win-$(Platform).pubxml
+ true
+
diff --git a/src/Tests/FunctionalTests/ClassActivation/Program.cs b/src/Tests/FunctionalTests/ClassActivation/Program.cs
index 2df28d0f6a..f9d2a3d7ae 100644
--- a/src/Tests/FunctionalTests/ClassActivation/Program.cs
+++ b/src/Tests/FunctionalTests/ClassActivation/Program.cs
@@ -11,7 +11,9 @@
using TestComponentCSharp;
using Windows.Foundation;
using Windows.Foundation.Collections;
+using Windows.UI.Xaml.Data;
using WindowsRuntime.InteropServices;
+using WindowsRuntime.Xaml;
CustomDisposableTest customDisposableTest = new();
customDisposableTest.Dispose();
@@ -179,6 +181,25 @@
}
}
+TestCustomPropertyProvider testCustomPropertyProvider = new();
+
+unsafe
+{
+ void* testCustomPropertyProviderUnknownPtr = WindowsRuntimeMarshal.ConvertToUnmanaged(testCustomPropertyProvider);
+
+ try
+ {
+ // We should be able to get an 'ICustomPropertyProvider' interface pointer
+ ComHelpers.EnsureQueryInterface(
+ unknownPtr: testCustomPropertyProviderUnknownPtr,
+ iids: [new Guid("7C925755-3E48-42B4-8677-76372267033F")]);
+ }
+ finally
+ {
+ WindowsRuntimeMarshal.Free(testCustomPropertyProviderUnknownPtr);
+ }
+}
+
sealed class TestComposable : Composable
{
}
@@ -194,6 +215,13 @@ public void Dispose()
}
}
+[Guid("3C832AA5-5F7E-46EE-B1BF-7FE03AE866AF")]
+[GeneratedComInterface]
+partial interface IClassicComAction
+{
+ void Invoke();
+}
+
class GenericBaseType : IEnumerable, IDisposable
{
public void Dispose()
@@ -261,6 +289,22 @@ IEnumerator> IEnumerable>.GetEnumerato
}
}
+[GeneratedCustomPropertyProvider]
+sealed partial class TestCustomPropertyProvider
+{
+ public string Text => "Hello";
+
+ public int Number { get; set; }
+
+ public int this[string key]
+ {
+ get => 0;
+ set { }
+ }
+
+ public static string Info { get; set; }
+}
+
class GenericFactory
{
// This method is caling a generic one, which then constructs a generic type.
@@ -290,13 +334,6 @@ public static IAsyncOperation MakeAsyncOperation()
}
}
-[Guid("3C832AA5-5F7E-46EE-B1BF-7FE03AE866AF")]
-[GeneratedComInterface]
-partial interface IClassicComAction
-{
- void Invoke();
-}
-
file static class ComHelpers
{
[SupportedOSPlatform("windows6.3")]
diff --git a/src/Tests/FunctionalTests/Directory.Build.props b/src/Tests/FunctionalTests/Directory.Build.props
index 7c0c7c3ac2..bf8b4dc243 100644
--- a/src/Tests/FunctionalTests/Directory.Build.props
+++ b/src/Tests/FunctionalTests/Directory.Build.props
@@ -42,5 +42,16 @@
true
+
+
+
+ false
+
+
+
+
+
+
+
diff --git a/src/Tests/SourceGenerator2Test/Directory.Build.props b/src/Tests/SourceGenerator2Test/Directory.Build.props
new file mode 100644
index 0000000000..629129c597
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/Directory.Build.props
@@ -0,0 +1,9 @@
+
+
+
+ true
+
+
+
+
+
diff --git a/src/Tests/SourceGenerator2Test/Extensions/ReferenceAssembliesExtensions.cs b/src/Tests/SourceGenerator2Test/Extensions/ReferenceAssembliesExtensions.cs
new file mode 100644
index 0000000000..43ef97bfbb
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/Extensions/ReferenceAssembliesExtensions.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.IO;
+using Microsoft.CodeAnalysis.Testing;
+
+namespace WindowsRuntime.SourceGenerator;
+
+///
+/// Extensions for the type.
+///
+internal static class ReferenceAssembliesExtensions
+{
+ ///
+ /// The lazy-loaded instance for .NET 10 assemblies.
+ ///
+ private static readonly Lazy Net100 = new(static () =>
+ {
+ // Given we use a different nuget feed, we pass nuget.config.
+ string nugetConfigFilePath = Path.Combine(Path.GetDirectoryName(typeof(ReferenceAssembliesExtensions).Assembly.Location), "nuget.config");
+
+ ReferenceAssemblies referenceAssembly = new(
+ targetFramework: "net10.0",
+ referenceAssemblyPackage: new PackageIdentity("Microsoft.NETCore.App.Ref", "10.0.1"),
+ referenceAssemblyPath: Path.Combine("ref", "net10.0"));
+ return referenceAssembly.WithNuGetConfigFilePath(nugetConfigFilePath);
+ });
+
+ extension(ReferenceAssemblies.Net)
+ {
+ ///
+ /// Gets the value for .NET 10 reference assemblies.
+ ///
+ public static ReferenceAssemblies Net100 => Net100.Value; // TODO: remove when https://github.com/dotnet/roslyn-sdk/issues/1233 is resolved
+ }
+}
\ No newline at end of file
diff --git a/src/Tests/SourceGenerator2Test/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs b/src/Tests/SourceGenerator2Test/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs
new file mode 100644
index 0000000000..d1976d465e
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/Helpers/CSharpAnalyzerTest{TAnalyzer}.cs
@@ -0,0 +1,78 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Testing;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Testing;
+using Microsoft.UI.Xaml.Controls;
+using Windows.ApplicationModel.Core;
+
+namespace WindowsRuntime.SourceGenerator.Tests.Helpers;
+
+///
+/// A custom that uses a specific C# language version to parse code.
+///
+/// The type of the analyzer to test.
+internal sealed class CSharpAnalyzerTest : CSharpAnalyzerTest
+ where TAnalyzer : DiagnosticAnalyzer, new()
+{
+ ///
+ /// Whether to enable unsafe blocks.
+ ///
+ private readonly bool _allowUnsafeBlocks;
+
+ ///
+ /// The C# language version to use to parse code.
+ ///
+ private readonly LanguageVersion _languageVersion;
+
+ ///
+ /// Creates a new instance with the specified parameters.
+ ///
+ /// Whether to enable unsafe blocks.
+ /// The C# language version to use to parse code.
+ private CSharpAnalyzerTest(bool allowUnsafeBlocks, LanguageVersion languageVersion)
+ {
+ _allowUnsafeBlocks = allowUnsafeBlocks;
+ _languageVersion = languageVersion;
+ }
+
+ ///
+ protected override CompilationOptions CreateCompilationOptions()
+ {
+ return new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: _allowUnsafeBlocks);
+ }
+
+ ///
+ protected override ParseOptions CreateParseOptions()
+ {
+ return new CSharpParseOptions(_languageVersion, DocumentationMode.Diagnose);
+ }
+
+ ///
+ /// The source code to analyze.
+ /// The list of expected diagnostic for the test (used as alternative to the markdown syntax).
+ /// Whether to enable unsafe blocks.
+ /// The language version to use to run the test.
+ public static Task VerifyAnalyzerAsync(
+ string source,
+ ReadOnlySpan expectedDiagnostics = default,
+ bool allowUnsafeBlocks = true,
+ LanguageVersion languageVersion = LanguageVersion.CSharp14)
+ {
+ CSharpAnalyzerTest test = new(allowUnsafeBlocks, languageVersion) { TestCode = source };
+
+ test.TestState.ReferenceAssemblies = ReferenceAssemblies.Net.Net100;
+ test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(WindowsRuntimeObject).Assembly.Location));
+ test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(CoreApplication).Assembly.Location));
+ test.TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(Button).Assembly.Location));
+ test.TestState.ExpectedDiagnostics.AddRange([.. expectedDiagnostics]);
+
+ return test.RunAsync(CancellationToken.None);
+ }
+}
\ No newline at end of file
diff --git a/src/Tests/SourceGenerator2Test/Helpers/CSharpGeneratorTest{TGenerator}.cs b/src/Tests/SourceGenerator2Test/Helpers/CSharpGeneratorTest{TGenerator}.cs
new file mode 100644
index 0000000000..15a75a1d0d
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/Helpers/CSharpGeneratorTest{TGenerator}.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using Basic.Reference.Assemblies;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.UI.Xaml.Controls;
+using Windows.ApplicationModel.Core;
+
+namespace WindowsRuntime.SourceGenerator.Tests.Helpers;
+
+///
+/// A helper type to run source generator tests.
+///
+/// The type of generator to test.
+internal static class CSharpGeneratorTest
+ where TGenerator : IIncrementalGenerator, new()
+{
+ ///
+ /// Verifies the resulting sources produced by a source generator.
+ ///
+ /// The input source to process.
+ /// The expected source to be generated.
+ /// The language version to use to run the test.
+ public static void VerifySources(string source, (string Filename, string Source) result, LanguageVersion languageVersion = LanguageVersion.CSharp14)
+ {
+ RunGenerator(source, languageVersion, out Compilation compilation, out ImmutableArray diagnostics);
+
+ // Ensure that no diagnostics were generated
+ CollectionAssert.AreEquivalent((Diagnostic[])[], diagnostics);
+
+ // Update the assembly version using the version from the assembly of the input generators.
+ // This allows the tests to not need updates whenever the version of the generators changes.
+ // Also normalize line endings to 'LF', so the test files don't have to worry about that.
+ string expectedText = result.Source.Replace("", $"\"{typeof(TGenerator).Assembly.GetName().Version}\"").Replace("\r\n", "\n");
+ string actualText = compilation.SyntaxTrees.Single(tree => Path.GetFileName(tree.FilePath) == result.Filename).ToString();
+
+ Assert.AreEqual(expectedText, actualText);
+ }
+
+ ///
+ /// Creates a compilation from a given source.
+ ///
+ /// The input source to process.
+ /// The language version to use to run the test.
+ /// The resulting object.
+ private static CSharpCompilation CreateCompilation(string source, LanguageVersion languageVersion = LanguageVersion.CSharp12)
+ {
+ // Get all assembly references for the .NET TFM and 'WinRT.Runtime'
+ IEnumerable metadataReferences =
+ [
+ .. Net100.References.All,
+ MetadataReference.CreateFromFile(typeof(WindowsRuntimeObject).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(CoreApplication).Assembly.Location),
+ MetadataReference.CreateFromFile(typeof(Button).Assembly.Location)
+ ];
+
+ // Parse the source text
+ SyntaxTree sourceTree = CSharpSyntaxTree.ParseText(
+ source,
+ CSharpParseOptions.Default.WithLanguageVersion(languageVersion));
+
+ // Create the original compilation
+ return CSharpCompilation.Create(
+ "original",
+ [sourceTree],
+ metadataReferences,
+ new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true));
+ }
+
+ ///
+ /// Runs a generator and gathers the output results.
+ ///
+ /// The input source to process.
+ /// The language version to use to run the test.
+ ///
+ ///
+ private static void RunGenerator(
+ string source,
+ LanguageVersion languageVersion,
+ out Compilation compilation,
+ out ImmutableArray diagnostics)
+ {
+ Compilation originalCompilation = CreateCompilation(source, languageVersion);
+
+ // Create the generator driver with the D2D shader generator
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(new TGenerator()).WithUpdatedParseOptions(originalCompilation.SyntaxTrees.First().Options);
+
+ // Run all source generators on the input source code
+ _ = driver.RunGeneratorsAndUpdateCompilation(originalCompilation, out compilation, out diagnostics);
+ }
+}
\ No newline at end of file
diff --git a/src/Tests/SourceGenerator2Test/Properties/AssemblyInfo.cs b/src/Tests/SourceGenerator2Test/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..d1e038ca49
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/Properties/AssemblyInfo.cs
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+[assembly: Parallelize]
\ No newline at end of file
diff --git a/src/Tests/SourceGenerator2Test/SourceGenerator2Test.csproj b/src/Tests/SourceGenerator2Test/SourceGenerator2Test.csproj
new file mode 100644
index 0000000000..48c194ae68
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/SourceGenerator2Test.csproj
@@ -0,0 +1,55 @@
+
+
+ Exe
+ net10.0
+ x64;x86
+ false
+
+
+
+ win-x86
+ x86
+
+
+
+ win-x64
+ x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
diff --git a/src/Tests/SourceGenerator2Test/Test_CustomPropertyProviderGenerator.cs b/src/Tests/SourceGenerator2Test/Test_CustomPropertyProviderGenerator.cs
new file mode 100644
index 0000000000..8a5aeadca8
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/Test_CustomPropertyProviderGenerator.cs
@@ -0,0 +1,1064 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Threading.Tasks;
+using WindowsRuntime.SourceGenerator.Tests.Helpers;
+
+namespace WindowsRuntime.SourceGenerator.Tests;
+
+[TestClass]
+public class Test_CustomPropertyProviderGenerator
+{
+ [TestMethod]
+ public async Task ValidClass_MixedProperties()
+ {
+ const string source = """
+ using WindowsRuntime.Xaml;
+
+ namespace MyNamespace;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyClass
+ {
+ public string Name => "";
+
+ public int Age { get; set; }
+
+ public int this[int index]
+ {
+ get => 0;
+ set { }
+ }
+ }
+ """;
+
+ const string result = """
+ //
+ #pragma warning disable
+
+ namespace MyNamespace
+ {
+ ///
+ partial class MyClass : Microsoft.UI.Xaml.Data.ICustomPropertyProvider
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ global::System.Type Microsoft.UI.Xaml.Data.ICustomPropertyProvider.Type => typeof(MyClass);
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string name)
+ {
+ return name switch
+ {
+ nameof(Name) => global::WindowsRuntime.Xaml.Generated.MyClass_Name.Instance,
+ nameof(Age) => global::WindowsRuntime.Xaml.Generated.MyClass_Age.Instance,
+ _ => null
+ };
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string name, global::System.Type type)
+ {
+ if (type == typeof(int))
+ {
+ return global::WindowsRuntime.Xaml.Generated.MyClass_this__int.Instance;
+ }
+
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ string Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()
+ {
+ return ToString();
+ }
+ }
+ }
+
+ namespace WindowsRuntime.Xaml.Generated
+ {
+ using global::System;
+ using global::System.CodeDom.Compiler;
+ using global::System.Diagnostics;
+ using global::System.Diagnostics.CodeAnalysis;
+ using global::Microsoft.UI.Xaml.Data;
+
+ ///
+ /// The implementation for .
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_Name : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_Name Instance = new();
+
+ ///
+ public bool CanRead => true;
+
+ ///
+ public bool CanWrite => false;
+
+ ///
+ public string Name => "Name";
+
+ ///
+ public Type Type => typeof(string);
+
+ ///
+ public object GetValue(object target)
+ {
+ return ((global::MyNamespace.MyClass)target).Name;
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ /// The implementation for .
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_Age : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_Age Instance = new();
+
+ ///
+ public bool CanRead => true;
+
+ ///
+ public bool CanWrite => true;
+
+ ///
+ public string Name => "Age";
+
+ ///
+ public Type Type => typeof(int);
+
+ ///
+ public object GetValue(object target)
+ {
+ return ((global::MyNamespace.MyClass)target).Age;
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ ((global::MyNamespace.MyClass)target).Age = (int)value;
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ /// The implementation for 's indexer.
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_this__int : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_this__int Instance = new();
+
+ ///
+ public bool CanRead => true;
+
+ ///
+ public bool CanWrite => true;
+
+ ///
+ public string Name => "this";
+
+ ///
+ public Type Type => typeof(int);
+
+ ///
+ public object GetValue(object target)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ return ((global::MyNamespace.MyClass)target)[(int)index];
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ ((global::MyNamespace.MyClass)target)[(int)index] = (int)value;
+ }
+ }
+ }
+ """;
+
+ CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyClass.g.cs", result));
+ }
+
+ [TestMethod]
+ public async Task ValidClass_NoProperties()
+ {
+ const string source = """
+ using WindowsRuntime.Xaml;
+
+ namespace MyNamespace;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyClass
+ {
+ }
+ """;
+
+ const string result = """
+ //
+ #pragma warning disable
+
+ namespace MyNamespace
+ {
+ ///
+ partial class MyClass : Microsoft.UI.Xaml.Data.ICustomPropertyProvider
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ global::System.Type Microsoft.UI.Xaml.Data.ICustomPropertyProvider.Type => typeof(MyClass);
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string name)
+ {
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string name, global::System.Type type)
+ {
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ string Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()
+ {
+ return ToString();
+ }
+ }
+ }
+ """;
+
+ CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyClass.g.cs", result));
+ }
+
+ [TestMethod]
+ public async Task ValidClass_NormalPropertiesOnly()
+ {
+ const string source = """
+ using WindowsRuntime.Xaml;
+
+ namespace MyNamespace;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyClass
+ {
+ public string Name => "";
+
+ public int Age { get; set; }
+ }
+ """;
+
+ const string result = """
+ //
+ #pragma warning disable
+
+ namespace MyNamespace
+ {
+ ///
+ partial class MyClass : Microsoft.UI.Xaml.Data.ICustomPropertyProvider
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ global::System.Type Microsoft.UI.Xaml.Data.ICustomPropertyProvider.Type => typeof(MyClass);
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string name)
+ {
+ return name switch
+ {
+ nameof(Name) => global::WindowsRuntime.Xaml.Generated.MyClass_Name.Instance,
+ nameof(Age) => global::WindowsRuntime.Xaml.Generated.MyClass_Age.Instance,
+ _ => null
+ };
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string name, global::System.Type type)
+ {
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ string Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()
+ {
+ return ToString();
+ }
+ }
+ }
+
+ namespace WindowsRuntime.Xaml.Generated
+ {
+ using global::System;
+ using global::System.CodeDom.Compiler;
+ using global::System.Diagnostics;
+ using global::System.Diagnostics.CodeAnalysis;
+ using global::Microsoft.UI.Xaml.Data;
+
+ ///
+ /// The implementation for .
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_Name : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_Name Instance = new();
+
+ ///
+ public bool CanRead => true;
+
+ ///
+ public bool CanWrite => false;
+
+ ///
+ public string Name => "Name";
+
+ ///
+ public Type Type => typeof(string);
+
+ ///
+ public object GetValue(object target)
+ {
+ return ((global::MyNamespace.MyClass)target).Name;
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ /// The implementation for .
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_Age : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_Age Instance = new();
+
+ ///
+ public bool CanRead => true;
+
+ ///
+ public bool CanWrite => true;
+
+ ///
+ public string Name => "Age";
+
+ ///
+ public Type Type => typeof(int);
+
+ ///
+ public object GetValue(object target)
+ {
+ return ((global::MyNamespace.MyClass)target).Age;
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ ((global::MyNamespace.MyClass)target).Age = (int)value;
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+ """;
+
+ CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyClass.g.cs", result));
+ }
+
+ [TestMethod]
+ public async Task ValidClass_IndexerPropertiesOnly()
+ {
+ const string source = """
+ using WindowsRuntime.Xaml;
+
+ namespace MyNamespace;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyClass
+ {
+ public int this[int index]
+ {
+ get => 0;
+ set { }
+ }
+ }
+ """;
+
+ const string result = """
+ //
+ #pragma warning disable
+
+ namespace MyNamespace
+ {
+ ///
+ partial class MyClass : Microsoft.UI.Xaml.Data.ICustomPropertyProvider
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ global::System.Type Microsoft.UI.Xaml.Data.ICustomPropertyProvider.Type => typeof(MyClass);
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string name)
+ {
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string name, global::System.Type type)
+ {
+ if (type == typeof(int))
+ {
+ return global::WindowsRuntime.Xaml.Generated.MyClass_this__int.Instance;
+ }
+
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ string Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()
+ {
+ return ToString();
+ }
+ }
+ }
+
+ namespace WindowsRuntime.Xaml.Generated
+ {
+ using global::System;
+ using global::System.CodeDom.Compiler;
+ using global::System.Diagnostics;
+ using global::System.Diagnostics.CodeAnalysis;
+ using global::Microsoft.UI.Xaml.Data;
+
+ ///
+ /// The implementation for 's indexer.
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_this__int : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_this__int Instance = new();
+
+ ///
+ public bool CanRead => true;
+
+ ///
+ public bool CanWrite => true;
+
+ ///
+ public string Name => "this";
+
+ ///
+ public Type Type => typeof(int);
+
+ ///
+ public object GetValue(object target)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ return ((global::MyNamespace.MyClass)target)[(int)index];
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ ((global::MyNamespace.MyClass)target)[(int)index] = (int)value;
+ }
+ }
+ }
+ """;
+
+ CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyClass.g.cs", result));
+ }
+
+ [TestMethod]
+ public async Task ValidClass_ReadOnlyProperty()
+ {
+ const string source = """
+ using WindowsRuntime.Xaml;
+
+ namespace MyNamespace;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyClass
+ {
+ public string Name => "";
+ }
+ """;
+
+ const string result = """
+ //
+ #pragma warning disable
+
+ namespace MyNamespace
+ {
+ ///
+ partial class MyClass : Microsoft.UI.Xaml.Data.ICustomPropertyProvider
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ global::System.Type Microsoft.UI.Xaml.Data.ICustomPropertyProvider.Type => typeof(MyClass);
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string name)
+ {
+ return name switch
+ {
+ nameof(Name) => global::WindowsRuntime.Xaml.Generated.MyClass_Name.Instance,
+ _ => null
+ };
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string name, global::System.Type type)
+ {
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ string Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()
+ {
+ return ToString();
+ }
+ }
+ }
+
+ namespace WindowsRuntime.Xaml.Generated
+ {
+ using global::System;
+ using global::System.CodeDom.Compiler;
+ using global::System.Diagnostics;
+ using global::System.Diagnostics.CodeAnalysis;
+ using global::Microsoft.UI.Xaml.Data;
+
+ ///
+ /// The implementation for .
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_Name : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_Name Instance = new();
+
+ ///
+ public bool CanRead => true;
+
+ ///
+ public bool CanWrite => false;
+
+ ///
+ public string Name => "Name";
+
+ ///
+ public Type Type => typeof(string);
+
+ ///
+ public object GetValue(object target)
+ {
+ return ((global::MyNamespace.MyClass)target).Name;
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+ """;
+
+ CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyClass.g.cs", result));
+ }
+
+ [TestMethod]
+ public async Task ValidClass_WriteOnlyProperty()
+ {
+ const string source = """
+ using WindowsRuntime.Xaml;
+
+ namespace MyNamespace;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyClass
+ {
+ public string Name { set { } }
+ }
+ """;
+
+ const string result = """
+ //
+ #pragma warning disable
+
+ namespace MyNamespace
+ {
+ ///
+ partial class MyClass : Microsoft.UI.Xaml.Data.ICustomPropertyProvider
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ global::System.Type Microsoft.UI.Xaml.Data.ICustomPropertyProvider.Type => typeof(MyClass);
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string name)
+ {
+ return name switch
+ {
+ nameof(Name) => global::WindowsRuntime.Xaml.Generated.MyClass_Name.Instance,
+ _ => null
+ };
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string name, global::System.Type type)
+ {
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ string Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()
+ {
+ return ToString();
+ }
+ }
+ }
+
+ namespace WindowsRuntime.Xaml.Generated
+ {
+ using global::System;
+ using global::System.CodeDom.Compiler;
+ using global::System.Diagnostics;
+ using global::System.Diagnostics.CodeAnalysis;
+ using global::Microsoft.UI.Xaml.Data;
+
+ ///
+ /// The implementation for .
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_Name : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_Name Instance = new();
+
+ ///
+ public bool CanRead => false;
+
+ ///
+ public bool CanWrite => true;
+
+ ///
+ public string Name => "Name";
+
+ ///
+ public Type Type => typeof(string);
+
+ ///
+ public object GetValue(object target)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ ((global::MyNamespace.MyClass)target).Name = (string)value;
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+ """;
+
+ CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyClass.g.cs", result));
+ }
+
+ [TestMethod]
+ public async Task ValidClass_StaticProperty()
+ {
+ const string source = """
+ using WindowsRuntime.Xaml;
+
+ namespace MyNamespace;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyClass
+ {
+ public static int Count { get; set; }
+ }
+ """;
+
+ const string result = """
+ //
+ #pragma warning disable
+
+ namespace MyNamespace
+ {
+ ///
+ partial class MyClass : Microsoft.UI.Xaml.Data.ICustomPropertyProvider
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ global::System.Type Microsoft.UI.Xaml.Data.ICustomPropertyProvider.Type => typeof(MyClass);
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string name)
+ {
+ return name switch
+ {
+ nameof(Count) => global::WindowsRuntime.Xaml.Generated.MyClass_Count.Instance,
+ _ => null
+ };
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string name, global::System.Type type)
+ {
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ string Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()
+ {
+ return ToString();
+ }
+ }
+ }
+
+ namespace WindowsRuntime.Xaml.Generated
+ {
+ using global::System;
+ using global::System.CodeDom.Compiler;
+ using global::System.Diagnostics;
+ using global::System.Diagnostics.CodeAnalysis;
+ using global::Microsoft.UI.Xaml.Data;
+
+ ///
+ /// The implementation for .
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_Count : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_Count Instance = new();
+
+ ///
+ public bool CanRead => true;
+
+ ///
+ public bool CanWrite => true;
+
+ ///
+ public string Name => "Count";
+
+ ///
+ public Type Type => typeof(int);
+
+ ///
+ public object GetValue(object target)
+ {
+ return global::MyNamespace.MyClass.Count;
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ global::MyNamespace.MyClass.Count = (int)value;
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+ """;
+
+ CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyClass.g.cs", result));
+ }
+
+ [TestMethod]
+ public async Task ValidClass_ReadOnlyIndexer()
+ {
+ const string source = """
+ using WindowsRuntime.Xaml;
+
+ namespace MyNamespace;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyClass
+ {
+ public string this[int index] => "";
+ }
+ """;
+
+ const string result = """
+ //
+ #pragma warning disable
+
+ namespace MyNamespace
+ {
+ ///
+ partial class MyClass : Microsoft.UI.Xaml.Data.ICustomPropertyProvider
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ global::System.Type Microsoft.UI.Xaml.Data.ICustomPropertyProvider.Type => typeof(MyClass);
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string name)
+ {
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ Microsoft.UI.Xaml.Data.ICustomProperty Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string name, global::System.Type type)
+ {
+ if (type == typeof(int))
+ {
+ return global::WindowsRuntime.Xaml.Generated.MyClass_this__int.Instance;
+ }
+
+ return null;
+ }
+
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CustomPropertyProviderGenerator", )]
+ string Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()
+ {
+ return ToString();
+ }
+ }
+ }
+
+ namespace WindowsRuntime.Xaml.Generated
+ {
+ using global::System;
+ using global::System.CodeDom.Compiler;
+ using global::System.Diagnostics;
+ using global::System.Diagnostics.CodeAnalysis;
+ using global::Microsoft.UI.Xaml.Data;
+
+ ///
+ /// The implementation for 's indexer.
+ ///
+ [GeneratedCode("CustomPropertyProviderGenerator", )]
+ [DebuggerNonUserCode]
+ [ExcludeFromCodeCoverage]
+ file sealed class MyClass_this__int : ICustomProperty
+ {
+ ///
+ /// Gets the singleton instance for this custom property.
+ ///
+ public static readonly MyClass_this__int Instance = new();
+
+ ///
+ public bool CanRead => true;
+
+ ///
+ public bool CanWrite => false;
+
+ ///
+ public string Name => "this";
+
+ ///
+ public Type Type => typeof(string);
+
+ ///
+ public object GetValue(object target)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public void SetValue(object target, object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public object GetIndexedValue(object target, object index)
+ {
+ return ((global::MyNamespace.MyClass)target)[(int)index];
+ }
+
+ ///
+ public void SetIndexedValue(object target, object value, object index)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+ """;
+
+ CSharpGeneratorTest.VerifySources(source, ("MyNamespace.MyClass.g.cs", result));
+ }
+}
\ No newline at end of file
diff --git a/src/Tests/SourceGenerator2Test/Test_GeneratedCustomPropertyProviderAttributeArgumentAnalyzer.cs b/src/Tests/SourceGenerator2Test/Test_GeneratedCustomPropertyProviderAttributeArgumentAnalyzer.cs
new file mode 100644
index 0000000000..715feb3539
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/Test_GeneratedCustomPropertyProviderAttributeArgumentAnalyzer.cs
@@ -0,0 +1,394 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Testing;
+using WindowsRuntime.SourceGenerator.Diagnostics;
+using WindowsRuntime.SourceGenerator.Tests.Helpers;
+using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<
+ WindowsRuntime.SourceGenerator.Diagnostics.GeneratedCustomPropertyProviderAttributeArgumentAnalyzer,
+ Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
+
+namespace WindowsRuntime.SourceGenerator.Tests;
+
+[TestClass]
+public class Test_GeneratedCustomPropertyProviderAttributeArgumentAnalyzer
+{
+ [TestMethod]
+ [DataRow("class")]
+ [DataRow("struct")]
+ public async Task DefaultConstructor_DoesNotWarn(string modifier)
+ {
+ string source = $$"""
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial {{modifier}} MyType
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task EmptyArrays_DoesNotWarn()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { }, new Type[] { })]
+ public partial class MyType
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task ValidPropertyName_DoesNotWarn()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { "Name" }, new Type[] { })]
+ public partial class MyType
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task ValidIndexerType_DoesNotWarn()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { }, new Type[] { typeof(int) })]
+ public partial class MyType
+ {
+ public string this[int index] => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task ValidPropertyNameAndIndexerType_DoesNotWarn()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { "Name" }, new Type[] { typeof(int) })]
+ public partial class MyType
+ {
+ public string Name { get; set; }
+ public string this[int index] => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task InheritedPropertyName_DoesNotWarn()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ public class BaseType
+ {
+ public string Name { get; set; }
+ }
+
+ [GeneratedCustomPropertyProvider(new string[] { "Name" }, new Type[] { })]
+ public partial class MyType : BaseType;
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task InheritedIndexerType_DoesNotWarn()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ public class BaseType
+ {
+ public string this[int index] => "";
+ }
+
+ [GeneratedCustomPropertyProvider(new string[] { }, new Type[] { typeof(int) })]
+ public partial class MyType : BaseType;
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task MultipleValidPropertyNames_DoesNotWarn()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { "Name", "Age" }, new Type[] { })]
+ public partial class MyType
+ {
+ public string Name { get; set; }
+ public int Age { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task OverriddenPropertyName_DoesNotWarn()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ public class BaseType
+ {
+ public virtual string Name { get; set; }
+ }
+
+ [GeneratedCustomPropertyProvider(new string[] { "Name" }, new Type[] { })]
+ public partial class MyType : BaseType
+ {
+ public override string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task NullPropertyName_Warns()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { null }, new Type[] { })]
+ public partial class {|CSWINRT2004:MyType|}
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task NullAmongValidPropertyNames_Warns()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { "Name", null }, new Type[] { })]
+ public partial class {|CSWINRT2004:MyType|}
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task NullIndexerType_Warns()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { }, new Type[] { null })]
+ public partial class {|CSWINRT2005:MyType|}
+ {
+ public string this[int index] => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task NullAmongValidIndexerTypes_Warns()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { }, new Type[] { typeof(int), null })]
+ public partial class {|CSWINRT2005:MyType|}
+ {
+ public string this[int index] => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task PropertyNameNotFound_Warns()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { "Missing" }, new Type[] { })]
+ public partial class {|CSWINRT2006:MyType|}
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task PropertyNameMatchesPrivateProperty_Warns()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { "Secret" }, new Type[] { })]
+ public partial class {|CSWINRT2006:MyType|}
+ {
+ private string Secret { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task MultiplePropertyNamesNotFound_WarnsForEach()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { "Missing1", "Missing2" }, new Type[] { })]
+ public partial class MyType
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, expectedDiagnostics: [
+ VerifyCS.Diagnostic("CSWINRT2006").WithSpan(5, 22, 5, 28).WithArguments("Missing1", "MyType"),
+ VerifyCS.Diagnostic("CSWINRT2006").WithSpan(5, 22, 5, 28).WithArguments("Missing2", "MyType")]);
+ }
+
+ [TestMethod]
+ public async Task IndexerTypeNotFound_WrongType_Warns()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { }, new Type[] { typeof(string) })]
+ public partial class {|CSWINRT2007:MyType|}
+ {
+ public string this[int index] => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task IndexerTypeNotFound_NoIndexer_Warns()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { }, new Type[] { typeof(int) })]
+ public partial class {|CSWINRT2007:MyType|}
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task IndexerTypeMatchesMultiParameterIndexer_Warns()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { }, new Type[] { typeof(int) })]
+ public partial class {|CSWINRT2007:MyType|}
+ {
+ public string this[int row, int col] => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task MultipleIndexerTypesNotFound_WarnsForEach()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { }, new Type[] { typeof(string), typeof(double) })]
+ public partial class MyType
+ {
+ public string this[int index] => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, expectedDiagnostics: [
+ VerifyCS.Diagnostic("CSWINRT2007").WithSpan(5, 22, 5, 28).WithArguments("double", "MyType"),
+ VerifyCS.Diagnostic("CSWINRT2007").WithSpan(5, 22, 5, 28).WithArguments("string", "MyType")]);
+ }
+
+ [TestMethod]
+ public async Task MixedInvalidArguments_WarnsForEach()
+ {
+ string source = """
+ using System;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider(new string[] { "Missing", null }, new Type[] { typeof(string), null })]
+ public partial class MyType
+ {
+ public string Name { get; set; }
+ public string this[int index] => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, expectedDiagnostics: [
+ VerifyCS.Diagnostic("CSWINRT2004").WithSpan(5, 22, 5, 28).WithArguments("MyType"),
+ VerifyCS.Diagnostic("CSWINRT2005").WithSpan(5, 22, 5, 28).WithArguments("MyType"),
+ VerifyCS.Diagnostic("CSWINRT2006").WithSpan(5, 22, 5, 28).WithArguments("Missing", "MyType"),
+ VerifyCS.Diagnostic("CSWINRT2007").WithSpan(5, 22, 5, 28).WithArguments("string", "MyType")]);
+ }
+}
diff --git a/src/Tests/SourceGenerator2Test/Test_GeneratedCustomPropertyProviderExistingMemberImplementationAnalyzer.cs b/src/Tests/SourceGenerator2Test/Test_GeneratedCustomPropertyProviderExistingMemberImplementationAnalyzer.cs
new file mode 100644
index 0000000000..9810434058
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/Test_GeneratedCustomPropertyProviderExistingMemberImplementationAnalyzer.cs
@@ -0,0 +1,232 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Threading.Tasks;
+using Microsoft;
+using Microsoft.CodeAnalysis.Testing;
+using WindowsRuntime.SourceGenerator.Diagnostics;
+using WindowsRuntime.SourceGenerator.Tests.Helpers;
+using VerifyCS = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<
+ WindowsRuntime.SourceGenerator.Diagnostics.GeneratedCustomPropertyProviderExistingMemberImplementationAnalyzer,
+ Microsoft.CodeAnalysis.Testing.DefaultVerifier>;
+
+namespace WindowsRuntime.SourceGenerator.Tests;
+
+[TestClass]
+public class Test_GeneratedCustomPropertyProviderExistingMemberImplementationAnalyzer
+{
+ [TestMethod]
+ [DataRow("class")]
+ [DataRow("struct")]
+ public async Task ValidType_NoInterface_DoesNotWarn(string modifier)
+ {
+ string source = $$"""
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial {{modifier}} MyType
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task ValidType_NoMembers_DoesNotWarn()
+ {
+ string source = $$"""
+ using Microsoft.UI.Xaml.Data;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyType : ICustomPropertyProvider
+ {
+ public string Name { get; set; }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, expectedDiagnostics: [
+ // /0/Test0.cs(5,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.GetCustomProperty(string)'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(5, 31, 5, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string)"),
+ // /0/Test0.cs(5,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.GetIndexedProperty(string, Type)'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(5, 31, 5, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string, System.Type)"),
+ // /0/Test0.cs(5,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.GetStringRepresentation()'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(5, 31, 5, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()"),
+ // /0/Test0.cs(5,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.Type'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(5, 31, 5, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.Type")]);
+ }
+
+ [TestMethod]
+ public async Task TypeWithExplicitInterfaceImplementation_Warns()
+ {
+ string source = """
+ using System;
+ using Microsoft.UI.Xaml.Data;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class {|CSWINRT2003:MyType|} : ICustomPropertyProvider
+ {
+ Type ICustomPropertyProvider.Type => typeof(MyType);
+ ICustomProperty ICustomPropertyProvider.GetCustomProperty(string name) => null;
+ ICustomProperty ICustomPropertyProvider.GetIndexedProperty(string name, Type type) => null;
+ string ICustomPropertyProvider.GetStringRepresentation() => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task TypeWithExplicitInterfaceImplementation_Incomplete_Warns()
+ {
+ string source = """
+ using System;
+ using Microsoft.UI.Xaml.Data;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyType : ICustomPropertyProvider
+ {
+ Type ICustomPropertyProvider.Type => typeof(MyType);
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, expectedDiagnostics: [
+ // /0/Test0.cs(6,22): error CSWINRT2003: The type 'MyType' cannot use '[GeneratedCustomPropertyProvider]' because it already has or inherits implementations for one or more 'ICustomPropertyProvider' members
+ VerifyCS.Diagnostic().WithSpan(6, 22, 6, 28).WithArguments("MyType"),
+ // /0/Test0.cs(6,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.GetCustomProperty(string)'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(6, 31, 6, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string)"),
+ // /0/Test0.cs(6,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.GetIndexedProperty(string, Type)'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(6, 31, 6, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string, System.Type)"),
+ // /0/Test0.cs(6,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.GetStringRepresentation()'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(6, 31, 6, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()")]);
+ }
+
+ [TestMethod]
+ public async Task TypeWithImplicitInterfaceImplementation_Warns()
+ {
+ string source = """
+ using System;
+ using Microsoft.UI.Xaml.Data;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class {|CSWINRT2003:MyType|} : ICustomPropertyProvider
+ {
+ public Type Type => typeof(MyType);
+ public ICustomProperty GetCustomProperty(string name) => null;
+ public ICustomProperty GetIndexedProperty(string name, Type type) => null;
+ public string GetStringRepresentation() => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task TypeWithImplicitInterfaceImplementation_Incomplete_Warns()
+ {
+ string source = """
+ using System;
+ using Microsoft.UI.Xaml.Data;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyType : ICustomPropertyProvider
+ {
+ public Type Type => typeof(MyType);
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source, expectedDiagnostics: [
+ // /0/Test0.cs(6,22): error CSWINRT2003: The type 'MyType' cannot use '[GeneratedCustomPropertyProvider]' because it already has or inherits implementations for one or more 'ICustomPropertyProvider' members
+ VerifyCS.Diagnostic().WithSpan(6, 22, 6, 28).WithArguments("MyType"),
+ // /0/Test0.cs(6,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.GetCustomProperty(string)'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(6, 31, 6, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetCustomProperty(string)"),
+ // /0/Test0.cs(6,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.GetIndexedProperty(string, Type)'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(6, 31, 6, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetIndexedProperty(string, System.Type)"),
+ // /0/Test0.cs(6,31): error CS0535: 'MyType' does not implement interface member 'ICustomPropertyProvider.GetStringRepresentation()'
+ DiagnosticResult.CompilerError("CS0535").WithSpan(6, 31, 6, 54).WithArguments("MyType", "Microsoft.UI.Xaml.Data.ICustomPropertyProvider.GetStringRepresentation()")]);
+ }
+
+ [TestMethod]
+ public async Task TypeInheritingFromImplementingBase_Warns()
+ {
+ string source = """
+ using System;
+ using Microsoft.UI.Xaml.Data;
+ using WindowsRuntime.Xaml;
+
+ public class BaseType : ICustomPropertyProvider
+ {
+ Type ICustomPropertyProvider.Type => typeof(BaseType);
+ ICustomProperty ICustomPropertyProvider.GetCustomProperty(string name) => null;
+ ICustomProperty ICustomPropertyProvider.GetIndexedProperty(string name, Type type) => null;
+ string ICustomPropertyProvider.GetStringRepresentation() => "";
+ }
+
+ [GeneratedCustomPropertyProvider]
+ public partial class {|CSWINRT2003:MyType|} : BaseType;
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task TypeWithGeneratedExplicitInterfaceImplementation_DoesNotWarn()
+ {
+ string source = """
+ using System;
+ using System.CodeDom.Compiler;
+ using Microsoft.UI.Xaml.Data;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class MyType : ICustomPropertyProvider
+ {
+ public string Name { get; set; }
+
+ [GeneratedCode("CustomPropertyProviderGenerator", "1.0.0")]
+ Type ICustomPropertyProvider.Type => typeof(MyType);
+ [GeneratedCode("CustomPropertyProviderGenerator", "1.0.0")]
+ ICustomProperty ICustomPropertyProvider.GetCustomProperty(string name) => null;
+ [GeneratedCode("CustomPropertyProviderGenerator", "1.0.0")]
+ ICustomProperty ICustomPropertyProvider.GetIndexedProperty(string name, Type type) => null;
+ [GeneratedCode("CustomPropertyProviderGenerator", "1.0.0")]
+ string ICustomPropertyProvider.GetStringRepresentation() => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ public async Task TypeWithMixedGeneratedAndUserImplementation_Warns()
+ {
+ string source = """
+ using System;
+ using System.CodeDom.Compiler;
+ using Microsoft.UI.Xaml.Data;
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial class {|CSWINRT2003:MyType|} : ICustomPropertyProvider
+ {
+ public string Name { get; set; }
+
+ Type ICustomPropertyProvider.Type => typeof(MyType);
+ [GeneratedCode("CustomPropertyProviderGenerator", "1.0.0")]
+ ICustomProperty ICustomPropertyProvider.GetCustomProperty(string name) => null;
+ [GeneratedCode("CustomPropertyProviderGenerator", "1.0.0")]
+ ICustomProperty ICustomPropertyProvider.GetIndexedProperty(string name, Type type) => null;
+ [GeneratedCode("CustomPropertyProviderGenerator", "1.0.0")]
+ string ICustomPropertyProvider.GetStringRepresentation() => "";
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+}
diff --git a/src/Tests/SourceGenerator2Test/Test_GeneratedCustomPropertyProviderTargetTypeAnalyzer.cs b/src/Tests/SourceGenerator2Test/Test_GeneratedCustomPropertyProviderTargetTypeAnalyzer.cs
new file mode 100644
index 0000000000..421242ce8d
--- /dev/null
+++ b/src/Tests/SourceGenerator2Test/Test_GeneratedCustomPropertyProviderTargetTypeAnalyzer.cs
@@ -0,0 +1,97 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Threading.Tasks;
+using WindowsRuntime.SourceGenerator.Diagnostics;
+using WindowsRuntime.SourceGenerator.Tests.Helpers;
+
+namespace WindowsRuntime.SourceGenerator.Tests;
+
+[TestClass]
+public class Test_GeneratedCustomPropertyProviderTargetTypeAnalyzer
+{
+ [TestMethod]
+ [DataRow("class")]
+ [DataRow("struct")]
+ public async Task ValidTargetType_DoesNotWarn(string modifier)
+ {
+ string source = $$"""
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public partial {{modifier}} MyType;
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ [DataRow("class")]
+ [DataRow("struct")]
+ public async Task ValidTargetType_InValidHierarchy_DoesNotWarn(string modifier)
+ {
+ string source = $$"""
+ using WindowsRuntime.Xaml;
+
+ public partial struct A
+ {
+ public partial class B
+ {
+ [GeneratedCustomPropertyProvider]
+ public partial {{modifier}} MyType;
+ }
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ [DataRow("abstract partial class")]
+ [DataRow("static partial class")]
+ [DataRow("ref partial struct")]
+ public async Task InvalidTargetType_Warns(string modifiers)
+ {
+ string source = $$"""
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public {{modifiers}} {|CSWINRT2000:MyType|};
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ [DataRow("class")]
+ [DataRow("struct")]
+ public async Task TypeNotPartial_Warns(string modifier)
+ {
+ string source = $$"""
+ using WindowsRuntime.Xaml;
+
+ [GeneratedCustomPropertyProvider]
+ public {{modifier}} {|CSWINRT2001:MyType|};
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+
+ [TestMethod]
+ [DataRow("class")]
+ [DataRow("struct")]
+ public async Task TypeNotInPartialTypeHierarchy_Warns(string modifier)
+ {
+ string source = $$"""
+ using WindowsRuntime.Xaml;
+
+ public class ParentType
+ {
+ [GeneratedCustomPropertyProvider]
+ public partial {{modifier}} {|CSWINRT2001:MyType|};
+ }
+ """;
+
+ await CSharpAnalyzerTest.VerifyAnalyzerAsync(source);
+ }
+}
\ No newline at end of file
diff --git a/src/WinRT.Runtime2/Xaml.Attributes/GeneratedCustomPropertyProvider.cs b/src/WinRT.Runtime2/Xaml.Attributes/GeneratedCustomPropertyProvider.cs
new file mode 100644
index 0000000000..1ac293ad4e
--- /dev/null
+++ b/src/WinRT.Runtime2/Xaml.Attributes/GeneratedCustomPropertyProvider.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System;
+
+namespace WindowsRuntime.Xaml;
+
+///
+/// An attribute used to indicate the properties which are bindable, for XAML (WinUI) scenarios.
+///
+///
+/// This attribute will cause binding code to be generated to provide support via the Windows.UI.Xaml.Data.ICustomPropertyProvider
+/// and Microsoft.UI.Xaml.Data.ICustomPropertyProvider infrastructure, for the specified properties on the annotated type.
+///
+///
+///
+[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
+public sealed class GeneratedCustomPropertyProviderAttribute : Attribute
+{
+ ///
+ /// Creates a new instance.
+ ///
+ ///
+ /// Using this constructor will mark all public properties as bindable.
+ ///
+ public GeneratedCustomPropertyProviderAttribute()
+ {
+ }
+
+ ///
+ /// Creates a new instance with the specified parameters.
+ ///
+ /// The name of the non-indexer public properties to mark as bindable.
+ /// The parameter type of the indexer public properties to mark as bindable.
+ public GeneratedCustomPropertyProviderAttribute(string[] propertyNames, Type[] indexerPropertyTypes)
+ {
+ PropertyNames = propertyNames;
+ IndexerPropertyTypes = indexerPropertyTypes;
+ }
+
+ ///
+ /// Gets the name of the non-indexer public properties to mark as bindable.
+ ///
+ ///
+ /// If , all public properties are considered bindable.
+ ///
+ public string[]? PropertyNames { get; }
+
+ ///
+ /// Gets the parameter type of the indexer public properties to mark as bindable.
+ ///
+ ///
+ /// If , all indexer public properties are considered bindable.
+ ///
+ public Type[]? IndexerPropertyTypes { get; }
+}
\ No newline at end of file
diff --git a/src/build.cmd b/src/build.cmd
index c151eeb0a7..404bb60f87 100644
--- a/src/build.cmd
+++ b/src/build.cmd
@@ -189,6 +189,16 @@ if ErrorLevel 1 (
exit /b !ErrorLevel!
)
+:sourcegenerator2test
+rem Running Source Generator 2 Unit Tests
+echo Running source generator 2 tests for %cswinrt_platform% %cswinrt_configuration%
+call :exec %dotnet_exe% test --verbosity normal --no-build --logger trx;LogFilePath=%~dp0sourcegenerator2test_%cswinrt_version_string%.trx %this_dir%Tests\SourceGenerator2Test\SourceGenerator2Test.csproj /nologo /m /p:platform=%cswinrt_platform%;configuration=%cswinrt_configuration% -- RunConfiguration.TreatNoTestsAsError=true
+if ErrorLevel 1 (
+ echo.
+ echo ERROR: Source generator 2 unit test failed, skipping NuGet pack
+ exit /b !ErrorLevel!
+)
+
:hosttest
rem Run WinRT.Host tests
echo Running cswinrt host tests for %cswinrt_platform% %cswinrt_configuration%
diff --git a/src/cswinrt.slnx b/src/cswinrt.slnx
index af6d7709c9..d4087f6519 100644
--- a/src/cswinrt.slnx
+++ b/src/cswinrt.slnx
@@ -206,6 +206,14 @@
+
+
+
+
+
+
+
+