Skip to content

Commit

Permalink
[Rge] Implement the code in the transformer to parse Export attrs.
Browse files Browse the repository at this point in the history
Add the code that will parse an attribute data for a
Foundation.ExportAttribute.
  • Loading branch information
mandel-macaque committed Jan 20, 2025
1 parent 05a3831 commit e28dd68
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ public static bool HasAttribute (this ISymbol symbol, string attribute)
return false;
}

delegate string? GetAttributeNames ();
internal delegate string? GetAttributeNames ();

delegate bool TryParse<T> (AttributeData data, [NotNullWhen (true)] out T? value) where T : struct;
internal delegate bool TryParse<T> (AttributeData data, [NotNullWhen (true)] out T? value) where T : struct;

static T? GetAttribute<T> (this ISymbol symbol, GetAttributeNames getAttributeNames, TryParse<T> tryParse)
internal static T? GetAttribute<T> (this ISymbol symbol, GetAttributeNames getAttributeNames, TryParse<T> tryParse)
where T : struct
{
var attributes = symbol.GetAttributeData ();
Expand All @@ -113,6 +113,9 @@ public static bool HasAttribute (this ISymbol symbol, string attribute)
return exportData.Value;
return null;
}

internal static T? GetAttribute<T> (this ISymbol symbol, string attributeName, TryParse<T> tryParse) where T : struct
=> GetAttribute (symbol, () => attributeName, tryParse);

/// <summary>
/// Returns if a type is blittable or not.
Expand Down
92 changes: 90 additions & 2 deletions src/rgen/Microsoft.Macios.Transformer/Attributes/ExportData.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,95 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using ObjCRuntime;

namespace Microsoft.Macios.Transformer.Attributes;

public struct ExportData : IEquatable<ExportData> {

/// <summary>
/// The exported native selector.
/// </summary>
public string? Selector { get; }

/// <summary>
/// Argument semantics to use with the selector.
/// </summary>
public ArgumentSemantic ArgumentSemantic { get; } = ArgumentSemantic.None;

public ExportData (string? selector)
{
Selector = selector;
}

public ExportData (string? selector, ArgumentSemantic argumentSemantic)
{
Selector = selector;
ArgumentSemantic = argumentSemantic;
}

/// <summary>
/// Try to parse the attribute data to retrieve the information of an ExportAttribute&lt;T&gt;.
/// </summary>
/// <param name="attributeData">The attribute data to be parsed.</param>
/// <param name="data">The parsed data. Null if we could not parse the attribute data.</param>
/// <returns>True if the data was parsed.</returns>
public static bool TryParse (AttributeData attributeData,
[NotNullWhen (true)] out ExportData? data)
{
data = null;
var count = attributeData.ConstructorArguments.Length;
string? selector;
ArgumentSemantic argumentSemantic = ArgumentSemantic.None;

// custom marshal directive values

switch (count) {
case 1:
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;
default:
// 0 should not be an option..
return false;
}

if (attributeData.NamedArguments.Length == 0) {
data = new (selector, argumentSemantic);
return true;
}

foreach (var (name, value) in attributeData.NamedArguments) {
switch (name) {
case "Selector":
selector = (string?) value.Value!;
break;
case "ArgumentSemantic":
argumentSemantic = (ArgumentSemantic) value.Value!;
break;
default:
data = null;
return false;
}
}

data = new(selector, argumentSemantic);
return true;
}

public bool Equals (ExportData other)
{
throw new NotImplementedException ();
if (Selector != other.Selector)
return false;
return ArgumentSemantic == other.ArgumentSemantic;
}

/// <inheritdoc />
Expand All @@ -19,7 +101,7 @@ public override bool Equals (object? obj)
/// <inheritdoc />
public override int GetHashCode ()
{
throw new NotImplementedException ();
return HashCode.Combine (Selector, ArgumentSemantic);
}

public static bool operator == (ExportData x, ExportData y)
Expand All @@ -31,4 +113,10 @@ public override int GetHashCode ()
{
return !(x == y);
}

/// <inheritdoc />
public override string ToString ()
{
return $"{{ Selector: '{Selector ?? "null" }', ArgumentSemantic: {ArgumentSemantic} }}";
}
}
1 change: 1 addition & 0 deletions src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ static class AttributesNames {
public const string DisableDefaultCtorAttribute = "DisableDefaultCtorAttribute";
public const string DisposeAttribute = "DisposeAttribute";
public const string ErrorDomainAttribute = "ErrorDomainAttribute";
public const string ExportAttribute = "Foundation.ExportAttribute";
public const string FieldAttribute = "Foundation.FieldAttribute";
public const string AdviceAttribute = "Foundation.AdviceAttribute";
public const string ModelAttribute = "Foundation.ModelAttribute";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Macios.Transformer.Attributes;
using Xamarin.Tests;
using Xamarin.Utils;
using Microsoft.Macios.Generator.Extensions;
using ObjCRuntime;

namespace Microsoft.Macios.Transformer.Tests.DataModel;

public class ExportDataTests : BaseTransformerTestClass {


class TestDataTryCreate : IEnumerable<object []> {
public IEnumerator<object []> GetEnumerator ()
{
const string selectorOnly = @"
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: selectorOnly, Path: "/some/random/path.cs"), new ExportData (selector: "prepare")];

const string selectorWithArgumentSemantic = @"
using System;
using Foundation;
using ObjCRuntime;
using UIKit;
namespace Test;
[NoTV]
[MacCatalyst (13, 1)]
[DisableDefaultCtor]
[Abstract]
[BaseType (typeof (NSObject))]
interface UIFeedbackGenerator : UIInteraction {
[Export (""prepare"", ArgumentSemantic.Retain)]
void Prepare ();
}
";
yield return [(Source: selectorWithArgumentSemantic, Path: "/some/random/path.cs"), new ExportData (selector: "prepare", argumentSemantic: ArgumentSemantic.Retain)];

}

IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
}

[Theory]
[AllSupportedPlatformsClassData<TestDataTryCreate>]
public void TryeCreateTests (ApplePlatform platform, (string Source, string Path) source, ExportData 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<MethodDeclarationSyntax> ()
.FirstOrDefault ();
Assert.NotNull (declaration);

var symbol = semanticModel.GetDeclaredSymbol (declaration);
Assert.NotNull (symbol);
var exportData = symbol.GetAttribute<ExportData> (AttributesNames.ExportAttribute, ExportData.TryParse);
Assert.Equal (expectedData, exportData);
}
}

0 comments on commit e28dd68

Please sign in to comment.