Skip to content

Commit

Permalink
[Rgen] Provide a factory method that creates the objc msg send calls. (
Browse files Browse the repository at this point in the history
…#22190)

Create a factory method that will create the invoke expression for a
objc msg method. This factory will prepend the two first parameters,
handle and selector, and will add any extra argument nodes to the call.

---------

Co-authored-by: GitHub Actions Autoformatter <github-actions-autoformatter@xamarin.com>
  • Loading branch information
mandel-macaque and GitHub Actions Autoformatter authored Feb 17, 2025
1 parent a24d91d commit de74ece
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,15 +1,110 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Microsoft.Macios.Generator.Emitters;

static partial class BindingSyntaxFactory {
readonly static string Runtime = "Runtime";
public const string Runtime = "Runtime";
public const string ClassPtr = "class_ptr";

public static CompilationUnitSyntax GetNSObject (string nsObjectType, ArgumentListSyntax argumentList,
bool suppressNullableWarning = false)
=> StaticInvocationGenericExpression (Runtime, "GetNSObject", nsObjectType, argumentList,
suppressNullableWarning);

/// <summary>
/// Returns the expression to get the handle of a selector.
/// </summary>
/// <param name="selector">The selector whose handle we want to retrieve.</param>
/// <returns>The expression to retrieve a selector handle.</returns>
public static InvocationExpressionSyntax GetHandle (string selector)
{
// (selector)
var args = ArgumentList (SingletonSeparatedList (
Argument (LiteralExpression (
SyntaxKind.StringLiteralExpression,
Literal (selector))))).NormalizeWhitespace ();

// Selector.GetHandle (selector)
return InvocationExpression (
MemberAccessExpression (
SyntaxKind.SimpleMemberAccessExpression,
IdentifierName ("Selector"),
IdentifierName ("GetHandle").WithTrailingTrivia (Space)))
.WithArgumentList (args);
}

/// <summary>
/// Generates the "this.Handle" expression.
/// </summary>
/// <returns></returns>
public static MemberAccessExpressionSyntax ThisHandle ()
{
return MemberAccessExpression (
SyntaxKind.SimpleMemberAccessExpression,
ThisExpression (),
IdentifierName ("Handle"));
}

/// <summary>
/// Generates the expression to call the objc_msgSend method.
/// </summary>
/// <param name="objcMsgSendMethod">The name of the method in the messaging namespace.</param>
/// <param name="selector">The selector.</param>
/// <param name="parameters">An optional argument list.</param>
/// <returns>The expression needed to call a specific messaging method.</returns>
public static InvocationExpressionSyntax MessagingInvocation (string objcMsgSendMethod, string selector,
ImmutableArray<ArgumentSyntax> parameters = default)
{
// the size of the arguments is 2 + the optional arguments
// [0] = the handle
// [1] = the selector
// [2] = the arguments
// but to be able to use the SeparatedList we need to add a comma for each argument
// except for the last one, so we need to add parametersCount - 1 commas
var parametersCount = 2 + parameters.Length;
var args = new SyntaxNodeOrToken [(2 * parametersCount) - 1];
// the first two arguments are the selector and the handle, we add those by hand
args [0] = Argument (ThisHandle ());
args [1] = Token (SyntaxKind.CommaToken).WithTrailingTrivia (Space);
args [2] = Argument (GetHandle (selector));

// we need to add the commas and the arguments provided by the user of the api
if (parameters.Length > 0) {
// add a comma because we know we added the selector
args [3] = Token (SyntaxKind.CommaToken).WithTrailingTrivia (Space);
var argsIndex = 4; // start at 4 because we added the first 2 parameters + 2 separators
var parametersIndex = 0;
while (argsIndex < args.Length) {
var currentParameter = parameters [parametersIndex++];
args [argsIndex++] = currentParameter;
if (argsIndex < args.Length - 1) {
args [argsIndex++] = Token (SyntaxKind.CommaToken).WithTrailingTrivia (Space);
}
}
}

// generates: (this.Handle, Selector.GetHandle (selector), args)
var argumentList = ArgumentList (SeparatedList<ArgumentSyntax> (args));
// generates: ObjCRuntime.Messaging.objc_msgSend (this.Handle, Selector.GetHandle (selector), args)
var invocation = InvocationExpression (
MemberAccessExpression (
SyntaxKind.SimpleMemberAccessExpression,
MemberAccessExpression (
SyntaxKind.SimpleMemberAccessExpression,
AliasQualifiedName (
IdentifierName (
Token (SyntaxKind.GlobalKeyword)),
IdentifierName ("ObjCRuntime")),
IdentifierName ("Messaging")),
IdentifierName (objcMsgSendMethod).WithTrailingTrivia (Space)))
.WithArgumentList (argumentList);
return invocation;
}
}
4 changes: 2 additions & 2 deletions src/rgen/Microsoft.Macios.Generator/Emitters/ClassEmitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,9 @@ public bool TryEmit (in BindingContext bindingContext, [NotNullWhen (false)] out

if (!bindingContext.Changes.IsStatic) {
classBlock.AppendGeneratedCodeAttribute (optimizable: true);
classBlock.WriteLine ($"static readonly NativeHandle class_ptr = Class.GetHandle (\"{registrationName}\");");
classBlock.WriteLine ($"static readonly NativeHandle {ClassPtr} = Class.GetHandle (\"{registrationName}\");");
classBlock.WriteLine ();
classBlock.WriteLine ("public override NativeHandle ClassHandle => class_ptr;");
classBlock.WriteLine ($"public override NativeHandle ClassHandle => {ClassPtr};");
classBlock.WriteLine ();

EmitDefaultConstructors (bindingContext: bindingContext,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Xunit;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Microsoft.Macios.Generator.Emitters.BindingSyntaxFactory;

namespace Microsoft.Macios.Generator.Tests.Emitters;

public class BindingSyntaxFactoryRuntimeTests {

[Theory]
[InlineData ("Test", "Selector.GetHandle (\"Test\")")]
[InlineData ("name", "Selector.GetHandle (\"name\")")]
[InlineData ("setName:", "Selector.GetHandle (\"setName:\")")]
void GetHandleTest (string selector, string expectedDeclaration)
{
var declaration = GetHandle (selector);
Assert.Equal (expectedDeclaration, declaration.ToFullString ());
}

class TestDataMessagingInvocationTests : IEnumerable<object []> {
public IEnumerator<object []> GetEnumerator ()
{
// no extra params
yield return [
"IntPtr_objc_msgSend",
"string",
ImmutableArray<ArgumentSyntax>.Empty,
"global::ObjCRuntime.Messaging.IntPtr_objc_msgSend (this.Handle, Selector.GetHandle (\"string\"))"
];

// one param extra
ImmutableArray<ArgumentSyntax> args = ImmutableArray.Create (
Argument (IdentifierName ("arg1"))
);
yield return [
"IntPtr_objc_msgSend",
"string",
args,
"global::ObjCRuntime.Messaging.IntPtr_objc_msgSend (this.Handle, Selector.GetHandle (\"string\"), arg1)"
];

// several params
args = ImmutableArray.Create (
Argument (IdentifierName ("arg1")),
Argument (IdentifierName ("arg2")),
Argument (IdentifierName ("arg3"))
);
yield return [
"IntPtr_objc_msgSend",
"string",
args,
"global::ObjCRuntime.Messaging.IntPtr_objc_msgSend (this.Handle, Selector.GetHandle (\"string\"), arg1, arg2, arg3)"
];

// out parameter
args = ImmutableArray.Create (
Argument (PrefixUnaryExpression (SyntaxKind.AddressOfExpression, IdentifierName ("errorValue")))
);

yield return [
"IntPtr_objc_msgSend",
"string",
args,
"global::ObjCRuntime.Messaging.IntPtr_objc_msgSend (this.Handle, Selector.GetHandle (\"string\"), &errorValue)"
];
}

IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
}

[Theory]
[ClassData (typeof (TestDataMessagingInvocationTests))]
void MessagingInvocationTests (string objcMsgSendMethod, string selector, ImmutableArray<ArgumentSyntax> parameters,
string expectedDeclaration)
{
var declaration = MessagingInvocation (objcMsgSendMethod, selector, parameters);
Assert.Equal (expectedDeclaration, declaration.ToFullString ());
}
}

10 comments on commit de74ece

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

@vs-mobiletools-engineering-service2

This comment was marked as outdated.

Please sign in to comment.