Skip to content

Commit 7eb6a1e

Browse files
mandel-macaqueGitHub Actions Autoformatter
andauthored
[RGen] Add support to inline protocol factory methods as constructors. (#23812)
This change allows to inline the factory methods found in protocols as constructors. --------- Co-authored-by: GitHub Actions Autoformatter <github-actions-autoformatter@xamarin.com>
1 parent d677a13 commit 7eb6a1e

22 files changed

+1008
-19
lines changed

src/rgen/Microsoft.Macios.Generator/DataModel/AttributeCodeChange.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ public static ImmutableArray<AttributeCodeChange> From (SyntaxList<AttributeList
9393
var bucket = ImmutableArray.CreateBuilder<AttributeCodeChange> ();
9494
foreach (AttributeListSyntax attributeListSyntax in attributes) {
9595
foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes) {
96-
var x = semanticModel.GetSymbolInfo (attributeSyntax);
9796
if (semanticModel.GetSymbolInfo (attributeSyntax).Symbol is not IMethodSymbol attributeSymbol)
9897
continue; // if we can't get the symbol, ignore it
9998
var name = attributeSymbol.ContainingType.ToDisplayString ();

src/rgen/Microsoft.Macios.Generator/DataModel/Binding.Generator.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,12 +283,14 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray<string> @
283283
context.SemanticModel.GetSymbolData (
284284
declaration: enumDeclaration,
285285
bindingType: BindingType.SmartEnum,
286+
context: context,
286287
name: out name,
287288
typeInfo: out typeInfo,
288289
baseClass: out baseClass,
289290
interfaces: out interfaces,
290291
outerClasses: out outerClasses,
291292
namespaces: out namespaces,
293+
protocolConstructors: out _, // no constructors in enums
292294
symbolAvailability: out availability,
293295
bindingInfo: out bindingInfo);
294296
FullyQualifiedSymbol = enumDeclaration.GetFullyQualifiedIdentifier (context.SemanticModel);
@@ -349,12 +351,14 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray<string> @
349351
context.SemanticModel.GetSymbolData (
350352
declaration: classDeclaration,
351353
bindingType: classDeclaration.GetBindingType (context.SemanticModel),
354+
context: context,
352355
name: out name,
353356
baseClass: out baseClass,
354357
typeInfo: out typeInfo,
355358
interfaces: out interfaces,
356359
outerClasses: out outerClasses,
357360
namespaces: out namespaces,
361+
protocolConstructors: out protocolConstructors,
358362
symbolAvailability: out availability,
359363
bindingInfo: out bindingInfo);
360364
FullyQualifiedSymbol = classDeclaration.GetFullyQualifiedIdentifier (context.SemanticModel);
@@ -393,12 +397,14 @@ internal Binding (BindingInfo bindingInfo, string name, ImmutableArray<string> @
393397
context.SemanticModel.GetSymbolData (
394398
declaration: interfaceDeclaration,
395399
bindingType: BindingType.Protocol,
400+
context: context,
396401
name: out name,
397402
typeInfo: out typeInfo,
398403
baseClass: out baseClass,
399404
interfaces: out interfaces,
400405
outerClasses: out outerClasses,
401406
namespaces: out namespaces,
407+
protocolConstructors: out _, // ingored in interfaces
402408
symbolAvailability: out availability,
403409
bindingInfo: out bindingInfo);
404410
FullyQualifiedSymbol = interfaceDeclaration.GetFullyQualifiedIdentifier (context.SemanticModel);
@@ -480,6 +486,8 @@ public override string ToString ()
480486
sb.AppendJoin (", ", EnumMembers);
481487
sb.Append ("], Constructors: [");
482488
sb.AppendJoin (", ", Constructors);
489+
sb.Append ("], ProtocolConstructors: [");
490+
sb.AppendJoin (", ", ProtocolConstructors);
483491
sb.Append ("], Properties: [");
484492
sb.AppendJoin (", ", Properties);
485493
sb.Append ("], ParentProtocolProperties: [");

src/rgen/Microsoft.Macios.Generator/DataModel/Binding.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,31 @@ public ImmutableArray<Constructor> Constructors {
244244
/// </summary>
245245
public ImmutableArray<string> ConstructorSelectors => [.. constructorIndex.Keys];
246246

247+
readonly Dictionary<string, int> protocolConstructorIndex = new ();
248+
readonly ImmutableArray<Constructor> protocolConstructors = [];
249+
250+
/// <summary>
251+
/// Gets the constructors inherited from parent protocols.
252+
/// </summary>
253+
public ImmutableArray<Constructor> ProtocolConstructors {
254+
get => protocolConstructors;
255+
init {
256+
protocolConstructors = value;
257+
// populate the constructor index for fast lookup using the symbol name
258+
for (var index = 0; index < protocolConstructors.Length; index++) {
259+
var constructor = protocolConstructors [index];
260+
if (constructor.Selector is null)
261+
continue;
262+
protocolConstructorIndex [constructor.Selector] = index;
263+
}
264+
}
265+
}
266+
267+
/// <summary>
268+
/// Returns all the selectors for the constructors.
269+
/// </summary>
270+
public ImmutableArray<string> ProtocolConstructorSelectors => [.. protocolConstructorIndex.Keys];
271+
247272
readonly Dictionary<string, int> eventsIndex = new ();
248273
readonly ImmutableArray<Event> events = [];
249274

src/rgen/Microsoft.Macios.Generator/DataModel/Constructor.Generator.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,15 @@ readonly partial struct Constructor {
3131
public Location? Location { get; init; }
3232

3333
/// <summary>
34-
/// True if the cosntructor was marked to skip its registration.
34+
/// True if the constructor was marked to skip its registration.
3535
/// </summary>
3636
public bool SkipRegistration => ExportMethodData.Flags.HasFlag (ObjCBindings.Constructor.SkipRegistration);
3737

38+
/// <summary>
39+
/// True if the constructor is thread safe.
40+
/// </summary>
41+
public bool IsThreadSafe => ExportMethodData.Flags.HasFlag (ObjCBindings.Constructor.IsThreadSafe);
42+
3843
public Constructor (string type,
3944
SymbolAvailability symbolAvailability,
4045
ExportData<ObjCBindings.Constructor> exportData,

src/rgen/Microsoft.Macios.Generator/DataModel/Constructor.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ namespace Microsoft.Macios.Generator.DataModel;
2828
/// </summary>
2929
public bool IsNullOrDefault => State == StructState.Default;
3030

31+
/// <summary>
32+
/// Gets or sets a value indicating whether the constructor comes from a protocol factory method.
33+
/// </summary>
34+
public bool IsProtocolConstructor { get; init; }
35+
3136
/// <summary>
3237
/// Type name that owns the constructor.
3338
/// </summary>

src/rgen/Microsoft.Macios.Generator/DataModel/Method.Generator.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ public static bool TryCreate (MethodDeclarationSyntax declaration, RootContext c
210210
change = new (
211211
type: method.ContainingSymbol.ToDisplayString ().Trim (), // we want the full name
212212
name: method.Name,
213-
returnType: new TypeInfo (method.ReturnType, context),
213+
returnType: new TypeInfo (method.ReturnType, context, includeEvents: false),
214214
symbolAvailability: method.GetSupportedPlatforms (),
215215
exportMethodData: exportData,
216216
attributes: attributes,
@@ -315,7 +315,7 @@ .. Modifiers.Where (m =>
315315
/// A new <see cref="Constructor"/> instance if the method is a factory method;
316316
/// otherwise, an uninitialized <see cref="Constructor"/> instance.
317317
/// </returns>
318-
public Constructor ToConstructor (TypeInfo targetClass)
318+
public Constructor ToConstructor (string targetClass)
319319
{
320320
// if the method is not a factory, we cannot convert it to a constructor so we will return the default value
321321
// which is an uninitialized instance
@@ -325,13 +325,17 @@ public Constructor ToConstructor (TypeInfo targetClass)
325325
// we need to create a constructor with the same modifiers, parameters and the availability of the method
326326
// since there is no guarantee that the target class has the same availability as the method
327327
return new (
328-
type: targetClass.Name,
329-
exportData: new (ExportMethodData.Selector),
328+
type: targetClass,
329+
exportData: IsThreadSafe
330+
? new (ExportMethodData.Selector) { Flags = ObjCBindings.Constructor.IsThreadSafe }
331+
: new (ExportMethodData.Selector),
330332
symbolAvailability: SymbolAvailability,
331333
attributes: [], // we do not really care about the attributes on the constructor that is going to be inlined
332334
modifiers: modifiers,
333335
parameters: Parameters
334-
);
336+
) {
337+
IsProtocolConstructor = true
338+
};
335339
}
336340

337341
/// <summary>

src/rgen/Microsoft.Macios.Generator/DataModel/TypeInfo.Generator.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,16 @@ static ImmutableArray<EventInfo> GetInterfaceEvents (string typeNamespace, IType
124124
return parentMethodsBucket.ToImmutable ();
125125
}
126126

127-
internal TypeInfo (ITypeSymbol symbol, RootContext context) : this (symbol)
127+
/// <summary>
128+
/// Initializes a new instance of the <see cref="TypeInfo"/> struct from a type symbol, with additional generator-specific information.
129+
/// </summary>
130+
/// <param name="symbol">The type symbol to create the <see cref="TypeInfo"/> from.</param>
131+
/// <param name="context">The root context for compilation information.</param>
132+
/// <param name="includeEvents">A flag to indicate whether to include event information for protocols.</param>
133+
internal TypeInfo (ITypeSymbol symbol, RootContext context, bool includeEvents = true) : this (symbol)
128134
{
129135
NeedsStret = symbol.NeedsStret (context.Compilation);
130-
if (symbol.IsProtocol ()) {
136+
if (symbol.IsProtocol () && includeEvents) {
131137
Events = GetInterfaceEvents (string.Join ('.', Namespace), symbol, context);
132138
}
133139
}

src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
3+
4+
using System;
35
using System.Collections.Generic;
46
using System.Collections.Immutable;
5-
using System.ComponentModel;
67
using System.Diagnostics.CodeAnalysis;
78
using System.IO;
89
using System.Linq;
@@ -18,9 +19,31 @@
1819
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
1920
using Constructor = ObjCBindings.Constructor;
2021
using Property = Microsoft.Macios.Generator.DataModel.Property;
22+
using TypeInfo = Microsoft.Macios.Generator.DataModel.TypeInfo;
2123

2224
namespace Microsoft.Macios.Generator.Emitters;
2325

26+
file class ConstructorParameterComparer : IEqualityComparer<ImmutableArray<TypeInfo>> {
27+
/// <summary>
28+
/// Determines equality by requiring the same method name and identical ordered parameter type sequence.
29+
/// </summary>
30+
public bool Equals (ImmutableArray<TypeInfo> x, ImmutableArray<TypeInfo> y)
31+
{
32+
return x.SequenceEqual (y);
33+
}
34+
35+
/// <summary>
36+
/// Computes a hash code combining the method name and ordered parameter types.
37+
/// </summary>
38+
public int GetHashCode (ImmutableArray<TypeInfo> obj)
39+
{
40+
var hash = new HashCode ();
41+
foreach (var t in obj)
42+
hash.Add (t);
43+
return hash.ToHashCode ();
44+
}
45+
}
46+
2447
/// <summary>
2548
/// Emitter for Objective-C classes.
2649
/// </summary>
@@ -54,9 +77,30 @@ public string GetSymbolName (in Binding binding)
5477
/// <param name="classBlock">Current class block.</param>
5578
void EmitConstructors (in BindingContext context, TabbedWriter<StringWriter> classBlock)
5679
{
80+
// merge the constructors and the protocol constructors for the current class, use a dict to avoid duplicates
81+
var allConstructors = new Dictionary<ImmutableArray<TypeInfo>, DataModel.Constructor> (new ConstructorParameterComparer ());
82+
foreach (var constructor in context.Changes.Constructors) {
83+
if (constructor.Selector is null)
84+
continue;
85+
var key = constructor.Parameters.Select (x => x.Type).ToImmutableArray ();
86+
allConstructors.TryAdd (key, constructor);
87+
}
88+
89+
foreach (var constructor in context.Changes.ProtocolConstructors) {
90+
if (constructor.Selector is null)
91+
continue;
92+
var key = constructor.Parameters.Select (x => x.Type).ToImmutableArray ();
93+
allConstructors.TryAdd (key, constructor);
94+
}
95+
96+
// create the ui thread check to be used in the constructors that come from a protocol factory method
97+
var uiThreadCheck = (context.NeedsThreadChecks)
98+
? EnsureUiThread (context.RootContext.CurrentPlatform)
99+
: null;
100+
57101
// When dealing with constructors we cannot sort them by name because the name is always the same as the class
58102
// instead we will sort them by the selector name so that we will always generate the constructors in the same order
59-
foreach (var constructor in context.Changes.Constructors.OrderBy (c => c.ExportMethodData.Selector)) {
103+
foreach (var constructor in allConstructors.Values.OrderBy (c => c.ExportMethodData.Selector)) {
60104
classBlock.AppendMemberAvailability (constructor.SymbolAvailability);
61105
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
62106
if (constructor.ExportMethodData.Flags.HasFlag (Constructor.DesignatedInitializer)) {
@@ -68,6 +112,12 @@ void EmitConstructors (in BindingContext context, TabbedWriter<StringWriter> cla
68112
}
69113

70114
using (var constructorBlock = classBlock.CreateBlock (constructor.ToDeclaration (withBaseNSFlag: true).ToString (), block: true)) {
115+
if (uiThreadCheck is not null && constructor is { IsProtocolConstructor: true, IsThreadSafe: false }) {
116+
// if we are dealing with a protocol constructor, we need to ensure we are on the UI thread, this
117+
// happens for example with NSCoding in ui elements.
118+
constructorBlock.Write (uiThreadCheck.ToString ());
119+
constructorBlock.WriteLine ();
120+
}
71121
// retrieve the method invocation via the factory, this will generate the necessary arguments
72122
// transformations and the invocation
73123
var invocations = GetInvocations (constructor);
@@ -122,12 +172,12 @@ void EmitNotifications (in ImmutableArray<Property> properties, TabbedWriter<Str
122172
: notification.ExportFieldData.FieldData.Type;
123173
// use the raw writer which makes it easier to read in this case
124174
notificationClass.WriteRaw (
125-
@$"public static {NSObject} {name} ({EventHandler}<{eventType}> handler)
175+
@$"public static {NSObject} {name} ({BindingSyntaxFactory.EventHandler}<{eventType}> handler)
126176
{{
127177
return {notificationCenter}.AddObserver ({notification.Name}, notification => handler (null, new {eventType} (notification)));
128178
}}
129179
130-
public static NSObject {name} ({NSObject} objectToObserve, {EventHandler}<{eventType}> handler)
180+
public static NSObject {name} ({NSObject} objectToObserve, {BindingSyntaxFactory.EventHandler}<{eventType}> handler)
131181
{{
132182
return {notificationCenter}.AddObserver ({notification.Name}, notification => handler (null, new {eventType} (notification)), objectToObserve);
133183
}}
@@ -193,8 +243,8 @@ void EmitEvents (in BindingContext bindingContext, in ImmutableArray<Property> d
193243
foreach (var eventInfo in property.ExportPropertyData.StrongDelegateType.Events) {
194244
// create the event args type name
195245
var eventHandler = eventInfo.EventArgsType is null
196-
? EventHandler.ToString ()
197-
: $"{EventHandler}<{eventInfo.EventArgsType}>";
246+
? BindingSyntaxFactory.EventHandler.ToString ()
247+
: $"{BindingSyntaxFactory.EventHandler}<{eventInfo.EventArgsType}>";
198248
using (var eventBlock =
199249
classBlock.CreateBlock ($"public event {eventHandler} {eventInfo.Name}", true)) {
200250
eventBlock.WriteLine ($"add {{ {ensureMethod} ()!.{eventInfo.Name.Decapitalize ()} += value; }}");

src/rgen/Microsoft.Macios.Generator/Extensions/SemanticModelExtensions.Generator.cs

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,64 @@
55
using Microsoft.CodeAnalysis;
66
using Microsoft.CodeAnalysis.CSharp.Syntax;
77
using Microsoft.Macios.Generator.Availability;
8+
using Microsoft.Macios.Generator.Context;
89
using Microsoft.Macios.Generator.DataModel;
910
using TypeInfo = Microsoft.Macios.Generator.DataModel.TypeInfo;
1011

1112
namespace Microsoft.Macios.Generator.Extensions;
1213

1314
static partial class SemanticModelExtensions {
1415

16+
/// <summary>
17+
/// Retrieves all the data from a symbol needed for a binding/transformation.
18+
/// </summary>
19+
/// <param name="symbol">The symbol being queried.</param>
20+
/// <param name="name">Symbol name.</param>
21+
/// <param name="baseClass">Symbol base class.</param>
22+
/// <param name="interfaces">List of the interfaces implemented by the symbol.</param>
23+
/// <param name="outerClasses">List of the outer classes of the symbol.</param>
24+
/// <param name="namespaces">Collection with the namespaces of the symbol.</param>
25+
/// <param name="protocolConstructors">Collection with the protocol constructors to inline.</param>
26+
/// <param name="symbolAvailability">The symbols availability.</param>
27+
public static void GetSymbolData (ISymbol? symbol,
28+
RootContext context,
29+
out string name,
30+
out string? baseClass,
31+
out ImmutableArray<string> interfaces,
32+
out ImmutableArray<OuterClass> outerClasses,
33+
out ImmutableArray<string> namespaces,
34+
out ImmutableArray<Constructor> protocolConstructors,
35+
out SymbolAvailability symbolAvailability)
36+
{
37+
name = symbol?.Name ?? string.Empty;
38+
baseClass = null;
39+
var interfacesBucket = ImmutableArray.CreateBuilder<string> ();
40+
var protocolConstructorsBucket = ImmutableArray.CreateBuilder<Constructor> ();
41+
if (symbol is INamedTypeSymbol namedTypeSymbol) {
42+
baseClass = namedTypeSymbol.BaseType?.ToDisplayString ().Trim ();
43+
foreach (var symbolInterface in namedTypeSymbol.Interfaces) {
44+
interfacesBucket.Add (symbolInterface.ToDisplayString ().Trim ());
45+
if (symbolInterface.IsProtocol ()) {
46+
// get the constructors for the protocol binding info to get its methods and from those
47+
// the inline constructors
48+
foreach (var member in symbolInterface.GetMembers ()) {
49+
if (member is IMethodSymbol methodSymbol &&
50+
Method.TryCreate (methodSymbol, context, out var method) &&
51+
method.Value.IsFactory) {
52+
var constructor = method.Value.ToConstructor (name);
53+
if (!constructor.IsNullOrDefault)
54+
protocolConstructorsBucket.Add (constructor);
55+
}
56+
}
57+
}
58+
}
59+
}
60+
symbolAvailability = symbol?.GetSupportedPlatforms () ?? new SymbolAvailability ();
61+
interfaces = interfacesBucket.ToImmutable ();
62+
protocolConstructors = protocolConstructorsBucket.ToImmutable ();
63+
(namespaces, outerClasses) = GetNamespaceArrayAndOuterClasses (symbol);
64+
}
65+
1566
/// <summary>
1667
/// Extracts symbol data from a type declaration including binding information, type details, and availability.
1768
/// </summary>
@@ -24,23 +75,26 @@ static partial class SemanticModelExtensions {
2475
/// <param name="interfaces">When this method returns, contains the implemented interface names.</param>
2576
/// <param name="outerClasses">When this method returns, contains the outer class hierarchy.</param>
2677
/// <param name="namespaces">When this method returns, contains the namespace hierarchy.</param>
78+
/// <param name="protocolConstructors"></param>
2779
/// <param name="symbolAvailability">When this method returns, contains the platform availability information.</param>
2880
/// <param name="bindingInfo">When this method returns, contains the binding-specific information based on the binding type.</param>
2981
public static void GetSymbolData (this SemanticModel self, BaseTypeDeclarationSyntax declaration,
3082
BindingType bindingType,
83+
RootContext context,
3184
out string name,
3285
out string? baseClass,
3386
out TypeInfo typeInfo,
3487
out ImmutableArray<string> interfaces,
3588
out ImmutableArray<OuterClass> outerClasses,
3689
out ImmutableArray<string> namespaces,
90+
out ImmutableArray<Constructor> protocolConstructors,
3791
out SymbolAvailability symbolAvailability,
3892
out BindingInfo bindingInfo)
3993
{
4094
var symbol = self.GetDeclaredSymbol (declaration);
4195
// only named types have type info
4296
typeInfo = (symbol is INamedTypeSymbol namedTypeSymbol) ? new (namedTypeSymbol) : TypeInfo.Default;
43-
GetSymbolData (symbol, out name, out baseClass, out interfaces, out outerClasses, out namespaces, out symbolAvailability);
97+
GetSymbolData (symbol, context, out name, out baseClass, out interfaces, out outerClasses, out namespaces, out protocolConstructors, out symbolAvailability);
4498
if (symbol is null)
4599
bindingInfo = default;
46100
else {

0 commit comments

Comments
 (0)