From 9236f034d20a67e2173b21076cf46392547ec483 Mon Sep 17 00:00:00 2001 From: Rolf Bjarne Kvinge Date: Mon, 10 Nov 2025 19:17:17 +0100 Subject: [PATCH] Expose AvailabilityAttr in its various forms to ClangSharp. --- .../ClangSharp.Interop/Extensions/CXCursor.cs | 45 ++++ .../ClangSharp.Interop/clang/VersionTuple.cs | 204 ++++++++++++++++++ .../clangsharp/clangsharp.cs | 24 ++- sources/ClangSharp/Cursors/Attrs/Attr.cs | 12 ++ sources/libClangSharp/ClangSharp.cpp | 93 ++++++++ sources/libClangSharp/ClangSharp.h | 12 ++ tests/ClangSharp.UnitTests/ObjectiveCTest.cs | 120 +++++++++++ 7 files changed, 509 insertions(+), 1 deletion(-) create mode 100644 sources/ClangSharp.Interop/clang/VersionTuple.cs diff --git a/sources/ClangSharp.Interop/Extensions/CXCursor.cs b/sources/ClangSharp.Interop/Extensions/CXCursor.cs index f889bdc3..f3b40dd0 100644 --- a/sources/ClangSharp.Interop/Extensions/CXCursor.cs +++ b/sources/ClangSharp.Interop/Extensions/CXCursor.cs @@ -513,6 +513,51 @@ public readonly string AttrKindSpelling public readonly CXAvailabilityKind Availability => clang.getCursorAvailability(this); + public readonly VersionTuple? AvailabilityAttributeDeprecated + { + get + { + VersionTuple rv = default; + if (clangsharp.Cursor_getAvailabilityAttributeDeprecated(this, &rv) == 1) + { + return rv; + } + return null; + } + } + + public readonly VersionTuple? AvailabilityAttributeIntroduced + { + get + { + VersionTuple rv = default; + if (clangsharp.Cursor_getAvailabilityAttributeIntroduced(this, &rv) == 1) + { + return rv; + } + return null; + } + } + + public readonly CXString AvailabilityAttributeMessage => clangsharp.Cursor_getAvailabilityAttributeMessage(this); + + public readonly VersionTuple? AvailabilityAttributeObsoleted + { + get + { + VersionTuple rv = default; + if (clangsharp.Cursor_getAvailabilityAttributeObsoleted(this, &rv) == 1) + { + return rv; + } + return null; + } + } + + public readonly CXString AvailabilityAttributePlatformIdentifierName => clangsharp.Cursor_getAvailabilityAttributePlatformIdentifierName(this); + + public bool AvailabilityAttributeUnavailable => clangsharp.Cursor_getAvailabilityAttributeUnavailable(this) != 0; + public readonly CXBinaryOperatorKind BinaryOperatorKind => clangsharp.Cursor_getBinaryOpcode(this); public readonly CXString BinaryOperatorKindSpelling diff --git a/sources/ClangSharp.Interop/clang/VersionTuple.cs b/sources/ClangSharp.Interop/clang/VersionTuple.cs new file mode 100644 index 00000000..952804a9 --- /dev/null +++ b/sources/ClangSharp.Interop/clang/VersionTuple.cs @@ -0,0 +1,204 @@ +// Copyright (c) .NET Foundation and Contributors. All Rights Reserved. Licensed under the MIT License (MIT). See License.md in the repository root for more information. + +// Ported from https://github.com/llvm/llvm-project/tree/llvmorg-21.1.8/clang/include/clang-c +// Original source is Copyright (c) the LLVM Project and Contributors. Licensed under the Apache License v2.0 with LLVM Exceptions. See NOTICE.txt in the project root for license information. + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace ClangSharp.Interop; + +[StructLayout(LayoutKind.Sequential)] +public struct VersionTuple : IEquatable +{ + public static readonly VersionTuple Empty; + + private readonly uint major; + private readonly uint minor; + private readonly uint subminor; + private readonly uint build; + + private const uint HasFlag = 0x80000000u; // 1 << 31; + private const uint ValueMask = uint.MaxValue & ~HasFlag; + + public VersionTuple(uint major, uint minor, uint subminor, uint build) + { + this.major = major; + this.minor = minor | HasFlag; + this.subminor = subminor | HasFlag; + this.build = build | HasFlag; + } + + public VersionTuple(uint major, uint minor, uint subminor) + { + this.major = major; + this.minor = minor | HasFlag; + this.subminor = subminor | HasFlag; + build = 0; + } + + public VersionTuple(uint major, uint minor) + { + this.major = major; + this.minor = minor | HasFlag; + subminor = 0; + build = 0; + } + + public VersionTuple(uint major) + { + this.major = major; + minor = 0; + subminor = 0; + build = 0; + } + + public uint Major => major; + + public uint? Minor + { + get + { + if (HasMinor) + { + return minor & ValueMask; + } + return null; + } + } + + public uint? Subminor + { + get + { + if (HasSubminor) + { + return subminor & ValueMask; + } + return null; + } + } + + public uint? Build + { + get + { + if (HasBuild) + { + return build & ValueMask; + } + return null; + } + } + + private uint MinorOrZero => Minor ?? 0; + private uint SubminorOrZero => Subminor ?? 0; + private uint BuildOrZero => Build ?? 0; + + public bool HasMinor => (minor & HasFlag) == HasFlag; + public bool HasSubminor => (subminor & HasFlag) == HasFlag; + public bool HasBuild => (build & HasFlag) == HasFlag; + + public bool IsEmpty => major == 0 && minor == 0 && subminor == 0 && build == 0; + + public static bool operator ==(VersionTuple x, VersionTuple y) + { + return x.Major == y.Major && + x.MinorOrZero == y.MinorOrZero && + x.SubminorOrZero == y.SubminorOrZero && + x.BuildOrZero == y.BuildOrZero; + } + + public static bool operator !=(VersionTuple x, VersionTuple y) + => !(x == y); + + public static bool operator <(VersionTuple x, VersionTuple y) + { + if (x.Major != y.Major) + { + return x.Major < y.Major; + } + if (x.MinorOrZero != y.MinorOrZero) + { + return x.MinorOrZero < y.MinorOrZero; + } + if (x.SubminorOrZero != y.SubminorOrZero) + { + return x.MinorOrZero < y.MinorOrZero; + } + if (x.BuildOrZero != y.BuildOrZero) + { + return x.BuildOrZero < y.BuildOrZero; + } + return false; + } + + public static bool operator >(VersionTuple x, VersionTuple y) + { + if (x.Major != y.Major) + { + return x.Major > y.Major; + } + if (x.MinorOrZero != y.MinorOrZero) + { + return x.MinorOrZero > y.MinorOrZero; + } + if (x.SubminorOrZero != y.SubminorOrZero) + { + return x.MinorOrZero > y.MinorOrZero; + } + if (x.BuildOrZero != y.BuildOrZero) + { + return x.BuildOrZero > y.BuildOrZero; + } + return false; + } + + public static bool operator <=(VersionTuple x, VersionTuple y) + => x == y || x < y; + + public static bool operator >=(VersionTuple x, VersionTuple y) + => x == y || x > y; + + public override bool Equals(object? obj) + { + if (obj is VersionTuple vt) + { + return vt == this; + } + return false; + } + + bool IEquatable.Equals(VersionTuple version) + { + return this == version; + } + + public override int GetHashCode() + { + return HashCode.Combine(major, minor, subminor, build); + } + + public override string ToString() + { + var sb = new StringBuilder(); + _ = sb.Append(Major); + if (HasMinor) + { + _ = sb.Append('.'); + _ = sb.Append(Minor); + if (HasSubminor) + { + _ = sb.Append('.'); + _ = sb.Append(Subminor); + if (HasBuild) + { + _ = sb.Append('.'); + _ = sb.Append(Build); + } + } + } + return sb.ToString(); + } +} diff --git a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs index cb0ea241..998d33c3 100644 --- a/sources/ClangSharp.Interop/clangsharp/clangsharp.cs +++ b/sources/ClangSharp.Interop/clangsharp/clangsharp.cs @@ -6,7 +6,7 @@ namespace ClangSharp.Interop; -public static partial class @clangsharp +public static unsafe partial class @clangsharp { [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getArgument", ExactSpelling = true)] public static extern CXCursor Cursor_getArgument(CXCursor C, [NativeTypeName("unsigned int")] uint i); @@ -33,6 +33,28 @@ public static partial class @clangsharp [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getAttrKind", ExactSpelling = true)] public static extern CX_AttrKind Cursor_getAttrKind(CXCursor C); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getAvailabilityAttributeDeprecated", ExactSpelling = true)] + [return: NativeTypeName("unsigned int")] + public static extern uint Cursor_getAvailabilityAttributeDeprecated(CXCursor param0, [NativeTypeName("llvm::VersionTuple *")] VersionTuple* version); + + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getAvailabilityAttributeIntroduced", ExactSpelling = true)] + [return: NativeTypeName("unsigned int")] + public static extern uint Cursor_getAvailabilityAttributeIntroduced(CXCursor param0, [NativeTypeName("llvm::VersionTuple *")] VersionTuple* version); + + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getAvailabilityAttributeMessage", ExactSpelling = true)] + public static extern CXString Cursor_getAvailabilityAttributeMessage(CXCursor C); + + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getAvailabilityAttributeObsoleted", ExactSpelling = true)] + [return: NativeTypeName("unsigned int")] + public static extern uint Cursor_getAvailabilityAttributeObsoleted(CXCursor param0, [NativeTypeName("llvm::VersionTuple *")] VersionTuple* version); + + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getAvailabilityAttributePlatformIdentifierName", ExactSpelling = true)] + public static extern CXString Cursor_getAvailabilityAttributePlatformIdentifierName(CXCursor C); + + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getAvailabilityAttributeUnavailable", ExactSpelling = true)] + [return: NativeTypeName("unsigned int")] + public static extern uint Cursor_getAvailabilityAttributeUnavailable(CXCursor param0); + [DllImport("libClangSharp", CallingConvention = CallingConvention.Cdecl, EntryPoint = "clangsharp_Cursor_getBase", ExactSpelling = true)] public static extern CXCursor Cursor_getBase(CXCursor C, [NativeTypeName("unsigned int")] uint i); diff --git a/sources/ClangSharp/Cursors/Attrs/Attr.cs b/sources/ClangSharp/Cursors/Attrs/Attr.cs index 3d3a79b5..1d626a08 100644 --- a/sources/ClangSharp/Cursors/Attrs/Attr.cs +++ b/sources/ClangSharp/Cursors/Attrs/Attr.cs @@ -464,6 +464,18 @@ private protected Attr(CXCursor handle) : base(handle, handle.Kind) _ => new Attr(handle), }; + public VersionTuple? AvailabilityAttributeDeprecated => Handle.AvailabilityAttributeDeprecated; + + public VersionTuple? AvailabilityAttributeIntroduced => Handle.AvailabilityAttributeIntroduced; + + public string AvailabilityAttributeMessage => Handle.AvailabilityAttributeMessage.ToString(); + + public VersionTuple? AvailabilityAttributeObsoleted => Handle.AvailabilityAttributeObsoleted; + + public string AvailabilityAttributePlatformIdentifierName => Handle.AvailabilityAttributePlatformIdentifierName.ToString(); + + public bool AvailabilityAttributeUnavailable => Handle.AvailabilityAttributeUnavailable; + public bool IsImplicit => Handle.IsImplicit; public bool IsPackExpansion => Handle.IsPackExpansion; diff --git a/sources/libClangSharp/ClangSharp.cpp b/sources/libClangSharp/ClangSharp.cpp index 1338c0e1..51664135 100644 --- a/sources/libClangSharp/ClangSharp.cpp +++ b/sources/libClangSharp/ClangSharp.cpp @@ -314,6 +314,99 @@ CX_AttrKind clangsharp_Cursor_getAttrKind(CXCursor C) { return CX_AttrKind_Invalid; } +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getAvailabilityAttributeDeprecated(CXCursor C, llvm::VersionTuple* version) +{ + if (clang_isAttribute(C.kind)) { + const Attr* A = getCursorAttr(C); + + StringRef message; + if (const auto *Availability = dyn_cast(A)) { + *version = Availability->getDeprecated(); + return 1; + } + } + + return 0; +} + +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getAvailabilityAttributeIntroduced(CXCursor C, llvm::VersionTuple* version) +{ + if (clang_isAttribute(C.kind)) { + const Attr* A = getCursorAttr(C); + + StringRef message; + if (const auto *Availability = dyn_cast(A)) { + *version = Availability->getIntroduced(); + return 1; + } + } + + return 0; +} + +CLANGSHARP_LINKAGE CXString clangsharp_Cursor_getAvailabilityAttributeMessage(CXCursor C) +{ + if (clang_isAttribute(C.kind)) { + const Attr* A = getCursorAttr(C); + + StringRef message; + if (const auto *Unavailable = dyn_cast(A)) { + message = Unavailable->getMessage(); + } else if (const auto *Availability = dyn_cast(A)) { + message = Availability->getMessage(); + } + + return createDup(message); + } + + return createEmpty(); +} + +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getAvailabilityAttributeObsoleted(CXCursor C, llvm::VersionTuple* version) +{ + if (clang_isAttribute(C.kind)) { + const Attr* A = getCursorAttr(C); + + StringRef message; + if (const auto *Availability = dyn_cast(A)) { + *version = Availability->getObsoleted(); + return 1; + } + } + + return 0; +} + +CLANGSHARP_LINKAGE CXString clangsharp_Cursor_getAvailabilityAttributePlatformIdentifierName(CXCursor C) +{ + if (clang_isAttribute(C.kind)) { + const Attr* A = getCursorAttr(C); + + StringRef message; + if (const auto *Availability = dyn_cast(A)) { + message = Availability->getPlatform()->getName(); + } + + return createDup(message); + } + + return createEmpty(); +} + +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getAvailabilityAttributeUnavailable(CXCursor C) +{ + if (clang_isAttribute(C.kind)) { + const Attr* A = getCursorAttr(C); + + StringRef message; + if (const auto *Availability = dyn_cast(A)) { + return Availability->getUnavailable(); + } + } + + return 0; +} + CXCursor clangsharp_Cursor_getBase(CXCursor C, unsigned i) { if (isDeclOrTU(C.kind)) { const Decl* D = getCursorDecl(C); diff --git a/sources/libClangSharp/ClangSharp.h b/sources/libClangSharp/ClangSharp.h index c8a609c7..86160c1f 100644 --- a/sources/libClangSharp/ClangSharp.h +++ b/sources/libClangSharp/ClangSharp.h @@ -251,6 +251,18 @@ CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getAttr(CXCursor C, unsigned i); CLANGSHARP_LINKAGE CX_AttrKind clangsharp_Cursor_getAttrKind(CXCursor C); +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getAvailabilityAttributeDeprecated(CXCursor, llvm::VersionTuple* version); + +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getAvailabilityAttributeIntroduced(CXCursor, llvm::VersionTuple* version); + +CLANGSHARP_LINKAGE CXString clangsharp_Cursor_getAvailabilityAttributeMessage(CXCursor C); + +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getAvailabilityAttributeObsoleted(CXCursor, llvm::VersionTuple* version); + +CLANGSHARP_LINKAGE CXString clangsharp_Cursor_getAvailabilityAttributePlatformIdentifierName(CXCursor C); + +CLANGSHARP_LINKAGE unsigned clangsharp_Cursor_getAvailabilityAttributeUnavailable(CXCursor); + CLANGSHARP_LINKAGE CXCursor clangsharp_Cursor_getBase(CXCursor C, unsigned i); CLANGSHARP_LINKAGE CXBinaryOperatorKind clangsharp_Cursor_getBinaryOpcode(CXCursor C); diff --git a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs index 7588ba0c..f9e2a57b 100644 --- a/tests/ClangSharp.UnitTests/ObjectiveCTest.cs +++ b/tests/ClangSharp.UnitTests/ObjectiveCTest.cs @@ -169,6 +169,126 @@ @interface MyClass } } + [Test] + public void Attribute_AvailabilityAttributes() + { + AssertNeedNewClangSharp(); + + var inputContents = $$""" +__attribute__((availability(ios,unavailable,message="Use another class"))) +__attribute__((availability(tvos,introduced=10.0))) +__attribute__((availability(macosx,introduced=10.15.3))) +__attribute__((availability(maccatalyst,introduced=14.3.0))) +@interface MyClass + @property int P1 + __attribute__((availability(ios,unavailable,message="Not this property"))) + __attribute__((availability(tvos,obsoleted=11.0,message="Obsoleted on tvOS"))) + __attribute__((availability(macosx,obsoleted=11.0,message="Obsoleted on macOS"))) + __attribute__((availability(maccatalyst,obsoleted=15.0,message="Obsoleted on Mac Catalyst"))) + ; + + -(void) instanceMethod + __attribute__((availability(ios,unavailable,message="Not this instance method"))) + __attribute__((availability(tvos,deprecated=12.0,message="Deprecated on tvOS"))) + __attribute__((availability(macosx,deprecated=12.0,message="Deprecated on macOS"))) + __attribute__((availability(maccatalyst,deprecated=16.0,message="Deprecated on Mac Catalyst"))) + ; + + +(void) staticMethod __attribute__((unavailable("elsewhere"))) + __attribute__((availability(ios,unavailable,message="Not this static method"))) + __attribute__((availability(tvos,introduced=10.0,deprecated=11.0,obsoleted=12.0,message="Gone on tvOS"))) + __attribute__((availability(macosx,introduced=10.0.1,deprecated=11.0.2,obsoleted=12.0.3,message="Gone on macOS"))) + __attribute__((availability(maccatalyst,introduced=10.0.0,deprecated=11.0.0,obsoleted=12.0.0,message="Gone on Mac Catalyst"))) + ; +@end +"""; + using var translationUnit = CreateTranslationUnit(inputContents, "objective-c++"); + + var classes = translationUnit.TranslationUnitDecl.Decls.OfType().ToList(); + Assert.That(classes.Count, Is.GreaterThanOrEqualTo(1), $"At least one class"); + var myClass = classes.SingleOrDefault(v => v.Name == "MyClass")!; + Assert.That(myClass, Is.Not.Null, "MyClass"); + + var assertVersionTuple = new Action((VersionTuple? actual, VersionTuple? expected, string info) => { + if (expected is null) + { + if (actual is not null) + { + Assert.Fail($"Expected null VersionTuple, got: {actual} -- {info}"); + } + return; + } + else + { + if (actual is null) + { + Assert.Fail($"Expected non-null VersionTuple ({expected.Value}), got null -- {info}"); + return; + } + } + + Assert.That(actual.ToString(), Is.EqualTo(expected.ToString()), info); + }); + + var assertAttribute = new Action((Attr attrib, string? message, string? platform, VersionTuple? introduced, VersionTuple? deprecated, VersionTuple? obsoleted, bool unavailable, string info) => { + Assert.That(attrib, Is.Not.Null, $"attrib: {info}"); + + info += $" (platform: {attrib.AvailabilityAttributePlatformIdentifierName})"; + Assert.That(attrib.AvailabilityAttributeMessage, Is.EqualTo(message), $"Message: {info}"); + Assert.That(attrib.AvailabilityAttributePlatformIdentifierName, Is.EqualTo(platform), $"PlatformIdentifierName: {info}"); + assertVersionTuple(attrib.AvailabilityAttributeIntroduced, introduced, $"Introduced: {info}"); + assertVersionTuple(attrib.AvailabilityAttributeDeprecated, deprecated, $"Deprecated: {info}"); + assertVersionTuple(attrib.AvailabilityAttributeObsoleted, obsoleted, $"Obsoleted: {info}"); + Assert.That(attrib.AvailabilityAttributeUnavailable, Is.EqualTo(unavailable), $"Unavailable: {info}"); + }); + + Assert.Multiple(() => { + var myClassAttrs = myClass.Attrs; + Assert.That(myClassAttrs.Count, Is.EqualTo(4), "myClassAttrs.Count"); + assertAttribute(myClassAttrs[0], "Use another class", "ios", new VersionTuple(0), new VersionTuple(0), new VersionTuple(0), true, "myClass Attr - iOS"); + assertAttribute(myClassAttrs[1], "", "tvos", new VersionTuple(10, 0), new VersionTuple(0), new VersionTuple(0), false, "myClass Attr - tvOS"); + assertAttribute(myClassAttrs[2], "", "macos", new VersionTuple(10, 15, 3), new VersionTuple(0), new VersionTuple(0), false, "myClass Attr - macOS"); + assertAttribute(myClassAttrs[3], "", "maccatalyst", new VersionTuple(14, 3, 0), new VersionTuple(0), new VersionTuple(0), false, "myClass Attr - Mac Catalyst"); + + var methodP1 = myClass.Methods.SingleOrDefault(v => v.Name == "P1")!; + Assert.That(methodP1, Is.Not.Null, "methodP1"); + var methodP1Attrs = methodP1.Attrs; + Assert.That(methodP1Attrs.Count, Is.EqualTo(4), "methodP1Attrs.Count"); + assertAttribute(methodP1Attrs[0], "Not this property", "ios", new VersionTuple(0), new VersionTuple(0), new VersionTuple(0), true, "methodP1 Attr - P1 - iOS"); + assertAttribute(methodP1Attrs[1], "Obsoleted on tvOS", "tvos", new VersionTuple(0), new VersionTuple(0), new VersionTuple(11, 0), false, "methodP1 Attr - tvOS"); + assertAttribute(methodP1Attrs[2], "Obsoleted on macOS", "macos", new VersionTuple(0), new VersionTuple(0), new VersionTuple(11, 0), false, "methodP1 Attr - macOS"); + assertAttribute(methodP1Attrs[3], "Obsoleted on Mac Catalyst", "maccatalyst", new VersionTuple(0), new VersionTuple(0), new VersionTuple(15, 0), false, "methodP1 Attr - Mac Catalyst"); + + var methodSetP1 = myClass.Methods.SingleOrDefault(v => v.Name == "setP1:")!; + Assert.That(methodSetP1, Is.Not.Null, "methodP1"); + var methodSetP1Attrs = methodSetP1.Attrs; + Assert.That(methodSetP1Attrs.Count, Is.EqualTo(4), "methodSetP1Attrs.Count"); + assertAttribute(methodSetP1Attrs[0], "Not this property", "ios", new VersionTuple(0), new VersionTuple(0), new VersionTuple(0), true, "methodSetP1Attrs Attr - P1 - iOS"); + assertAttribute(methodSetP1Attrs[1], "Obsoleted on tvOS", "tvos", new VersionTuple(0), new VersionTuple(0), new VersionTuple(11, 0), false, "methodSetP1Attrs Attr - tvOS"); + assertAttribute(methodSetP1Attrs[2], "Obsoleted on macOS", "macos", new VersionTuple(0), new VersionTuple(0), new VersionTuple(11, 0), false, "methodSetP1Attrs Attr - macOS"); + assertAttribute(methodSetP1Attrs[3], "Obsoleted on Mac Catalyst", "maccatalyst", new VersionTuple(0), new VersionTuple(0), new VersionTuple(15, 0), false, "methodSetP1Attrs Attr - Mac Catalyst"); + + var methodInstanceMethod = myClass.Methods.SingleOrDefault(v => v.Name == "instanceMethod")!; + Assert.That(methodInstanceMethod, Is.Not.Null, "methodP1"); + var methodInstanceMethodAttrs = methodInstanceMethod.Attrs; + Assert.That(methodInstanceMethodAttrs.Count, Is.EqualTo(4), "methodInstanceMethodAttrs.Count"); + assertAttribute(methodInstanceMethodAttrs[0], "Not this instance method", "ios", new VersionTuple(0), new VersionTuple(0), new VersionTuple(0), true, "methodInstanceMethodAttrs Attr - P1 - iOS"); + assertAttribute(methodInstanceMethodAttrs[1], "Deprecated on tvOS", "tvos", new VersionTuple(0), new VersionTuple(12, 0), new VersionTuple(0), false, "methodInstanceMethodAttrs Attr - tvOS"); + assertAttribute(methodInstanceMethodAttrs[2], "Deprecated on macOS", "macos", new VersionTuple(0), new VersionTuple(12, 0), new VersionTuple(0), false, "methodInstanceMethodAttrs Attr - macOS"); + assertAttribute(methodInstanceMethodAttrs[3], "Deprecated on Mac Catalyst", "maccatalyst", new VersionTuple(0), new VersionTuple(16, 0), new VersionTuple(0), false, "methodInstanceMethodAttrs Attr - Mac Catalyst"); + + var methodStaticMethod = myClass.Methods.SingleOrDefault(v => v.Name == "staticMethod")!; + Assert.That(methodStaticMethod, Is.Not.Null, "methodP1"); + var methodStaticMethodAttrs = methodStaticMethod.Attrs; + Assert.That(methodStaticMethodAttrs.Count, Is.EqualTo(5), "methodStaticMethodAttrs.Count"); + assertAttribute(methodStaticMethodAttrs[0], "elsewhere", "", null, null, null, false, "methodStaticMethodAttrs Attr - P1 - unavailable"); + assertAttribute(methodStaticMethodAttrs[1], "Not this static method", "ios", new VersionTuple(0), new VersionTuple(0), new VersionTuple(0), true, "methodStaticMethodAttrs Attr - P1 - iOS"); + assertAttribute(methodStaticMethodAttrs[2], "Gone on tvOS", "tvos", new VersionTuple(10, 0), new VersionTuple(11, 0), new VersionTuple(12, 0), false, "methodStaticMethodAttrs Attr - tvOS"); + assertAttribute(methodStaticMethodAttrs[3], "Gone on macOS", "macos", new VersionTuple(10, 0, 1), new VersionTuple(11, 0, 2), new VersionTuple(12, 0, 3), false, "methodStaticMethodAttrs Attr - macOS"); + assertAttribute(methodStaticMethodAttrs[4], "Gone on Mac Catalyst", "maccatalyst", new VersionTuple(10, 0, 0), new VersionTuple(11, 0, 0), new VersionTuple(12, 0, 0), false, "methodStaticMethodAttrs Attr - Mac Catalyst"); + }); + } + [Test] public void Attribute_PrettyPrint() {