Type Checker/Inference Issues with overrides

Hi,

Back Story, actual problem below

We've been trying to improve the experience for new-comers to server-side Swift and Vapor by providing a JavaScript style then option for futures. The idea is that this would work on any EventLoopFuture from SwiftNIO, whether the completion returned a Value or EventLoopFuture<Value> and whether the completion could throw or not. Essentially, this is 4 options matching map, flatMap, flatMapThrowing and a flatFlatMapThrowing, but can be condensed into 2 options - flatMapThrowing and flatFlatMapThrowing as they can handle completion handlers that either throw an error or don't

The Errors

I wrote two extensions for NIO's EventLoopFuture:

import NIO

extension EventLoopFuture {
    @inlinable
    public func then<NewValue>(file: StaticString = #file, line: UInt = #line, _ callback: @escaping (Value) throws -> NewValue) -> EventLoopFuture<NewValue> {
        self.flatMapThrowing(callback)
    }

    @inlinable
    public func then<NewValue>(file: StaticString = #file, line: UInt = #line, _ callback: @escaping (Value) throws -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
        return flatMap(file: file, line: line) { result in
            do {
                return try callback(result)
            } catch {
                return self.eventLoop.makeFailedFuture(error, file: file, line: line)
            }
        }
    }
}

These should handle all the cases we need. However the compiler is getting confused between the two. For example I have a route handler:

func testTryInMapHandler(_ req: Request) throws -> EventLoopFuture<User> {
    let updatedUser = try req.content.decode(User.self)
    return User.find(req.parameters.get("userID"), on: req.db)
        .unwrap(or: Abort(.notFound))
        .then { user in
            user.name = updatedUser.name
            user.username = updatedUser.username
            return user.save(on: req.db).then {
                try print("User ID is \(user.requireID())")
                return user
            }
    }
}

That works if only the first function in the extension is available, but fails if both are with the error Cannot convert return expression of type 'Optional<User>.WrappedType' (aka 'User') to return type 'EventLoopFuture<User>' on return user. I believe the compiler should be able to work this out.

This was tested with Xcode 11.4 release by the way

cc @tanner0101 @johannesweiss @MrLotU

3 Likes

cc @xedin @hborla :)

@0xTim The reason why it can't figure out which function to use is related to the fact that bodies of multi-expression closures are type-checked separately from enclosing context so body of the top level then has to say in what its type is going to be and apparently it picks a second overload.

You can try making this

user.name = updatedUser.name
user.username = updatedUser.username
return user.save(on: req.db).then {
    try print("User ID is \(user.requireID())")
    return user
}

a separate (local) function that way everything should type-check the way you expect it to.

Terms of Service

Privacy Policy

Cookie Policy