5

Take a very close look at the following:

// Note that this protocol can only be applied to reference types.
protocol Ref: class {
  var zibbles: Int { get set }
}

class Reference: Ref {
  var zibbles: Int = 42
}

// Note very carefully that we are NOT passing an
// instance, but a type itself.
func thwip<T: AnyObject>(into target: T.Type) {

}

// This compiles.
thwip(into: Reference.self)

// This fails to compile.
thwip(into: Ref.self) 

However rare the case may be, this is something the language should be able to do as a matter of completeness. The compiler knows that any instance of Ref must conform to AnyObject, so the type constraint on thwip should work, but it does not.

Note that if we remove the AnyObject constraint from thwip, it then compiles, but this defeats the purpose. I want to know whether the passed-in protocol has reference semantics or not.

// thwip without AnyObject
func thwip<T>(into target: T.Type) {
}

// Compiles, but useless to me
thwip(into: Ref.self)

Note that the following change also does not help us:

// Here we've changed class to AnyObject
protocol Ref: AnyObject {
  var zibbles: Int { get set }
}

This is because Ref: class and Ref: AnyObject are synonyms. You can confirm this by putting them both together:

protocol Ref: class, AnyObject {}

The compiler will warn you about redundant conformance, though it will still compile.

Note that there is a very similar, but better-known compiler error:

protocol Base {}

protocol Ref: Base {}

func thwip<T: Base>(into target: T.Type) {
}

// Does not compile
thwip(into: Ref.self)

However, the compiler gives completely different errors for this case versus the one I'm having a problem with. In my case, I get "Cannot invoke 'thwip' with an argument list of type '(type: Ref.Protocol)'". In the latter case, I get "In argument type 'Ref.Protocol', 'Ref' does not conform to expected type 'Base'".

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Gregory Higley
  • 15,923
  • 9
  • 67
  • 96
  • I think the protocol is abstract, so doesn't have anything to show in self. – Bista Oct 02 '18 at 05:45
  • Shouldn't it receive a `class` type and not a `protocol`? – Rakesha Shastri Oct 02 '18 at 05:46
  • I need it to receive a protocol type. If you remove the `AnyObject` constraint from `thwip`, it compiles, but then it defeats the purpose. I need to know whether the passed-in protocol has reference semantics or not. – Gregory Higley Oct 02 '18 at 05:47
  • As `protocol` can not conform to itself with one exception `Any` so `Ref.self` can not fulfill the concrete type requirement for `target` argument. One way is to change `AnyObject` to `Any` and inside `thwip` use control statement to check if the type is of `Ref` type as `if target is Ref.Type { print("Reference type") }` – Kamran Oct 02 '18 at 06:08
  • @Kamran: That won't work for me, unfortunately. I need to know whether the `Ref` protocol has reference semantics _at compile time_. Knowing it at runtime is useless to me. In addition, I don't care about the `Ref` protocol itself. I need to know whether _any_ protocol I might pass has reference semantics. – Gregory Higley Oct 02 '18 at 06:15
  • Just created a bug for this: https://bugs.swift.org/browse/SR-8893 – Gregory Higley Oct 02 '18 at 06:23
  • @Kamran In addition, we aren't talking here about a protocol conforming to itself. That has nothing to do with this. – Gregory Higley Oct 02 '18 at 06:27
  • Interesting... Maybe you can use type erasure to be able to do what you want, although it has extra boilerplate code... – J. Doe Oct 02 '18 at 07:03
  • @J.Doe Yeah, I'm experimenting with various options, including type erasure. – Gregory Higley Oct 02 '18 at 07:04
  • Ultimately the reason I want this is for my dependency resolution library, for the feature known as "KeyPath Injection". I want to make sure that registered injections have only reference semantics, since KeyPath Injection should only be used with reference types. (It currently is available for all types, but I'd like to change that.) https://github.com/Prosumma/Guise – Gregory Higley Oct 02 '18 at 07:06
  • 3
    The `Ref` protocol defines constraint that all types conforming to it must also conform to `AnyObject` (be a `class`). The protocol `Ref` itself does not conform to `AnyObject`. That's the problem here. The `:` operator for protocols doesn't mean conformance, it means inheritance. Your generic function can accept only class types. The protocol type is not a class type. It's a protocol. – Sulthan Oct 02 '18 at 07:27
  • @Sulthan: I'm afraid that's incorrect. Please read the question more carefully. Or if it is correct, then the Swift language needs some other way to constrain protocols as parameters to have reference semantics. – Gregory Higley Oct 02 '18 at 07:28
  • @Sulthan Try doing this a different way. If instead of `: class` I conform to some other protocol with `: OtherProtocol`, and then say `thwip(into target: T.Type)`, I get a completely different compiler error. – Gregory Higley Oct 02 '18 at 07:34
  • 1
    Well this is somewhat related to ___protocol doesn't conform to itself or any other type in it's hierarchy___. And as @Sulthan pointed out __The `:` operator for protocols doesn't mean conformance, it means inheritance__ you should not expect `func thwip(into target: T.Type)` can take a call as `thwip(into: Ref.self)`. To resolve the type constraint in generics, you need to have a **concrete** type not a meta type. More on this topic [here](https://stackoverflow.com/a/45239416/3687801) – nayem Oct 02 '18 at 09:09
  • OK, I'm convinced that this is related to Swift's lack of open existentials. That said, I still need a way _at compile time_ to distinguish between a protocol that has reference semantics and one that does not, but I suppose that is another question. – Gregory Higley Oct 02 '18 at 11:49
  • Although you get a different error message, this is the same issue as [protocols not conforming to themselves](https://stackoverflow.com/a/43408193/2976878). Though your specific example should be sound AFAIK, it's currently not supported. Btw it's worth noting that `AnyObject` *strictly* conforming to itself is supported – i.e `func foo(_ : T.Type) {}; foo(AnyObject.self)`. – Hamish Oct 02 '18 at 14:12
  • @Hamish: Do you know any way then to determine _at compile time_ whether a protocol has reference semantics without having an instance? – Gregory Higley Oct 02 '18 at 14:15
  • @Sulthan Turns out you were correct in the end. My apologies. – Gregory Higley Oct 02 '18 at 14:21
  • @GregoryHigley I'm afraid I don't know of a way to do that currently. I haven't looked into your use case in too much detail, so I could well be missing something, but couldn't you use an `inout` parameter for `resolve(into:)`? That should allow you to work with value types as well as reference types, removing the requirement of having to work with reference semantics. – Hamish Oct 02 '18 at 15:47
  • @Hamish. Unfortunately, that doesn't work with my most common use case, which is where the argument to `resolve(into:)` is `self`. – Gregory Higley Oct 02 '18 at 15:49
  • @GregoryHigley Ah right, I see. – Hamish Oct 02 '18 at 15:51
  • 2
    If Hamish doesn't know, nobody knows :( – J. Doe Oct 02 '18 at 16:54
  • I have solved it in a sense by forcing the use of `ReferenceWritableKeyPath`s, e.g., if I say `resolver.into(injectable: SomeProtocol.self).inject(\.someAttribute).register()`, it won't compile unless `\.someAttribute` is a RWKP. – Gregory Higley Oct 02 '18 at 17:57

0 Answers0