From 4d9a459481f6e353d14202beac65a30dd09676a5 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Saenz Date: Sun, 26 Jan 2025 20:26:56 -0500 Subject: [PATCH] [Rgen] Add support to parse the strong dict attr in the transformer. --- .../Attributes/StrongDictionaryData.cs | 93 +++++++++++++++++++ .../AttributesNames.cs | 6 ++ .../Attributes/StrongDictionaryDataTests.cs | 65 +++++++++++++ 3 files changed, 164 insertions(+) create mode 100644 src/rgen/Microsoft.Macios.Transformer/Attributes/StrongDictionaryData.cs create mode 100644 tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/StrongDictionaryDataTests.cs diff --git a/src/rgen/Microsoft.Macios.Transformer/Attributes/StrongDictionaryData.cs b/src/rgen/Microsoft.Macios.Transformer/Attributes/StrongDictionaryData.cs new file mode 100644 index 00000000000..f238f54ccb0 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/Attributes/StrongDictionaryData.cs @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Transformer.Attributes; + +readonly struct StrongDictionaryData : IEquatable { + + public string TypeWithKeys { get; } + public string? Suffix { get; } + + public StrongDictionaryData (string typeWithKeys) + { + TypeWithKeys = typeWithKeys; + } + + public StrongDictionaryData (string typeWithKeys, string? suffix) + { + TypeWithKeys = typeWithKeys; + Suffix = suffix; + } + + public static bool TryParse (AttributeData attributeData, + [NotNullWhen (true)] out StrongDictionaryData? data) + { + data = null; + var count = attributeData.ConstructorArguments.Length; + string typeWithKeys; + string? suffix = null; + + // custom marshal directive values + + switch (count) { + case 1: + typeWithKeys = (string) attributeData.ConstructorArguments [0].Value!; + break; + default: + // 0 should not be an option.. + return false; + } + + if (attributeData.NamedArguments.Length == 0) { + data = new (typeWithKeys); + return true; + } + + foreach (var (argumentName, value) in attributeData.NamedArguments) { + switch (argumentName) { + case "TypeWithKeys": + typeWithKeys = (string?) value.Value!; + break; + case "Suffix": + suffix = (string?) value.Value!; + break; + } + } + + data = new (typeWithKeys, suffix); + return true; + } + + public bool Equals (StrongDictionaryData other) + { + if (TypeWithKeys != other.TypeWithKeys) + return false; + return Suffix == other.Suffix; + } + + /// + public override bool Equals (object? obj) + { + return obj is StrongDictionaryData other && Equals (other); + } + + /// + public override int GetHashCode () + => HashCode.Combine (TypeWithKeys, Suffix); + + public static bool operator == (StrongDictionaryData x, StrongDictionaryData y) + { + return x.Equals (y); + } + + public static bool operator != (StrongDictionaryData x, StrongDictionaryData y) + { + return !(x == y); + } + + public override string ToString () + => $"{{ TypeWithKeys: '{TypeWithKeys}' Suffix: '{Suffix}' }}"; +} diff --git a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs index 0fb39f6624b..8bef60912df 100644 --- a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs +++ b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs @@ -264,6 +264,12 @@ static class AttributesNames { /// [BindingFlag (AttributeTargets.Class)] public const string StaticAttribute = "StaticAttribute"; + + /// + /// When this attribute is applied to an interface, it directs the generator to + /// create a strongly typed DictionaryContainer for the specified fields. + /// + [BindingAttribute(typeof(StrongDictionaryData), AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Property)] public const string StrongDictionaryAttribute = "StrongDictionaryAttribute"; /// diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/StrongDictionaryDataTests.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/StrongDictionaryDataTests.cs new file mode 100644 index 00000000000..537901455df --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/StrongDictionaryDataTests.cs @@ -0,0 +1,65 @@ +// 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 StrongDictionaryDataTests : BaseTransformerTestClass { + + class TestDataTryCreate : IEnumerable { + public IEnumerator GetEnumerator () + { + const string path = "/some/random/path.cs"; + + const string strongDictionary = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +[StrongDictionary (""AVCapturePhotoSettingsThumbnailFormatKeys"")] +interface AVCapturePhotoSettingsThumbnailFormat { + NSString Codec { get; set; } + NSNumber Width { get; set; } + NSNumber Height { get; set; } +} +"; + + yield return [(Source: strongDictionary, Path: "/some/random/path.cs"), new StrongDictionaryData("AVCapturePhotoSettingsThumbnailFormatKeys")]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + void TryCreateTests (ApplePlatform platform, (string Source, string Path) source, StrongDictionaryData expectedData) + { + // create a compilation used to create the transformer + var compilation = CreateCompilation (platform, sources: source); + var syntaxTree = compilation.SyntaxTrees.ForSource (source); + Assert.NotNull (syntaxTree); + + var semanticModel = compilation.GetSemanticModel (syntaxTree); + Assert.NotNull (semanticModel); + + var declaration = syntaxTree.GetRoot () + .DescendantNodes ().OfType () + .FirstOrDefault (); + Assert.NotNull (declaration); + + var symbol = semanticModel.GetDeclaredSymbol (declaration); + Assert.NotNull (symbol); + var attribute = symbol.GetAttribute (AttributesNames.StrongDictionaryAttribute, StrongDictionaryData.TryParse); + Assert.Equal (expectedData, attribute); + } +}