Swift is overly worried about me mixing up flatMap and compactMap

I would expect the following to compile, and I'd like to know if the warning and error are incorrect:

let a = [1, 2, 3, 4]
let b = a.flatMap { // WARNING: flatMap' is deprecated: Please use compactMap(_:) for the case where closure returns an optional value
    let (q, r) = $0.quotientAndRemainder(dividingBy: 3)
    return [q, r] // ERROR: Cannot convert return expression of type '[Int]' to return type 'String?'
}
print(b)

A workaround is to help the compiler by being more explicit about the closure type:

let a = [1, 2, 3, 4]
let b = a.flatMap { (i) -> [Int] in
    let (q, r) = i.quotientAndRemainder(dividingBy: 3)
    return [q, r]
}
print(b) // [0, 1, 0, 2, 1, 0, 1, 1]
3 Likes

Swift doesn't do type-inference over multiple statements in a closure, so instead it's checking the call to flatMap in one phase, deducing a type for the closure, and then doing a type-check of the closure body in a second phase.

I have no idea how it settled on (Int) -> String? as the type for the closure passed to flatMap, but that's what's happening.

4 Likes

It might be choosing the Collection.flatMap method from:

For the other Sequence.flatMap methods:

  • the first has /*, obsoleted: 5.0*/ commented out;
  • the second is obsoleted: 4 so can it be removed now?

Cc: @moiseev

1 Like

Sounds right. The typechecker prefers functions with the most concrete types, regardless of their availability.

As for the removal of functions that are obsoleted: 4 (of which we have a few, apparently) /cc @Ben_Cohen

1 Like

Ah, I had thought I had eliminated all these here but it looks like I missed one. BRB.

4 Likes

Hmm, is the conclusion that my initial example (repeated below) should compile?

let a = [1, 2, 3, 4]
let b = a.flatMap { // WARNING: flatMap' is deprecated: Please use compactMap(_:) for the case where closure returns an optional value
    let (q, r) = $0.quotientAndRemainder(dividingBy: 3)
    return [q, r] // ERROR: Cannot convert return expression of type '[Int]' to return type 'String?'
}
print(b)

Nah, it should not compile. Swift only does type inference if the closure has a single statement (i.e what John said). As yours spans multiple statements, you need to provide the type context explicitly, like in your 'workaround' example.

The removal of the obsoleted methods would simply result in a more sensible error message.

1 Like

Ok ... FWIW pressing ctrl+cmd+J (jump to definition) on flatMap in:

let a = [1, 2, 3, 4]
let b = a.flatMap { // WARNING: flatMap' is deprecated: Please use compactMap(_:) for the case where closure returns an optional value
    let (q, r) = $0.quotientAndRemainder(dividingBy: 3)
    return [q, r] // ERROR: Cannot convert return expression of type '[Int]' to return type 'String?'
}
print(b)

takes me to the flatMap of enum Optional.

(This happens with both Swift 4.2 and Swift 5 using the latest snapshot.)

Can anyone explain why/how Optional should have anything to do with this?


Wouldn't the removal of the obsoleted methods result in no optional-returning methods at all named flatMap (except eg Optional.flatMap which I assume should not be considered at all as a possible candidate), meaning the only relevant methods named flatMap left to choose from would have the expected signature?

What would the more sensible error message be (approximately)?

I believe it would say 'Unable to infer complex closure return type; add explicit type to disambiguate'.

1 Like

/cc @akyrtzi on this one.

It would, but we can only delete the methods obsoleted in Swift 3, because we no longer support the -swift-version 3 mode in the compiler. And the one causing troubles is still only deprecated in 4.1.

In Xcode 10.2 beta 1 I see a popup with additional related options on Optional but the first one does take you to Swift.Collection.Array, though whether it jumps to a visible declaration or not seems to depend on how the closure is written.

If you could file a Jira and provide more details like the configuration and screenshots, it would be helpful to track down.

2 Likes