Skip to content

Commit

Permalink
[Rgen] Add factory method that creates a NSArray aux variable. (#22066)
Browse files Browse the repository at this point in the history
To be able to send arrays to objc we need to convert them to a NSArray.
This change adds a factory method that will create the aux variables for
when we need that conversion.

The factory allows to prepend the declaration with a using caluse to
simplify the memory management in the bindings.
  • Loading branch information
mandel-macaque authored Jan 29, 2025
1 parent 5c242ec commit e597395
Show file tree
Hide file tree
Showing 2 changed files with 144 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.Macios.Generator.Extensions;
using TypeInfo = Microsoft.Macios.Generator.DataModel.TypeInfo;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using Parameter = Microsoft.Macios.Generator.DataModel.Parameter;

namespace Microsoft.Macios.Generator.Emitters;

Expand Down Expand Up @@ -105,8 +106,70 @@ static partial class BindingSyntaxFactory {
whenFalse: castZero);
}

/// <summary>
/// Returns the aux nsarray variable for an array object. This method will do the following:
/// 1. Check if the object is nullable or not.
/// 2. Use the correct NSArray method depending on the content of the array.
/// </summary>
/// <param name="parameter">The parameter whose aux variable we want to generate.</param>
/// <param name="withUsing">If the using clause should be added to the declaration.</param>
/// <returns>The variable declaration for the NSArray aux variable of the parameter.</returns>
internal static LocalDeclarationStatementSyntax? GetNSArrayAuxVariable (in Parameter parameter,
bool withUsing = false)
{
if (!parameter.Type.IsArray)
return null;
var nsArrayFactoryMethod = parameter.Type.Name switch {
"string" => "FromStrings",
_ => "FromNSObjects" // the general assumption is that we are working with nsobjects unless we have a bind form
};
// syntax that calls the NSArray factory method using the parameter: NSArray.FromNSObjects (targetTensors);
var factoryInvocation = InvocationExpression (MemberAccessExpression (SyntaxKind.SimpleMemberAccessExpression,
IdentifierName ("NSArray"), IdentifierName (nsArrayFactoryMethod).WithTrailingTrivia (Space)))
.WithArgumentList (
ArgumentList (SingletonSeparatedList<ArgumentSyntax> (
Argument (IdentifierName (parameter.Name)))));

// variable name
var variableName = parameter.GetNameForVariableType (Parameter.VariableType.NSArray);
if (variableName is null)
return null;
var declarator = VariableDeclarator (Identifier (variableName));
// we have the basic constructs, now depending on if the variable is nullable or not, we need to write the initializer
if (parameter.Type.IsNullable) {
// writes the param ? null : NSArray expression
var nullCheck = ConditionalExpression (
IsPatternExpression (
IdentifierName (parameter.Name).WithLeadingTrivia (Space).WithTrailingTrivia (Space),
ConstantPattern (LiteralExpression (SyntaxKind.NullLiteralExpression).WithLeadingTrivia (Space)
.WithTrailingTrivia (Space))),
LiteralExpression (
SyntaxKind.NullLiteralExpression).WithLeadingTrivia (Space).WithTrailingTrivia (Space),
factoryInvocation.WithLeadingTrivia (Space));

// translates to = x ? null : NSArray.FromNSObject (parameterName), notice we added the '=' here
declarator = declarator.WithInitializer (EqualsValueClause (nullCheck).WithLeadingTrivia (Space));
} else {
// translates to = NSArray.FromNSObject (parameterName);
declarator = declarator.WithInitializer (EqualsValueClause (factoryInvocation.WithLeadingTrivia (Space))
.WithLeadingTrivia (Space));
}

// complicated way to write 'var auxVariableName = '
var variableDeclaration = VariableDeclaration (IdentifierName (
Identifier (TriviaList (), SyntaxKind.VarKeyword, "var", "var", TriviaList ())))
.WithTrailingTrivia (Space)
.WithVariables (SingletonSeparatedList (declarator));
var statement = LocalDeclarationStatement (variableDeclaration);
// add using if requested
return withUsing
? statement.WithUsingKeyword (Token (SyntaxKind.UsingKeyword).WithTrailingTrivia (Space))
: statement;
}

static string? GetObjCMessageSendMethodName<T> (ExportData<T> exportData,
TypeInfo returnType, ImmutableArray<Parameter> parameters, bool isSuper = false, bool isStret = false) where T : Enum
TypeInfo returnType, ImmutableArray<Parameter> parameters, bool isSuper = false, bool isStret = false)
where T : Enum
{
var flags = exportData.Flags;
if (flags is null)
Expand Down Expand Up @@ -139,6 +202,7 @@ static partial class BindingSyntaxFactory {
if (isStret) {
sb.Append ("_stret");
}

// loop over params and get their native handler name
if (parameters.Length > 0) {
sb.Append ('_');
Expand All @@ -153,10 +217,12 @@ static partial class BindingSyntaxFactory {
} else if (flags.HasMarshalNativeExceptions ()) {
sb.Append ("_exception");
}

return sb.ToString ();
}

public static (string? Getter, string? Setter) GetObjCMessageSendMethods (in Property property, bool isSuper = false, bool isStret = false)
public static (string? Getter, string? Setter) GetObjCMessageSendMethods (in Property property,
bool isSuper = false, bool isStret = false)
{
if (property.IsProperty) {
// the getter and the setter depend of the accessors that have been set for the property, we do not want
Expand All @@ -181,13 +247,14 @@ public static (string? Getter, string? Setter) GetObjCMessageSendMethods (in Pro
[property.ValueParameter], isSuper, isStret);
}
}

return (Getter: getterMsgSend, Setter: setterMsgSend);
}

return default;
}

public static string? GetObjCMessageSendMethod (in Method method, bool isSuper = false, bool isStret = false)
=> GetObjCMessageSendMethodName (method.ExportMethodData, method.ReturnType, method.Parameters, isSuper, isStret);

=> GetObjCMessageSendMethodName (method.ExportMethodData, method.ReturnType, method.Parameters, isSuper,
isStret);
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,77 @@ void CastToByteTests ()
conditionalExpr = CastToByte (intParameter);
Assert.Null (conditionalExpr);
}

class TestDataGetNSArrayAuxVariableTest : IEnumerable<object []> {
public IEnumerator<object []> GetEnumerator ()
{
// not array

yield return [
new Parameter (0, ReturnTypeForInt (isNullable: false), "myParam"),
null!,
false];

// not nullable string[]
yield return [
new Parameter (0, ReturnTypeForArray ("string", isNullable: false), "myParam"),
"var nsa_myParam = NSArray.FromStrings (myParam);",
false];

yield return [
new Parameter (0, ReturnTypeForArray ("string", isNullable: false), "myParam"),
"using var nsa_myParam = NSArray.FromStrings (myParam);",
true];

// nullable string []
yield return [
new Parameter (0, ReturnTypeForArray ("string", isNullable: true), "myParam"),
"var nsa_myParam = myParam is null ? null : NSArray.FromStrings (myParam);",
false];

yield return [
new Parameter (0, ReturnTypeForArray ("string", isNullable: true), "myParam"),
"using var nsa_myParam = myParam is null ? null : NSArray.FromStrings (myParam);",
true];

// nsstrings

yield return [
new Parameter (0, ReturnTypeForArray ("NSString", isNullable: false), "myParam"),
"var nsa_myParam = NSArray.FromNSObjects (myParam);",
false];

yield return [
new Parameter (0, ReturnTypeForArray ("NSString", isNullable: false), "myParam"),
"using var nsa_myParam = NSArray.FromNSObjects (myParam);",
true];

yield return [
new Parameter (0, ReturnTypeForArray ("NSString", isNullable: true), "myParam"),
"var nsa_myParam = myParam is null ? null : NSArray.FromNSObjects (myParam);",
false];

yield return [
new Parameter (0, ReturnTypeForArray ("NSString", isNullable: true), "myParam"),
"using var nsa_myParam = myParam is null ? null : NSArray.FromNSObjects (myParam);",
true];


}

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

[Theory]
[ClassData (typeof (TestDataGetNSArrayAuxVariableTest))]
void GetNSArrayAuxVariableTests (in Parameter parameter, string? expectedDeclaration, bool withUsing)
{
var declaration = GetNSArrayAuxVariable (in parameter, withUsing: withUsing);
if (expectedDeclaration is null) {
Assert.Null (declaration);
} else {
Assert.NotNull (declaration);
Assert.Equal (expectedDeclaration, declaration.ToString ());
}
}
}

0 comments on commit e597395

Please sign in to comment.