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.

Parse the attribute and retrieve all the needed data. We will use the
full name type for events rather than a INamedType.
  • Loading branch information
mandel-macaque committed Jan 20, 2025
1 parent beb6033 commit ff17012
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,12 +1,21 @@
// 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 ff17012

Please sign in to comment.