Skip to content

Commit

Permalink
[Rgen] Add Async attr parsing to the transformer.
Browse files Browse the repository at this point in the history
  • Loading branch information
mandel-macaque committed Jan 28, 2025
1 parent c35a6bd commit a6332d4
Show file tree
Hide file tree
Showing 3 changed files with 322 additions and 0 deletions.
155 changes: 155 additions & 0 deletions src/rgen/Microsoft.Macios.Transformer/Attributes/AsyncData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;

namespace Microsoft.Macios.Transformer.Attributes;

readonly struct AsyncData : IEquatable<AsyncData> {
/// <summary>
/// Diff the constructor used in the bindings.
/// </summary>
internal enum ConstructorType {
ResultType,
MethodName
}

public string? ResultType { get; init; } // this in the attr is a type, but we do not care for the transformation
public string? MethodName { get; init; }
public string? ResultTypeName { get; init; }
public string? PostNonResultSnippet { get; init; }

public AsyncData () {}

public AsyncData (string resultType, ConstructorType constructorType)
{
if (constructorType == ConstructorType.ResultType)
ResultType = resultType;
else
MethodName = resultType;
}

public static bool TryParse (AttributeData attributeData,
[NotNullWhen (true)] out AsyncData? data)
{
data = null;
var count = attributeData.ConstructorArguments.Length;
ConstructorType constructorType = ConstructorType.MethodName;
string? resultType = null;
string? resultTypeName = null;
string? methodName = null;
string? postNonResultSnippet = null;

switch (count) {
case 0:
break;
case 1:
// we have to diff constructors that take a single parameter, either a string or a type
if (attributeData.ConstructorArguments[0].Value! is string methodNameValue) {
constructorType = ConstructorType.MethodName;
methodName = methodNameValue;
} else {
constructorType = ConstructorType.ResultType;
resultType = ((INamedTypeSymbol) attributeData.ConstructorArguments[0].Value!).ToDisplayString();
}
break;
default:
// 0 should not be an option..
return false;
}

if (attributeData.NamedArguments.Length == 0) {
if (constructorType == ConstructorType.ResultType)
data = new (resultType!, ConstructorType.ResultType);
else
data = new (methodName!, ConstructorType.MethodName);
return true;
}

foreach (var (argumentName, value) in attributeData.NamedArguments) {
switch (argumentName) {
case "ResultType":
resultType = ((INamedTypeSymbol) value.Value!).ToDisplayString();
break;
case "MethodName":
methodName = (string) value.Value!;
break;
case "ResultTypeName":
resultTypeName = (string) value.Value!;
break;
case "PostNonResultSnippet":
postNonResultSnippet = (string) value.Value!;
break;
default:
data = null;
return false;
}
}

if (count == 0) {
// use the default constructor and use the init properties
data = new() {
ResultType = resultType,
MethodName = methodName,
ResultTypeName = resultTypeName,
PostNonResultSnippet = postNonResultSnippet
};
return true;
}

switch (constructorType)
{
case ConstructorType.MethodName:
data = new (methodName!, ConstructorType.MethodName) {
ResultType = resultType,
ResultTypeName = resultTypeName,
PostNonResultSnippet = postNonResultSnippet
};
break;
case ConstructorType.ResultType:
data = new (resultType!, ConstructorType.ResultType) {
MethodName = methodName,
ResultTypeName = resultTypeName,
PostNonResultSnippet = postNonResultSnippet
};
break;
}

return false;
}

public bool Equals (AsyncData other)
{
if (ResultType != other.ResultType)
return false;
if (MethodName != other.MethodName)
return false;
if (ResultTypeName != other.ResultTypeName)
return false;
return PostNonResultSnippet == other.PostNonResultSnippet;
}

/// <inheritdoc />
public override bool Equals (object? obj)
{
return obj is AsyncData other && Equals (other);
}

/// <inheritdoc />
public override int GetHashCode ()
=> HashCode.Combine (ResultType, MethodName, ResultTypeName, PostNonResultSnippet);

public static bool operator == (AsyncData x, AsyncData y)
{
return x.Equals (y);
}

public static bool operator != (AsyncData x, AsyncData y)
{
return !(x == y);
}

public override string ToString ()
=> $"{{ ResultType: '{ResultType ?? "null"}', MethodName: '{MethodName ?? "null"}', ResultTypeName: '{ResultTypeName ?? "null"}', PostNonResultSnippet: '{PostNonResultSnippet ?? "null"}' }}";
}
3 changes: 3 additions & 0 deletions src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ static class AttributesNames {
/// </summary>
[BindingFlag (AttributeTargets.Method | AttributeTargets.Property)]
public const string AutoreleaseAttribute = "AutoreleaseAttribute";

[BindingAttribute(typeof(AsyncData), AttributeTargets.Method)]
public const string AsyncAttribute = "AsyncAttribute";

[BindingAttribute(typeof(BackingFieldTypeData), AttributeTargets.Enum)]
public const string BackingFieldTypeAttribute = "BackingFieldTypeAttribute";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Macios.Generator.Extensions;
using Microsoft.Macios.Transformer.Attributes;
using Xamarin.Tests;
using Xamarin.Utils;

namespace Microsoft.Macios.Transformer.Tests.Attributes;

public class AsyncDataTests : BaseTransformerTestClass {

class TestDataTryCreate : IEnumerable<object []> {
public IEnumerator<object []> GetEnumerator ()
{
const string path = "/some/random/path.cs";

const string simpleAsyncMethod = @"
using System;
using AppKit;
using Foundation;
using ObjCRuntime;
namespace Test;
[NoMacCatalyst]
[BaseType (typeof (NSObject))]
[DisableDefaultCtor]
interface NSTableViewDiffableDataSource {
[Export (""applySnapshot:animatingDifferences:completion:"")]
[Async]
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
}
";
yield return [(Source: simpleAsyncMethod, Path: path), new AsyncData ()];

const string asyncResultTypeName = @"
using System;
using AppKit;
using Foundation;
using ObjCRuntime;
namespace Test;
[NoMacCatalyst]
[BaseType (typeof (NSObject))]
[DisableDefaultCtor]
interface NSTableViewDiffableDataSource {
[Export (""applySnapshot:animatingDifferences:completion:"")]
[Async (ResultTypeName=""NSSpellCheckerCandidates"")]
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
}
";

yield return [(Source: asyncResultTypeName, Path: path), new AsyncData {
ResultTypeName = "NSSpellCheckerCandidates"
}];

const string asyncMethodName = @"
using System;
using AppKit;
using Foundation;
using ObjCRuntime;
namespace Test;
[NoMacCatalyst]
[BaseType (typeof (NSObject))]
[DisableDefaultCtor]
interface NSTableViewDiffableDataSource {
[Export (""applySnapshot:animatingDifferences:completion:"")]
[Async (""ApplyTheSnapshotAsync"")]
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
}
";

yield return [(Source: asyncMethodName, Path: path), new AsyncData {
MethodName = "ApplyTheSnapshotAsync"
}];

const string asyncTypeOf = @"
using System;
using AppKit;
using Foundation;
using ObjCRuntime;
namespace Test;
public class SampleResult {}
[NoMacCatalyst]
[BaseType (typeof (NSObject))]
[DisableDefaultCtor]
interface NSTableViewDiffableDataSource {
[Export (""applySnapshot:animatingDifferences:completion:"")]
[Async (ResultType = typeof (SampleResult))]
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
}
";

yield return [(Source: asyncTypeOf, Path: path), new AsyncData {
ResultType = "Test.SampleResult"
}];

const string postResult = @"
using System;
using AppKit;
using Foundation;
using ObjCRuntime;
namespace Test;
public class SampleResult {}
[NoMacCatalyst]
[BaseType (typeof (NSObject))]
[DisableDefaultCtor]
interface NSTableViewDiffableDataSource {
[Export (""applySnapshot:animatingDifferences:completion:"")]
[Async (ResultTypeName = ""NSUrlSessionDataTaskRequest"", PostNonResultSnippet = ""result.Resume ();"")]
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
}
";

yield return [(Source: postResult, Path: path), new AsyncData {
ResultTypeName = "NSUrlSessionDataTaskRequest",
PostNonResultSnippet = "result.Resume ();"
}];
}

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

[Theory]
[AllSupportedPlatformsClassData<TestDataTryCreate>]
void TryCreateTests (ApplePlatform platform, (string Source, string Path) source, AsyncData expectedData)
{
// create a compilation used to create the transformer
var compilation = CreateCompilation (platform, sources: source);
var syntaxTree = compilation.SyntaxTrees.ForSource (source);
Assert.NotNull (syntaxTree);

var semanticModel = compilation.GetSemanticModel (syntaxTree);
Assert.NotNull (semanticModel);

var declaration = syntaxTree.GetRoot ()
.DescendantNodes ().OfType<MethodDeclarationSyntax> ()
.FirstOrDefault ();
Assert.NotNull (declaration);

var symbol = semanticModel.GetDeclaredSymbol (declaration);
Assert.NotNull (symbol);
var attribute = symbol.GetAttribute<AsyncData> (AttributesNames.AsyncAttribute, AsyncData.TryParse);
Assert.Equal (expectedData, attribute);
}
}

0 comments on commit a6332d4

Please sign in to comment.