Can I specify that an associatedtype is a function?

Can you show the exact signature of the function that's making that complaint?

Remove @escaping from my first code block above.

Oh wait, if I also remove the where clause the error goes away. So I guess you're right...

1 Like

The only two situations in the language where you get a non-escaping function type are if you either have a literal function type in parameter position, or a reference to a type alias with a function type in parameter position. Eg:

func foo(fn: () -> ()) {} // fn is non-escaping by default

typealias Fn = () -> ()
func foo(fn: Fn) {} // fn is non-escaping by default

In these cases, the compiler also allows you to state the @escaping attribute.

This is similar to your example:

func g(_: @escaping () -> ()) {}
  
protocol P {
  associatedtype A
}
  
struct S: P {
  typealias A = () -> ()
}

struct G<T: P> {} 
  
extension G where T == S {
  func f(_ fn: @escaping T.A) {
    g(fn)
  }
}

Now, f() in the extension references T.A in parameter position. T is concretely S, so T.A is interpreted as a concrete type reference S.A. Since A is a type alias with function type, T.A resolves to a non-escaping function type under the current rules, and we have to apply @escaping to pass the function to g().

However, if T.A is not concretely known to be a function type, eg, if you instead had the following:

// g() is just generic now
func g<T>(_: T) {}

extension G where T: P {  // no same-type requirement
  func f(_ fn: T.A) {
    g(fn)
  }
}

Then @escaping is an error when applied to T.A, because it's just a type parameter. It is also not necessary, because an abstract type parameter cannot be non-escaping.

There's actually an inconsistency here, because T in the below is never non-escaping:

struct G<T> {}

extension G where T == () -> () {
  func f(_ fn: T) {
    g(fn)  // fine
  }
}

But it can be explained away that T itself is not a reference to a concrete type alias; it's still just a type parameter. (I think perhaps the interaction between @escaping and type aliases is too magical, and we should always resolve type aliases as escaping function types, even when referenced from function parameter position. But that would be a breaking change that will require a language mode.)

2 Likes

In this case, the compiler can, fully statically, know that the argument to init(_:executing:) is a function type and needs to be @escaping because you've constrained Call == SyncCall and SyncCall.Callback is a function type.

2 Likes

Yeah, in retrospect it should have been obvious. When there's no indication at all that a parameter is going to be a function type, as in the non-specialized version of my initializer, then @escaping doesn't make sense. I guess what I missed was that when it turns out to be a function type, then it's implicitly escaping.

Yeah, the formal restriction that makes this all work out is that non-escaping function types cannot be substituted for type parameters; you can’t pass them around or store them anywhere “generically”.

1 Like

I think I narrowed the problem down to the minimum. It seems impossible to refer to an asynchronous typed throwing function, within a constrained function.

struct Container<Value> {
  let value: Value

  func getValue<Error>() -> Value
  where Value == @Sendable () async throws(Error) -> Void {
    value
  }

  func performValue<Error>() async throws(Error)
  where Value == @Sendable () async throws(Error) -> Void {
    try await value()
  }
}
let container = Container { @Sendable () async in }
await container.value() // Works fine.
_ = container.getValue() // Command SwiftCompile failed with a nonzero exit code
await container.performValue() // Compiles, but never returns.
1 Like