Skip to content

Commit de4505a

Browse files
[Rgen] Add Async attr parsing to the transformer. (#22068)
1 parent cd2b8d3 commit de4505a

File tree

3 files changed

+325
-0
lines changed

3 files changed

+325
-0
lines changed
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using Microsoft.CodeAnalysis;
6+
7+
namespace Microsoft.Macios.Transformer.Attributes;
8+
9+
readonly struct AsyncData : IEquatable<AsyncData> {
10+
/// <summary>
11+
/// Diff the constructor used in the bindings.
12+
/// </summary>
13+
internal enum ConstructorType {
14+
ResultType,
15+
MethodName
16+
}
17+
18+
public string? ResultType { get; init; } // this in the attr is a type, but we do not care for the transformation
19+
public string? MethodName { get; init; }
20+
public string? ResultTypeName { get; init; }
21+
public string? PostNonResultSnippet { get; init; }
22+
23+
public AsyncData () { }
24+
25+
public AsyncData (string resultType, ConstructorType constructorType)
26+
{
27+
if (constructorType == ConstructorType.ResultType)
28+
ResultType = resultType;
29+
else
30+
MethodName = resultType;
31+
}
32+
33+
public static bool TryParse (AttributeData attributeData,
34+
[NotNullWhen (true)] out AsyncData? data)
35+
{
36+
data = null;
37+
var count = attributeData.ConstructorArguments.Length;
38+
ConstructorType constructorType = ConstructorType.MethodName;
39+
string? resultType = null;
40+
string? resultTypeName = null;
41+
string? methodName = null;
42+
string? postNonResultSnippet = null;
43+
44+
switch (count) {
45+
case 0:
46+
break;
47+
case 1:
48+
// we have to diff constructors that take a single parameter, either a string or a type
49+
if (attributeData.ConstructorArguments [0].Value! is string methodNameValue) {
50+
constructorType = ConstructorType.MethodName;
51+
methodName = methodNameValue;
52+
} else {
53+
constructorType = ConstructorType.ResultType;
54+
resultType = ((INamedTypeSymbol) attributeData.ConstructorArguments [0].Value!).ToDisplayString ();
55+
}
56+
break;
57+
default:
58+
// 0 should not be an option..
59+
return false;
60+
}
61+
62+
if (attributeData.NamedArguments.Length == 0) {
63+
if (constructorType == ConstructorType.ResultType)
64+
data = new (resultType!, ConstructorType.ResultType);
65+
else
66+
data = new (methodName!, ConstructorType.MethodName);
67+
return true;
68+
}
69+
70+
foreach (var (argumentName, value) in attributeData.NamedArguments) {
71+
switch (argumentName) {
72+
case "ResultType":
73+
resultType = ((INamedTypeSymbol) value.Value!).ToDisplayString ();
74+
break;
75+
case "MethodName":
76+
methodName = (string) value.Value!;
77+
break;
78+
case "ResultTypeName":
79+
resultTypeName = (string) value.Value!;
80+
break;
81+
case "PostNonResultSnippet":
82+
postNonResultSnippet = (string) value.Value!;
83+
break;
84+
default:
85+
data = null;
86+
return false;
87+
}
88+
}
89+
90+
if (count == 0) {
91+
// use the default constructor and use the init properties
92+
data = new () {
93+
ResultType = resultType,
94+
MethodName = methodName,
95+
ResultTypeName = resultTypeName,
96+
PostNonResultSnippet = postNonResultSnippet
97+
};
98+
return true;
99+
}
100+
101+
switch (constructorType) {
102+
case ConstructorType.MethodName:
103+
data = new (methodName!, ConstructorType.MethodName) {
104+
ResultType = resultType,
105+
ResultTypeName = resultTypeName,
106+
PostNonResultSnippet = postNonResultSnippet
107+
};
108+
break;
109+
case ConstructorType.ResultType:
110+
data = new (resultType!, ConstructorType.ResultType) {
111+
MethodName = methodName,
112+
ResultTypeName = resultTypeName,
113+
PostNonResultSnippet = postNonResultSnippet
114+
};
115+
break;
116+
}
117+
118+
return false;
119+
}
120+
121+
public bool Equals (AsyncData other)
122+
{
123+
if (ResultType != other.ResultType)
124+
return false;
125+
if (MethodName != other.MethodName)
126+
return false;
127+
if (ResultTypeName != other.ResultTypeName)
128+
return false;
129+
return PostNonResultSnippet == other.PostNonResultSnippet;
130+
}
131+
132+
/// <inheritdoc />
133+
public override bool Equals (object? obj)
134+
{
135+
return obj is AsyncData other && Equals (other);
136+
}
137+
138+
/// <inheritdoc />
139+
public override int GetHashCode ()
140+
=> HashCode.Combine (ResultType, MethodName, ResultTypeName, PostNonResultSnippet);
141+
142+
public static bool operator == (AsyncData x, AsyncData y)
143+
{
144+
return x.Equals (y);
145+
}
146+
147+
public static bool operator != (AsyncData x, AsyncData y)
148+
{
149+
return !(x == y);
150+
}
151+
152+
public override string ToString ()
153+
=> $"{{ ResultType: '{ResultType ?? "null"}', MethodName: '{MethodName ?? "null"}', ResultTypeName: '{ResultTypeName ?? "null"}', PostNonResultSnippet: '{PostNonResultSnippet ?? "null"}' }}";
154+
}

src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ static class AttributesNames {
3333
/// </summary>
3434
[BindingFlag (AttributeTargets.Method | AttributeTargets.Property)]
3535
public const string AutoreleaseAttribute = "AutoreleaseAttribute";
36+
37+
[BindingAttribute(typeof(AsyncData), AttributeTargets.Method)]
38+
public const string AsyncAttribute = "AsyncAttribute";
3639

3740
[BindingAttribute(typeof(BackingFieldTypeData), AttributeTargets.Enum)]
3841
public const string BackingFieldTypeAttribute = "BackingFieldTypeAttribute";
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Collections;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using Microsoft.Macios.Generator.Extensions;
8+
using Microsoft.Macios.Transformer.Attributes;
9+
using Xamarin.Tests;
10+
using Xamarin.Utils;
11+
12+
namespace Microsoft.Macios.Transformer.Tests.Attributes;
13+
14+
public class AsyncDataTests : BaseTransformerTestClass {
15+
16+
class TestDataTryCreate : IEnumerable<object []> {
17+
public IEnumerator<object []> GetEnumerator ()
18+
{
19+
const string path = "/some/random/path.cs";
20+
21+
const string simpleAsyncMethod = @"
22+
using System;
23+
using AppKit;
24+
using Foundation;
25+
using ObjCRuntime;
26+
27+
namespace Test;
28+
29+
[NoMacCatalyst]
30+
[BaseType (typeof (NSObject))]
31+
[DisableDefaultCtor]
32+
interface NSTableViewDiffableDataSource {
33+
34+
[Export (""applySnapshot:animatingDifferences:completion:"")]
35+
[Async]
36+
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
37+
}
38+
";
39+
yield return [(Source: simpleAsyncMethod, Path: path), new AsyncData ()];
40+
41+
const string asyncResultTypeName = @"
42+
using System;
43+
using AppKit;
44+
using Foundation;
45+
using ObjCRuntime;
46+
47+
namespace Test;
48+
49+
[NoMacCatalyst]
50+
[BaseType (typeof (NSObject))]
51+
[DisableDefaultCtor]
52+
interface NSTableViewDiffableDataSource {
53+
54+
[Export (""applySnapshot:animatingDifferences:completion:"")]
55+
[Async (ResultTypeName=""NSSpellCheckerCandidates"")]
56+
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
57+
}
58+
";
59+
60+
yield return [(Source: asyncResultTypeName, Path: path),
61+
new AsyncData {
62+
ResultTypeName = "NSSpellCheckerCandidates"
63+
}];
64+
65+
const string asyncMethodName = @"
66+
using System;
67+
using AppKit;
68+
using Foundation;
69+
using ObjCRuntime;
70+
71+
namespace Test;
72+
73+
[NoMacCatalyst]
74+
[BaseType (typeof (NSObject))]
75+
[DisableDefaultCtor]
76+
interface NSTableViewDiffableDataSource {
77+
78+
[Export (""applySnapshot:animatingDifferences:completion:"")]
79+
[Async (""ApplyTheSnapshotAsync"")]
80+
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
81+
}
82+
";
83+
84+
yield return [(Source: asyncMethodName, Path: path),
85+
new AsyncData {
86+
MethodName = "ApplyTheSnapshotAsync"
87+
}];
88+
89+
const string asyncTypeOf = @"
90+
using System;
91+
using AppKit;
92+
using Foundation;
93+
using ObjCRuntime;
94+
95+
namespace Test;
96+
97+
public class SampleResult {}
98+
99+
[NoMacCatalyst]
100+
[BaseType (typeof (NSObject))]
101+
[DisableDefaultCtor]
102+
interface NSTableViewDiffableDataSource {
103+
104+
[Export (""applySnapshot:animatingDifferences:completion:"")]
105+
[Async (ResultType = typeof (SampleResult))]
106+
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
107+
}
108+
";
109+
110+
yield return [(Source: asyncTypeOf, Path: path),
111+
new AsyncData {
112+
ResultType = "Test.SampleResult"
113+
}];
114+
115+
const string postResult = @"
116+
using System;
117+
using AppKit;
118+
using Foundation;
119+
using ObjCRuntime;
120+
121+
namespace Test;
122+
123+
public class SampleResult {}
124+
125+
[NoMacCatalyst]
126+
[BaseType (typeof (NSObject))]
127+
[DisableDefaultCtor]
128+
interface NSTableViewDiffableDataSource {
129+
130+
[Export (""applySnapshot:animatingDifferences:completion:"")]
131+
[Async (ResultTypeName = ""NSUrlSessionDataTaskRequest"", PostNonResultSnippet = ""result.Resume ();"")]
132+
void ApplySnapshot (NSObject snapshot, bool animatingDifferences, [NullAllowed] Action completion);
133+
}
134+
";
135+
136+
yield return [(Source: postResult, Path: path),
137+
new AsyncData {
138+
ResultTypeName = "NSUrlSessionDataTaskRequest",
139+
PostNonResultSnippet = "result.Resume ();"
140+
}];
141+
}
142+
143+
IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
144+
}
145+
146+
[Theory]
147+
[AllSupportedPlatformsClassData<TestDataTryCreate>]
148+
void TryCreateTests (ApplePlatform platform, (string Source, string Path) source, AsyncData expectedData)
149+
{
150+
// create a compilation used to create the transformer
151+
var compilation = CreateCompilation (platform, sources: source);
152+
var syntaxTree = compilation.SyntaxTrees.ForSource (source);
153+
Assert.NotNull (syntaxTree);
154+
155+
var semanticModel = compilation.GetSemanticModel (syntaxTree);
156+
Assert.NotNull (semanticModel);
157+
158+
var declaration = syntaxTree.GetRoot ()
159+
.DescendantNodes ().OfType<MethodDeclarationSyntax> ()
160+
.FirstOrDefault ();
161+
Assert.NotNull (declaration);
162+
163+
var symbol = semanticModel.GetDeclaredSymbol (declaration);
164+
Assert.NotNull (symbol);
165+
var attribute = symbol.GetAttribute<AsyncData> (AttributesNames.AsyncAttribute, AsyncData.TryParse);
166+
Assert.Equal (expectedData, attribute);
167+
}
168+
}

0 commit comments

Comments
 (0)