Skip to content

Allow move-only types as suites. #619

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions Sources/Testing/Parameterization/TypeInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public struct TypeInfo: Sendable {
///
/// - Parameters:
/// - type: The concrete metatype.
case type(_ type: Any.Type)
case type(_ type: any ~Copyable.Type)

/// The type info represents a metatype, but a reference to that metatype is
/// not available at runtime.
Expand All @@ -38,7 +38,7 @@ public struct TypeInfo: Sendable {
///
/// If this instance was created from a type name, or if it was previously
/// encoded and decoded, the value of this property is `nil`.
public var type: Any.Type? {
public var type: (any ~Copyable.Type)? {
if case let .type(type) = _kind {
return type
}
Expand All @@ -57,7 +57,7 @@ public struct TypeInfo: Sendable {
///
/// - Parameters:
/// - type: The type which this instance should describe.
init(describing type: Any.Type) {
init(describing type: any ~Copyable.Type) {
_kind = .type(type)
}

Expand Down Expand Up @@ -172,7 +172,9 @@ extension TypeInfo {
}
switch _kind {
case let .type(type):
return _mangledTypeName(type)
// _mangledTypeName() works with move-only types, but its signature has
// not been updated yet. SEE: rdar://134278607
return _mangledTypeName(unsafeBitCast(type, to: Any.Type.self))
case let .nameOnly(_, _, mangledName):
return mangledName
}
Expand Down Expand Up @@ -299,7 +301,9 @@ extension TypeInfo: Hashable {
public static func ==(lhs: Self, rhs: Self) -> Bool {
switch (lhs._kind, rhs._kind) {
case let (.type(lhs), .type(rhs)):
return lhs == rhs
// == and ObjectIdentifier do not support move-only metatypes, so compare
// the bits of the types directly. SEE: rdar://134276458
return unsafeBitCast(lhs, to: UnsafeRawPointer.self) == unsafeBitCast(rhs, to: UnsafeRawPointer.self)
default:
return lhs.fullyQualifiedNameComponents == rhs.fullyQualifiedNameComponents
}
Expand Down
72 changes: 49 additions & 23 deletions Sources/Testing/Test+Macro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ extension Test {
/// - Warning: This function is used to implement the `@Suite` macro. Do not
/// call it directly.
public static func __type(
_ containingType: Any.Type,
_ containingType: any ~Copyable.Type,
displayName: String? = nil,
traits: [any SuiteTrait],
sourceLocation: SourceLocation
Expand Down Expand Up @@ -159,15 +159,21 @@ extension Test {
/// call it directly.
public static func __function(
named testFunctionName: String,
in containingType: Any.Type?,
in containingType: (any ~Copyable.Type)?,
xcTestCompatibleSelector: __XCTestCompatibleSelector?,
displayName: String? = nil,
traits: [any TestTrait],
sourceLocation: SourceLocation,
parameters: [__Parameter] = [],
testFunction: @escaping @Sendable () async throws -> Void
) -> Self {
let containingTypeInfo = containingType.map(TypeInfo.init(describing:))
// Don't use Optional.map here due to a miscompile/crash. Expand out to an
// if expression instead. SEE: rdar://134280902
let containingTypeInfo: TypeInfo? = if let containingType {
TypeInfo(describing: containingType)
} else {
nil
}
let caseGenerator = { @Sendable in Case.Generator(testFunction: testFunction) }
return Self(name: testFunctionName, displayName: displayName, traits: traits, sourceLocation: sourceLocation, containingTypeInfo: containingTypeInfo, xcTestCompatibleSelector: xcTestCompatibleSelector, testCases: caseGenerator, parameters: [])
}
Expand Down Expand Up @@ -235,7 +241,7 @@ extension Test {
/// call it directly.
public static func __function<C>(
named testFunctionName: String,
in containingType: Any.Type?,
in containingType: (any ~Copyable.Type)?,
xcTestCompatibleSelector: __XCTestCompatibleSelector?,
displayName: String? = nil,
traits: [any TestTrait],
Expand All @@ -244,7 +250,11 @@ extension Test {
parameters paramTuples: [__Parameter],
testFunction: @escaping @Sendable (C.Element) async throws -> Void
) -> Self where C: Collection & Sendable, C.Element: Sendable {
let containingTypeInfo = containingType.map(TypeInfo.init(describing:))
let containingTypeInfo: TypeInfo? = if let containingType {
TypeInfo(describing: containingType)
} else {
nil
}
let parameters = paramTuples.parameters
let caseGenerator = { @Sendable in Case.Generator(arguments: try await collection(), parameters: parameters, testFunction: testFunction) }
return Self(name: testFunctionName, displayName: displayName, traits: traits, sourceLocation: sourceLocation, containingTypeInfo: containingTypeInfo, xcTestCompatibleSelector: xcTestCompatibleSelector, testCases: caseGenerator, parameters: parameters)
Expand Down Expand Up @@ -366,7 +376,7 @@ extension Test {
/// call it directly.
public static func __function<C1, C2>(
named testFunctionName: String,
in containingType: Any.Type?,
in containingType: (any ~Copyable.Type)?,
xcTestCompatibleSelector: __XCTestCompatibleSelector?,
displayName: String? = nil,
traits: [any TestTrait],
Expand All @@ -375,7 +385,11 @@ extension Test {
parameters paramTuples: [__Parameter],
testFunction: @escaping @Sendable (C1.Element, C2.Element) async throws -> Void
) -> Self where C1: Collection & Sendable, C1.Element: Sendable, C2: Collection & Sendable, C2.Element: Sendable {
let containingTypeInfo = containingType.map(TypeInfo.init(describing:))
let containingTypeInfo: TypeInfo? = if let containingType {
TypeInfo(describing: containingType)
} else {
nil
}
let parameters = paramTuples.parameters
let caseGenerator = { @Sendable in try await Case.Generator(arguments: collection1(), collection2(), parameters: parameters, testFunction: testFunction) }
return Self(name: testFunctionName, displayName: displayName, traits: traits, sourceLocation: sourceLocation, containingTypeInfo: containingTypeInfo, xcTestCompatibleSelector: xcTestCompatibleSelector, testCases: caseGenerator, parameters: parameters)
Expand All @@ -390,7 +404,7 @@ extension Test {
/// call it directly.
public static func __function<C, E1, E2>(
named testFunctionName: String,
in containingType: Any.Type?,
in containingType: (any ~Copyable.Type)?,
xcTestCompatibleSelector: __XCTestCompatibleSelector?,
displayName: String? = nil,
traits: [any TestTrait],
Expand All @@ -399,7 +413,11 @@ extension Test {
parameters paramTuples: [__Parameter],
testFunction: @escaping @Sendable ((E1, E2)) async throws -> Void
) -> Self where C: Collection & Sendable, C.Element == (E1, E2), E1: Sendable, E2: Sendable {
let containingTypeInfo = containingType.map(TypeInfo.init(describing:))
let containingTypeInfo: TypeInfo? = if let containingType {
TypeInfo(describing: containingType)
} else {
nil
}
let parameters = paramTuples.parameters
let caseGenerator = { @Sendable in Case.Generator(arguments: try await collection(), parameters: parameters, testFunction: testFunction) }
return Self(name: testFunctionName, displayName: displayName, traits: traits, sourceLocation: sourceLocation, containingTypeInfo: containingTypeInfo, xcTestCompatibleSelector: xcTestCompatibleSelector, testCases: caseGenerator, parameters: parameters)
Expand All @@ -417,7 +435,7 @@ extension Test {
/// call it directly.
public static func __function<Key, Value>(
named testFunctionName: String,
in containingType: Any.Type?,
in containingType: (any ~Copyable.Type)?,
xcTestCompatibleSelector: __XCTestCompatibleSelector?,
displayName: String? = nil,
traits: [any TestTrait],
Expand All @@ -426,7 +444,11 @@ extension Test {
parameters paramTuples: [__Parameter],
testFunction: @escaping @Sendable ((Key, Value)) async throws -> Void
) -> Self where Key: Sendable, Value: Sendable {
let containingTypeInfo = containingType.map(TypeInfo.init(describing:))
let containingTypeInfo: TypeInfo? = if let containingType {
TypeInfo(describing: containingType)
} else {
nil
}
let parameters = paramTuples.parameters
let caseGenerator = { @Sendable in Case.Generator(arguments: try await dictionary(), parameters: parameters, testFunction: testFunction) }
return Self(name: testFunctionName, displayName: displayName, traits: traits, sourceLocation: sourceLocation, containingTypeInfo: containingTypeInfo, xcTestCompatibleSelector: xcTestCompatibleSelector, testCases: caseGenerator, parameters: parameters)
Expand All @@ -438,7 +460,7 @@ extension Test {
/// call it directly.
public static func __function<C1, C2>(
named testFunctionName: String,
in containingType: Any.Type?,
in containingType: (any ~Copyable.Type)?,
xcTestCompatibleSelector: __XCTestCompatibleSelector?,
displayName: String? = nil,
traits: [any TestTrait],
Expand All @@ -447,7 +469,11 @@ extension Test {
parameters paramTuples: [__Parameter],
testFunction: @escaping @Sendable (C1.Element, C2.Element) async throws -> Void
) -> Self where C1: Collection & Sendable, C1.Element: Sendable, C2: Collection & Sendable, C2.Element: Sendable {
let containingTypeInfo = containingType.map(TypeInfo.init(describing:))
let containingTypeInfo: TypeInfo? = if let containingType {
TypeInfo(describing: containingType)
} else {
nil
}
let parameters = paramTuples.parameters
let caseGenerator = { @Sendable in
Case.Generator(arguments: try await zippedCollections(), parameters: parameters) {
Expand All @@ -460,22 +486,22 @@ extension Test {

// MARK: - Helper functions

/// A value that abstracts away whether or not the `try` keyword is needed on an
/// expression.
/// A function that abstracts away whether or not the `try` keyword is needed on
/// an expression.
///
/// - Warning: This value is used to implement the `@Test` macro. Do not use
/// - Warning: This function is used to implement the `@Test` macro. Do not use
/// it directly.
@inlinable public var __requiringTry: Void {
@inlinable get throws {}
@inlinable public func __requiringTry<T>(_ value: consuming T) throws -> T where T: ~Copyable {
value
}

/// A value that abstracts away whether or not the `await` keyword is needed on
/// an expression.
/// A function that abstracts away whether or not the `await` keyword is needed
/// on an expression.
///
/// - Warning: This value is used to implement the `@Test` macro. Do not use
/// - Warning: This function is used to implement the `@Test` macro. Do not use
/// it directly.
@inlinable public var __requiringAwait: Void {
@inlinable get async {}
@inlinable public func __requiringAwait<T>(_ value: consuming T, isolation: isolated (any Actor)? = #isolation) async -> T where T: ~Copyable {
value
}

#if !SWT_NO_GLOBAL_ACTORS
Expand Down
6 changes: 3 additions & 3 deletions Sources/TestingMacros/TestDeclarationMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -278,17 +278,17 @@ public struct TestDeclarationMacro: PeerMacro, Sendable {
// detecting isolation to other global actors.
lazy var isMainActorIsolated = !functionDecl.attributes(named: "MainActor", inModuleNamed: "Swift").isEmpty
var forwardCall: (ExprSyntax) -> ExprSyntax = {
"try await (\($0), Testing.__requiringTry, Testing.__requiringAwait).0"
"try await Testing.__requiringTry(Testing.__requiringAwait(\($0)))"
}
let forwardInit = forwardCall
if functionDecl.noasyncAttribute != nil {
if isMainActorIsolated {
forwardCall = {
"try await MainActor.run { try (\($0), Testing.__requiringTry).0 }"
"try await MainActor.run { try Testing.__requiringTry(\($0)) }"
}
} else {
forwardCall = {
"try { try (\($0), Testing.__requiringTry).0 }()"
"try { try Testing.__requiringTry(\($0)) }()"
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions Tests/TestingTests/NonCopyableSuiteTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

@testable @_spi(ForToolsIntegrationOnly) import Testing

@Suite("Non-Copyable Tests")
struct NonCopyableTests: ~Copyable {
@Test static func staticMe() {}
@Test borrowing func borrowMe() {}
@Test consuming func consumeMe() {}
@Test mutating func mutateMe() {}

@Test borrowing func typeComparison() {
let lhs = TypeInfo(describing: Self.self)
let rhs = TypeInfo(describing: Self.self)

#expect(lhs == rhs)
#expect(lhs.hashValue == rhs.hashValue)
}

@available(_mangledTypeNameAPI, *)
@Test borrowing func mangledTypeName() {
#expect(TypeInfo(describing: Self.self).mangledName != nil)
}
}
4 changes: 3 additions & 1 deletion Tests/TestingTests/ObjCInteropTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ struct ObjCAndXCTestInteropTests {
#expect(steps.count > 0)
for step in steps {
let selector = try #require(step.test.xcTestCompatibleSelector)
let testCaseClass = try #require(step.test.containingTypeInfo?.type as? NSObject.Type)
// A compiler crash occurs here without the bitcast. SEE: rdar://134277439
let type = unsafeBitCast(step.test.containingTypeInfo?.type, to: Any.Type?.self)
let testCaseClass = try #require(type as? NSObject.Type)
#expect(testCaseClass.instancesRespond(to: selector))
}
}
Expand Down