Skip to content

Commit b0f4ddb

Browse files
mandel-macaqueCopilotGitHub Actions Autoformatter
authored
[RGen] Emit the methods for protocol wrappers. (#23463)
We need to add the following changes: - Add helper methods to convert a protocol method to a wrapper method, mainly remove not needed modifiers. - Add export attributes just for wrapper methods context is a protocol and method is not a extension. - EmitMethod will use the send directly when we are generating a wrapper method. - Add missing protocol member attrs for methods. --------- Co-authored-by: Copilot <[email protected]> Co-authored-by: GitHub Actions Autoformatter <[email protected]>
1 parent e5e96e1 commit b0f4ddb

File tree

10 files changed

+152
-18
lines changed

10 files changed

+152
-18
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,21 @@ public Method ToProtocolMethod (TypeInfo protocol)
257257
],
258258
};
259259
}
260+
261+
/// <summary>
262+
/// Converts the current method into a method suitable for a protocol wrapper class.
263+
/// This involves removing modifiers like 'unsafe', 'partial', and 'virtual'.
264+
/// </summary>
265+
/// <returns>A new <see cref="Method"/> instance with updated modifiers for the protocol wrapper.</returns>
266+
public Method ToProtocolWrapperMethod ()
267+
{
268+
// contains the exact same data but the modifiers are updated to remove virtual and partial if they are present
269+
return this with {
270+
Modifiers = [
271+
.. Modifiers.Where (m =>
272+
!m.IsKind (SyntaxKind.PartialKeyword) &&
273+
!m.IsKind (SyntaxKind.VirtualKeyword)),
274+
]
275+
};
276+
}
260277
}

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,11 @@ void EmitField (string selector, string selectorName)
8282
/// <summary>
8383
/// Emits the body for a method that does not return a value.
8484
/// </summary>
85+
/// <param name="context">Current binding context.</param>
8586
/// <param name="method">The method for which to generate the body.</param>
8687
/// <param name="invocations">The method invocations and argument transformations.</param>
8788
/// <param name="methodBlock">The writer for the method block.</param>
88-
static void EmitVoidMethodBody (in Method method, in MethodInvocations invocations, TabbedWriter<StringWriter> methodBlock)
89+
static void EmitVoidMethodBody (in BindingContext context, in Method method, in MethodInvocations invocations, TabbedWriter<StringWriter> methodBlock)
8990
{
9091
// validate and init the needed temp variables
9192
foreach (var argument in invocations.Arguments) {
@@ -98,7 +99,8 @@ static void EmitVoidMethodBody (in Method method, in MethodInvocations invocatio
9899
methodBlock.Write (argument.PreCallConversion, verifyTrivia: false);
99100
}
100101

101-
if (method.IsExtension) {
102+
// if we are dealing with a protocol or an extension method, we need to call send directly
103+
if (context.Changes.BindingType == BindingType.Protocol || method.IsExtension) {
102104
methodBlock.WriteRaw (
103105
$@"{ExpressionStatement (invocations.Send)}
104106
{ExpressionStatement (KeepAlive (method.This))}
@@ -127,7 +129,7 @@ static void EmitVoidMethodBody (in Method method, in MethodInvocations invocatio
127129
/// <param name="method">The method for which to generate the body.</param>
128130
/// <param name="invocations">The method invocations and argument transformations.</param>
129131
/// <param name="methodBlock">The writer for the method block.</param>
130-
static void EmitReturnMethodBody (in Method method, in MethodInvocations invocations, TabbedWriter<StringWriter> methodBlock)
132+
static void EmitReturnMethodBody (in BindingContext context, in Method method, in MethodInvocations invocations, TabbedWriter<StringWriter> methodBlock)
131133
{
132134
// similar to the void method but we need to create a temp variable to store the return value
133135
// and do any conversions that might be needed for the return value, for example byte to bool
@@ -144,7 +146,8 @@ static void EmitReturnMethodBody (in Method method, in MethodInvocations invocat
144146
methodBlock.Write (argument.PreCallConversion, verifyTrivia: false);
145147
}
146148

147-
if (method.IsExtension) {
149+
// if we are dealing with a protocol or an extension method, we need to call send directly
150+
if (context.Changes.BindingType == BindingType.Protocol || method.IsExtension) {
148151
methodBlock.WriteRaw (
149152
$@"{tempDeclaration}
150153
{ExpressionStatement (invocations.Send)}
@@ -191,6 +194,12 @@ public static void EmitMethod (this IClassEmitter self, in BindingContext contex
191194
classBlock.AppendMemberAvailability (method.SymbolAvailability);
192195
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
193196

197+
// append the export attribute to the method just in case it is a protocol method in a wrapper class,
198+
// that is when the method is not an extension method and the binding type is protocol.
199+
if (context.Changes.BindingType == BindingType.Protocol && !method.IsExtension) {
200+
classBlock.AppendExportAttribute (method.ExportMethodData);
201+
}
202+
194203
using (var methodBlock = classBlock.CreateBlock (method.ToDeclaration ().ToString (), block: true)) {
195204
// write any possible thread check at the beginning of the method
196205
if (uiThreadCheck is not null) {
@@ -203,9 +212,9 @@ public static void EmitMethod (this IClassEmitter self, in BindingContext contex
203212
var invocations = GetInvocations (method);
204213

205214
if (method.ReturnType.IsVoid) {
206-
EmitVoidMethodBody (method, invocations, methodBlock);
215+
EmitVoidMethodBody (context, method, invocations, methodBlock);
207216
} else {
208-
EmitReturnMethodBody (method, invocations, methodBlock);
217+
EmitReturnMethodBody (context, method, invocations, methodBlock);
209218
}
210219
}
211220

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,12 @@ public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out
192192
builder.AppendProtocolMemberData (protocolMember);
193193
}
194194

195+
// append the methods to the protocol member data
196+
foreach (var method in bindingContext.Changes.Methods.OrderBy (m => m.Name)) {
197+
var protocolMember = new ProtocolMemberData (method);
198+
builder.AppendProtocolMemberData (protocolMember);
199+
}
200+
195201
var modifiers = $"{string.Join (' ', bindingContext.Changes.Modifiers)} ";
196202
// class declaration, the analyzer should ensure that the class is static, otherwise it will fail to compile with an error.
197203
using (var interfaceBlock = builder.CreateBlock (

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ namespace Microsoft.Macios.Generator.Emitters;
1818
/// <summary>
1919
/// Emits the wrapper class for a protocol.
2020
/// </summary>
21-
class ProtocolWrapperEmitter : ICodeEmitter {
21+
class ProtocolWrapperEmitter : IClassEmitter {
2222
/// <inheritdoc />
2323
public string GetSymbolName (in Binding binding) => Nomenclator.GetProtocolWrapperName (binding.Name);
2424

@@ -32,8 +32,8 @@ class ProtocolWrapperEmitter : ICodeEmitter {
3232
/// <param name="classBlock">The writer for the class block.</param>
3333
void EmitDefaultConstructors (in BindingContext bindingContext, TabbedWriter<StringWriter> classBlock)
3434
{
35-
classBlock.WriteLine ("// Implement default constructor");
3635
classBlock.WriteLine ();
36+
classBlock.WriteLine ("// Implement default constructor");
3737
}
3838

3939
/// <summary>
@@ -48,8 +48,8 @@ void EmitProperties (in BindingContext context, TabbedWriter<StringWriter> class
4848
.OrderBy (p => p.Name);
4949

5050
foreach (var property in allProperties) {
51-
classBlock.WriteLine ($"// Implement property: {property.Name}");
5251
classBlock.WriteLine ();
52+
classBlock.WriteLine ($"// Implement property: {property.Name}");
5353
}
5454
}
5555

@@ -65,7 +65,7 @@ void EmitMethods (in BindingContext context, TabbedWriter<StringWriter> classBlo
6565
.OrderBy (m => m.Name);
6666

6767
foreach (var method in allMethods) {
68-
classBlock.WriteLine ($"// Implement method: {method.Name}");
68+
this.EmitMethod (context, method.ToProtocolWrapperMethod (), classBlock);
6969
classBlock.WriteLine ();
7070
}
7171
}

src/rgen/Microsoft.Macios.Generator/IO/TabbedStringBuilderExtensions.cs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ static class TabbedStringBuilderExtensions {
1919
/// <param name="self">A tabbed string builder.</param>
2020
/// <param name="optimizable">If the binding is Optimizable or not.</param>
2121
/// <returns>The current builder.</returns>
22-
public static TabbedWriter<StringWriter> AppendGeneratedCodeAttribute (this TabbedWriter<StringWriter> self, bool optimizable = true)
22+
public static TabbedWriter<StringWriter> AppendGeneratedCodeAttribute (this TabbedWriter<StringWriter> self,
23+
bool optimizable = true)
2324
{
2425
if (optimizable) {
2526
const string attr = "[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]";
@@ -39,7 +40,8 @@ public static TabbedWriter<StringWriter> AppendGeneratedCodeAttribute (this Tabb
3940
/// <param name="className">The class that contains the notification.</param>
4041
/// <param name="notification">The name of the notification.</param>
4142
/// <returns></returns>
42-
public static TabbedWriter<StringWriter> AppendNotificationAdvice (this TabbedWriter<StringWriter> self, in string className, in string notification)
43+
public static TabbedWriter<StringWriter> AppendNotificationAdvice (this TabbedWriter<StringWriter> self,
44+
in string className, in string notification)
4345
{
4446
string attr =
4547
$"[Advice (\"Use '{className}.Notifications.{notification}' helper method instead.\")]";
@@ -52,7 +54,8 @@ public static TabbedWriter<StringWriter> AppendNotificationAdvice (this TabbedWr
5254
/// </summary>
5355
/// <param name="self">A tabbed string builder.</param>
5456
/// <returns>The current builder.</returns>
55-
public static TabbedWriter<StringWriter> AppendEditorBrowsableAttribute (this TabbedWriter<StringWriter> self, EditorBrowsableState state)
57+
public static TabbedWriter<StringWriter> AppendEditorBrowsableAttribute (this TabbedWriter<StringWriter> self,
58+
EditorBrowsableState state)
5659
{
5760
string attr = $"[EditorBrowsable (EditorBrowsableState.{state})]";
5861
self.WriteLine (attr);
@@ -206,13 +209,15 @@ public static TabbedWriter<StringWriter> AppendProtocolMemberData (this TabbedWr
206209

207210
// the following are optional since we might not have a proxy for a callback but are used in both cases (methods and properties)
208211
if (protocolMemberData.ReturnTypeDelegateProxy is not null) {
209-
sb.Append ($", ReturnTypeDelegateProxy = typeof ({protocolMemberData.ReturnTypeDelegateProxy.Value.GetIdentifierSyntax ()})");
212+
sb.Append (
213+
$", ReturnTypeDelegateProxy = typeof ({protocolMemberData.ReturnTypeDelegateProxy.Value.GetIdentifierSyntax ()})");
210214
}
211215

212216
if (protocolMemberData.ParameterBlockProxy.Length > 0) {
213217
// build the parameter block proxy string array
214218
var blockProxies = string.Join (", ",
215-
protocolMemberData.ParameterBlockProxy.Select (x => x is null ? "null" : $"typeof ({x.Value.GetIdentifierSyntax ()})"));
219+
protocolMemberData.ParameterBlockProxy.Select (x =>
220+
x is null ? "null" : $"typeof ({x.Value.GetIdentifierSyntax ()})"));
216221
sb.Append ($", ParameterBlockProxy = new Type? [] {{ {blockProxies} }}");
217222
}
218223

@@ -252,4 +257,19 @@ public static TabbedWriter<StringWriter> AppendRequiredMemberAttribute (this Tab
252257
self.WriteLine ($"[{RequiredMember}]");
253258
return self;
254259
}
260+
261+
/// <summary>
262+
/// Appends an `[Export<Method>]` attribute to the current writer.
263+
/// This is a simplified version that only includes the selector, as required by the registrar.
264+
/// </summary>
265+
/// <param name="self">A tabbed string writer.</param>
266+
/// <param name="exportData">The export data for the method, from which the selector is extracted.</param>
267+
/// <returns>The current writer.</returns>
268+
public static TabbedWriter<StringWriter> AppendExportAttribute (this TabbedWriter<StringWriter> self,
269+
ExportData<ObjCBindings.Method> exportData)
270+
{
271+
// the resitrar does not care about other flags but the selector, so we can just use the selector
272+
self.WriteLine ($"[Export<Method> (\"{exportData.Selector}\")]");
273+
return self;
274+
}
255275
}

tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/InterfaceCodeChangesTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1251,7 +1251,7 @@ void CodeChangesFromInterfaceDeclaration (ApplePlatform platform, string inputTe
12511251
var node = sourceTrees [0].GetRoot ()
12521252
.DescendantNodes ()
12531253
.OfType<InterfaceDeclarationSyntax> ()
1254-
.LastOrDefault (); // always get the last one so that inheritance tests are correct
1254+
.LastOrDefault (); // always get the last one so that inherirance tests are correct
12551255
Assert.NotNull (node);
12561256
var semanticModel = compilation.GetSemanticModel (sourceTrees [0]);
12571257
var changes = Binding.FromDeclaration (node, semanticModel);

tests/rgen/Microsoft.Macios.Generator.Tests/DataModel/MethodTests/FromDeclarationTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections;
66
using System.Collections.Generic;
77
using System.Linq;
8+
using Microsoft.CodeAnalysis;
89
using Microsoft.CodeAnalysis.CSharp;
910
using Microsoft.CodeAnalysis.CSharp.Syntax;
1011
using Microsoft.Macios.Generator.Attributes;
@@ -1113,4 +1114,43 @@ interface IAVAudioMixing {
11131114
Assert.True (protocolMethod.IsExtension);
11141115
}
11151116

1117+
[Theory]
1118+
[AllSupportedPlatforms]
1119+
void ToProtocolWrapperMethodTests (ApplePlatform platform)
1120+
{
1121+
var inputText = @"
1122+
using AVFoundation;
1123+
using Foundation;
1124+
using ObjCBindings;
1125+
using ObjCRuntime;
1126+
using System.Runtime.Versioning;
1127+
1128+
namespace Microsoft.Macios.Generator.Tests.Protocols.Data;
1129+
1130+
[SupportedOSPlatform (""ios"")]
1131+
[SupportedOSPlatform (""tvos"")]
1132+
[SupportedOSPlatform (""macos"")]
1133+
[SupportedOSPlatform (""maccatalyst13.1"")]
1134+
[BindingType<Protocol>]
1135+
interface IAVAudioMixing {
1136+
1137+
[Export<Method> (""destinationForMixer:bus:"")]
1138+
public virtual unsafe partial AVAudioMixingDestination? DestinationForMixer (AVAudioNode mixer, nuint bus);
1139+
}
1140+
";
1141+
1142+
var (compilation, syntaxTrees) = CreateCompilation (platform, sources: inputText);
1143+
Assert.Single (syntaxTrees);
1144+
var semanticModel = compilation.GetSemanticModel (syntaxTrees [0]);
1145+
var declaration = syntaxTrees [0].GetRoot ()
1146+
.DescendantNodes ().OfType<MethodDeclarationSyntax> ()
1147+
.FirstOrDefault ();
1148+
Assert.NotNull (declaration);
1149+
Assert.True (Method.TryCreate (declaration, semanticModel, out var changes));
1150+
Assert.NotNull (changes);
1151+
var protocolWrapperMethod = changes.Value.ToProtocolWrapperMethod ();
1152+
Assert.DoesNotContain (protocolWrapperMethod.Modifiers, m => m.IsKind (SyntaxKind.VirtualKeyword));
1153+
Assert.DoesNotContain (protocolWrapperMethod.Modifiers, m => m.IsKind (SyntaxKind.PartialKeyword));
1154+
Assert.Contains (protocolWrapperMethod.Modifiers, m => m.IsKind (SyntaxKind.PublicKeyword));
1155+
}
11161156
}

tests/rgen/Microsoft.Macios.Generator.Tests/IO/TabbedStringBuilderTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,32 @@ void AppendDynamicDependencyAttributeWithMethodTests (Method method, string expe
878878
Assert.Equal (expectedString, result);
879879
}
880880

881+
public static IEnumerable<object []> AppendExportAttributeTestData {
882+
get {
883+
// Simple selector
884+
yield return [new ExportData<ObjCBindings.Method> ("mySelector:"), "[Export<Method> (\"mySelector:\")]\n"];
885+
886+
// Selector with multiple parts
887+
yield return [new ExportData<ObjCBindings.Method> ("mySelector:withObject:"), "[Export<Method> (\"mySelector:withObject:\")]\n"];
888+
889+
// Selector with no parameters
890+
yield return [new ExportData<ObjCBindings.Method> ("mySelector"), "[Export<Method> (\"mySelector\")]\n"];
891+
892+
// Null selector
893+
yield return [new ExportData<ObjCBindings.Method> (null), "[Export<Method> (\"\")]\n"];
894+
}
895+
}
896+
897+
[Theory]
898+
[MemberData (nameof (AppendExportAttributeTestData))]
899+
void AppendExportAttributeTests (ExportData<ObjCBindings.Method> exportData, string expectedString)
900+
{
901+
var block = new TabbedStringBuilder (sb);
902+
block.AppendExportAttribute (exportData);
903+
var result = block.ToCode ();
904+
Assert.Equal (expectedString, result);
905+
}
906+
881907
[Fact]
882908
public void ClearTests ()
883909
{

tests/rgen/Microsoft.Macios.Generator.Tests/Protocols/Data/ExpectedIAVAudio3DMixing.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ internal static void _SetSourceMode (this IAVAudio3DMixing self, global::AVFound
349349

350350
internal unsafe sealed class AVAudio3DMixingWrapper : BaseWrapper, IAVAudio3DMixing
351351
{
352+
352353
// Implement default constructor
353354

354355
// Implement property: Obstruction
@@ -364,5 +365,4 @@ internal unsafe sealed class AVAudio3DMixingWrapper : BaseWrapper, IAVAudio3DMix
364365
// Implement property: ReverbBlend
365366

366367
// Implement property: SourceMode
367-
368368
}

tests/rgen/Microsoft.Macios.Generator.Tests/Protocols/Data/ExpectedIAVAudioMixing.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ namespace Microsoft.Macios.Generator.Tests.Protocols.Data;
1616
[SupportedOSPlatform ("maccatalyst13.1")]
1717
[Protocol (Name = "AVAudioMixing", WrapperType = typeof (AVAudioMixingWrapper))]
1818
[ProtocolMember (IsRequired = true, IsProperty = true, IsStatic = false, Name = "Volume", Selector = "volume", PropertyType = typeof (float), GetterSelector = "volume", SetterSelector = "setVolume:", ArgumentSemantic = ArgumentSemantic.None)]
19+
[ProtocolMember (IsRequired = true, IsProperty = false, IsStatic = false, Name = "DestinationForMixer", Selector = "destinationForMixer:bus:", ReturnType = typeof (global::AVFoundation.AVAudioMixingDestination), ParameterType = new Type [] { typeof (global::AVFoundation.AVAudioNode), typeof (global::System.UIntPtr) }, ParameterByRef = new bool [] { false, false }, ParameterBlockProxy = new Type? [] { null, null })]
1920
public partial interface IAVAudioMixing : INativeObject, IDisposable
2021
{
2122

@@ -100,6 +101,7 @@ public virtual partial float Volume
100101

101102
internal unsafe sealed class AVAudioMixingWrapper : BaseWrapper, IAVAudioMixing
102103
{
104+
103105
// Implement default constructor
104106

105107
// Implement property: Obstruction
@@ -118,6 +120,20 @@ internal unsafe sealed class AVAudioMixingWrapper : BaseWrapper, IAVAudioMixing
118120

119121
// Implement property: Volume
120122

121-
// Implement method: DestinationForMixer
123+
[SupportedOSPlatform ("macos")]
124+
[SupportedOSPlatform ("ios")]
125+
[SupportedOSPlatform ("tvos")]
126+
[SupportedOSPlatform ("maccatalyst13.1")]
127+
[BindingImpl (BindingImplOptions.GeneratedCode | BindingImplOptions.Optimizable)]
128+
[Export<Method> ("destinationForMixer:bus:")]
129+
public global::AVFoundation.AVAudioMixingDestination? DestinationForMixer (global::AVFoundation.AVAudioNode mixer, global::System.UIntPtr bus)
130+
{
131+
var mixer__handle__ = mixer!.GetNonNullHandle (nameof (mixer));
132+
global::AVFoundation.AVAudioMixingDestination? ret;
133+
ret = global::ObjCRuntime.Runtime.GetNSObject<global::AVFoundation.AVAudioMixingDestination> (global::ObjCRuntime.Messaging.NativeHandle_objc_msgSend_NativeHandle_UIntPtr (this.Handle, global::ObjCRuntime.Selector.GetHandle ("destinationForMixer:bus:"), mixer__handle__, bus));
134+
global::System.GC.KeepAlive (this);
135+
global::System.GC.KeepAlive (mixer);
136+
return ret;
137+
}
122138

123139
}

0 commit comments

Comments
 (0)