-
Notifications
You must be signed in to change notification settings - Fork 518
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Rgen] Add Async attr parsing to the transformer.
- Loading branch information
1 parent
c35a6bd
commit a6332d4
Showing
3 changed files
with
322 additions
and
0 deletions.
There are no files selected for viewing
155 changes: 155 additions & 0 deletions
155
src/rgen/Microsoft.Macios.Transformer/Attributes/AsyncData.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"}' }}"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/AsyncDataTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |