Unexpected downcast of Generic when using optional self

Example:

// Calling this function
func resolve<Service: Sendable>() async throws -> Service

// Specify that we want service "Foo"
// Unexpected Generic cast to <Optional(Foo)>
let service: Foo = try await self?.servicesProvider.resolve()

// This middle layer forces Generic to resolve as <Foo>
actor ServicesProvider {
  var foo: Foo {
    get async throws {
      try await resolve()
    }
  }
}
// Safe resolve where Generic always converts to <Foo>
let service = try await self?.servicesProvider.foo

I could not find anywhere in the the-swift-programming-language/generic that it's the Caller and not the Definition that casts Generic to a function.

// Would expect the definition of "let service: Foo" to be responsible for Generic casting
let service: Foo = try await self?.servicesProvider.resolve()

let service: Foo // <-- Expect this to cast to "func resolve<Foo>() -> async throws Foo"
= try await self?.servicesProvider.resolve() // <-- actually the Caller casts Generic which converts to  Optional<Foo> in the resolve func

This is not true. Why do you think it?

Optional chaining is just sugar for flatMap*. It always returns an Optional. self? isn't special, but it is an Optional.

actor A {
  func f() {
    Task { [weak self] in
      // These are both `Foo?`, not `Foo`.
      let service1 = try await self.asyncFlatMap { try await $0.servicesProvider.resolve() as Foo }
      let service2 = try await self.asyncFlatMap { try await $0.servicesProvider.foo }
    }
  }

  let servicesProvider = ServicesProvider()
}

enum Foo { }

actor ServicesProvider {
  var foo: Foo {
    get async throws { try await resolve() }
  }

  func resolve<Service: Sendable>() async throws -> Service { fatalError() }
}

* There is no async version of flatMap in the standard library.

extension Optional {
  func asyncFlatMap<Error, Transformed: ~Copyable>(
    _ transform: (Wrapped) async throws(Error) -> Transformed?
  ) async throws(Error) -> Transformed? {
    guard let self else { return nil }
    return try await transform(self)
  }
}

I recommend just not using self?.

guard let self else { return }
let service: Foo = try await servicesProvider.resolve()
1 Like

Yes with the unintuitive generic downcast we refactored to never use generic functions with optional self. I would still like to try to understand this behaviour so let me further specify why I'm confused.

I did not refer to the return value as yes that's Optional Foo i referred to the Generic Type casted in resolve() which looks like this: func resolve<Service: Sendable>() async throws -> Service

Example:

@MainActor
final class AppFooViewModel: ObservableObject {
  @Published private(set) var state = .resolving
  private let servicesProvider: any ServicesProvider
  private var fooStreamTask: Task<Void, any Error>?
  
  init(services: ServicesProvider) {
    servicesProvider = services
    resolveServices()
  }
  deinit { fooStreamTask?.canel() }

  func resolveServices() {
    fooStreamTask?.cancel()
    fooStreamTask = Task { [weak self] in
      do {
        // ### Example 1. Does not resolve <Service: Sendable>  
        // - Compare this downcast
        guard let foo: Foo = try await self?.servicesProvider.resolve() else {...}

        // ### Example 2.  Resolves <Service: Sendable>
        // - With this "reference downcast"
        guard let foo = try await self?.servicesProvider.foo else {...}
        
        // (Legacy code) Reason for not guarding strong self to resolve service
        // Async stream setup...
        for await bar in foo.barStream {
          guard let self else { return }
          self.state = .active(bar.fooStep)
        }
      } catch {
        await Fallback.missingService(Foo.self)
      }
    }
  }
}

In these two examples I'm confused to why Example 2 works but not Example 1 when
in both cases the resolve() is coming from an optional wrapper self?

  • Example 1 does not downcast <Foo: Sendable> to resolve() when:
    • Type is defined by a parameter clause "let foo: Foo ="
  • Example 2 correctly downcasts <Foo: Sendable> to resolve() when:
    • Type is defined in the initialized clause " = try await self?.servicesProvider.foo"

Looking at the ServicesProvider protocol:

protocol ServicesProvider {
  var servicesResolver: ServicesResolver { get }
  func resolve<Service: Sendable>() async throws -> Service {}
}

extension ServicesProvider {
  // This is what makes Example 2 safely resolve Generic as:
  // resolve<Foo: Sendable>() async throws -> Foo {}
  var foo: Foo {
    get async throws { try await resolve() }
  }

  func resolve<Service: Sendable>() async throws -> Service {
    try await servicesResolver.resolve()
  }
}

Describing the difference in AppServicesResolver:

final actor AppServicesResolver: ServicesResolver {
  func resolve<Service: Sendable>() async throws -> Service {
    let name = String(describing: Service.self)
    // ### Example 1:
    // Service.self == ((Foo & AnyObject)?.Type) (Foo & AnyObject)?
    // name == "Optional<Foo>"
    // ###
    
    // ### Example 2:
    // Service.self == ((Foo & AnyObject).Protocol) Foo & AnyObject
    // name == "Foo"
    // ###

    guard let service = try await resoveBy(name: name) as? Service else {
      // Example 1: No Service named "Optional<Foo>"
      throw ServiceProviderError.couldNotResoveBy(name: name)
    }
    // ...
    return service
  }
  
  private func resolveBy(name: String) async throws -> any Sendable {...}
}

Sorry for long code when simply trying to differentiate the distinction between defining Type on the parameter side contrary on the initialize side when calling a generic function.

Similar distinction could be summarized in short with asyncFlatMap where
both examples expect to return Optional Foo but only the foo2 is successfully resolved.

// Non working result similar to Example 1.
guard let foo1: Foo = try await self.asyncFlatMap({ try await $0.servicesProvider.resolve() }) else {...}
// Working result similar to Example 2.
guard let foo2 = try await self.asyncFlatMap({ try await $0.servicesProvider.resolve() as Foo }) else {...}

In my mind the func resolve<Service: Sendable>() async throws -> Service should have gotten the "Defined Type" in both cases -> Foo and not be affected by any "wrappers".

Why does Swift handle the Type definition differently in these cases?
This distinction is what I could not find in "the-swift-programming-language/generic"

Edit:
Also what is the logic here if Service would resolve:

func resolve<Service: Sendable>() async throws -> Service {...}
// ### Example 1:
// Service.self == ((Foo & AnyObject)?.Type) (Foo & AnyObject)?
// name == "Optional<Foo>"
// Would it be correct that T == <Optional<Service>> here?
// ###
// I'm interpreting that resolve would look like this:
func resolve<Optional<Service>>() async throws -> <Optional<Service>> {...}
// What would happen here when Foo != Optional<Foo>
guard let foo: Foo = try await self?.servicesProvider.resolve() else {...}

Using this code above, in the Task I posted, does what you want, as does:

guard let explicit: Foo = try await self?.servicesProvider.resolve() else { return }
guard let cast = try await self?.servicesProvider.resolve() as Foo? else { return }

I would try it your more complex example, but you haven't pasted enough code to compile. It would be useful if you could whittle things down to a minimal reproducible example.

Tnx for the help!

Here is an fast example to paste in ContentView on a fresh App.

import SwiftUI

struct ContentView: View {
  @StateObject private var viewModel: ViewModel

  init() {
    _viewModel = StateObject(wrappedValue: ViewModel())
  }

  var body: some View {
    VStack {
      Image(systemName: "globe")
        .imageScale(.large)
        .foregroundStyle(.tint)
      Text("Hello, world!")
    }
    .padding()
    .task {
      await viewModel.fetchData()
    }
  }
}

@MainActor
final class ViewModel: ObservableObject {
  let servicesResolver = ServicesResolver()

  func fetchData() async {
    // Registering implementation Foo as FooService
    await servicesResolver.register(serviceType: FooService.self, Foo())

    Task { [weak self] in

      // DOES NOT WORK ❌
      do {
        if let service: FooService = try await self?.servicesResolver.resolve() {
          print("Got the service \(service)")
        } else {
          print("Service not found")
        }
      } catch {
        print("Got error \(error)")
      }

      // WORKS ✅
//      do {
//        if let service: FooService = try await self?.servicesResolver.foo {
//          print("Got the service \(service)")
//        } else {
//          print("Service not found")
//        }
//      } catch {
//        print("Got error \(error)")
//      }
    }
  }
}

// MARK: - ServicesResolver

actor ServicesResolver {
  private var services: [String: any Sendable] = [:]

  func register<Service: Sendable>(serviceType: Service.Type, _ service: Service) {
    let name =  serviceName(serviceType)
    services[name] = service
  }

  func resolve<Service: Sendable>() async throws -> Service {
    let name = serviceName(Service.self)

    print("Resolving service \(Service.self): name = \(name)")

    if let service = services[name] as? Service {
      return service
    } else {
      throw Errors.serviceNotFound
    }
  }

  private func serviceName(_ serviceType: (some Sendable).Type) -> String {
    String(describing: serviceType.self)
  }
  enum Errors: Error {
    case serviceNotFound
  }
}

// MARK: - Foo

protocol FooService: Sendable {}

actor Foo: FooService {}

extension ServicesResolver {
  var foo: FooService {
    get async throws {
      try await resolve()
    }
  }
}

#Preview {
  ContentView()
}

I see. The problem revolves around needing to have an unwrapped optional for its metatype string (which I don't recommend, but is workable). You can't get that with if let or guard let, which will perform optional promotion because of this:

My two best ideas for you, other than, again, never using self?, are:

if let self,
case let service: any FooService = try await servicesResolver.resolve() {
func resolve<Service: Sendable>(_: Service.Type = Service.self)

+

if let service = try await self?.servicesResolver.resolve((any FooService).self)

That said, your dictionary will never return nil. You can't unwrap the result. "("Service not found")" is not a correct representation of what's going on there. This is:

do {
  guard let self else { print("Self (not service) not found"); return }
  let service: any FooService = try await servicesResolver.resolve()
  print("Got the service \(service)")
} catch {
  print("Got error \(error)")
}
1 Like

Thank you :folded_hands:
This was the promotion I was confused about and now I see!

Also to answer my own question:

  • What would happen here when let foo: Foo != Optional<Foo>?
  • Answer: guard let of course handles this.
// This works fine when:
guard let service: FooService = try await self?.servicesResolver.resolve() else {...}
// Prints: "Got the service Foo"
print("Got the service \(service)")

// Hardcoding serviceName to "FooService":
func resolve<Service: Sendable>() async throws -> Service {
  let name = "FooService"
  guard let service = services[name] as? Service else {...}
  // Prints: "returning service: Optional(Foo) as? Optional<FooService>"
  print("returning service: \(service.self) as? \(Service.self)")
  return service
}
1 Like