From 9cf6c254be51afcb2241c05e64c0759c366af1d9 Mon Sep 17 00:00:00 2001 From: Manuel de la Pena Saenz Date: Tue, 21 Jan 2025 11:07:36 -0500 Subject: [PATCH] [Rgen] Add support to the transformer to parse the old xamarin availability. We re-use the availability struct from the code generator and we only provide a new data struct for the old attrs. --- .../Microsoft.Macios.Bindings.Analyzer.csproj | 3 + .../Attributes/SupportedOSPlatformData.cs | 6 + .../Attributes/UnsupportedOSPlatformData.cs | 6 + .../Availability/PlatformAvailability.cs | 1 + .../Availability/SymbolAvailabilityBuilder.cs | 12 ++ .../Extensions/TypeSymbolExtensions.Core.cs | 20 +++ .../TypeSymbolExtensions.Generator.cs | 21 +-- .../Attributes/XamarinAvailabilityData.cs | 82 ++++++++++ .../TypeSymbolExtensions.Transformer.cs | 57 +++++++ .../Attributes/AvailabilityTests.cs | 147 ++++++++++++++++++ 10 files changed, 335 insertions(+), 20 deletions(-) create mode 100644 src/rgen/Microsoft.Macios.Transformer/Attributes/XamarinAvailabilityData.cs create mode 100644 tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/AvailabilityTests.cs diff --git a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj index ca1f355f8e9d..750d7b795410 100644 --- a/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj +++ b/src/rgen/Microsoft.Macios.Bindings.Analyzer/Microsoft.Macios.Bindings.Analyzer.csproj @@ -69,6 +69,9 @@ Generator/Extensions/TypeSymbolExtensions.Core.cs + + Generator/Extensions/TypeSymbolExtensions.Generator.cs + Generator/Extensions/TypedConstantExtensions.cs diff --git a/src/rgen/Microsoft.Macios.Generator/Attributes/SupportedOSPlatformData.cs b/src/rgen/Microsoft.Macios.Generator/Attributes/SupportedOSPlatformData.cs index 35d0a168b6e2..df1897f04a3c 100644 --- a/src/rgen/Microsoft.Macios.Generator/Attributes/SupportedOSPlatformData.cs +++ b/src/rgen/Microsoft.Macios.Generator/Attributes/SupportedOSPlatformData.cs @@ -28,6 +28,12 @@ internal SupportedOSPlatformData (string platformName) (Platform, Version) = platformName.GetPlatformAndVersion (); } + internal SupportedOSPlatformData (ApplePlatform platform, Version version) + { + Platform = platform; + Version = version; + } + /// /// Try to parse the attribute data to retrieve the information of an SupportedOSPlatformAttribute. /// diff --git a/src/rgen/Microsoft.Macios.Generator/Attributes/UnsupportedOSPlatformData.cs b/src/rgen/Microsoft.Macios.Generator/Attributes/UnsupportedOSPlatformData.cs index cdf39b7b59a8..72b2ca9ea549 100644 --- a/src/rgen/Microsoft.Macios.Generator/Attributes/UnsupportedOSPlatformData.cs +++ b/src/rgen/Microsoft.Macios.Generator/Attributes/UnsupportedOSPlatformData.cs @@ -34,6 +34,12 @@ internal UnsupportedOSPlatformData (string platformName) (Platform, Version) = platformName.GetPlatformAndVersion (); } + internal UnsupportedOSPlatformData (ApplePlatform platform) + { + Platform = platform; + Version = new Version (); + } + internal UnsupportedOSPlatformData (string platformName, string? message = null) : this (platformName) { Message = message; diff --git a/src/rgen/Microsoft.Macios.Generator/Availability/PlatformAvailability.cs b/src/rgen/Microsoft.Macios.Generator/Availability/PlatformAvailability.cs index da94d3bf971a..798d932436c5 100644 --- a/src/rgen/Microsoft.Macios.Generator/Availability/PlatformAvailability.cs +++ b/src/rgen/Microsoft.Macios.Generator/Availability/PlatformAvailability.cs @@ -150,6 +150,7 @@ public bool Equals (PlatformAvailability other) var obsoleteComparer = new DictionaryComparer (); var unsupportedComparer = new DictionaryComparer (); + var x = Equals (SupportedVersion, other.SupportedVersion); return Platform == other.Platform && Equals (SupportedVersion, other.SupportedVersion) && unsupportedComparer.Equals (unsupported, other.unsupported) && diff --git a/src/rgen/Microsoft.Macios.Generator/Availability/SymbolAvailabilityBuilder.cs b/src/rgen/Microsoft.Macios.Generator/Availability/SymbolAvailabilityBuilder.cs index c606e858c0e2..320a4eefcaf6 100644 --- a/src/rgen/Microsoft.Macios.Generator/Availability/SymbolAvailabilityBuilder.cs +++ b/src/rgen/Microsoft.Macios.Generator/Availability/SymbolAvailabilityBuilder.cs @@ -22,6 +22,18 @@ public sealed class Builder { internal Builder () { } + /// + /// Return the immutable version of the current data in the builder. + /// + public IEnumerable PlatformAvailabilities { + get { + // return the immutable version of the builder data + foreach (var availability in platforms.Values) { + yield return availability.ToImmutable (); + } + } + } + /// /// Returns the PlatformAvailability for the given platform. If we did not have a builder for the /// platform, a new one is created and added to the dictionary. diff --git a/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Core.cs b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Core.cs index fcc378661967..05b51420c114 100644 --- a/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Core.cs +++ b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Core.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Runtime.InteropServices; using Microsoft.CodeAnalysis; +using Microsoft.Macios.Generator.Availability; namespace Microsoft.Macios.Generator.Extensions; @@ -236,4 +237,23 @@ public static void GetInheritance ( parents = parentsBuilder.ToImmutable (); interfaces = [.. interfacesSet]; } + + /// + /// Returns the symbol availability taking into account the parent symbols availability. + /// + /// That means that the attributes used on the current symbol are merged with the attributes used + /// in all the symbol parents following the correct child-parent order. + /// + /// The symbol whose availability we want to retrieve. + /// A symbol availability structure for the symbol. + public static SymbolAvailability GetSupportedPlatforms (this ISymbol symbol) + { + var availability = GetAvailabilityForSymbol (symbol); + // get the parents and return the merge + foreach (var parent in GetParents (symbol)) { + availability = availability.MergeWithParent (GetAvailabilityForSymbol (parent)); + } + + return availability; + } } diff --git a/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Generator.cs b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Generator.cs index 70d64b650396..ccc7e64fec6c 100644 --- a/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Generator.cs +++ b/src/rgen/Microsoft.Macios.Generator/Extensions/TypeSymbolExtensions.Generator.cs @@ -58,26 +58,7 @@ static SymbolAvailability GetAvailabilityForSymbol (this ISymbol symbol) return builder.ToImmutable (); } - - /// - /// Returns the symbol availability taking into account the parent symbols availability. - /// - /// That means that the attributes used on the current symbol are merged with the attributes used - /// in all the symbol parents following the correct child-parent order. - /// - /// The symbol whose availability we want to retrieve. - /// A symbol availability structure for the symbol. - public static SymbolAvailability GetSupportedPlatforms (this ISymbol symbol) - { - var availability = GetAvailabilityForSymbol (symbol); - // get the parents and return the merge - foreach (var parent in GetParents (symbol)) { - availability = availability.MergeWithParent (GetAvailabilityForSymbol (parent)); - } - - return availability; - } - + public static bool IsSmartEnum (this ITypeSymbol symbol) { // a type is a smart enum if its type is a enum one AND it was decorated with the diff --git a/src/rgen/Microsoft.Macios.Transformer/Attributes/XamarinAvailabilityData.cs b/src/rgen/Microsoft.Macios.Transformer/Attributes/XamarinAvailabilityData.cs new file mode 100644 index 000000000000..b58682ff5f75 --- /dev/null +++ b/src/rgen/Microsoft.Macios.Transformer/Attributes/XamarinAvailabilityData.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.Macios.Generator.Attributes; +using Xamarin.Utils; + +namespace Microsoft.Macios.Transformer.Attributes; + +static class XamarinAvailabilityData { + + static readonly IReadOnlyDictionary supportedAttributes = new Dictionary { + { AttributesNames.iOSAttribute, ApplePlatform.iOS }, + { AttributesNames.TVAttribute, ApplePlatform.TVOS }, + { AttributesNames.MacAttribute, ApplePlatform.MacOSX }, + { AttributesNames.MacCatalystAttribute, ApplePlatform.MacCatalyst }, + }; + + static readonly IReadOnlyDictionary unsupportedAttributes = new Dictionary { + { AttributesNames.NoiOSAttribute, ApplePlatform.iOS }, + { AttributesNames.NoTVAttribute, ApplePlatform.TVOS }, + { AttributesNames.NoMacAttribute, ApplePlatform.MacOSX }, + { AttributesNames.NoMacCatalystAttribute, ApplePlatform.MacCatalyst }, + }; + + public static bool TryParseSupportedOSData (string attributeName, AttributeData attributeData, + [NotNullWhen (true)] out SupportedOSPlatformData? data) + { + data = null; + if (!supportedAttributes.TryGetValue (attributeName, out var platform)) { + // not a supported attribute + return false; + } + + var count = attributeData.ConstructorArguments.Length; + int major = 0; + int? minor = null; + int? subminor = null; + + // custom marshal directive values + + switch (count) { + case 1: + major = (byte) attributeData.ConstructorArguments [0].Value!; + break; + case 2: + major = (byte) attributeData.ConstructorArguments [0].Value!; + minor = (byte) attributeData.ConstructorArguments [1].Value!; + break; + case 3: + major = (byte) attributeData.ConstructorArguments [0].Value!; + minor = (byte) attributeData.ConstructorArguments [1].Value!; + subminor = (byte) attributeData.ConstructorArguments [2].Value!; + break; + default: + // 0 should not be an option.. + return false; + } + + data = (major, minor, subminor) switch { + (_, null, null) => new(platform, new($"{major}")), + (_, not null, null) => new(platform, new(major, minor.Value)), + (_,not null, not null) => new(platform, new(major, minor.Value, subminor.Value)), + _ => throw new ArgumentOutOfRangeException ("Could not parse the version") + }; + return true; + } + + public static bool TryParseUnsupportedOSData (string attributeName, AttributeData attributeData, + [NotNullWhen (true)] out UnsupportedOSPlatformData? data) + { + data = null; + if (!unsupportedAttributes.TryGetValue (attributeName, out var platform)) { + // not a supported attribute + return false; + } + + data = new UnsupportedOSPlatformData (platform); + return true; + } +} diff --git a/src/rgen/Microsoft.Macios.Transformer/Extensions/TypeSymbolExtensions.Transformer.cs b/src/rgen/Microsoft.Macios.Transformer/Extensions/TypeSymbolExtensions.Transformer.cs index 0b1b6a70cb97..f6062e68bd21 100644 --- a/src/rgen/Microsoft.Macios.Transformer/Extensions/TypeSymbolExtensions.Transformer.cs +++ b/src/rgen/Microsoft.Macios.Transformer/Extensions/TypeSymbolExtensions.Transformer.cs @@ -1,12 +1,69 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections.Immutable; using Microsoft.CodeAnalysis; +using Microsoft.Macios.Generator.Attributes; +using Microsoft.Macios.Generator.Availability; +using Microsoft.Macios.Transformer.Attributes; +using Xamarin.Utils; namespace Microsoft.Macios.Generator.Extensions; static partial class TypeSymbolExtensions { + static readonly ImmutableArray allSupportedPlatforms = [ + ApplePlatform.iOS, + ApplePlatform.TVOS, + ApplePlatform.MacOSX, + ApplePlatform.MacCatalyst + ]; + + /// + /// Return the symbol availability WITHOUT taking into account the parent symbols availability. + /// + /// The symbols whose availability attributes we want to retrieve. + /// The symbol availability WITHOUT taking into account the parent symbols. + /// This is a helper method, you probably don't want to use it. + internal static SymbolAvailability GetAvailabilityForSymbol (this ISymbol symbol) + { + //get the attribute of the symbol and look for the Supported and Unsupported attributes and + // add the different platforms to the result hashsets + var builder = SymbolAvailability.CreateBuilder (); + var boundAttributes = symbol.GetAttributes (); + if (boundAttributes.Length == 0) { + // no attrs in the symbol, therefore the symbol is supported in all platforms + return builder.ToImmutable (); + } + + foreach (var attributeData in boundAttributes) { + var attrName = attributeData.AttributeClass?.ToDisplayString (); + if (string.IsNullOrEmpty (attrName)) + continue; + + if (XamarinAvailabilityData.TryParseSupportedOSData (attrName, attributeData, out var availabilityData)) { + builder.Add (availabilityData.Value); + } + + if (XamarinAvailabilityData.TryParseUnsupportedOSData (attrName, attributeData, + out var unsupportedOsPlatformData)) { + builder.Add (unsupportedOsPlatformData.Value); + } + } + + // if a platform was not ignore or had a specific version, then it is supported, loop over what we got + // and add the missing platforms with the default version + var supportedPlatforms = builder.PlatformAvailabilities.ToArray () + .Select (a => a.Platform); + + // add data to all not added platforms + foreach (var platform in allSupportedPlatforms.Except (supportedPlatforms)) { + builder.Add (new SupportedOSPlatformData(platform, new Version())); + } + + return builder.ToImmutable (); + } + public static bool IsSmartEnum (this ITypeSymbol symbol) { throw new NotImplementedException (); diff --git a/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/AvailabilityTests.cs b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/AvailabilityTests.cs new file mode 100644 index 000000000000..4800b0823d04 --- /dev/null +++ b/tests/rgen/Microsoft.Macios.Transformer.Tests/Attributes/AvailabilityTests.cs @@ -0,0 +1,147 @@ +// 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.Attributes; +using Microsoft.Macios.Generator.Availability; +using Microsoft.Macios.Generator.Extensions; +using Xamarin.Tests; +using Xamarin.Utils; + +namespace Microsoft.Macios.Transformer.Tests.Attributes; + +public class AvailabilityTests : BaseTransformerTestClass { + class TestDataTryCreate : IEnumerable { + public IEnumerator GetEnumerator () + { + const string path = "/some/random/path.cs"; + var builder = SymbolAvailability.CreateBuilder (); + + const string allPlatformsIncluded = @" +using System; +using ObjCRuntime; +using Foundation; + +namespace Test; + +[DisableDefaultCtor] +[Abstract] // abstract class that should not be used directly +[BaseType (typeof (NSObject))] +interface UIFeedbackGenerator : UIInteraction { + + [Export (""prepare"")] + void Prepare (); +} +"; + builder.Add (new SupportedOSPlatformData ("ios")); + builder.Add (new SupportedOSPlatformData ("tvos")); + builder.Add (new SupportedOSPlatformData ("macos")); + builder.Add (new SupportedOSPlatformData ("maccatalyst")); + yield return [(Source: allPlatformsIncluded, Path: path), builder.ToImmutable ()]; + + builder.Clear (); + const string singlePlatformRemoved = @" +using System; +using ObjCRuntime; +using Foundation; + +namespace Test; + +[NoTV] +[DisableDefaultCtor] +[Abstract] // abstract class that should not be used directly +[BaseType (typeof (NSObject))] +interface UIFeedbackGenerator : UIInteraction { + + [Export (""prepare"")] + void Prepare (); +} +"; + + builder.Add (new SupportedOSPlatformData ("ios")); + builder.Add (new UnsupportedOSPlatformData ("tvos")); + builder.Add (new SupportedOSPlatformData ("macos")); + builder.Add (new SupportedOSPlatformData ("maccatalyst")); + yield return [(Source: singlePlatformRemoved, Path: path), builder.ToImmutable ()]; + + + builder.Clear (); + const string onePlatformSpecificVersion = @" +using System; +using ObjCRuntime; +using Foundation; + +namespace Test; + +[MacCatalyst (13, 1)] +[DisableDefaultCtor] +[Abstract] // abstract class that should not be used directly +[BaseType (typeof (NSObject))] +interface UIFeedbackGenerator : UIInteraction { + + [Export (""prepare"")] + void Prepare (); +} +"; + + builder.Add (new SupportedOSPlatformData ("ios")); + builder.Add (new SupportedOSPlatformData("tvos")); + builder.Add (new SupportedOSPlatformData ("macos")); + builder.Add (new SupportedOSPlatformData ("maccatalyst13.1")); + yield return [(Source: onePlatformSpecificVersion, Path: path), builder.ToImmutable ()]; + + builder.Clear (); + const string allPlatformsRemovedButOne = @" +using System; +using Foundation; +using ObjCRuntime; +using UIKit; + +namespace Test; + +[NoTV, NoMacCatalyst, NoiOS] +[BaseType (typeof (NSObject))] +interface UIFeedbackGenerator : UIInteraction { + + [Export (""prepare"")] + void Prepare (); +} +"; + + builder.Add (new UnsupportedOSPlatformData("ios")); + builder.Add (new UnsupportedOSPlatformData("tvos")); + builder.Add (new SupportedOSPlatformData ("macos")); + builder.Add (new UnsupportedOSPlatformData("maccatalyst")); + yield return [(Source: allPlatformsRemovedButOne, Path: path), builder.ToImmutable ()]; + } + + IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); + } + + [Theory] + [AllSupportedPlatformsClassData] + void TryCreateTests (ApplePlatform platform, (string Source, string Path) source, SymbolAvailability expectedData) + { + var compilation = CreateCompilation (platform, sources: source); + var x = compilation.GetDiagnostics (); + var syntaxTree = compilation.SyntaxTrees.FirstOrDefault(t => t.FilePath == source.Path); + Assert.NotNull (syntaxTree); + var declaration = syntaxTree.GetRoot () + .DescendantNodes ().OfType () + .FirstOrDefault (); + Assert.NotNull (declaration); + + var semanticModel = compilation.GetSemanticModel (syntaxTree); + Assert.NotNull (semanticModel); + + var symbol = semanticModel.GetDeclaredSymbol (declaration); + Assert.NotNull (symbol); + + // the transformation does not care about the parents, we want the exact same as was added by + // the developer. + var availability = symbol.GetAvailabilityForSymbol (); + Assert.Equal (expectedData, availability); + } +}