@@ -101,6 +101,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
101
101
GenerateCCWForGenericInstantiation ) ;
102
102
103
103
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 ) ;
104
113
}
105
114
106
115
// Restrict to non-projected classes which can be instantiated
@@ -123,6 +132,14 @@ private static bool IsComponentType(SyntaxNode node)
123
132
! GeneratorHelper . IsWinRTType ( declaration ) ; // Making sure it isn't an RCW we are projecting.
124
133
}
125
134
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
+
126
143
private static ( VtableAttribute , EquatableArray < VtableAttribute > ) GetVtableAttributeToAdd (
127
144
GeneratorSyntaxContext context ,
128
145
TypeMapper typeMapper ,
@@ -224,6 +241,117 @@ private static (VtableAttribute, VtableAttribute) GetVtableAttributesForTaskAdap
224
241
return default ;
225
242
}
226
243
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
+
227
355
private static string ToFullyQualifiedString ( ISymbol symbol )
228
356
{
229
357
// Used to ensure class names within generics are fully qualified to avoid
@@ -1278,6 +1406,119 @@ private static string LookupRuntimeClassName(Type type)
1278
1406
addSource ( $ "WinRT{ classPrefix } GlobalVtableLookup.g.cs", source . ToString ( ) ) ;
1279
1407
}
1280
1408
}
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
+ }
1281
1522
}
1282
1523
1283
1524
internal readonly record struct GenericParameter (
@@ -1303,6 +1544,23 @@ internal sealed record VtableAttribute(
1303
1544
bool IsPublic ,
1304
1545
string RuntimeClassName = default ) ;
1305
1546
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
+
1306
1564
/// <summary>
1307
1565
/// A model describing a type info in a type hierarchy.
1308
1566
/// </summary>
0 commit comments