Failed to produce diagnostic for expression in u ? f : g, f:()->Int, g:()->Int

I was surprised when I was able to reproduce the error reported in this post by @freeux.

@main
enum Driver {
    static func main() async throws {
        let f = f_or_g (true)
        print (f ())
        
        let g = f_or_g (false)
        print (g ())
    }
}

func f () -> Int {
    return 2
}

func g () -> Int {
    return 3
}


#if false
    // Case 1: Does not compile
    func f_or_g (_ u: Bool) -> () -> Int {
        return u ? f : g
        // Error: Failed to produce diagnostic for expression
    }
#elseif true
    // Case 2: Compiles
    func f_or_g (_ u: Bool) -> () -> Int {
        if u {
            return f
        }
        else {
            return g
        }
    }
#else
    // Case 3: Compiles
    func f_or_g (_ u: Bool) -> @Sendable () -> Int {
        return u ? f : g
    }
#endif

Why does Case 1 not compile, but the other two cases do? Also, why is @Sendable needed in Case 3?

Note that Case 1 and Case 3 are almost identical except for the return type.

What's going on?

It’s a type checker bug. It finds two solutions, either you can convert the result of the ternary to strip off @Sendable, or you can convert each branch first. The two solutions then cannot be disambiguated, so it produces a diagnostic.

While this could be fixed by adding more hacks to solution ranking, that wouldn’t address the related performance problem this introduces, where type checking can take exponential time even when the expression doesn’t involve any overloading.

Instead I’ve been chipping away at the cases where we must attempt more than concrete type binding for a single type variable (for example here, we can just say the result of the ternary is @Sendable without considering the other possibility), which should eventually completely address these sorts of problems.

3 Likes

I don't understand it. The code in the linked thread that didn't compile is something like this:

    func f_or_g (_ u: Bool) -> ()  -> Int {
        return u ? f : g // This doesn't compile
    }

I think there is no @Sendable or convention involved in the code?

EDIT: or is function implicitly @Sendable and the closure returned by f_or_g isn't marked @Sendable, so there is convention indeed? If so, I think behaviors like this completely defeat Swift language's progressive disclosure purpose (not a blame for the designers, but just that progressive disclosure is a difficult goal).

Yeah, an unapplied function reference is @Sendable if all captures are @Sendable; in this case the functions are declared at the top level so they have no captures, and the rule applies vacuously.

Not sure that really applies here, a Sendable function type is a subtype of a non-Sendable function type, so the sendability of the function reference doesn’t matter if the user doesn’t mention @Sendable (except for bugs like this one).

1 Like

Thank you, @Slava_Pestov

I understand now. However, the diagnostic message could be improved because I get an informative error message if I write this:

func f_or_g (_ u: Bool) -> (Int) -> Int {
     return u ? f : g
     // Error: Cannot convert value of type '@Sendable () -> Int' to specified type '(Int) -> Int'
}

Now, it tells me that the type of the return-value expression is '@Sendable () -> Int', which is much, much more revealing than the message 'Failed to produce diagnostic for expression'. :slight_smile:

Ah, but in the original example, the code is valid and should be accepted. So any possible diagnostic you could emit would be misleading. It’s just a bug that needs to be fixed.

2 Likes