1

I am trying to make an extension to a generic class which is constrained by the type of the generic. This is a simplification of the code I believe should work:

struct Thing<T> {
    var value: T
}

struct ContainsStringThing {
    var stringThing: Thing<String>
}

extension Thing where T == String {                           // <-- Error location
    func containedStringThing() -> ContainsStringThing {
        return ContainsStringThing(stringThing: value)
    }
}

However, I get the following error message:

Same-type requirement makes generic Parameter 'T' non-generic

I searched for a way to fix this and found a suggestion to use a protocol to constrain the extension instead of a type: link to article. After doing that I ended up with this:

protocol StringProtocol { }

struct Thing<T> {
    var value: T
}

struct ContainsStringThing {
    var stringThing: Thing<StringProtocol>
}

extension Thing where T: StringProtocol {
    func containedStringThing() -> ContainsStringThing {
        return ContainsStringThing(stringThing: self)         // <-- Error location
    }
}

Now it does let me constrain the extension but it shows a different error message:

Cannot convert value of type 'Thing<T>' to expected argument type 'Thing<StringProtocol>'

Basically now it knows that T conforms to protocol StringProtocol by itself, but it doesn't know it when referring to the whole object Thing<T>.

is there any workaround to this or should I submit it as an evolution proposal to the swift mailing list?

Note: All the code is tested on a playground, you can just copy and paste it to try it.

gabriellanata
  • 3,946
  • 2
  • 21
  • 27
  • The problem with your second code is that T does not conform to StringProtocol. It can't, because you have _no_ types that conform to StringProtocol! (A protocol is not a first-class type and cannot conform to itself.) – matt Jul 05 '16 at 22:07
  • The constrained extension should be declaring internally that T conforms to StringProtocol (in this instance). This means that you could assign T to anything that expects a type conforming to StringProtocol. By the same logic the type of the object which is Thing should also be equivalent to Thing, there is enough information to infer this. – gabriellanata Jul 05 '16 at 22:12

1 Answers1

0

As a workaround you can create a new instance of Thing with the value from self.

extension Thing where T: StringProtocol {
    func containedStringThing() -> ContainsStringThing {
        return ContainsStringThing(stringThing: Thing<StringProtocol>(value: self.value))
    }
}

In Swift 2.2 such extension constraint means that containedStringThing() function will be available for instance of Thing whose value type conforms to StringProtocol.

Would be nice if Swift compiler could infer type of self.value as StringProtocol. In such case you could just write ContainsStringThing(stringThing: self) instead of ContainsStringThing(stringThing: Thing<StringProtocol>(value: self.value)). You should definitely fill a proposal.

salabaha
  • 2,468
  • 1
  • 17
  • 18
  • Sadly this wouldn't work for me. As I said in the post this is a simplification of the problem, the Thing object is a lot more complex and just transferring the internal value itself would not be enough. – gabriellanata Jul 05 '16 at 22:02