nicklockwood / SwiftFormat

A command-line tool and Xcode Extension for formatting Swift code
MIT License
7.67k stars 623 forks source link

Hidden protocol requirements of DistributedActorSystem #1589

Open samalone opened 7 months ago

samalone commented 7 months ago

I've got some code that implements the DistributedActorSystem protocol. Apple's documentation says:

Note: Although the remoteCall methods are not expressed as protocol requirements in source, the compiler will provide the same errors as-if it was declared explicitly in this protocol.

The signature of the remoteCall method looks like:

    public func remoteCall<Act, Err, Res>(
        on actor: Act,
        target: RemoteCallTarget,
        invocation: inout InvocationEncoder,
        throwing: Err.Type,
        returning: Res.Type
    ) async throws -> Res where Act: DistributedActor, Act.ID == ActorID, Err: Error, Res: Codable {

SwiftFormat changes that to:

    func remoteCall<Act, Res>(
        on actor: Act,
        target: RemoteCallTarget,
        invocation: inout InvocationEncoder,
        throwing _: (some Error).Type,
        returning _: Res.Type
    ) async throws -> Res where Act: DistributedActor, Act.ID == ActorID, Res: Codable {

Is it practical to teach SwiftFormat about the hidden requirements of the DistributedActorSystem protocol so that it knows to leave the generic arguments alone?

nicklockwood commented 7 months ago

Is it practical to teach SwiftFormat about the hidden requirements of the DistributedActorSystem protocol so that it knows to leave the generic arguments alone?

It's not, since SwiftFormat only operates at a syntax level. However, AFAICT the changes SwiftFormat has made here have not altered the function signature, and it should still conform to the hidden protocol - is that not the case?

cc: @calda

calda commented 7 months ago

Err where Err: Error and some Error are treated the same by the compiler. Methods using the some syntax can implement protocol requirements using the non-some syntax and vice versa.

If this results in some sort of build failure, I'd be interested in seeing the sample code both before and after the transformation applied by SwiftFormat. If this is causing a build failure in this specific case, we can certainly consider special casing it, e.g. by hardcoding knowledge of this specific func with this specific set of generic arguments. (I'd want to see the build failure first though!)

nicklockwood commented 7 months ago

@calda (unrelated) shouldn't Res have been converted to some Codable here as well?

samalone commented 7 months ago

The function signatures before and after the transformation are in my original comment.

I suspect the issue is that SwiftFormat eliminated the second generic argument. Even though some Error might be equivalent conceptually, perhaps the compiler treats it as the third generic argument rather than the second?

The compiler output is:

/Volumes/Campfire/Projects/websocket-actor-system/Sources/WebSocketActors/WebSocketActorSystem.swift:76:20: error: class 'WebSocketActorSystem' is missing witness for protocol requirement 'remoteCall'
public final class WebSocketActorSystem: DistributedActorSystem,
                   ^
/Volumes/Campfire/Projects/websocket-actor-system/Sources/WebSocketActors/WebSocketActorSystem.swift:76:20: note: protocol 'DistributedActorSystem' requires function 'remoteCall' with signature:
func remoteCall<Act, Err, Res>(
    on actor: Act,
    target: RemoteCallTarget,
    invocation: inout InvocationEncoder,
    throwing: Err.Type,
    returning: Res.Type
) async throws -> Res
  where Act: DistributedActor,
        Act.ID == ActorID,
        Err: Error,
        Res: SerializationRequirement

public final class WebSocketActorSystem: DistributedActorSystem,
                   ^

/Volumes/Campfire/Projects/websocket-actor-system/Sources/WebSocketActors/WebSocketActorSystem.swift:76:20: Class 'WebSocketActorSystem' is missing witness for protocol requirement 'remoteCall'

If you want to play with this yourself, the project is available at https://github.com/samalone/websocket-actor-system. The remoteCall function is in Sources/WebSocketActors/WebSocketActorSystem.swift line 466. Just remember that my .swiftformat file has opaqueGenericParameters disabled, so you'll need to re-enable it.

samalone commented 7 months ago

@calda (unrelated) shouldn't Res have been converted to some Codable here as well?

The function body later passes Res.self to another function, which is probably why Res can't be replaced with some Codable.