Skip to content

Commit

Permalink
[Rgen] Add the parsing code for the BaseTypeAttribute in the transfor…
Browse files Browse the repository at this point in the history
…mer. (#22021)
  • Loading branch information
mandel-macaque authored Jan 21, 2025
1 parent 727a00f commit 9516bf1
Show file tree
Hide file tree
Showing 4 changed files with 361 additions and 3 deletions.
158 changes: 158 additions & 0 deletions src/rgen/Microsoft.Macios.Transformer/Attributes/BaseTypeData.cs
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 ();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,6 @@ public static bool TryParse (AttributeData attributeData,
selector = (string?) attributeData.ConstructorArguments [0].Value!;
break;
case 2:
// there are two possible cases in this situation.
// 1. The second argument is an ArgumentSemantic
// 2. The second argument is a T
selector = (string?) attributeData.ConstructorArguments [0].Value!;
argumentSemantic = (ArgumentSemantic) attributeData.ConstructorArguments [1].Value!;
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Macios.Transformer.Attributes;

namespace Microsoft.Macios.Generator.DataModel;

/// <summary>
/// This struct works as a union to store the possible BindingTypeData that can be present in the bindings.
/// </summary>
readonly struct BindingInfo : IEquatable<BindingInfo> {

public BaseTypeData BaseTypeData { get; }

public BindingInfo (BaseTypeData baseTypeData)
{
BaseTypeData = baseTypeData;
}

/// <inheritdoc />
public bool Equals (BindingInfo other)
{
Expand Down
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);
}
}

0 comments on commit 9516bf1

Please sign in to comment.