apple / swift-testing

A modern, expressive testing package for Swift
Apache License 2.0
1.47k stars 52 forks source link

Type placeholders are not supported. #486

Closed JessyCatterwaul closed 2 weeks ago

JessyCatterwaul commented 2 weeks ago

Description

I don't know how true this is as a blanket statement. I just started experimenting with the framework.

Expected behavior

You can replicate "XCTests" that use type placeholders.

For example, this one.

import XCTest

class TestCase: XCTestCase {
  func test() throws {
    _ = try XCTUnwrap(_?.ƒ(0))
  }
}

extension Optional {
  static func ƒ(_ optional: Self) -> Self { optional }
}

Actual behavior

Type placeholder not allowed here Could not infer type for placeholder Cannot use optional chaining on non-optional value of type '_.Type'

Steps to reproduce

@Test func test() throws {
  try #require(_?.ƒ(0))
}

swift-testing version/commit hash

Whatever's in the first Xcode 16 beta.

Swift & OS version (output of swift --version ; uname -a)

swift-driver version: 1.109.2 Apple Swift version 6.0 (swiftlang-6.0.0.3.300 clang-1600.0.20.10) Target: arm64-apple-macosx14.0 Darwin Jessys-2021-MBP.local 23.5.0 Darwin Kernel Version 23.5.0: Wed May 1 20:12:58 PDT 2024; root:xnu-10063.121.3~5/RELEASE_ARM64_T6000 arm64

grynspan commented 2 weeks ago

Type placeholders do not provide enough contextual information for macro expansion to occur correctly.

JessyCatterwaul commented 2 weeks ago

It's of course not acceptable to lose the functionality. Is this a bug to be logged at https://github.com/apple/swift-syntax/issues?

grynspan commented 2 weeks ago

Macros only operate on syntax without type information, so the type of _? in this context cannot be inferred. Swift Testing's macro expansions work by breaking down complex expressions into smaller subexpressions and then evaluating the overall expression in a closure. The compiler cannot type-check _? in this context.

What I can do here is detect the use of a type placeholder and skip the breakdown entirely. That will allow the code to compile, but it won't let you get subexpression values. If that's sufficient for your needs, feel free to reopen and I can put up a PR.

stmontgomery commented 2 weeks ago

Out of curiosity @JessyCatterwaul, do you have a real-world use case in mind for this? The reduced example is helpful but I'm wondering what the underlying motivation is

JessyCatterwaul commented 2 weeks ago

Thanks, @grynspan. I don't know if what you suggest would solve the problem, so I'm not going to take up any more of your time with it.


@stmontgomery I'm beginning to extract some code into a new package, updating to utilize new tools such as Swift Testing.

I had a test for "Optional.zip"…

public extension Optional {
  /// Exchange a tuple of optionals for a single optional tuple.
  /// - Returns: `nil` if any tuple element is `nil`.
  @inlinable static func zip<each Element>(_ optional: (repeat (each Element)?)) -> Self
  where Wrapped == (repeat each Element) { 
    …

…which looked like this.

let jenies: (_?, _?) = ("👖", "🧞‍♂️")   
XCTAssert(try XCTUnwrap(.zip(jenies)) == ("👖", "🧞‍♂️"))

I logged #487 when my first attempt at conversion failed.

 #expect(try #require(.zip(jenies)) == ("👖", "🧞‍♂️"))

The first place I've gone, over the last two years, for adding optionality when implicit typing doesn't work, is _?. Swift Testing is enforcing the old school style of Optional, instead. This would be okay with me if _? hadn't been allowed elsewhere for the past two years. I especially don't like the inconsistency of being able to use _? on one line, and then having to switch to Optional on the next.

let jenies: (_?, _?) = ("👖", "🧞‍♂️")
#expect(try #require(Optional.zip(jenies)) == ("👖", "🧞‍♂️"))