Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions src/rgen/Microsoft.Macios.Transformer/Attributes/AsyncData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// 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,168 @@
// 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);
}
}
Loading