swiftxcodegenericsvaporserver-side-swift

Why Xcode keeps substituting generics with '_'


I am making a server with Swift 5 and Vapor 3. When setting up a route I want to call a function from my controller that returns an optional like so:

//Person.swift

struct Person: Content {
    ...
}
//PersonController.swift

func update(_ request: Request) throws -> Future<Person> {
    let uuid = try request.parameters.next(UUID.self)

    return try request.content.decode(Person.self).flatMap { content in
        request.withPooledConnection(to: DatabaseIdentifier<PostgreSQLDatabase>.psql) { connection in
            /*
            *  No code completion beyond this point,
            *  even connection appears as type '_' instead of
            *  PostgreSQLConnection (not relevant to the question tho,
            *  just worth noting)
            */
            if content.lastName != nil {
                return connection.raw("Very long SQL query...")
                .binds([...])
                .first(decoding: Person.self)
            }

            return connection.raw("Other long SQL query")
            .binds([...])
            .first(decoding: Person.self)
        }
    }

}
router.put("people", UUID.parameter, use: personController.update)

But then I get this error

Cannot convert value of type '(Request) throws -> EventLoopFuture<Person?>' to expected argument type '(Request) throws -> _'

I see many instances when working with Vapor in which Xcode just gives up on autocompleting, and everything is just typed as _. Mostly inside closures that are used as callbacks. It is VERY annoying, and quite frankly I am unsure if it is caused because of Vapor, Swift, or Xcode. It is a huge PITA but it all gets resolved once I compile, the types just get sorted out. However in this case it is just not working.

So the question is: Why is does Xcode say the expected type is (Request) throws -> _ when the actual definition for Request.put(_:use:) requires a (Request) throws -> T and how does that make a difference between T being Future<Person> and Future<Person?>?


Solution

  • The .first method you are calling here:

    return connection.raw("Other long SQL query")
    .binds([...])
    .first(decoding: Person.self)
    

    Returns an Future<Optional<Person>>, or Future<Person?>. You route handler's return type is Future<Person>, so your return type is incorrect. But even if you did change the handler's return type, that wouldn't fix it.

    Your main problem is that you can't return an optional from a route handler, because in no way is Optional ever conformed to ResponseEncodable. You could add the conformance yourself if you wanted to.

    If you don't want to add the conformance, you can just use the .unwrap(or:) method after you decode the query result:

    return connection.raw("Other long SQL query")
    .binds([...])
    .first(decoding: Person.self)
    .unwrap(or: Abort(.notFound))
    

    This will check to see if the value in the future exists. If it does, then the value is passed on. Otherwise, the future chain receives the error you pass in and that will be returned instead.