From 540182f1f90874cad76829c29f521f0a057315b8 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Saenz Date: Mon, 27 Jan 2025 07:38:47 -0500 Subject: [PATCH] [Rgen] Add code needed to parse the several snippet attrs. Allow the transformer to detect this. We will need to think a way to port them to the new API style. --- .../Attributes/SnippetData.cs | 97 +++++++++++++++++++ .../AttributesNames.cs | 27 ++++++ .../Attributes/SnippetDataTests.cs | 62 ++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 src/rgen/Microsoft.Macios.Transformer/Attributes/SnippetData.cs create mode 100644 tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/SnippetDataTests.cs diff --git a/src/rgen/Microsoft.Macios.Transformer/Attributes/SnippetData.cs b/src/rgen/Microsoft.Macios.Transformer/Attributes/SnippetData.cs new file mode 100644 index 00000000000..94d3f74dc3d --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/Attributes/SnippetData.cs @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Macios.Transformer.Attributes; + +readonly struct SnippetData : IEquatable { + + public string Code { get; } + + public bool Optimizable { get; } + + public SnippetData (string code) + { + Code = code; + } + + public SnippetData (string code, bool optimizable) + { + Code = code; + Optimizable = optimizable; + } + + public static bool TryParse (AttributeData attributeData, + [NotNullWhen (true)] out SnippetData? data) + { + data = null; + var count = attributeData.ConstructorArguments.Length; + string code; + var optimizable = false; + + // custom marshal directive values + + switch (count) { + case 1: + code = (string) attributeData.ConstructorArguments [0].Value!; + break; + default: + // 0 should not be an option.. + return false; + } + + if (attributeData.NamedArguments.Length == 0) { + data = new (code); + return true; + } + + foreach (var (argumentName, value) in attributeData.NamedArguments) { + switch (argumentName) { + case "Code": + code = (string?) value.Value!; + break; + case "Optimizable": + optimizable = (bool) value.Value!; + break; + default: + data = null; + return false; + } + } + + data = new (code, optimizable); + return true; + } + + public bool Equals (SnippetData other) + { + if (Code != other.Code) + return false; + return Optimizable == other.Optimizable; + } + + /// + public override bool Equals (object? obj) + { + return obj is SnippetData other && Equals (other); + } + + /// + public override int GetHashCode () + => HashCode.Combine (Code, Optimizable); + + public static bool operator == (SnippetData x, SnippetData y) + { + return x.Equals (y); + } + + public static bool operator != (SnippetData x, SnippetData y) + { + return !(x == y); + } + + public override string ToString () + => $"{{ Code: '{Code}' Optimizable: {Optimizable} }}"; +} diff --git a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs index 8bef60912df..afe3ff0f658 100644 --- a/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs +++ b/src/rgen/Microsoft.Macios.Transformer/AttributesNames.cs @@ -83,7 +83,13 @@ static class AttributesNames { [BindingFlag (AttributeTargets.Parameter | AttributeTargets.Property)] public const string DisableZeroCopyAttribute = "DisableZeroCopyAttribute"; + /// + /// Code to run from a generated Dispose method, before any generated code is executed + /// Adding this attribute will, by default, make the method non-optimizable by the SDK tools + /// + [BindingAttribute(typeof(ErrorDomainData), AttributeTargets.Interface | AttributeTargets.Class)] public const string DisposeAttribute = "DisposeAttribute"; + public const string EditorBrowsableAttribute = "System.ComponentModel.EditorBrowsableAttribute"; [BindingAttribute(typeof(ErrorDomainData), AttributeTargets.Enum)] @@ -220,6 +226,20 @@ static class AttributesNames { [BindingFlag] public const string PlainStringAttribute = "PlainStringAttribute"; + /// + /// PostSnippet code is inserted before returning, before paramters are disposed/released + /// Adding this attribute will, by default, make the method non-optimizable by the SDK tools + /// + [BindingAttribute (typeof(SnippetData), AttributeTargets.Method | AttributeTargets.Property)] + public const string PostSnippetAttribute = "PostSnippetAttribute"; + + /// + /// PreSnippet code is inserted after the parameters have been validated/marshalled + /// Adding this attribute will, by default, make the method non-optimizable by the SDK tools + /// + [BindingAttribute (typeof(SnippetData), AttributeTargets.Method | AttributeTargets.Property)] + public const string PreSnippetAttribute = "PreSnippetAttribute"; + /// /// When this attribute is applied to the interface definition it will flag the default constructor as private. /// @@ -228,6 +248,13 @@ static class AttributesNames { [BindingFlag (AttributeTargets.Property)] public const string ProbePresenceAttribute = "ProbePresenceAttribute"; + + /// + /// PrologueSnippet code is inserted before any code is generated + /// Adding this attribute will, by default, make the method non-optimizable by the SDK tools + /// + [BindingAttribute (typeof(SnippetData), AttributeTargets.Method | AttributeTargets.Property)] + public const string PrologueSnippetAttribute = "PrologueSnippetAttribute"; /// /// Use this attribute to instruct the binding generator that the binding for this particular method should be diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/SnippetDataTests.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/SnippetDataTests.cs new file mode 100644 index 00000000000..dc867315cff --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/SnippetDataTests.cs @@ -0,0 +1,62 @@ +// 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 SnippetDataTests : BaseTransformerTestClass { + class TestDataTryCreate : IEnumerable { + + public IEnumerator GetEnumerator () + { + const string path = "/some/random/path.cs"; + + const string disposeAttribute = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +[BaseType (typeof (NSControl))] +[Dispose (""dispatcher = null;"", Optimizable = true)] +interface NSButton { } +"; + + yield return [(Source: disposeAttribute, Path: path), new SnippetData ("dispatcher = null;", true)]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + void TryCreateTests (ApplePlatform platform, (string Source, string Path) source, SnippetData 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.DisposeAttribute, SnippetData.TryParse); + Assert.Equal (expectedData, attribute); + } +}