Confusing diagnostic: "Cannot specialize a non-generic definition" on String.map<T>

My code:

aString.map {            // 🛑 Cannot infer return type for closure with multiple statements; add explicit type to disambiguate
    if shouldWrap {
        return tagStart + String($0) + tagEnd }
    else {
        return String($0)
    }
}

To close the return type inference hole I thought it would be simplest to just specify the type parameter T, but then I get this error message:

aString.map<String> {    // 🛑 Cannot specialize a non-generic definition

But the declaration of String.map looks like this:

public func map<T>(_ transform: (Character) throws -> T) rethrows -> [T]

I'm not sure but I think that error message is wrong. This is a generic definition, is it not? And if so, why can't I specialize it?

I also tried adding the parentheses in case it was a trailing closure issue but it is not so.

For now, I wrote out the closure signature like so:

char -> String in

Or use ternary:

aString.map {
    shouldWrap ? tagStart + String($0) + tagEnd : String($0)
}

Yeah, that fixes the original error by changing the "complex closure" back to a simple one. :slightly_smiling_face:

There is no syntax for explicitly specifying type arguments to a function. Instead, you're supposed to use type inference. From Generics.rst:

We require that all type parameters for a generic function be deducible. We introduce this restriction so that we can avoid introducing a syntax for explicitly specifying type arguments to a generic function, e.g.,:

var y : Int = cast<Int>(x) // not permitted: < is the less-than operator

This syntax is horribly ambiguous in C++, and with good type argument deduction, should not be necessary in Swift.


This is a good solution, and probably the one I'd use. Another possible solution is to use the return type to aid type deduction:

let processedStrings: [String] = aString.map { ... }

With that being said, I think the diagnostic for aString.map<String> { could be made a bit more clear. Perhaps Function calls do not allow explicit type arguments could be a better message?

5 Likes

The bad diagnostic appears to be related to literals:

extension ExpressibleByIntegerLiteral {
    func f<T>(_ t: T) { }
}
let s = ""
let i = 0
i.f<String>(s)    // Cannot explicitly specialize a generic function
1.f<String>(s)    // Cannot specialize a non-generic definition
3 Likes

Thanks for the info! That rationale kind of makes sense to me, even though it's a bit inconsistent that I'm allowed to use the explicit syntax with type names like Set<Int>.

Another possible solution is to use the return type to aid type deduction

I actually also have .joined() chained in my code so that doesn't work.

Perhaps Function calls do not allow explicit type arguments could be a better message?

Yes, that would be better!

Hm, in my code I'm actually not using a literal.

Looks like you can trigger the same bad diagnostic from something that just doesn't type check properly...

let s = ""
let i = 2
// String.f doesn't exist
s.f<Int16>(i) { }