Search code examples
swiftgenericsambiguous

How do I disambiguate this call to a generic function with a function parameter in Swift?


I'm writing a parser class that expects to read a sequence of tokens in a specific order. Some productions in its grammar have non-terminals that are optional, so I was wanting to make a generic "maybe" function that could be passed the function responsible for parsing the non-terminal as a callback. Typically, the function would throw an error on failure, but since it is optional is some cases, the maybe function would suppress the error. However, Swift is providing the error "Type of expression is ambiguous without more context," and I can't figure out the correct set of casts and/or typings to disambiguate it.

Here is the minimal amount of code I was able to write to recreate the error:

public struct VariableDeclaration {
    public let identifier: Identifier
    public let type: String?
}

public struct Identifier { }

public class Parser {
    
    public func parseVariableDeclaration() throws -> VariableDeclaration {
        let identifier = try self.parseIdentifier()
        let type = self.maybe(self.parseType)
        return VariableDeclaration(identifier: identifier, type: type)
    }
    
    public func parseIdentifier() throws -> Identifier { return Identifier() }
    
    public func parseType() throws -> String { return "" }
    
    public func maybe<T>(_ callback: (Parser) -> () throws -> T) -> T? {
        do {
            return try callback(self)()
        }
        catch {
            return nil
        }
    }
}

And here are some of my failed attempts at disambiguating the problematic line:

let type: String? self.maybe(self.parseType)
let type = self.maybe(self.parseType) as String?
let type = self.maybe<String>(self.parseType)

Solution

  • The issue here is not the generic parameters. Your first and second attempt would tell the compiler what type T should be.

    The issue is the value you pass as callback, which has the following signature:

    (Parser) -> () throws -> T
    

    You are passing in self.parseType which has the following signature:

    () throws -> String
    

    What would work is using Self.parseType (notice the capital S) or Parser.parseType as value for callback.

    Alternatively, you could define maybe like this:

    public func maybe<T>(_ callback: (Parser) throws -> T) -> T? {
        do {
            return try callback(self)
        } catch {
            return nil
        }
    }
    

    And then call it like this:

    let type = self.maybe { try $0.parseType() }