Nimble: Xcode 12 beta 5 and 6 build error `ambiguous use of 'expect'`

Created on 19 Aug 2020  ·  22Comments  ·  Source: Quick/Nimble

  • [x] I have read CONTRIBUTING and have done my best to follow them.

What did you do?

I installed Xcode 12 beta 5 and 6 and tried to build our existing (test) code:

it("should throw an error") {
    expect {
        try JSONDecoder().decode(SomeModel.self, from: data)
    }.to(throwError())
}

What did you expect to happen?

I expected the code to compile.

What actually happened instead?

The code failed to compile with a ambiguous use of 'expect' error. With these two candidates:

  • Sources/Nimble/DSL.swift#L1-L8

    • public func expect<T>(_ expression: @autoclosure @escaping () throws -> T?, file: FileString = #file, line: UInt = #line) -> Expectation<T>

  • Sources/Nimble/DSL.swift#L10-L17: Found this candidate

    • public func expect<T>(_ file: FileString = #file, line: UInt = #line, expression: @escaping () throws -> T?) -> Expectation<T>

Environment

List the software versions you're using:

  • Quick: 2.2.1 + 3.0.0
  • Nimble: 8.1.1 + 9.0.0-rc.1
  • Xcode Version: 12.0 beta 5 (12A8189h), 12.0 beta 6 (12A8189n)
  • Swift Version: Xcode Default

Please also mention which package manager you used and its version. Delete the
other package managers in this list:

  • Carthage: 0.35.0

Project that demonstrates the issue

https://github.com/rastersize/Xcode12Beta5Nimble

After cloning do the following:

  1. Run carthage.sh bootstrap

    • The script works around a Carthage bug with Xcode 12 beta 4+ and universal binaries. As newer versions of Xcode 12 will produce an arm64 slice for both the simulator and device, and a universal binary can’t contain two slices for the same architecture.

  2. Open the Xcode project
  3. Build for testing

Workaround

Change the call-site of expect to specify using the non-autoclosure function. That is:

it("should throw an error") {
    expect(expression: {
        try JSONDecoder().decode(SomeModel.self, from: data)
    }).to(throwError())
}

Not as pretty but it works for now.

Most helpful comment

Thanks for your work @ikesyo !

I tested out #821 and it resolved a lot of the issues I was facing. On a minor note I noticed that this no longer works

expect({
  if 1 == 1 {
    return .failed(reason: "this failed")
  }
  return .succeeded
}).to(succeed())

This can be fixed by returning a closure instead i.e. return { .succeeded } but I am curious if this regression can be reverted?

@ketenshi Another workaround I found is by adding () -> ToSucceedResult in like so:

expect({ () -> ToSucceedResult in
  if 1 == 1 {
    return .failed(reason: "this failed")
  }
  return .succeeded
}).to(succeed())

Not sure if this behaviour is expected in Nimble 9.

All 22 comments

This is probably a regression in the Swift compiler to be honest, and should be resolved there I guess. Especially as it just crashed Swiftc while compiling some more code. Filed with Apple as FB8464226 and in the Swift Bug tracker as SR-13421.

Added a sample project that demonstrates the issue with Nimble 9.0.0-rc.1.

I'm on the same configuration and I'm getting a similar error, Illegal instruction: 4, but always when evaluating expect{}.toEventually()

CompileSwift normal x86_64 (in target 'LoungeFoundationTests' from project 'LoungeFoundation')
[...]
fromType->getCanonicalType() = (bound_generic_struct_type decl=Nimble.(file).Predicate@/Users/ngiancecchi/Projects/ios-app/Carthage/Checkouts/Nimble/Sources/Nimble/Matchers/Predicate.swift:20:15
  (protocol_composition_type))
toType->getCanonicalType() = (bound_generic_struct_type decl=Nimble.(file).Predicate@/Users/ngiancecchi/Projects/ios-app/Carthage/Checkouts/Nimble/Sources/Nimble/Matchers/Predicate.swift:20:15
  (function_type escaping
    (input=function_params num_params=0)
    (output=tuple_type num_elements=0)))
Stack dump:
0.  Program arguments: [...]
1.  Apple Swift version 5.3 (swiftlang-1200.0.28.1 clang-1200.0.30.1)
2.  While evaluating request TypeCheckSourceFileRequest(source_file "/Users/ngiancecchi/Projects/ios-app/Subprojects/LoungeFoundation/LoungeFoundationTests/ConfigTests.swift")
3.  While evaluating request TypeCheckFunctionBodyUntilRequest(LoungeFoundationTests.(file).ConfigTests.testPostsNotification()@/Users/ngiancecchi/Projects/ios-app/Subprojects/LoungeFoundation/LoungeFoundationTests/ConfigTests.swift:46:10, )
4.  While type-checking statement at [/Users/ngiancecchi/Projects/ios-app/Subprojects/LoungeFoundation/LoungeFoundationTests/ConfigTests.swift:46:34 - line:53:5] RangeText="{
        let newConfig: Config = .mockedWith(appDomainID: 5555)
        let expectedNotification = Notification(name: .newConfigAvailable, object: newConfig)

        expect {
            Config.current = newConfig
        }.toEventually(postNotifications(contain(expectedNotification, from: newConfig)))
    "
5.  While type-checking expression at [/Users/ngiancecchi/Projects/ios-app/Subprojects/LoungeFoundation/LoungeFoundationTests/ConfigTests.swift:50:9 - line:52:89] RangeText="expect {
            Config.current = newConfig
        }.toEventually(postNotifications(contain(expectedNotification, from: newConfig))"
0  swift                    0x00000001087a5a85 llvm::sys::PrintStackTrace(llvm::raw_ostream&) + 37
1  swift                    0x00000001087a4a85 llvm::sys::RunSignalHandlers() + 85
2  swift                    0x00000001087a603f SignalHandler(int) + 111
3  libsystem_platform.dylib 0x00007fff6ed735fd _sigtramp + 29
4  libsystem_kernel.dylib   0x00007fff6ecbf35e __ioctl + 10
5  swift                    0x0000000104ff1413 (anonymous namespace)::ExprRewriter::coerceCallArguments(swift::Expr*, swift::AnyFunctionType*, swift::ConcreteDeclRef, swift::ApplyExpr*, llvm::ArrayRef<swift::Identifier>, swift::constraints::ConstraintLocatorBuilder) + 8899
6  swift                    0x0000000104fee352 (anonymous namespace)::ExprRewriter::finishApply(swift::ApplyExpr*, swift::Type, swift::constraints::ConstraintLocatorBuilder, swift::constraints::ConstraintLocatorBuilder) + 11970
7  swift                    0x0000000104ff9b5d (anonymous namespace)::ExprRewriter::visitApplyExpr(swift::ApplyExpr*) + 477
8  swift                    0x0000000104fd0b02 (anonymous namespace)::ExprWalker::walkToExprPost(swift::Expr*) + 18
9  swift                    0x0000000104fc71e3 (anonymous namespace)::ExprWalker::rewriteTarget(swift::constraints::SolutionApplicationTarget) + 371
10 swift                    0x0000000104fc6ed0 swift::constraints::ConstraintSystem::applySolution(swift::constraints::Solution&, swift::constraints::SolutionApplicationTarget) + 6400
11 swift                    0x00000001051aff1a swift::TypeChecker::typeCheckExpression(swift::constraints::SolutionApplicationTarget&, bool&, swift::OptionSet<swift::TypeCheckExprFlags, unsigned int>) + 1242
12 swift                    0x00000001051af988 swift::TypeChecker::typeCheckExpression(swift::Expr*&, swift::DeclContext*, swift::TypeLoc, swift::ContextualTypePurpose, swift::OptionSet<swift::TypeCheckExprFlags, unsigned int>) + 360
13 swift                    0x0000000105294e4b swift::ASTVisitor<(anonymous namespace)::StmtChecker, void, swift::Stmt*, void, void, void, void>::visit(swift::Stmt*) + 9115
14 swift                    0x000000010529069a bool (anonymous namespace)::StmtChecker::typeCheckStmt<swift::BraceStmt>(swift::BraceStmt*&) + 314
15 swift                    0x000000010528f0a7 swift::TypeCheckFunctionBodyUntilRequest::evaluate(swift::Evaluator&, swift::AbstractFunctionDecl*, swift::SourceLoc) const + 2231
16 swift                    0x0000000105297e7d llvm::Expected<swift::TypeCheckFunctionBodyUntilRequest::OutputType> swift::Evaluator::getResultCached<swift::TypeCheckFunctionBodyUntilRequest, (void*)0>(swift::TypeCheckFunctionBodyUntilRequest const&) + 925
17 swift                    0x0000000105290148 swift::TypeCheckFunctionBodyUntilRequest::OutputType swift::evaluateOrDefault<swift::TypeCheckFunctionBodyUntilRequest>(swift::Evaluator&, swift::TypeCheckFunctionBodyUntilRequest, swift::TypeCheckFunctionBodyUntilRequest::OutputType) + 440
18 swift                    0x00000001052cda84 swift::TypeCheckSourceFileRequest::evaluate(swift::Evaluator&, swift::SourceFile*) const + 1380
19 swift                    0x00000001052d0699 llvm::Expected<swift::TypeCheckSourceFileRequest::OutputType> swift::Evaluator::getResultUncached<swift::TypeCheckSourceFileRequest>(swift::TypeCheckSourceFileRequest const&) + 953
20 swift                    0x00000001052cd394 swift::TypeCheckSourceFileRequest::OutputType swift::evaluateOrDefault<swift::TypeCheckSourceFileRequest>(swift::Evaluator&, swift::TypeCheckSourceFileRequest, swift::TypeCheckSourceFileRequest::OutputType) + 164
21 swift                    0x0000000104448e78 swift::CompilerInstance::performSemaUpTo(swift::SourceFile::ASTStage_t) + 7576
22 swift                    0x000000010430996d swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 6845
23 swift                    0x000000010428e277 main + 1255
24 libdyld.dylib            0x00007fff6eb7acc9 start + 1
25 libdyld.dylib            0x00000000000000d4 start + 18446603338658632716
error: Illegal instruction: 4 (in target 'LoungeFoundationTests' from project 'LoungeFoundation')

I talked to a colleague about this and they suggested that it could be due to the new “forward scan” behavior for trailing closures. Noting that Nimble was apparently called out as one of the libraries that would break.

It appears this is still an issue with Xcode 12 Beta 6 and the version of Swift it bundles with.

It seems like Xcode Beta 6 is near identical to Beta 5. Same version of swiftc and almost no difference in build version for Xcode itself. Hopefully coming version will fix it 🤞

Here's to hoping for a fix whilst Xcode 12 remains in Beta.

@rastersize - is it worthwhile tagging each beta individually or updating the defect title with the lastest beta version as they progress without a fix? I'm sure there are many others who'll be searching for a very similar thing 😃

@sraiteri Good suggestion, I've updated the title and issue description. As well as the SR.

Hey folks, could you try my possible fix of #820?

@ikesyo I tried out your possible fix branch with the codebase I work on that had ~185 errors before. With the branch/PR I no longer get these errors, or at least the compiler didn’t get to it. Instead I got two different errors:

  • to(_:description:)' is deprecated: Use Predicate instead

    • For a statement like expect(status).to(equal(.pending(inviteId: "foo"))) where status is an enum that conforms to Equatable and pending is a case of that enum.

  • Cannot convert value of type 'Double' to expected argument type 'DispatchTimeInterval'

    • For a statement like toEventually(..., timeout: 3.0).

Note that I was using Nimble 8.1.2 before using the branch, so maybe it’s related to the 8->9 version change?

so maybe it’s related to the 8->9 version change?

@rastersize Exactly, those are not related to #820.

@ikesyo I also tried out your branch, and I also am unsure of what is expected behavior in 9.0 (I'm coming from 8.0.7). But I've observed these breakages:

  • calling a throwing function inside expect no longer works:
    expect(try Blah.throwBlah()).to(equal("")) // Call can throw, but it is executed in a non-throwing autoclosure
  • calls like: expect(something.that.returns.a.bool(), file: file, line: line).toEventually(beTrue()) fail because the expression is now expected after file and line params, not before.
  • calls like this result in compilation error: extra trailing closure passed in call:
expect(file, line: line) { () -> T in
                    return try something()
          }.toNot(throwError())

I understand the iOS 14 release was announced just today, and am of course not expecting miracles! But are you able to provide any guidance on when we should expect to see a compatible Nimble release? That would greatly help me, and hopefully others, plan their iOS 14 updates. Thank you!

As a workaround, adding this function got my tests to run on Nimble 8.x using Xcode 12 GM.

public func expect<T>(_ expression: @escaping () throws -> T?) -> Expectation<T> {
    Nimble.expect(expression: expression)
}

calling a throwing function inside expect no longer works:

That is due to #821 and I now understand that the change was not correct. I'll revert it.

calls like: expect(something.that.returns.a.bool(), file: file, line: line).toEventually(beTrue()) fail because the expression is now expected after file and line params, not before.

Could you please reorder arguments.

calls like this result in compilation error: extra trailing closure passed in call:

Adding file argument label should fix that: expect(file: file, ...).

Just wanted to note that we seem to be running into the same issue (both the "expect" ambiguity and the Illegal Instruction crash). Happy to help test this, thanks @ikesyo for your quick work here!

Thanks for your work @ikesyo !

I tested out https://github.com/Quick/Nimble/pull/821 and it resolved a lot of the issues I was facing. On a minor note I noticed that this no longer works

expect({
  if 1 == 1 {
    return .failed(reason: "this failed")
  }
  return .succeeded
}).to(succeed())

This can be fixed by returning a closure instead i.e. return { .succeeded } but I am curious if this regression can be reverted?

Thanks for your work @ikesyo !

I tested out #821 and it resolved a lot of the issues I was facing. On a minor note I noticed that this no longer works

expect({
  if 1 == 1 {
    return .failed(reason: "this failed")
  }
  return .succeeded
}).to(succeed())

This can be fixed by returning a closure instead i.e. return { .succeeded } but I am curious if this regression can be reverted?

@ketenshi Another workaround I found is by adding () -> ToSucceedResult in like so:

expect({ () -> ToSucceedResult in
  if 1 == 1 {
    return .failed(reason: "this failed")
  }
  return .succeeded
}).to(succeed())

Not sure if this behaviour is expected in Nimble 9.

Confirming the issue is still present on GM, so a high chance it will be still in the final release, and the changes in #820 still fix it.

Just released v9.0.0-rc.3 including the fix for this. Please try it!

@ikesyo - I tried the fix and it's working for me! Thanks a bunch

We tried the rc.3 build and we were able to resolve our issues as well. This is awesome, thanks for your work @ikesyo!

Was this page helpful?
0 / 5 - 0 ratings