@@ -101,6 +101,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
101101 GenerateCCWForGenericInstantiation ) ;
102102
103103 context . RegisterImplementationSourceOutput ( vtablesToAddOnLookupTable . Collect ( ) . Combine ( properties ) , GenerateVtableLookupTable ) ;
104+
105+ var bindableCustomPropertyAttributes = context . SyntaxProvider . CreateSyntaxProvider (
106+ static ( n , _ ) => NeedCustomPropertyImplementation ( n ) ,
107+ static ( n , _ ) => n )
108+ . Select ( ( data , _ ) => GetBindableCustomProperties ( data ) )
109+ . Where ( static bindableCustomProperties => bindableCustomProperties != default )
110+ . Collect ( )
111+ . Combine ( properties ) ;
112+ context . RegisterImplementationSourceOutput ( bindableCustomPropertyAttributes , GenerateBindableCustomProperties ) ;
104113 }
105114
106115 // Restrict to non-projected classes which can be instantiated
@@ -123,6 +132,14 @@ private static bool IsComponentType(SyntaxNode node)
123132 ! GeneratorHelper . IsWinRTType ( declaration ) ; // Making sure it isn't an RCW we are projecting.
124133 }
125134
135+ private static bool NeedCustomPropertyImplementation ( SyntaxNode node )
136+ {
137+ return node is ClassDeclarationSyntax declaration &&
138+ ! declaration . Modifiers . Any ( m => m . IsKind ( SyntaxKind . StaticKeyword ) || m . IsKind ( SyntaxKind . AbstractKeyword ) ) &&
139+ declaration . Modifiers . Any ( m => m . IsKind ( SyntaxKind . PartialKeyword ) ) &&
140+ GeneratorHelper . HasBindableCustomPropertyAttribute ( declaration ) ;
141+ }
142+
126143 private static ( VtableAttribute , EquatableArray < VtableAttribute > ) GetVtableAttributeToAdd (
127144 GeneratorSyntaxContext context ,
128145 TypeMapper typeMapper ,
@@ -224,6 +241,117 @@ private static (VtableAttribute, VtableAttribute) GetVtableAttributesForTaskAdap
224241 return default ;
225242 }
226243
244+ #nullable enable
245+ private static BindableCustomProperties GetBindableCustomProperties ( GeneratorSyntaxContext context )
246+ {
247+ var symbol = context . SemanticModel . GetDeclaredSymbol ( ( ClassDeclarationSyntax ) context . Node ) ! ;
248+ INamedTypeSymbol bindableCustomPropertyAttributeSymbol = context . SemanticModel . Compilation . GetTypeByMetadataName ( "WinRT.BindableCustomPropertyAttribute" ) ! ;
249+
250+ if ( bindableCustomPropertyAttributeSymbol is null ||
251+ ! symbol . TryGetAttributeWithType ( bindableCustomPropertyAttributeSymbol , out AttributeData ? attributeData ) )
252+ {
253+ return default ;
254+ }
255+
256+ List < BindableCustomProperty > bindableCustomProperties = new ( ) ;
257+
258+ // Make all public properties in the class bindable including ones in base type.
259+ if ( attributeData . ConstructorArguments . Length == 0 )
260+ {
261+ for ( var curSymbol = symbol ; curSymbol != null ; curSymbol = curSymbol . BaseType )
262+ {
263+ foreach ( var propertySymbol in curSymbol . GetMembers ( ) .
264+ Where ( m => m . Kind == SymbolKind . Property &&
265+ m . DeclaredAccessibility == Accessibility . Public ) )
266+ {
267+ AddProperty ( propertySymbol ) ;
268+ }
269+ }
270+ }
271+ // Make specified public properties in the class bindable including ones in base type.
272+ else if ( attributeData . ConstructorArguments is
273+ [
274+ { Kind : TypedConstantKind . Array , Values : [ ..] propertyNames } ,
275+ { Kind : TypedConstantKind . Array , Values : [ ..] propertyIndexerTypes }
276+ ] )
277+ {
278+ for ( var curSymbol = symbol ; curSymbol != null ; curSymbol = curSymbol . BaseType )
279+ {
280+ foreach ( var member in curSymbol . GetMembers ( ) )
281+ {
282+ if ( member is IPropertySymbol propertySymbol &&
283+ member . DeclaredAccessibility == Accessibility . Public )
284+ {
285+ if ( ! propertySymbol . IsIndexer &&
286+ propertyNames . Any ( p => p . Value is string value && value == propertySymbol . Name ) )
287+ {
288+ AddProperty ( propertySymbol ) ;
289+ }
290+ else if ( propertySymbol . IsIndexer &&
291+ // ICustomProperty only supports single indexer parameter.
292+ propertySymbol . Parameters . Length == 1 &&
293+ propertyIndexerTypes . Any ( p => p . Value is ISymbol typeSymbol && typeSymbol . Equals ( propertySymbol . Parameters [ 0 ] . Type , SymbolEqualityComparer . Default ) ) )
294+ {
295+ AddProperty ( propertySymbol ) ;
296+ }
297+ }
298+ }
299+ }
300+ }
301+
302+ var typeName = ToFullyQualifiedString ( symbol ) ;
303+ bool isGlobalNamespace = symbol . ContainingNamespace == null || symbol . ContainingNamespace . IsGlobalNamespace ;
304+ var @namespace = symbol . ContainingNamespace ? . ToDisplayString ( ) ;
305+ if ( ! isGlobalNamespace )
306+ {
307+ typeName = typeName [ ( @namespace ! . Length + 1 ) ..] ;
308+ }
309+
310+ EquatableArray < TypeInfo > classHierarchy = ImmutableArray < TypeInfo > . Empty ;
311+
312+ // Gather the type hierarchy, only if the type is nested (as an optimization)
313+ if ( symbol . ContainingType is not null )
314+ {
315+ List < TypeInfo > hierarchyList = new ( ) ;
316+
317+ for ( ITypeSymbol parent = symbol ; parent is not null ; parent = parent . ContainingType )
318+ {
319+ hierarchyList . Add ( new TypeInfo (
320+ parent . ToDisplayString ( SymbolDisplayFormat . MinimallyQualifiedFormat ) ,
321+ parent . TypeKind ,
322+ parent . IsRecord ) ) ;
323+ }
324+
325+ classHierarchy = ImmutableArray . CreateRange ( hierarchyList ) ;
326+ }
327+
328+ return new BindableCustomProperties (
329+ @namespace ,
330+ isGlobalNamespace ,
331+ typeName ,
332+ classHierarchy ,
333+ ToFullyQualifiedString ( symbol ) ,
334+ bindableCustomProperties . ToImmutableArray ( ) ) ;
335+
336+ void AddProperty ( ISymbol symbol )
337+ {
338+ if ( symbol is IPropertySymbol propertySymbol )
339+ {
340+ bindableCustomProperties . Add ( new BindableCustomProperty (
341+ propertySymbol . MetadataName ,
342+ ToFullyQualifiedString ( propertySymbol . Type ) ,
343+ // Make sure the property accessors are also public even if property itself is public.
344+ propertySymbol . GetMethod != null && propertySymbol . GetMethod . DeclaredAccessibility == Accessibility . Public ,
345+ propertySymbol . SetMethod != null && propertySymbol . SetMethod . DeclaredAccessibility == Accessibility . Public ,
346+ propertySymbol . IsIndexer ,
347+ propertySymbol . IsIndexer ? ToFullyQualifiedString ( propertySymbol . Parameters [ 0 ] . Type ) : "" ,
348+ propertySymbol . IsStatic
349+ ) ) ;
350+ }
351+ }
352+ }
353+ #nullable disable
354+
227355 private static string ToFullyQualifiedString ( ISymbol symbol )
228356 {
229357 // Used to ensure class names within generics are fully qualified to avoid
@@ -1278,6 +1406,119 @@ private static string LookupRuntimeClassName(Type type)
12781406 addSource ( $ "WinRT{ classPrefix } GlobalVtableLookup.g.cs", source . ToString ( ) ) ;
12791407 }
12801408 }
1409+
1410+ private static void GenerateBindableCustomProperties (
1411+ SourceProductionContext sourceProductionContext ,
1412+ ( ImmutableArray < BindableCustomProperties > bindableCustomProperties , ( bool isCsWinRTAotOptimizerEnabled , bool isCsWinRTComponent , bool isCsWinRTCcwLookupTableGeneratorEnabled ) properties ) value )
1413+ {
1414+ if ( ! value . properties . isCsWinRTAotOptimizerEnabled || value . bindableCustomProperties . Length == 0 )
1415+ {
1416+ return ;
1417+ }
1418+
1419+ StringBuilder source = new ( ) ;
1420+
1421+ foreach ( var bindableCustomProperties in value . bindableCustomProperties )
1422+ {
1423+ if ( ! bindableCustomProperties . IsGlobalNamespace )
1424+ {
1425+ source . AppendLine ( $$ """
1426+ namespace {{ bindableCustomProperties . Namespace }}
1427+ {
1428+ """ ) ;
1429+ }
1430+
1431+ var escapedClassName = GeneratorHelper . EscapeTypeNameForIdentifier ( bindableCustomProperties . ClassName ) ;
1432+
1433+ ReadOnlySpan < TypeInfo > classHierarchy = bindableCustomProperties . ClassHierarchy . AsSpan ( ) ;
1434+ // If the type is nested, correctly nest the type definition
1435+ for ( int i = classHierarchy . Length - 1 ; i > 0 ; i -- )
1436+ {
1437+ source . AppendLine ( $$ """
1438+ partial {{ classHierarchy [ i ] . GetTypeKeyword ( ) }} {{ classHierarchy [ i ] . QualifiedName }}
1439+ {
1440+ """ ) ;
1441+ }
1442+
1443+ source . AppendLine ( $$ """
1444+ partial class {{ ( classHierarchy . IsEmpty ? bindableCustomProperties . ClassName : classHierarchy [ 0 ] . QualifiedName ) }} : global::Microsoft.UI.Xaml.Data.IBindableCustomPropertyImplementation
1445+ {
1446+ global::Microsoft.UI.Xaml.Data.BindableCustomProperty global::Microsoft.UI.Xaml.Data.IBindableCustomPropertyImplementation.GetProperty(string name)
1447+ {
1448+ """ ) ;
1449+
1450+ foreach ( var property in bindableCustomProperties . Properties . Where ( p => ! p . IsIndexer ) )
1451+ {
1452+ var instanceAccessor = property . IsStatic ? bindableCustomProperties . QualifiedClassName : $$ """ (({{ bindableCustomProperties . QualifiedClassName }} )instance)""" ;
1453+
1454+ source . AppendLine ( $$ """
1455+ if (name == "{{ property . Name }} ")
1456+ {
1457+ return new global::Microsoft.UI.Xaml.Data.BindableCustomProperty(
1458+ {{ GetBoolAsString ( property . CanRead ) }} ,
1459+ {{ GetBoolAsString ( property . CanWrite ) }} ,
1460+ "{{ property . Name }} ",
1461+ typeof({{ property . Type }} ),
1462+ {{ ( property . CanRead ? $$ """ static (instance) => {{ instanceAccessor }} .{{ property . Name }} """ : "null" ) }} ,
1463+ {{ ( property . CanWrite ? $$ """ static (instance, value) => {{ instanceAccessor }} .{{ property . Name }} = ({{ property . Type }} )value""" : "null" ) }} ,
1464+ null,
1465+ null);
1466+ }
1467+ """ ) ;
1468+ }
1469+
1470+ source . AppendLine ( $$ """
1471+ return default;
1472+ }
1473+
1474+ global::Microsoft.UI.Xaml.Data.BindableCustomProperty global::Microsoft.UI.Xaml.Data.IBindableCustomPropertyImplementation.GetProperty(global::System.Type indexParameterType)
1475+ {
1476+ """ ) ;
1477+
1478+ foreach ( var property in bindableCustomProperties . Properties . Where ( p => p . IsIndexer ) )
1479+ {
1480+ var instanceAccessor = property . IsStatic ? bindableCustomProperties . QualifiedClassName : $$ """ (({{ bindableCustomProperties . QualifiedClassName }} )instance)""" ;
1481+
1482+ source . AppendLine ( $$ """
1483+ if (indexParameterType == typeof({{ property . IndexerType }} ))
1484+ {
1485+ return new global::Microsoft.UI.Xaml.Data.BindableCustomProperty(
1486+ {{ GetBoolAsString ( property . CanRead ) }} ,
1487+ {{ GetBoolAsString ( property . CanWrite ) }} ,
1488+ "{{ property . Name }} ",
1489+ typeof({{ property . Type }} ),
1490+ null,
1491+ null,
1492+ {{ ( property . CanRead ? $$ """ static (instance, index) => {{ instanceAccessor }} [({{ property . IndexerType }} )index]""" : "null" ) }} ,
1493+ {{ ( property . CanWrite ? $$ """ static (instance, value, index) => {{ instanceAccessor }} [({{ property . IndexerType }} )index] = ({{ property . Type }} )value""" : "null" ) }} );
1494+ }
1495+ """ ) ;
1496+ }
1497+
1498+ source . AppendLine ( $$ """
1499+ return default;
1500+ }
1501+ }
1502+ """ ) ;
1503+
1504+ // Close all brackets
1505+ for ( int i = classHierarchy . Length - 1 ; i > 0 ; i -- )
1506+ {
1507+ source . AppendLine ( "}" ) ;
1508+ }
1509+
1510+ if ( ! bindableCustomProperties . IsGlobalNamespace )
1511+ {
1512+ source . AppendLine ( $@ "}}") ;
1513+ }
1514+
1515+ source . AppendLine ( ) ;
1516+ }
1517+
1518+ sourceProductionContext . AddSource ( "WinRTCustomBindableProperties.g.cs" , source . ToString ( ) ) ;
1519+
1520+ static string GetBoolAsString ( bool value ) => value ? "true" : "false" ;
1521+ }
12811522 }
12821523
12831524 internal readonly record struct GenericParameter (
@@ -1303,6 +1544,23 @@ internal sealed record VtableAttribute(
13031544 bool IsPublic ,
13041545 string RuntimeClassName = default ) ;
13051546
1547+ internal readonly record struct BindableCustomProperty (
1548+ string Name ,
1549+ string Type ,
1550+ bool CanRead ,
1551+ bool CanWrite ,
1552+ bool IsIndexer ,
1553+ string IndexerType ,
1554+ bool IsStatic ) ;
1555+
1556+ internal readonly record struct BindableCustomProperties (
1557+ string Namespace ,
1558+ bool IsGlobalNamespace ,
1559+ string ClassName ,
1560+ EquatableArray < TypeInfo > ClassHierarchy ,
1561+ string QualifiedClassName ,
1562+ EquatableArray < BindableCustomProperty > Properties ) ;
1563+
13061564 /// <summary>
13071565 /// A model describing a type info in a type hierarchy.
13081566 /// </summary>
0 commit comments