-
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 the parsing code for the BaseTypeAttribute in the transfor…
…mer. (#22021)
- Loading branch information
1 parent
727a00f
commit 9516bf1
Showing
4 changed files
with
361 additions
and
3 deletions.
There are no files selected for viewing
158 changes: 158 additions & 0 deletions
158
src/rgen/Microsoft.Macios.Transformer/Attributes/BaseTypeData.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,158 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT License. | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
using System.Text; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.Macios.Generator; | ||
|
||
namespace Microsoft.Macios.Transformer.Attributes; | ||
|
||
readonly struct BaseTypeData : IEquatable<BaseTypeData> { | ||
|
||
public string BaseType { get; } // is a type in the attribute, but we do not care for the transformation | ||
public string? Name { get; init; } = null; | ||
public string [] Events { get; init; } = []; // it is a collection of types, but we do not care for the transformation | ||
public string [] Delegates { get; init; } = []; | ||
public bool Singleton { get; init; } | ||
|
||
public string? KeepRefUntil { get; init; } = null; | ||
|
||
public bool IsStubClass { get; init; } | ||
|
||
public BaseTypeData (string baseType) | ||
{ | ||
BaseType = baseType; | ||
} | ||
|
||
public static bool TryParse (AttributeData attributeData, | ||
[NotNullWhen (true)] out BaseTypeData? data) | ||
{ | ||
data = null; | ||
var count = attributeData.ConstructorArguments.Length; | ||
string baseType; | ||
string? name = null; | ||
string [] events = []; | ||
string [] delegates = []; | ||
var singleton = false; | ||
string? keepRefUntil = null; | ||
var isStubClass = false; | ||
|
||
// custom marshal directive values | ||
|
||
switch (count) { | ||
case 1: | ||
baseType = ((INamedTypeSymbol) attributeData.ConstructorArguments [0].Value!).ToDisplayString (); | ||
break; | ||
default: | ||
// 0 should not be an option.. | ||
return false; | ||
} | ||
|
||
if (attributeData.NamedArguments.Length == 0) { | ||
data = new (baseType); | ||
return true; | ||
} | ||
|
||
foreach (var (argumentName, value) in attributeData.NamedArguments) { | ||
switch (argumentName) { | ||
case "Name": | ||
name = (string?) value.Value!; | ||
break; | ||
case "Events": | ||
events = value.Values.Select ( | ||
v => ((INamedTypeSymbol) v.Value!).ToDisplayString ()) | ||
.ToArray (); | ||
break; | ||
case "Delegates": | ||
delegates = value.Values.Select (v => (string) v.Value!).ToArray (); | ||
break; | ||
case "Singleton": | ||
singleton = (bool) value.Value!; | ||
break; | ||
case "KeepRefUntil": | ||
keepRefUntil = (string?) value.Value!; | ||
break; | ||
case "IsStubClass": | ||
isStubClass = (bool) value.Value!; | ||
break; | ||
default: | ||
data = null; | ||
return false; | ||
} | ||
} | ||
|
||
data = new (baseType) { | ||
Name = name, | ||
Events = events, | ||
Delegates = delegates, | ||
Singleton = singleton, | ||
KeepRefUntil = keepRefUntil, | ||
IsStubClass = isStubClass, | ||
}; | ||
return true; | ||
} | ||
|
||
public bool Equals (BaseTypeData other) | ||
{ | ||
var stringCollectionComparer = new CollectionComparer<string?> (StringComparer.Ordinal); | ||
|
||
if (BaseType != other.BaseType) | ||
return false; | ||
if (Name != other.Name) | ||
return false; | ||
if (!stringCollectionComparer.Equals (Events, other.Events)) | ||
return false; | ||
if (!stringCollectionComparer.Equals (Delegates, other.Delegates)) | ||
return false; | ||
if (Singleton != other.Singleton) | ||
return false; | ||
if (KeepRefUntil != other.KeepRefUntil) | ||
return false; | ||
return IsStubClass == other.IsStubClass; | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override bool Equals (object? obj) | ||
{ | ||
return obj is BaseTypeData other && Equals (other); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public override int GetHashCode () | ||
{ | ||
var hash = new HashCode (); | ||
hash.Add (BaseType); | ||
hash.Add (Name); | ||
foreach (var e in Events) { | ||
hash.Add (e); | ||
} | ||
foreach (var d in Delegates) { | ||
hash.Add (d); | ||
} | ||
hash.Add (Singleton); | ||
hash.Add (KeepRefUntil); | ||
return hash.ToHashCode (); | ||
} | ||
|
||
public static bool operator == (BaseTypeData x, BaseTypeData y) | ||
{ | ||
return x.Equals (y); | ||
} | ||
|
||
public static bool operator != (BaseTypeData x, BaseTypeData y) | ||
{ | ||
return !(x == y); | ||
} | ||
|
||
public override string ToString () | ||
{ | ||
var sb = new StringBuilder ($"{{ BaseType: {BaseType}, Name: {Name ?? "null"}, "); | ||
sb.Append ("Events: ["); | ||
sb.AppendJoin (", ", Events); | ||
sb.Append ("], Delegates: ["); | ||
sb.AppendJoin (", ", Delegates); | ||
sb.Append ($"], Singleton: {Singleton}, KeepRefUntil: {KeepRefUntil ?? "null"}, IsStubClass: {IsStubClass} }}"); | ||
return sb.ToString (); | ||
} | ||
} |
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
9 changes: 9 additions & 0 deletions
9
src/rgen/Microsoft.Macios.Transformer/DataModel/BindingInfo.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
194 changes: 194 additions & 0 deletions
194
tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/BaseTypeDataTests.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,194 @@ | ||
// 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 BaseTypeDataTests : BaseTransformerTestClass { | ||
|
||
class TestDataTryCreate : IEnumerable<object []> { | ||
public IEnumerator<object []> GetEnumerator () | ||
{ | ||
var path = "/some/random/path.cs"; | ||
|
||
const string simpleBaseType = @" | ||
using System; | ||
using Foundation; | ||
using ObjCRuntime; | ||
using UIKit; | ||
namespace Test; | ||
[NoTV] | ||
[MacCatalyst (13, 1)] | ||
[DisableDefaultCtor] | ||
[Abstract] | ||
[BaseType (typeof (NSObject))] | ||
interface UIFeedbackGenerator : UIInteraction { | ||
[Export (""prepare"")] | ||
void Prepare (); | ||
} | ||
"; | ||
yield return [(Source: simpleBaseType, Path: path), new BaseTypeData ("Foundation.NSObject")]; | ||
|
||
const string baseTypeWithName = @" | ||
using System; | ||
using Foundation; | ||
using ObjCRuntime; | ||
using UIKit; | ||
namespace Test; | ||
[NoTV] | ||
[MacCatalyst (13, 1)] | ||
[DisableDefaultCtor] | ||
[Abstract] | ||
[BaseType (typeof (NSObject), Name =""MyObjcName"")] | ||
interface UIFeedbackGenerator : UIInteraction { | ||
[Export (""prepare"")] | ||
void Prepare (); | ||
} | ||
"; | ||
yield return [ | ||
(Source: baseTypeWithName, Path: path), | ||
new BaseTypeData ("Foundation.NSObject") { | ||
Name = "MyObjcName", | ||
}]; | ||
|
||
const string baseTypeWithEvents = @" | ||
using System; | ||
using Foundation; | ||
using ObjCRuntime; | ||
using UIKit; | ||
namespace Test; | ||
[NoiOS] | ||
[NoMacCatalyst] | ||
[BaseType (typeof (NSObject))] | ||
[Model] | ||
[Protocol] | ||
interface NSAnimationDelegate {} | ||
[BaseType (typeof (NSObject), Delegates = new string [] { ""WeakDelegate"" }, Events = new Type [] { typeof (NSAnimationDelegate) })] | ||
interface NSAnimation : NSCoding, NSCopying { | ||
[Export (""startAnimation"")] | ||
void StartAnimation (); | ||
} | ||
"; | ||
yield return [ | ||
(Source: baseTypeWithEvents, Path: path), | ||
new BaseTypeData ("Foundation.NSObject") { | ||
Delegates = ["WeakDelegate"], | ||
Events = ["Test.NSAnimationDelegate"] | ||
} | ||
]; | ||
|
||
const string singleton = @" | ||
using System; | ||
using Foundation; | ||
using ObjCRuntime; | ||
using UIKit; | ||
namespace Test; | ||
[NoTV] | ||
[MacCatalyst (13, 1)] | ||
[DisableDefaultCtor] | ||
[Abstract] | ||
[BaseType (typeof (NSObject), Singleton = true)] | ||
interface UIFeedbackGenerator : UIInteraction { | ||
[Export (""prepare"")] | ||
void Prepare (); | ||
} | ||
"; | ||
yield return [ | ||
(Source: singleton, Path: path), | ||
new BaseTypeData ("Foundation.NSObject") { | ||
Singleton = true | ||
}]; | ||
|
||
const string keepRefUntil = @" | ||
using System; | ||
using Foundation; | ||
using ObjCRuntime; | ||
using UIKit; | ||
namespace Test; | ||
[NoTV] | ||
[BaseType (typeof (NSObject), KeepRefUntil = ""Dismissed"")] | ||
[Deprecated (PlatformName.iOS, 8, 3, message: ""Use 'UIAlertController' with 'UIAlertControllerStyle.ActionSheet' instead."")] | ||
[MacCatalyst (13, 1)] | ||
[Deprecated (PlatformName.MacCatalyst, 13, 1, message: ""Use 'UIAlertController' with 'UIAlertControllerStyle.ActionSheet' instead."")] | ||
interface UIActionSheet { | ||
} | ||
"; | ||
yield return [ | ||
(Source: keepRefUntil, Path: path), | ||
new BaseTypeData ("Foundation.NSObject") { | ||
KeepRefUntil = "Dismissed", | ||
}]; | ||
|
||
const string isStubClass = @" | ||
using System; | ||
using Foundation; | ||
using ObjCRuntime; | ||
using UIKit; | ||
namespace Test; | ||
[NoTV] | ||
[MacCatalyst (13, 1)] | ||
[DisableDefaultCtor] | ||
[Abstract] | ||
[BaseType (typeof (NSObject), IsStubClass = true)] | ||
interface UIFeedbackGenerator : UIInteraction { | ||
[Export (""prepare"")] | ||
void Prepare (); | ||
} | ||
"; | ||
yield return [ | ||
(Source: isStubClass, Path: path), | ||
new BaseTypeData ("Foundation.NSObject") { | ||
IsStubClass = true | ||
}]; | ||
} | ||
|
||
IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); | ||
} | ||
|
||
[Theory] | ||
[AllSupportedPlatformsClassData<TestDataTryCreate>] | ||
void TryeCreateTests (ApplePlatform platform, (string Source, string Path) source, BaseTypeData expectedData) | ||
{ | ||
// create a compilation used to create the transformer | ||
var compilation = CreateCompilation (platform, sources: source); | ||
var syntaxTree = compilation.SyntaxTrees.FirstOrDefault (); | ||
Assert.NotNull (syntaxTree); | ||
|
||
var semanticModel = compilation.GetSemanticModel (syntaxTree); | ||
Assert.NotNull (semanticModel); | ||
|
||
var declaration = syntaxTree.GetRoot () | ||
.DescendantNodes ().OfType<BaseTypeDeclarationSyntax> () | ||
.LastOrDefault (); | ||
Assert.NotNull (declaration); | ||
|
||
var symbol = semanticModel.GetDeclaredSymbol (declaration); | ||
Assert.NotNull (symbol); | ||
var exportData = symbol.GetAttribute<BaseTypeData> (AttributesNames.BaseTypeAttribute, BaseTypeData.TryParse); | ||
Assert.Equal (expectedData, exportData); | ||
} | ||
} |