diff --git a/Sources/TestingMacros/Support/ConditionArgumentParsing.swift b/Sources/TestingMacros/Support/ConditionArgumentParsing.swift index 9f57d0981..7b0fe3c99 100644 --- a/Sources/TestingMacros/Support/ConditionArgumentParsing.swift +++ b/Sources/TestingMacros/Support/ConditionArgumentParsing.swift @@ -128,6 +128,64 @@ func removeParentheses(from expr: ExprSyntax) -> ExprSyntax? { return nil } +/// A class that walks a syntax tree looking for `try` and `await` expressions. +/// +/// - Bug: This class does not use `lexicalContext` to check for the presence of +/// `try` or `await` _outside_ the current macro expansion. +private final class _EffectFinder: SyntaxVisitor { + /// The effectful expressions discovered so far. + var effectfulExprs = [ExprSyntax]() + + /// Common implementation for `visit(_: TryExprSyntax)` and + /// `visit(_: AwaitExprSyntax)`. + /// + /// - Parameters: + /// - node: The `try` or `await` expression. + /// - expression: The `.expression` property of `node`. + /// + /// - Returns: Whether or not to recurse into `node`. + private func _visitEffectful(_ node: some ExprSyntaxProtocol, expression: ExprSyntax) -> SyntaxVisitorContinueKind { + if let parentNode = node.parent, parentNode.is(TryExprSyntax.self) { + // Suppress this expression as its immediate parent is also an effectful + // expression (e.g. it's a `try await` expression overall.) The diagnostic + // reported for the parent expression should include both as needed. + return .visitChildren + } else if expression.is(AsExprSyntax.self) { + // Do not walk into explicit `as T` casts. This provides an escape hatch + // for expressions that should not diagnose. + return .skipChildren + } else if let awaitExpr = expression.as(AwaitExprSyntax.self), awaitExpr.expression.is(AsExprSyntax.self) { + // As above but for `try await _ as T`. + return .skipChildren + } + effectfulExprs.append(ExprSyntax(node)) + return .visitChildren + } + + override func visit(_ node: TryExprSyntax) -> SyntaxVisitorContinueKind { + _visitEffectful(node, expression: node.expression) + } + + override func visit(_ node: AwaitExprSyntax) -> SyntaxVisitorContinueKind { + _visitEffectful(node, expression: node.expression) + } + + override func visit(_ node: AsExprSyntax) -> SyntaxVisitorContinueKind { + // Do not walk into explicit `as T` casts. This provides an escape hatch for + // expressions that should not diagnose. + return .skipChildren + } + + override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind { + // Do not walk into closures. Although they are not meant to be an escape + // hatch like `as` casts, it is very difficult (often impossible) to reason + // about effectful expressions inside the scope of a closure. If the closure + // is invoked locally, its caller will also need to say `try`/`await` and we + // can still diagnose those outer expressions. + return .skipChildren + } +} + // MARK: - /// Parse a condition argument from a binary operation expression. @@ -496,6 +554,17 @@ private func _parseCondition(from expr: ExprSyntax, for macro: some Freestanding /// /// - Returns: An instance of ``Condition`` describing `expr`. func parseCondition(from expr: ExprSyntax, for macro: some FreestandingMacroExpansionSyntax, in context: some MacroExpansionContext) -> Condition { + // Handle `await` first. If present in the expression or a subexpression, + // diagnose and don't expand further. + let effectFinder = _EffectFinder(viewMode: .sourceAccurate) + effectFinder.walk(expr) + guard effectFinder.effectfulExprs.isEmpty else { + for effectfulExpr in effectFinder.effectfulExprs { + context.diagnose(.effectfulExpressionNotParsed(effectfulExpr, in: macro)) + } + return Condition(expression: expr) + } + let result = _parseCondition(from: expr, for: macro, in: context) if result.arguments.count == 1, let onlyArgument = result.arguments.first { _diagnoseTrivialBooleanValue(from: onlyArgument.expression, for: macro, in: context) diff --git a/Sources/TestingMacros/Support/DiagnosticMessage.swift b/Sources/TestingMacros/Support/DiagnosticMessage.swift index 1365387f9..dd89095d6 100644 --- a/Sources/TestingMacros/Support/DiagnosticMessage.swift +++ b/Sources/TestingMacros/Support/DiagnosticMessage.swift @@ -32,9 +32,10 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { /// /// - Returns: A diagnostic message. static func condition(_ condition: ExprSyntax, isAlways value: Bool, in macro: some FreestandingMacroExpansionSyntax) -> Self { - Self( + let action = value ? "pass" : "fail" + return Self( syntax: Syntax(condition), - message: "#\(macro.macroName.textWithoutBackticks)(_:_:) will always \(value ? "pass" : "fail") here; use Bool(\(condition)) to silence this warning", + message: "\(_macroName(macro)) will always \(action) here; use 'Bool(\(condition))' to silence this warning", severity: value ? .note : .warning ) } @@ -50,11 +51,59 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func asExclamationMarkIsEvaluatedEarly(_ expr: AsExprSyntax, in macro: some FreestandingMacroExpansionSyntax) -> Self { return Self( syntax: Syntax(expr.asKeyword), - message: "The expression \(expr.trimmed) will be evaluated before #\(macro.macroName.textWithoutBackticks)(_:_:) is invoked; use as? instead of as! to silence this warning", + message: "Expression '\(expr.trimmed)' will be evaluated before \(_macroName(macro)) is invoked; use 'as?' instead of 'as!' to silence this warning", + severity: .warning + ) + } + + /// Create a diagnostic message stating that an effectful (`try` or `await`) + /// expression cannot be parsed and should be broken apart. + /// + /// - Parameters: + /// - expr: The expression being diagnosed. + /// - macro: The macro expression. + /// + /// - Returns: A diagnostic message. + static func effectfulExpressionNotParsed(_ expr: ExprSyntax, in macro: some FreestandingMacroExpansionSyntax) -> Self { + let effectful = if let tryExpr = expr.as(TryExprSyntax.self) { + if tryExpr.expression.is(AwaitExprSyntax.self) { + "throwing/asynchronous" + } else { + "throwing" + } + } else { + "asynchronous" + } + return Self( + syntax: Syntax(expr), + message: "Expression '\(expr.trimmed)' will not be expanded on failure; move the \(effectful) part out of the call to \(_macroName(macro))", severity: .warning ) } + /// Get the human-readable name of the given freestanding macro. + /// + /// - Parameters: + /// - macro: The freestanding macro node to name. + /// + /// - Returns: The name of the macro as understood by a developer, such as + /// `"'#expect(_:_:)'"`. Include single quotes. + private static func _macroName(_ macro: some FreestandingMacroExpansionSyntax) -> String { + "'#\(macro.macroName.textWithoutBackticks)(_:_:)'" + } + + /// Get the human-readable name of the given attached macro. + /// + /// - Parameters: + /// - attribute: The attached macro node to name. + /// + /// - Returns: The name of the macro as understood by a developer, such as + /// `"'@Test'"`. Include single quotes. + private static func _macroName(_ attribute: AttributeSyntax) -> String { + // SEE: https://github.com/apple/swift/blob/main/docs/Diagnostics.md?plain=1#L44 + "'\(attribute.attributeNameText)'" + } + /// Get a string corresponding to the specified syntax node (for instance, /// `"function"` for a function declaration.) /// @@ -117,7 +166,7 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { precondition(!attributes.isEmpty) return Self( syntax: Syntax(attributes.last!), - message: "The @\(attributes.last!.attributeNameText) attribute cannot be applied to \(_kindString(for: decl, includeA: true)) more than once.", + message: "Attribute \(_macroName(attributes.last!)) cannot be applied to \(_kindString(for: decl, includeA: true)) more than once", severity: .error ) } @@ -134,7 +183,7 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func genericDeclarationNotSupported(_ decl: some SyntaxProtocol, whenUsing attribute: AttributeSyntax, becauseOf genericClause: some SyntaxProtocol) -> Self { Self( syntax: Syntax(genericClause), - message: "The @\(attribute.attributeNameText) attribute cannot be applied to a generic \(_kindString(for: decl)).", + message: "Attribute \(_macroName(attribute)) cannot be applied to a generic \(_kindString(for: decl))", severity: .error ) } @@ -155,7 +204,7 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func availabilityAttributeNotSupported(_ availabilityAttribute: AttributeSyntax, on decl: some SyntaxProtocol, whenUsing attribute: AttributeSyntax) -> Self { Self( syntax: Syntax(availabilityAttribute), - message: "The @\(attribute.attributeNameText) attribute cannot be applied to this \(_kindString(for: decl)) because it has been marked \(availabilityAttribute.trimmed).", + message: "Attribute \(_macroName(attribute)) cannot be applied to this \(_kindString(for: decl)) because it has been marked '\(availabilityAttribute.trimmed)'", severity: .error ) } @@ -171,7 +220,7 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func attributeNotSupported(_ attribute: AttributeSyntax, on decl: some SyntaxProtocol) -> Self { Self( syntax: Syntax(decl), - message: "The @\(attribute.attributeNameText) attribute cannot be applied to \(_kindString(for: decl, includeA: true)).", + message: "Attribute \(_macroName(attribute)) cannot be applied to \(_kindString(for: decl, includeA: true))", severity: .error ) } @@ -187,8 +236,14 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func attributeHasNoEffect(_ attribute: AttributeSyntax, on decl: ExtensionDeclSyntax) -> Self { Self( syntax: Syntax(decl), - message: "The @\(attribute.attributeNameText) attribute has no effect when applied to an extension and should be removed.", - severity: .error + message: "Attribute \(_macroName(attribute)) has no effect when applied to an extension", + severity: .error, + fixIts: [ + FixIt( + message: MacroExpansionFixItMessage("Remove attribute \(_macroName(attribute))"), + changes: [.replace(oldNode: Syntax(attribute), newNode: Syntax("" as ExprSyntax))] + ), + ] ) } @@ -206,19 +261,19 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { case 0: return Self( syntax: Syntax(functionDecl), - message: "The @\(attribute.attributeNameText) attribute cannot specify arguments when used with \(functionDecl.completeName) because it does not take any.", + message: "Attribute \(_macroName(attribute)) cannot specify arguments when used with '\(functionDecl.completeName)' because it does not take any", severity: .error ) case 1: return Self( syntax: Syntax(functionDecl), - message: "The @\(attribute.attributeNameText) attribute must specify an argument when used with \(functionDecl.completeName).", + message: "Attribute \(_macroName(attribute)) must specify an argument when used with '\(functionDecl.completeName)'", severity: .error ) default: return Self( syntax: Syntax(functionDecl), - message: "The @\(attribute.attributeNameText) attribute must specify \(expectedArgumentCount) arguments when used with \(functionDecl.completeName).", + message: "Attribute \(_macroName(attribute)) must specify \(expectedArgumentCount) arguments when used with '\(functionDecl.completeName)'", severity: .error ) } @@ -236,7 +291,7 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func xcTestCaseNotSupported(_ decl: some SyntaxProtocol, whenUsing attribute: AttributeSyntax) -> Self { Self( syntax: Syntax(decl), - message: "The @\(attribute.attributeNameText) attribute cannot be applied to a subclass of XCTestCase.", + message: "Attribute \(_macroName(attribute)) cannot be applied to a subclass of 'XCTestCase'", severity: .error ) } @@ -252,7 +307,7 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func nonFinalClassNotSupported(_ decl: ClassDeclSyntax, whenUsing attribute: AttributeSyntax) -> Self { Self( syntax: Syntax(decl), - message: "The @\(attribute.attributeNameText) attribute cannot be applied to non-final class \(decl.name.textWithoutBackticks).", + message: "Attribute \(_macroName(attribute)) cannot be applied to non-final class '\(decl.name.textWithoutBackticks)'", severity: .error ) } @@ -269,7 +324,7 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func specifierNotSupported(_ specifier: TokenSyntax, on parameter: FunctionParameterSyntax, whenUsing attribute: AttributeSyntax) -> Self { Self( syntax: Syntax(parameter), - message: "The @\(attribute.attributeNameText) attribute cannot be applied to a function with a parameter marked '\(specifier.textWithoutBackticks)'.", + message: "Attribute \(_macroName(attribute)) cannot be applied to a function with a parameter marked '\(specifier.textWithoutBackticks)'", severity: .error ) } @@ -286,7 +341,7 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func returnTypeNotSupported(_ returnType: TypeSyntax, on decl: some SyntaxProtocol, whenUsing attribute: AttributeSyntax) -> Self { Self( syntax: Syntax(returnType), - message: "The result of this \(_kindString(for: decl)) will be discarded during testing.", + message: "The result of this \(_kindString(for: decl)) will be discarded during testing", severity: .warning ) } @@ -302,7 +357,7 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func tagExprNotSupported(_ tagExpr: some SyntaxProtocol, in attribute: AttributeSyntax) -> Self { Self( syntax: Syntax(tagExpr), - message: "The tag \(tagExpr.trimmed) cannot be used with the @\(attribute.attributeNameText) attribute. Pass a member of Tag or a string literal instead.", + message: "Tag '\(tagExpr.trimmed)' cannot be used with attribute \(_macroName(attribute)); pass a member of 'Tag' or a string literal instead", severity: .error ) } @@ -317,15 +372,15 @@ struct DiagnosticMessage: SwiftDiagnostics.DiagnosticMessage { static func optionalBoolExprIsAmbiguous(_ boolExpr: ExprSyntax) -> Self { Self( syntax: Syntax(boolExpr), - message: "Requirement '\(boolExpr.trimmed)' is ambiguous.", + message: "Requirement '\(boolExpr.trimmed)' is ambiguous", severity: .warning, fixIts: [ FixIt( - message: MacroExpansionFixItMessage("To unwrap an optional value, add 'as Bool?'."), + message: MacroExpansionFixItMessage("To unwrap an optional value, add 'as Bool?'"), changes: [.replace(oldNode: Syntax(boolExpr), newNode: Syntax("\(boolExpr) as Bool?" as ExprSyntax))] ), FixIt( - message: MacroExpansionFixItMessage("To check if a value is true, add '?? false'."), + message: MacroExpansionFixItMessage("To check if a value is true, add '?? false'"), changes: [.replace(oldNode: Syntax(boolExpr), newNode: Syntax("\(boolExpr) ?? false" as ExprSyntax))] ), ] diff --git a/Tests/TestingMacrosTests/ConditionMacroTests.swift b/Tests/TestingMacrosTests/ConditionMacroTests.swift index 634fba2e9..ace0ba791 100644 --- a/Tests/TestingMacrosTests/ConditionMacroTests.swift +++ b/Tests/TestingMacrosTests/ConditionMacroTests.swift @@ -331,6 +331,49 @@ struct ConditionMacroTests { #expect(diagnostics.isEmpty) } + @Test("#expect(try/await) produces a diagnostic", + arguments: [ + "#expect(try foo())": ["Expression 'try foo()' will not be expanded on failure; move the throwing part out of the call to '#expect(_:_:)'"], + "#expect(await foo())": ["Expression 'await foo()' will not be expanded on failure; move the asynchronous part out of the call to '#expect(_:_:)'"], + "#expect(try await foo())": ["Expression 'try await foo()' will not be expanded on failure; move the throwing/asynchronous part out of the call to '#expect(_:_:)'"], + "#expect(try await foo(try bar(await quux())))": [ + "Expression 'try await foo(try bar(await quux()))' will not be expanded on failure; move the throwing/asynchronous part out of the call to '#expect(_:_:)'", + "Expression 'try bar(await quux())' will not be expanded on failure; move the throwing part out of the call to '#expect(_:_:)'", + "Expression 'await quux()' will not be expanded on failure; move the asynchronous part out of the call to '#expect(_:_:)'", + ], + + // Diagnoses because the diagnostic for `await` is suppressed due to the + // `as T` cast, but the parentheses limit the effect of the suppression. + "#expect(try (await foo() as T))": ["Expression 'try (await foo() as T)' will not be expanded on failure; move the throwing part out of the call to '#expect(_:_:)'"], + ] + ) + func effectfulExpectationDiagnoses(input: String, diagnosticMessages: [String]) throws { + let (_, diagnostics) = try parse(input) + #expect(diagnostics.count == diagnosticMessages.count) + for message in diagnosticMessages { + #expect(diagnostics.contains { $0.diagMessage.message == message }, "Missing \(message): \(diagnostics.map(\.diagMessage.message))") + } + } + + @Test("#expect(try/await as Bool) suppresses its diagnostic", + arguments: [ + "#expect(try foo() as Bool)", + "#expect(await foo() as Bool)", + "#expect(try await foo(try await bar()) as Bool)", + "#expect(try foo() as T?)", + "#expect(await foo() as? T)", + "#expect(try await foo(try await bar()) as! T)", + "#expect((try foo()) as T)", + "#expect((await foo()) as T)", + "#expect((try await foo(try await bar())) as T)", + "#expect(try (await foo()) as T)", + ] + ) + func effectfulExpectationDiagnosticSuppressWithExplicitBool(input: String) throws { + let (_, diagnostics) = try parse(input) + #expect(diagnostics.isEmpty) + } + @Test("Macro expansion is performed within a test function") func macroExpansionInTestFunction() throws { let input = ##""" diff --git a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift index bcb226f3f..1bd5df1f1 100644 --- a/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift +++ b/Tests/TestingMacrosTests/TestDeclarationMacroTests.swift @@ -23,88 +23,88 @@ struct TestDeclarationMacroTests { arguments: [ // Generic declarations "@Suite struct S {}": - "The @Suite attribute cannot be applied to a generic structure.", + "Attribute 'Suite' cannot be applied to a generic structure", "@Suite struct S where X == Y {}": - "The @Suite attribute cannot be applied to a generic structure.", + "Attribute 'Suite' cannot be applied to a generic structure", "@Test func f() {}": - "The @Test attribute cannot be applied to a generic function.", + "Attribute 'Test' cannot be applied to a generic function", "@Test func f() where X == Y {}": - "The @Test attribute cannot be applied to a generic function.", + "Attribute 'Test' cannot be applied to a generic function", "@Test(arguments: []) func f(x: some T) {}": - "The @Test attribute cannot be applied to a generic function.", + "Attribute 'Test' cannot be applied to a generic function", "@Test(arguments: []) func f(x: (some T)?) {}": - "The @Test attribute cannot be applied to a generic function.", + "Attribute 'Test' cannot be applied to a generic function", // Multiple attributes on a declaration "@Suite @Suite struct S {}": - "The @Suite attribute cannot be applied to a structure more than once.", + "Attribute 'Suite' cannot be applied to a structure more than once", "@Suite @Suite final class C {}": - "The @Suite attribute cannot be applied to a class more than once.", + "Attribute 'Suite' cannot be applied to a class more than once", "@Test @Test func f() {}": - "The @Test attribute cannot be applied to a function more than once.", + "Attribute 'Test' cannot be applied to a function more than once", // Attributes on unsupported declarations "@Test var x = 0": - "The @Test attribute cannot be applied to a property.", + "Attribute 'Test' cannot be applied to a property", "@Test init() {}": - "The @Test attribute cannot be applied to an initializer.", + "Attribute 'Test' cannot be applied to an initializer", "@Test deinit {}": - "The @Test attribute cannot be applied to a deinitializer.", + "Attribute 'Test' cannot be applied to a deinitializer", "@Test subscript() -> Int {}": - "The @Test attribute cannot be applied to a subscript.", + "Attribute 'Test' cannot be applied to a subscript", "@Test typealias X = Y": - "The @Test attribute cannot be applied to a typealias.", + "Attribute 'Test' cannot be applied to a typealias", "enum E { @Test case c }": - "The @Test attribute cannot be applied to an enumeration case.", + "Attribute 'Test' cannot be applied to an enumeration case", "@Suite func f() {}": - "The @Suite attribute cannot be applied to a function.", + "Attribute 'Suite' cannot be applied to a function", "@Suite extension X {}": - "The @Suite attribute has no effect when applied to an extension and should be removed.", + "Attribute 'Suite' has no effect when applied to an extension", "@Test macro m()": - "The @Test attribute cannot be applied to a macro.", + "Attribute 'Test' cannot be applied to a macro", "@Test struct S {}": - "The @Test attribute cannot be applied to a structure.", + "Attribute 'Test' cannot be applied to a structure", "@Test enum E {}": - "The @Test attribute cannot be applied to an enumeration.", + "Attribute 'Test' cannot be applied to an enumeration", // Availability "@available(*, unavailable) @Suite struct S {}": - "The @Suite attribute cannot be applied to this structure because it has been marked @available(*, unavailable).", + "Attribute 'Suite' cannot be applied to this structure because it has been marked '@available(*, unavailable)'", "@available(*, noasync) @Suite enum E {}": - "The @Suite attribute cannot be applied to this enumeration because it has been marked @available(*, noasync).", + "Attribute 'Suite' cannot be applied to this enumeration because it has been marked '@available(*, noasync)'", "@available(macOS 999.0, *) @Suite final class C {}": - "The @Suite attribute cannot be applied to this class because it has been marked @available(macOS 999.0, *).", + "Attribute 'Suite' cannot be applied to this class because it has been marked '@available(macOS 999.0, *)'", "@_unavailableFromAsync @Suite actor A {}": - "The @Suite attribute cannot be applied to this actor because it has been marked @_unavailableFromAsync.", + "Attribute 'Suite' cannot be applied to this actor because it has been marked '@_unavailableFromAsync'", // XCTestCase "@Suite final class C: XCTestCase {}": - "The @Suite attribute cannot be applied to a subclass of XCTestCase.", + "Attribute 'Suite' cannot be applied to a subclass of 'XCTestCase'", "@Suite final class C: XCTest.XCTestCase {}": - "The @Suite attribute cannot be applied to a subclass of XCTestCase.", + "Attribute 'Suite' cannot be applied to a subclass of 'XCTestCase'", // Unsupported inheritance "@Suite class C {}": - "The @Suite attribute cannot be applied to non-final class C.", + "Attribute 'Suite' cannot be applied to non-final class 'C'", "@Suite protocol P {}": - "The @Suite attribute cannot be applied to a protocol.", + "Attribute 'Suite' cannot be applied to a protocol", // Invalid specifiers on arguments "@Test(arguments: [0]) func f(i: inout Int) {}": - "The @Test attribute cannot be applied to a function with a parameter marked 'inout'.", + "Attribute 'Test' cannot be applied to a function with a parameter marked 'inout'", "@Test(arguments: [MyActor()]) func f(i: isolated MyActor) {}": - "The @Test attribute cannot be applied to a function with a parameter marked 'isolated'.", + "Attribute 'Test' cannot be applied to a function with a parameter marked 'isolated'", "@Test(arguments: [0.0]) func f(i: _const Double) {}": - "The @Test attribute cannot be applied to a function with a parameter marked '_const'.", + "Attribute 'Test' cannot be applied to a function with a parameter marked '_const'", // Argument count mismatches. "@Test func f(i: Int) {}": - "The @Test attribute must specify an argument when used with f(i:).", + "Attribute 'Test' must specify an argument when used with 'f(i:)'", "@Test func f(i: Int, j: Int) {}": - "The @Test attribute must specify 2 arguments when used with f(i:j:).", + "Attribute 'Test' must specify 2 arguments when used with 'f(i:j:)'", "@Test(arguments: []) func f() {}": - "The @Test attribute cannot specify arguments when used with f() because it does not take any.", + "Attribute 'Test' cannot specify arguments when used with 'f()' because it does not take any", ] ) func apiMisuseErrors(input: String, expectedMessage: String) throws { @@ -121,13 +121,13 @@ struct TestDeclarationMacroTests { arguments: [ // return types "@Test func f() -> Int {}": - "The result of this function will be discarded during testing.", + "The result of this function will be discarded during testing", "@Test func f() -> Swift.String {}": - "The result of this function will be discarded during testing.", + "The result of this function will be discarded during testing", "@Test func f() -> Int? {}": - "The result of this function will be discarded during testing.", + "The result of this function will be discarded during testing", "@Test func f() -> (Int, Int) {}": - "The result of this function will be discarded during testing.", + "The result of this function will be discarded during testing", ] ) func apiMisuseWarnings(input: String, expectedMessage: String) throws { @@ -295,7 +295,7 @@ struct TestDeclarationMacroTests { #expect(diagnostics.count > 0) for diagnostic in diagnostics { #expect(diagnostic.diagMessage.severity == .error) - #expect(diagnostic.message == "The tag \(tagExpr) cannot be used with the @Test attribute. Pass a member of Tag or a string literal instead.") + #expect(diagnostic.message == "Tag '\(tagExpr)' cannot be used with attribute 'Test'; pass a member of 'Tag' or a string literal instead") } } } diff --git a/Tests/TestingTests/EventRecorderTests.swift b/Tests/TestingTests/EventRecorderTests.swift index f2b179654..fa60716f7 100644 --- a/Tests/TestingTests/EventRecorderTests.swift +++ b/Tests/TestingTests/EventRecorderTests.swift @@ -144,12 +144,11 @@ struct EventRecorderTests { One(.anyGraphemeCluster) " \(isSuite ? "Suite" : "Test") \(testName) started." } - #expect( - try buffer - .split(whereSeparator: \.isNewline) - .compactMap(testFailureRegex.wholeMatch(in:)) - .first != nil - ) + let match = try buffer + .split(whereSeparator: \.isNewline) + .compactMap(testFailureRegex.wholeMatch(in:)) + .first + #expect(match != nil) } @available(_regexAPI, *) diff --git a/Tests/TestingTests/IssueTests.swift b/Tests/TestingTests/IssueTests.swift index a0a944f3c..8e0d0d5c5 100644 --- a/Tests/TestingTests/IssueTests.swift +++ b/Tests/TestingTests/IssueTests.swift @@ -51,7 +51,7 @@ final class IssueTests: XCTestCase { } await Test { () throws in - #expect(try { throw MyError() }()) + #expect(try { throw MyError() }() as Bool) }.run(configuration: configuration) await Test { () throws in @@ -282,8 +282,8 @@ final class IssueTests: XCTestCase { } await Test { () throws in - #expect(try TypeWithMemberFunctions.n(0)) - #expect(TypeWithMemberFunctions.f(try { () throws in 0 }())) + #expect(try TypeWithMemberFunctions.n(0) as Bool) + #expect(TypeWithMemberFunctions.f(try { () throws in 0 }()) as Bool) }.run(configuration: configuration) await fulfillment(of: [expectationFailed], timeout: 0.0) diff --git a/Tests/TestingTests/MiscellaneousTests.swift b/Tests/TestingTests/MiscellaneousTests.swift index abf4252de..18299dd2d 100644 --- a/Tests/TestingTests/MiscellaneousTests.swift +++ b/Tests/TestingTests/MiscellaneousTests.swift @@ -194,24 +194,26 @@ struct TestsWithAsyncArguments { struct MiscellaneousTests { @Test("Free function's name") func unnamedFreeFunctionTest() async throws { - let testFunction = try #require(await Test.all.first(where: { $0.name.contains("freeSyncFunction") })) + let tests = await Test.all + let testFunction = try #require(tests.first(where: { $0.name.contains("freeSyncFunction") })) #expect(testFunction.name == "freeSyncFunction()") } @Test("Test suite type's name") func unnamedMemberFunctionTest() async throws { - let testType = try #require(await test(for: SendableTests.self)) + let testType = try #require(await test(for: SendableTests.self) as Test?) #expect(testType.name == "SendableTests") } @Test("Free function has custom display name") func namedFreeFunctionTest() async throws { - #expect(await Test.all.first { $0.displayName == "Named Free Sync Function" && !$0.isSuite && $0.containingType == nil } != nil) + let tests = await Test.all + #expect(tests.first { $0.displayName == "Named Free Sync Function" && !$0.isSuite && $0.containingType == nil } != nil) } @Test("Member function has custom display name") func namedMemberFunctionTest() async throws { - let testType = try #require(await test(for: NamedSendableTests.self)) + let testType = try #require(await test(for: NamedSendableTests.self) as Test?) #expect(testType.displayName == "Named Sendable test type") } @@ -319,18 +321,18 @@ struct MiscellaneousTests { @Test("Test.parameters property") func parametersProperty() async throws { do { - let theTest = try #require(await test(for: SendableTests.self)) + let theTest = try #require(await test(for: SendableTests.self) as Test?) #expect(theTest.parameters == nil) } do { - let test = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) + let test = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) let parameters = try #require(test.parameters) #expect(parameters.isEmpty) } catch {} do { - let test = try #require(await testFunction(named: "parameterized(i:)", in: NonSendableTests.self)) + let test = try #require(await testFunction(named: "parameterized(i:)", in: NonSendableTests.self) as Test?) let parameters = try #require(test.parameters) #expect(parameters.count == 1) let firstParameter = try #require(parameters.first) @@ -343,7 +345,7 @@ struct MiscellaneousTests { } catch {} do { - let test = try #require(await testFunction(named: "parameterized2(i:j:)", in: NonSendableTests.self)) + let test = try #require(await testFunction(named: "parameterized2(i:j:)", in: NonSendableTests.self) as Test?) let parameters = try #require(test.parameters) #expect(parameters.count == 2) let firstParameter = try #require(parameters.first) diff --git a/Tests/TestingTests/PlanTests.swift b/Tests/TestingTests/PlanTests.swift index 84973ced9..fc17afb2c 100644 --- a/Tests/TestingTests/PlanTests.swift +++ b/Tests/TestingTests/PlanTests.swift @@ -23,10 +23,10 @@ struct PlanTests { @Test("Selected tests by ID") func selectedTests() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -48,10 +48,10 @@ struct PlanTests { @Test("Multiple selected tests by ID") func multipleSelectedTests() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -74,10 +74,10 @@ struct PlanTests { @Test("Excluded tests by ID") func excludedTests() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -101,11 +101,11 @@ struct PlanTests { @Test("Selected tests by any tag") func selectedTestsByAnyTag() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) - let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) + let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -131,11 +131,11 @@ struct PlanTests { @Test("Selected tests by all tags") func selectedTestsByAllTags() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) - let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) + let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -161,11 +161,11 @@ struct PlanTests { @Test("Excluded tests by any tag") func excludedTestsByAnyTag() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) - let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) + let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -191,11 +191,11 @@ struct PlanTests { @Test("Excluded tests by all tags") func excludedTestsByAllTags() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) - let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) + let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -221,11 +221,11 @@ struct PlanTests { @Test("Mixed included and excluded tests by ID") func mixedIncludedAndExcludedTests() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) - let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) + let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -252,10 +252,10 @@ struct PlanTests { @Test("Combining test filter by ID with .unfiltered (rhs)") func combiningTestFilterWithUnfilteredRHS() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -281,10 +281,10 @@ struct PlanTests { @Test("Combining test filter by ID with .unfiltered (lhs)") func combiningTestFilterWithUnfilteredLHS() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -310,10 +310,10 @@ struct PlanTests { @Test("Combining test filter by ID with by tag") func combiningTestFilterByIDAndByTag() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -339,11 +339,11 @@ struct PlanTests { @Test("Combining test filters with .or") func combiningTestFilterWithOr() async throws { - let outerTestType = try #require(await test(for: SendableTests.self)) - let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self)) - let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self)) - let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self)) - let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self)) + let outerTestType = try #require(await test(for: SendableTests.self) as Test?) + let testA = try #require(await testFunction(named: "succeeds()", in: SendableTests.self) as Test?) + let innerTestType = try #require(await test(for: SendableTests.NestedSendableTests.self) as Test?) + let testB = try #require(await testFunction(named: "succeeds()", in: SendableTests.NestedSendableTests.self) as Test?) + let testC = try #require(await testFunction(named: "otherSucceeds()", in: SendableTests.NestedSendableTests.self) as Test?) let tests = [ outerTestType, @@ -371,9 +371,9 @@ struct PlanTests { @Test("Recursive trait application") func recursiveTraitApplication() async throws { - let outerTestType = try #require(await test(for: OuterTest.self)) + let outerTestType = try #require(await test(for: OuterTest.self) as Test?) // Intentionally omitting intermediate tests here... - let deeplyNestedTest = try #require(await testFunction(named: "example()", in: OuterTest.IntermediateType.InnerTest.self)) + let deeplyNestedTest = try #require(await testFunction(named: "example()", in: OuterTest.IntermediateType.InnerTest.self) as Test?) let tests = [outerTestType, deeplyNestedTest] @@ -389,10 +389,10 @@ struct PlanTests { @Test("Relative order of recursively applied traits") func recursiveTraitOrder() async throws { - let testSuiteA = try #require(await test(for: RelativeTraitOrderingTests.A.self)) - let testSuiteB = try #require(await test(for: RelativeTraitOrderingTests.A.B.self)) - let testSuiteC = try #require(await test(for: RelativeTraitOrderingTests.A.B.C.self)) - let testFuncX = try #require(await testFunction(named: "x()", in: RelativeTraitOrderingTests.A.B.C.self)) + let testSuiteA = try #require(await test(for: RelativeTraitOrderingTests.A.self) as Test?) + let testSuiteB = try #require(await test(for: RelativeTraitOrderingTests.A.B.self) as Test?) + let testSuiteC = try #require(await test(for: RelativeTraitOrderingTests.A.B.C.self) as Test?) + let testFuncX = try #require(await testFunction(named: "x()", in: RelativeTraitOrderingTests.A.B.C.self) as Test?) let tests = [testSuiteA, testSuiteB, testSuiteC, testFuncX] diff --git a/Tests/TestingTests/Runner.Plan.SnapshotTests.swift b/Tests/TestingTests/Runner.Plan.SnapshotTests.swift index cea4e4799..4c5964349 100644 --- a/Tests/TestingTests/Runner.Plan.SnapshotTests.swift +++ b/Tests/TestingTests/Runner.Plan.SnapshotTests.swift @@ -19,7 +19,7 @@ struct Runner_Plan_SnapshotTests { #if canImport(Foundation) @Test("Codable") func codable() async throws { - let suite = try #require(await test(for: Runner_Plan_SnapshotFixtures.self)) + let suite = try #require(await test(for: Runner_Plan_SnapshotFixtures.self) as Test?) var configuration = Configuration() configuration.setTestFilter(toInclude: [suite.id], includeHiddenTests: true) diff --git a/Tests/TestingTests/RunnerTests.swift b/Tests/TestingTests/RunnerTests.swift index 12c238b7f..28c811228 100644 --- a/Tests/TestingTests/RunnerTests.swift +++ b/Tests/TestingTests/RunnerTests.swift @@ -246,8 +246,8 @@ final class RunnerTests: XCTestCase { } func testConditionTraitsAreEvaluatedOutermostToInnermost() async throws { - let testSuite = try #require(await test(for: NeverRunTests.self)) - let testFunc = try #require(await testFunction(named: "duelingConditions()", in: NeverRunTests.self)) + let testSuite = try #require(await test(for: NeverRunTests.self) as Test?) + let testFunc = try #require(await testFunction(named: "duelingConditions()", in: NeverRunTests.self) as Test?) var configuration = Configuration() let selection = [testSuite.id] diff --git a/Tests/TestingTests/SwiftPMTests.swift b/Tests/TestingTests/SwiftPMTests.swift index abc85e80c..abdece31e 100644 --- a/Tests/TestingTests/SwiftPMTests.swift +++ b/Tests/TestingTests/SwiftPMTests.swift @@ -143,7 +143,7 @@ struct SwiftPMTests { configuration.eventHandler(Event(.runStarted, testID: nil, testCaseID: nil), eventContext) configuration.eventHandler(Event(.runEnded, testID: nil, testCaseID: nil), eventContext) } - #expect(try temporaryFileURL.checkResourceIsReachable()) + #expect(try temporaryFileURL.checkResourceIsReachable() as Bool) } #endif diff --git a/Tests/TestingTests/Test.SnapshotTests.swift b/Tests/TestingTests/Test.SnapshotTests.swift index ca58d6221..f3743ac10 100644 --- a/Tests/TestingTests/Test.SnapshotTests.swift +++ b/Tests/TestingTests/Test.SnapshotTests.swift @@ -40,12 +40,12 @@ struct Test_SnapshotTests { #expect(!snapshot.isParameterized) } do { - let test = try #require(await testFunction(named: "parameterized(i:)", in: MainActorIsolatedTests.self)) + let test = try #require(await testFunction(named: "parameterized(i:)", in: MainActorIsolatedTests.self) as Test?) let snapshot = Test.Snapshot(snapshotting: test) #expect(snapshot.isParameterized) } do { - let suite = try #require(await test(for: Self.self)) + let suite = try #require(await test(for: Self.self) as Test?) let snapshot = Test.Snapshot(snapshotting: suite) #expect(!snapshot.isParameterized) } @@ -59,12 +59,12 @@ struct Test_SnapshotTests { #expect(!snapshot.isSuite) } do { - let test = try #require(await testFunction(named: "parameterized(i:)", in: MainActorIsolatedTests.self)) + let test = try #require(await testFunction(named: "parameterized(i:)", in: MainActorIsolatedTests.self) as Test?) let snapshot = Test.Snapshot(snapshotting: test) #expect(!snapshot.isSuite) } do { - let suite = try #require(await test(for: Self.self)) + let suite = try #require(await test(for: Self.self) as Test?) let snapshot = Test.Snapshot(snapshotting: suite) #expect(snapshot.isSuite) }