Skip to content

Commit

Permalink
[Rgen] Add the needed code to parse a BindAsAttriubte. (#22083)
Browse files Browse the repository at this point in the history
This change is a little more interesting than the others because I had
to update the way we retrieve the attributes. Return attrbutes are only
present on a special collection for IMethodSymbols, this meant that we
had to add an extra pass for those attributes in the generic method in
pur extensions.

---------

Co-authored-by: GitHub Actions Autoformatter <[email protected]>
  • Loading branch information
mandel-macaque and GitHub Actions Autoformatter authored Jan 31, 2025
1 parent 0984d4d commit 53cbff0
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,23 @@ public static Dictionary<string, List<AttributeData>> GetAttributeData (this ISy
attributeDataList.Add (attributeData);
}

// if we are dealing with a method, we want to also return the attributes used for the return type
if (symbol is not IMethodSymbol methodSymbol)
return attributes;

var returnAttributes = methodSymbol.GetReturnTypeAttributes ();
foreach (var attributeData in returnAttributes) {
var attrName = attributeData.AttributeClass?.ToDisplayString ();
if (string.IsNullOrEmpty (attrName))
continue;
if (!attributes.TryGetValue (attrName, out var attributeDataList)) {
attributeDataList = new List<AttributeData> ();
attributes.Add (attrName, attributeDataList);
}

attributeDataList.Add (attributeData);
}

return attributes;
}

Expand Down
66 changes: 66 additions & 0 deletions src/rgen/Microsoft.Macios.Transformer/Attributes/BindAsData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

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

namespace Microsoft.Macios.Transformer.Attributes;

readonly record struct BindAsData {

public string Type { get; init; }
public string? OriginalType { get; init; }

public BindAsData (string type)
{
Type = type;
}

public BindAsData (string type, string? originalType)
{
Type = type;
OriginalType = originalType;

}

public static bool TryParse (AttributeData attributeData,
[NotNullWhen (true)] out BindAsData? data)
{
data = null;
var count = attributeData.ConstructorArguments.Length;
string type;
string? originalType = null;

switch (count) {
case 1:
type = ((ITypeSymbol) attributeData.ConstructorArguments [0].Value!).ToDisplayString ();
break;
default:
// 0 should not be an option..
return false;
}

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

foreach (var (argumentName, value) in attributeData.NamedArguments) {
switch (argumentName) {
case "Type":
type = ((ITypeSymbol) value.Value!).ToDisplayString ();
break;
case "OriginalType":
originalType = ((ITypeSymbol) value.Value!).ToDisplayString ();
break;
default:
data = null;
return false;
}
}

data = new (type, originalType);
return true;
}

}
3 changes: 3 additions & 0 deletions src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ static class AttributesNames {
[BindingAttribute(typeof(BindData), AttributeTargets.Method )]
public const string BindAttribute = "BindAttribute";

[BindingAttribute(typeof(BindAsData), AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter)]
public const string BindAsAttribute = "ObjCRuntime.BindAsAttribute";

/// <summary>
/// Use this attribute on a type definition to bind Objective-C categories and to expose those as C# extension
/// methods to mirror the way Objective-C exposes the functionality.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;
using Microsoft.CodeAnalysis;
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;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Microsoft.Macios.Transformer.Tests.Attributes;

public class BindAsDataTests : BaseTransformerTestClass {

class TestDataTryCreate : IEnumerable<object []> {
public IEnumerator<object []> GetEnumerator ()
{
var path = "/some/random/path.cs";

const string bindAsProperty = @"
using System;
using Foundation;
using ObjCRuntime;
using CoreMedia;
namespace Test;
[NoTV]
[MacCatalyst (13, 1)]
[DisableDefaultCtor]
[Abstract]
[BaseType (typeof (NSObject))]
interface UIFeedbackGenerator {
[BindAs (typeof (CMVideoDimensions []))]
[Export (""supportedMaxPhotoDimensions"")]
NSValue [] SupportedMaxPhotoDimensions { get; }
}
";

yield return [
PropertyDeclaration (IdentifierName ("string"), Identifier ("Hello")),
(Source: bindAsProperty, Path: path),
new BindAsData ("CoreMedia.CMVideoDimensions[]")];


const string bindAsReturnMethod = @"
using System;
using Foundation;
using ObjCRuntime;
using CoreMedia;
namespace Test;
[NoTV]
[MacCatalyst (13, 1)]
[DisableDefaultCtor]
[Abstract]
[BaseType (typeof (NSObject))]
interface UIFeedbackGenerator {
[return: BindAs (typeof (NSLinguisticTag []))]
[Export (""linguisticTagsInRange:scheme:options:orthography:tokenRanges:"")]
NSString [] GetLinguisticTags (NSRange range, NSString scheme, NSLinguisticTaggerOptions options, [NullAllowed] NSOrthography orthography, [NullAllowed] out NSValue [] tokenRanges);
}
";

yield return [
MethodDeclaration (PredefinedType (Token (SyntaxKind.StringKeyword)), Identifier ("Hello")),
(Source: bindAsReturnMethod, Path: path),
new BindAsData ("Foundation.NSLinguisticTag[]")];


const string parameterBindAs = @"
using System;
using Foundation;
using ObjCRuntime;
using CoreMedia;
namespace Test;
[NoTV]
[MacCatalyst (13, 1)]
[DisableDefaultCtor]
[Abstract]
[BaseType (typeof (NSObject))]
interface UIFeedbackGenerator {
[Export (""linguisticTagsInRange:scheme:options:orthography:tokenRanges:"")]
NSString [] GetLinguisticTags ([BindAs (typeof (ushort?))] NSString scheme);
}
";
var parameter = Parameter (Identifier ("variable"))
.WithType (PredefinedType (Token (SyntaxKind.StringKeyword)));
yield return [
parameter,
(Source: parameterBindAs, Path: path),
new BindAsData ("ushort?")];
}

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

[Theory]
[AllSupportedPlatformsClassData<TestDataTryCreate>]
void TryCreateTests<T> (ApplePlatform platform, T _, (string Source, string Path) source, BindAsData expectedData)
where T : CSharpSyntaxNode
{
// create a compilation used to create the transformer
var compilation = CreateCompilation (platform, sources: source);
var syntaxTree = compilation.SyntaxTrees.ForSource (source);
var trees = compilation.SyntaxTrees.Where (s => s.FilePath == source.Path).ToArray ();
Assert.Single (trees);
Assert.NotNull (syntaxTree);

var semanticModel = compilation.GetSemanticModel (syntaxTree);
Assert.NotNull (semanticModel);

var declaration = syntaxTree.GetRoot ()
.DescendantNodes ().OfType<T> ()
.LastOrDefault ();

Assert.NotNull (declaration);

var symbol = semanticModel.GetDeclaredSymbol (declaration);
Assert.NotNull (symbol);
var attribute = symbol.GetAttribute<BindAsData> (AttributesNames.BindAsAttribute, BindAsData.TryParse);
Assert.NotNull (attribute);
Assert.Equal (expectedData, attribute.Value);
}
}

0 comments on commit 53cbff0

Please sign in to comment.