diff --git a/Sources/Testing/Parameterization/TypeInfo.swift b/Sources/Testing/Parameterization/TypeInfo.swift index 671674531..5dde2700a 100644 --- a/Sources/Testing/Parameterization/TypeInfo.swift +++ b/Sources/Testing/Parameterization/TypeInfo.swift @@ -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. @@ -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 } @@ -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) } @@ -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 } @@ -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 } diff --git a/Sources/Testing/Test+Macro.swift b/Sources/Testing/Test+Macro.swift index 3a9ea30d9..7fc2cf0eb 100644 --- a/Sources/Testing/Test+Macro.swift +++ b/Sources/Testing/Test+Macro.swift @@ -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 @@ -159,7 +159,7 @@ 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], @@ -167,7 +167,13 @@ extension Test { 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: []) } @@ -235,7 +241,7 @@ 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], @@ -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) @@ -366,7 +376,7 @@ 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], @@ -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) @@ -390,7 +404,7 @@ 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], @@ -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) @@ -417,7 +435,7 @@ 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], @@ -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) @@ -438,7 +460,7 @@ 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], @@ -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) { @@ -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(_ 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(_ value: consuming T, isolation: isolated (any Actor)? = #isolation) async -> T where T: ~Copyable { + value } #if !SWT_NO_GLOBAL_ACTORS diff --git a/Sources/TestingMacros/TestDeclarationMacro.swift b/Sources/TestingMacros/TestDeclarationMacro.swift index 1f9025f08..28e416804 100644 --- a/Sources/TestingMacros/TestDeclarationMacro.swift +++ b/Sources/TestingMacros/TestDeclarationMacro.swift @@ -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)) }()" } } } diff --git a/Tests/TestingTests/NonCopyableSuiteTests.swift b/Tests/TestingTests/NonCopyableSuiteTests.swift new file mode 100644 index 000000000..56a530199 --- /dev/null +++ b/Tests/TestingTests/NonCopyableSuiteTests.swift @@ -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) + } +} diff --git a/Tests/TestingTests/ObjCInteropTests.swift b/Tests/TestingTests/ObjCInteropTests.swift index 179460af4..be12e520d 100644 --- a/Tests/TestingTests/ObjCInteropTests.swift +++ b/Tests/TestingTests/ObjCInteropTests.swift @@ -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)) } }