Why is there a "Type of expression is ambiguous without a type annotation." error when using a ternary operator on inferred function types in Swift?

Hello - I'm a first time poster here so please let me know if I'm in the right category. I posted this question on StackOverflow today and it was suggested I post here for further discussion. See original question here.


I'm reading the official Swift Programming Language Documentation and writing the code snippet examples in Playgrounds. Currently, I'm on Function Types as Return Types which seemed pretty straightforward. I'm using Xcode 16.2

The following code snippet copied from the Function Types as Return Types section is as follows:

func stepForward(_ input: Int) -> Int {
    return input + 1
}
func stepBackward(_ input: Int) -> Int {
    return input - 1
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}

However, the following error is seen for the ternary operator for stepBackward (and stepForward): "Type of expression is ambiguous without a type annotation."

Screenshot of the error in Playgrounds

This is a clean Playgrounds workspace with the default Hello World removed to verify the behavior.

I'm able to fix this by writing explicit type annotation as follows:

let forwardFunc: (Int) -> Int = stepForward
let backwardFunc: (Int) -> Int = stepBackward

@MainActor func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? backwardFunc : forwardFunc
}

This is to my understanding a good practice, but the documentation in the Functions section does not make clear this would be required.

Did something change in Swift recently that would make the documentation out of date? Is writing an explicit type annotation in these cases required now? I would like to understand why in this context the type annotation is now required, in particular with this ternary operator using inferred function types.

2 Likes

Seems bugged. These all compile.

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
  if backward { stepBackward } else { stepForward }
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
  backward ? stepBackward as (_) -> _ : stepForward
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
  backward ? stepBackward : stepForward as (_) -> _
}

This version of the code from the nested functions section compiles fine. The problem comes only with externally-scoped functions.

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
  func stepForward(input: Int) -> Int { return input + 1 }
  func stepBackward(input: Int) -> Int { return input - 1 }
  return backward ? stepBackward : stepForward
}

There is another example that seems to compile fine. When the problematic line is split into two.

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    let res = backward ? stepBackward : stepForward
    return res
}

That code compiles error free on my machine using:

  • No imports
  • Swift 5.9 (using Swift 6 compiler)
    • Swift version 6.0.3 (swift-6.0.3-RELEASE)
    • Target: x86_64-unknown-linux-gnu
  • Arch Linux (6.12.10-arch1-1)
  • VS Code (1.96.2)

I also plugged it into Swift Fiddle (which runs on Linux) running Swift 6.0.2, 5.9 and 2.2 with no errors.

I even use this pattern in my production projects (running on a Linux server) with no problems.


That code also compiles error free on my intel iMac with Xcode 16.2, Swift 6.0.3, building for iOS 17.5, with and without importing UIKit.

My best guess is a Swift Playgrounds bug or outdated cache somewhere. I've never used Swift Playgrounds so I can't really give you any solutions that usually work with Xcode or VS Code (like cleaning the build or closing and reopening the project).

2 Likes

Even your original example works okay outside of playgrounds for me (whoops, started writing before RandomHashTags posted), so I'm going to guess it's something specific to the processing that happens for interactivity there. That could also explain the poor message, if the problem is being caused by injected code and therefore "proper" diagnostics can't find anything to attach to. Still, worth filing an issue report about: either this is expected behavior for playgrounds and the compiler should give a better diagnostic (and the docs updated to mention this, probably), or it's not expected behavior and they can fix it.

3 Likes

Thanks! I'm not familiar with how to file an issue or bug report but I would like to. Could you point me in the right direction?

I imagine there are plenty of folks who use Playgrounds to learn code or try examples so this would be helpful to address.

What exactly is "outside"? It's not just Playgrounds.

Project? Works.
Swift package? Doesn't work.

(So you can use this code in an app, but not a library.)

You could contact Apple directly or post about it on their developer forums: Swift Playgrounds | Apple Developer Forums .

It worked in my Swift Packages when I tested it.

Where did you get yours? I used this one, in Xcode Version 16.2 (16C5031c).

I upgraded to 16C5032a, and it's still broken in packages.

There we go. Alter the manifest to see how Swift 6 broke it.

Compiles:

// swift-tools-version: 5.9

Doesn't compile:

// swift-tools-version: 6.0
1 Like

Very strange. I am still on 16B40, and it works there in a fresh package (as well as on the command line with a direct swiftc invocation where I originally tested). Maybe there's been a regression, or maybe it depends on something else being imported. Regardless, someone should look into it!

Instructions on how to report an issue are available on the main site under Contributing: Swift.org - Contributing

2 Likes

I think the problem here is stepForward and stepBackward are implicitly @Sendable. Type checker failed to cast from @Sendable (Int)->Int to (Int) -> Int.

Adding @Sendable to return type works:

func chooseStepFunction2(backward: Bool) -> @Sendable (Int) -> Int {
    return backward ? stepForward : stepBackward
}


This also works:

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    let sendableFunc = backward ? stepBackward : stepForward
    return sendableFunc // casts `@Sendable (Int)->Int` to `(Int) -> Int`
}


Seems like this implicit cast also doesn't happen in somewhere else like this:

4 Likes

Can confirm, Swift 6 language mode breaks it and adding @Sendable to the return type fixes it. This is definitely worthy of a GitHub issue for swiftlang/swift.

4 Likes

Other things which compile :person_shrugging::

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
  (backward ? stepBackward : stepForward) as @Sendable (_) -> _
}
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
  if backward { stepBackward as @Sendable (_) -> _ }
  else { stepForward as (_) -> _ }
}
1 Like

Thanks for the help all! And for the additional context and data points of verification. I will post an update once I file a bug/issue.

3 Likes

Update: I opened an issue, hopefully we get a fix soon (or an explanation). Swift 6 fails to cast inferred function types as return types from implicit @Sendable on return statement · Issue #78769 · swiftlang/swift · GitHub

4 Likes