Skip to content

Commit

Permalink
Added tests for attributes, including a failing one (currently skipped)
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlabelle committed Oct 21, 2023
1 parent 4f70794 commit bb61986
Show file tree
Hide file tree
Showing 31 changed files with 154 additions and 40 deletions.
8 changes: 4 additions & 4 deletions Sources/DotNetMetadata/Attributable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ public protocol Attributable: AnyObject {
}

extension Attributable {
public func firstAttribute(namespace: String, name: String) throws -> Attribute? {
public func findAttribute(namespace: String?, name: String) throws -> Attribute? {
try attributes.first { try $0.type.namespace == namespace && $0.type.name == name }
}

public func hasAttribute(namespace: String, name: String) throws -> Bool {
try firstAttribute(namespace: namespace, name: name) != nil
public func hasAttribute(namespace: String?, name: String) throws -> Bool {
try findAttribute(namespace: namespace, name: name) != nil
}

public func hasAttribute<T: AttributeType>(_ type: T.Type) throws -> Bool {
try attributes.contains { try $0.hasType(T.self) }
}

public func firstAttribute<T: AttributeType>(_ type: T.Type) throws -> T.Value? {
public func findAttribute<T: AttributeType>(_ type: T.Type) throws -> T.Value? {
guard let attribute = try attributes.first(where: { try $0.hasType(T.self) }) else { return nil }
return try attribute.decode(as: T.self)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/DotNetMetadata/AttributeTargets.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public struct AttributeTargets: Hashable, OptionSet {
Self(rawValue: left.rawValue & right.rawValue)
}

public static let none = Self(rawValue: 0)
public static let none = Self([])

/// Attribute can be applied to an assembly.
public static let assembly = Self(rawValue: 1)
Expand Down
2 changes: 1 addition & 1 deletion Sources/DotNetMetadata/AttributeType.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
public protocol AttributeType {
associatedtype Value = Self

static var namespace: String { get }
static var namespace: String? { get }
static var name: String { get }
static var validOn: AttributeTargets { get }
static var allowMultiple: Bool { get }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public struct AttributeUsageAttribute: AttributeType {
self.inherited = inherited
}

public static var namespace: String { "System" }
public static var namespace: String? { "System" }
public static var name: String { "AttributeUsageAttribute" }
public static var validOn: AttributeTargets { .class }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/DotNetMetadata/Attributes/ComImportAttribute.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
public enum ComImportAttribute: AttributeType {
public static var namespace: String { "System.Runtime.InteropServices" }
public static var namespace: String? { "System.Runtime.InteropServices" }
public static var name: String { "ComImportAttribute" }
public static var validOn: AttributeTargets { .class | .interface }
public static var allowMultiple: Bool { false }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
public enum ComVisibleAttribute: AttributeType {
public static var namespace: String { "System.Runtime.InteropServices" }
public static var namespace: String? { "System.Runtime.InteropServices" }
public static var name: String { "ComVisibleAttribute" }
public static var validOn: AttributeTargets { .assembly | .allTypes | .method | .property | .field }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/DotNetMetadata/Attributes/GuidAttribute.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
public enum GuidAttribute: AttributeType {
public static var namespace: String { "System.Runtime.InteropServices" }
public static var namespace: String? { "System.Runtime.InteropServices" }
public static var name: String { "GuidAttribute" }
public static var validOn: AttributeTargets { .assembly | .allTypes }
public static var allowMultiple: Bool { false }
Expand Down
7 changes: 7 additions & 0 deletions Sources/DotNetMetadataFormat/Signatures+reading.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ extension CustomAttribSig {
try consumeElem(buffer: &buffer, type: element)
})

case .defOrRef(index: _, class: true, genericArgs: _):
// Assume a System.Type
guard let canonicalName = try consumeSerString(buffer: &buffer) else {
throw InvalidFormatError.signatureBlob
}
fatalError()

default: throw InvalidFormatError.signatureBlob
}
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/ActivatableAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public struct ActivatableAttribute: AttributeType {
self.applicability = applicability
}

public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "ActivatableAttribute" }
public static var validOn: AttributeTargets { .class }
public static var allowMultiple: Bool { true }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/AllowMultipleAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata

/// Indicates that multiple instances of a custom attribute can be applied to a target.
public enum AllowMultipleAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "AllowMultipleAttribute" }
public static var validOn: AttributeTargets { .class }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/ApiContractAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata

/// Specifies that the type represents an API contract.
public enum ApiContractAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "ApiContractAttribute" }
public static var validOn: AttributeTargets { .enum }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/ComposableAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public struct ComposableAttribute: AttributeType {
self.applicability = applicability
}

public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "ComposableAttribute" }
public static var validOn: AttributeTargets { .class }
public static var allowMultiple: Bool { true }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/ContractVersionAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public struct ContractVersionAttribute: AttributeType {
self.init(contract: .type(contract), version: version)
}

public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "ContractVersionAttribute" }
public static var validOn: AttributeTargets { .allTypes | .allMembers }
public static var allowMultiple: Bool { true }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/DefaultAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata

/// Indicates the default interface for a runtime class.
public enum DefaultAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "DefaultAttribute" }
public static var validOn: AttributeTargets { .none } // No attribute target for interface implementations
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/DefaultOverloadAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import DotNetMetadata
/// Indicates that a method is the default overload method.
/// This attribute must be used with OverloadAttribute.
public enum DefaultOverloadAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "DefaultOverloadAttribute" }
public static var validOn: AttributeTargets { .method }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/DeprecatedAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public struct DeprecatedAttribute: AttributeType {
self.applicability = applicability
}

public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "DeprecatedAttribute" }
public static var validOn: AttributeTargets { .allTypes | .allMembers }
public static var allowMultiple: Bool { true }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/DualApiPartitionAttribute.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import DotNetMetadata

public enum DualApiPartitionAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "DualApiPartitionAttribute" }
public static var validOn: AttributeTargets { .class }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/ExclusiveToAttribute.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import DotNetMetadata

public enum ExclusiveToAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "ExclusiveToAttribute" }
public static var validOn: AttributeTargets { .interface }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/ExperimentalAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import DotNetMetadata
/// Indicates that a type or member should be marked in metadata as experimental,
/// and consequently may not be present in the final, released version of an SDK or library.
public enum ExperimentalAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "ExperimentalAttribute" }
public static var validOn: AttributeTargets { .allTypes }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/GuidAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata
import struct Foundation.UUID

public enum GuidAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "GuidAttribute" }
public static var validOn: AttributeTargets { .interface | .delegate }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/InternalAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata

/// Indicates that the interface contains internal methods.
public enum InternalAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "InternalAttribute" }
public static var validOn: AttributeTargets { .none } // No attribute target for interface implementations
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/LengthIsAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata

/// Indicates the number of array elements.
public enum LengthIsAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "LengthIsAttribute" }
public static var validOn: AttributeTargets { .param }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/NoExceptionAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata

/// Indicates that the interface contains protected methods.
public enum NoExceptionAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "NoExceptionAttribute" }
public static var validOn: AttributeTargets { .method | .property }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/OverloadAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata

/// Identifies the method as an overload in a language that supports overloading.
public enum OverloadAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "OverloadAttribute" }
public static var validOn: AttributeTargets { .method }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/ProtectedAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata

/// Indicates that the interface contains protected methods.
public enum ProtectedAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "ProtectedAttribute" }
public static var validOn: AttributeTargets { .none } // No attribute target for interface implementations
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/StaticAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public struct StaticAttribute: AttributeType {
self.applicability = applicability
}

public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "StaticAttribute" }
public static var validOn: AttributeTargets { .class }
public static var allowMultiple: Bool { true }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/ThreadingAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import DotNetMetadata

/// Indicates the threading model of a Windows Runtime component.
public enum ThreadingAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "ThreadingAttribute" }
public static var validOn: AttributeTargets { .class }
public static var allowMultiple: Bool { false }
Expand Down
2 changes: 1 addition & 1 deletion Sources/WindowsMetadata/VariantAttribute.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import DotNetMetadata
/// Indicates that the item is an instance of a variant **IInspectable**.
/// Applies to method parameters, properties, and return values of types.
public enum VariantAttribute: AttributeType {
public static var namespace: String { "Windows.Foundation.Metadata" }
public static var namespace: String? { "Windows.Foundation.Metadata" }
public static var name: String { "VariantAttribute" }
public static var validOn: AttributeTargets { .property | .param }
public static var allowMultiple: Bool { false }
Expand Down
108 changes: 108 additions & 0 deletions Tests/UnitTests/DotNetMetadata/AttributeTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
@testable import DotNetMetadata
import XCTest

internal final class AttributeTests: CompiledAssemblyTestCase {
internal override class var csharpCode: String {
"""
class MyAttributeAttribute: System.Attribute
{
public MyAttributeAttribute() {}
public MyAttributeAttribute(int i) {}
public MyAttributeAttribute(string s) {}
public MyAttributeAttribute(System.Type t) {}
public int Field;
public int Property { get; set; }
}
// Test attribute arguments
[MyAttribute(1)] struct IntArgument {}
[MyAttribute("1")] struct StringArgument {}
[MyAttribute(typeof(TypeArgument))] struct TypeArgument {}
[MyAttribute(Field = 42)] struct NamedFieldArgument {}
[MyAttribute(Property = 42)] struct NamedPropertyArgument {}
"""
}

public func testNumericArgument() throws {
let targetType = try XCTUnwrap(assembly.findDefinedType(fullName: "IntArgument"))
let attribute = try XCTUnwrap(targetType.findAttribute(namespace: nil, name: "MyAttributeAttribute"))
let arguments = try attribute.arguments
XCTAssertEqual(arguments.count, 1)
XCTAssertEqual(try attribute.namedArguments.count, 0)
guard case .constant(.int32(let i)) = arguments.first else {
XCTFail("Expected int32")
return
}
XCTAssertEqual(i, 1)
}

public func testStringArgument() throws {
let targetType = try XCTUnwrap(assembly.findDefinedType(fullName: "StringArgument"))
let attribute = try XCTUnwrap(targetType.findAttribute(namespace: nil, name: "MyAttributeAttribute"))
let arguments = try attribute.arguments
XCTAssertEqual(arguments.count, 1)
XCTAssertEqual(try attribute.namedArguments.count, 0)
guard case .constant(.string(let s)) = arguments.first else {
XCTFail("Expected string")
return
}
XCTAssertEqual(s, "1")
}

public func testTypeArgument() throws {
try XCTSkipIf(true, "Currently crashes")

let targetType = try XCTUnwrap(assembly.findDefinedType(fullName: "TypeArgument"))
let attribute = try XCTUnwrap(targetType.findAttribute(namespace: nil, name: "MyAttributeAttribute"))
let arguments = try attribute.arguments
XCTAssertEqual(arguments.count, 1)
XCTAssertEqual(try attribute.namedArguments.count, 0)
guard case .type(let type) = arguments.first else {
XCTFail("Expected type")
return
}
XCTAssertIdentical(type, targetType)
}

public func testNamedFieldArgument() throws {
let targetType = try XCTUnwrap(assembly.findDefinedType(fullName: "NamedFieldArgument"))
let attribute = try XCTUnwrap(targetType.findAttribute(namespace: nil, name: "MyAttributeAttribute"))
XCTAssertEqual(try attribute.arguments.count, 0)
let namedArguments = try attribute.namedArguments
XCTAssertEqual(namedArguments.count, 1)
let namedArgument = try XCTUnwrap(namedArguments.first)

guard case .field(let field) = namedArgument.target else {
XCTFail("Expected field")
return
}
XCTAssertEqual(field.name, "Field")

guard case .constant(.int32(let value)) = namedArgument.value else {
XCTFail("Expected int32")
return
}
XCTAssertEqual(value, 42)
}

public func testNamedPropertyArgument() throws {
let targetType = try XCTUnwrap(assembly.findDefinedType(fullName: "NamedPropertyArgument"))
let attribute = try XCTUnwrap(targetType.findAttribute(namespace: nil, name: "MyAttributeAttribute"))
XCTAssertEqual(try attribute.arguments.count, 0)
let namedArguments = try attribute.namedArguments
XCTAssertEqual(namedArguments.count, 1)
let namedArgument = try XCTUnwrap(namedArguments.first)

guard case .property(let property) = namedArgument.target else {
XCTFail("Expected property")
return
}
XCTAssertEqual(property.name, "Property")

guard case .constant(.int32(let value)) = namedArgument.value else {
XCTFail("Expected int32")
return
}
XCTAssertEqual(value, 42)
}
}
Loading

0 comments on commit bb61986

Please sign in to comment.