From c81e5007d7923be988d46ed829180231490f462e Mon Sep 17 00:00:00 2001 From: Tom McDonald Date: Thu, 20 Aug 2026 09:29:29 -0400 Subject: [PATCH] Fix CCW discovery for bindable property types Ensure property values exposed through GeneratedBindableCustomProperty contribute their concrete types to the generated CCW lookup table. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../WinRT.SourceGenerator/AotOptimizer.cs | 14 +++-- .../SourceGeneratorTest/AotOptimizerTests.cs | 53 +++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs b/src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs index 07b4aa662..55551c4a1 100644 --- a/src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs +++ b/src/Authoring/WinRT.SourceGenerator/AotOptimizer.cs @@ -1319,13 +1319,17 @@ private static EquatableArray GetVtableAttributesToAddOnLookupT } else if (context.Node is PropertyDeclarationSyntax propertyDeclaration) { + var isGeneratedBindableCustomPropertyClass = + context.SemanticModel.GetDeclaredSymbol(propertyDeclaration) is IPropertySymbol propertySymbol && + GeneratorHelper.IsGeneratedBindableCustomPropertyClass(context.SemanticModel.Compilation, propertySymbol.ContainingSymbol); + // Detect scenarios where the property declaration has an initializer and is to a boxed or cast type during initialization. if (propertyDeclaration.Initializer != null) { var leftSymbol = context.SemanticModel.GetSymbolInfo(propertyDeclaration.Type).Symbol; if (leftSymbol is INamedTypeSymbol namedType) { - AddVtableAttributesForExpression(propertyDeclaration.Initializer.Value, namedType); + AddVtableAttributesForExpression(propertyDeclaration.Initializer.Value, namedType, isGeneratedBindableCustomPropertyClass); } } else if (propertyDeclaration.ExpressionBody != null) @@ -1333,7 +1337,7 @@ private static EquatableArray GetVtableAttributesToAddOnLookupT var leftSymbol = context.SemanticModel.GetSymbolInfo(propertyDeclaration.Type).Symbol; if (leftSymbol is INamedTypeSymbol namedType) { - AddVtableAttributesForExpression(propertyDeclaration.ExpressionBody.Expression, namedType); + AddVtableAttributesForExpression(propertyDeclaration.ExpressionBody.Expression, namedType, isGeneratedBindableCustomPropertyClass); } } } @@ -1355,7 +1359,11 @@ private static EquatableArray GetVtableAttributesToAddOnLookupT var propertyTypeSymbol = context.SemanticModel.GetSymbolInfo(propertyDeclarationSyntax.Type).Symbol; if (propertyTypeSymbol is ITypeSymbol typeSymbol) { - AddVtableAttributesForExpression(returnDeclaration.Expression, typeSymbol); + var isGeneratedBindableCustomPropertyClass = + context.SemanticModel.GetDeclaredSymbol(propertyDeclarationSyntax) is IPropertySymbol propertySymbol && + GeneratorHelper.IsGeneratedBindableCustomPropertyClass(context.SemanticModel.Compilation, propertySymbol.ContainingSymbol); + + AddVtableAttributesForExpression(returnDeclaration.Expression, typeSymbol, isGeneratedBindableCustomPropertyClass); } } } diff --git a/src/Tests/SourceGeneratorTest/AotOptimizerTests.cs b/src/Tests/SourceGeneratorTest/AotOptimizerTests.cs index 58c162c7c..ec8c6eea7 100644 --- a/src/Tests/SourceGeneratorTest/AotOptimizerTests.cs +++ b/src/Tests/SourceGeneratorTest/AotOptimizerTests.cs @@ -121,6 +121,59 @@ public List M() Assert.IsFalse(generated.Contains("System.Collections.Generic.List`1[System.Int32]")); } + [TestMethod] + public void GeneratedBindableCustomProperty_DiscoversPropertyTypes() + { + const string source = """ + using System.Collections.ObjectModel; + using WinRT; + + [GeneratedBindableCustomProperty] + internal partial class ViewModel + { + private readonly ObservableCollection items = new(); + + public ObservableCollection Items + { + get { return items; } + } + + public ObservableCollection Numbers { get; } = new(); + + public ObservableCollection Values => new(); + } + """; + + string generated = RunAotOptimizer(source); + + Assert.IsTrue(generated.Contains("System.Collections.ObjectModel.ObservableCollection`1[System.String]")); + Assert.IsTrue(generated.Contains("System.Collections.ObjectModel.ObservableCollection`1[System.Int32]")); + Assert.IsTrue(generated.Contains("System.Collections.ObjectModel.ObservableCollection`1[System.Double]")); + Assert.IsTrue(generated.Contains("Windows.Foundation.Collections.IVector`1")); + } + + [TestMethod] + public void NonBindableProperties_DoNotDiscoverConcreteTypes() + { + const string source = """ + using System.Collections.ObjectModel; + + internal class ViewModel + { + private readonly ObservableCollection items = new(); + + public ObservableCollection Items + { + get { return items; } + } + } + """; + + string generated = RunAotOptimizer(source); + + Assert.IsFalse(generated.Contains("System.Collections.ObjectModel.ObservableCollection`1[System.String]")); + } + private static string RunAotOptimizer(string source) { SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(source, new CSharpParseOptions(LanguageVersion.Latest));