Allow protocol vars to match derived types


(Mark Anders) #1

Consider the following (you can paste it in a Playground to see the error):

class Node { }

class Containable : Node{}

protocol Refers {

    var to : Node {get}

}

class Link : Refers {

    var to : Node

    init(n : Node) {

        to = n

    }

}

class Contains : Refers {

    var to : Containable

    init(c : Containable) {

        to = c

    }

}

This currently does not work because it seems that to adopt a protocol, the
type of protocol var must match exactly.

It would be great if objects could be said to adopt a protocol if the type
of the var is the type or a derived type.
This would allow me to treat the structure in a type safe way (i.e. only a
Containable can have a Contains relationship),
while me to have a set of Refers and iterate through each Node.

Is there a reason why the type must match exactly? Or could protocols be
enhanced to to allow matching
derived types, similar to assignment and func parameter rules?

Mark


(Joe Groff) #2

No fundamental reason, this just isn't something we've had time to implement. It should be straightforward to support.

-Joe

···

On Mar 7, 2016, at 8:12 AM, Mark Anders via swift-evolution <swift-evolution@swift.org> wrote:

Consider the following (you can paste it in a Playground to see the error):

class Node { }
class Containable : Node{}

protocol Refers {
    var to : Node {get}
}

class Link : Refers {
    var to : Node
    init(n : Node) {
        to = n
    }
}

class Contains : Refers {
    var to : Containable
    init(c : Containable) {
        to = c
    }
}

This currently does not work because it seems that to adopt a protocol, the type of protocol var must match exactly.

It would be great if objects could be said to adopt a protocol if the type of the var is the type or a derived type.
This would allow me to treat the structure in a type safe way (i.e. only a Containable can have a Contains relationship),
while me to have a set of Refers and iterate through each Node.

Is there a reason why the type must match exactly? Or could protocols be enhanced to to allow matching
derived types, similar to assignment and func parameter rules?


(Slava Pestov) #3

Consider the following (you can paste it in a Playground to see the error):

class Node { }
class Containable : Node{}

protocol Refers {
    var to : Node {get}
}

class Link : Refers {
    var to : Node
    init(n : Node) {
        to = n
    }
}

class Contains : Refers {
    var to : Containable
    init(c : Containable) {
        to = c
    }
}

You can use an associated type for this instead,

protocol Refers {
  associatedtype NodeType
  var to: NodeType { get }
}

I agree that your example should work -- the rules for patching method overrides and protocol witnesses are more stringent than they need to be. There's an interesting engineering challenge in generalizing the logic and also cleaning it up to share as much code as possible with the subtype matching code in the constraint solver.

Slava

···

On Mar 7, 2016, at 8:12 AM, Mark Anders via swift-evolution <swift-evolution@swift.org> wrote:

This currently does not work because it seems that to adopt a protocol, the type of protocol var must match exactly.

It would be great if objects could be said to adopt a protocol if the type of the var is the type or a derived type.
This would allow me to treat the structure in a type safe way (i.e. only a Containable can have a Contains relationship),
while me to have a set of Refers and iterate through each Node.

Is there a reason why the type must match exactly? Or could protocols be enhanced to to allow matching
derived types, similar to assignment and func parameter rules?

Mark
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Howard Lovatt) #4

Looks like a compiler bug to me.

···

On Tuesday, 8 March 2016, Mark Anders via swift-evolution < swift-evolution@swift.org> wrote:

Consider the following (you can paste it in a Playground to see the error):

class Node { }

class Containable : Node{}

protocol Refers {

    var to : Node {get}

}

class Link : Refers {

    var to : Node

    init(n : Node) {

        to = n

    }

}

class Contains : Refers {

    var to : Containable

    init(c : Containable) {

        to = c

    }

}

This currently does not work because it seems that to adopt a protocol,
the type of protocol var must match exactly.

It would be great if objects could be said to adopt a protocol if the type
of the var is the type or a derived type.
This would allow me to treat the structure in a type safe way (i.e. only a
Containable can have a Contains relationship),
while me to have a set of Refers and iterate through each Node.

Is there a reason why the type must match exactly? Or could protocols be
enhanced to to allow matching
derived types, similar to assignment and func parameter rules?

Mark

--
-- Howard.


(Mark Anders) #5

So it seems like you would expect it to work as I thought it should. However, from reading the language reference
there is no mention of whether derived types are conforming types and there are no examples shown that demonstrate this either.

Can someone who knows verify whether this is a bug, or an oversight or as designed?

The workaround for me is to move everything in the base class which neuters the type system somewhat.

Mark

···

On March 7, 2016 at 3:53:37 PM, Howard Lovatt (howard.lovatt@gmail.com) wrote:

Looks like a compiler bug to me.

On Tuesday, 8 March 2016, Mark Anders via swift-evolution <swift-evolution@swift.org> wrote:
Consider the following (you can paste it in a Playground to see the error):

class Node { }
class Containable : Node{}

protocol Refers {
var to : Node {get}
}

class Link : Refers {
var to : Node
init(n : Node) {
to = n
}
}

class Contains : Refers {
var to : Containable
init(c : Containable) {
to = c
}
}

This currently does not work because it seems that to adopt a protocol, the type of protocol var must match exactly.

It would be great if objects could be said to adopt a protocol if the type of the var is the type or a derived type.
This would allow me to treat the structure in a type safe way (i.e. only a Containable can have a Contains relationship),
while me to have a set of Refers and iterate through each Node.

Is there a reason why the type must match exactly? Or could protocols be enhanced to to allow matching
derived types, similar to assignment and func parameter rules?

Mark

--
-- Howard.


(David Hart) #6

If it’s an obvious omission, is it better to have it as a bug than a formal proposal?

···

On 08 Mar 2016, at 00:16, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 7, 2016, at 8:12 AM, Mark Anders via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Consider the following (you can paste it in a Playground to see the error):

class Node { }
class Containable : Node{}

protocol Refers {
    var to : Node {get}
}

class Link : Refers {
    var to : Node
    init(n : Node) {
        to = n
    }
}

class Contains : Refers {
    var to : Containable
    init(c : Containable) {
        to = c
    }
}

This currently does not work because it seems that to adopt a protocol, the type of protocol var must match exactly.

It would be great if objects could be said to adopt a protocol if the type of the var is the type or a derived type.
This would allow me to treat the structure in a type safe way (i.e. only a Containable can have a Contains relationship),
while me to have a set of Refers and iterate through each Node.

Is there a reason why the type must match exactly? Or could protocols be enhanced to to allow matching
derived types, similar to assignment and func parameter rules?

No fundamental reason, this just isn't something we've had time to implement. It should be straightforward to support.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Slava Pestov) #7

I would suggest a formal proposal because its a bit involved I think. It should ideally match the subtyping rules, so

- A matches B if A is a subclass of B
- A matches P if A conforms to P
- A matches A?
- A! matches A? and vice versa
- T1 -> U1 matches T2 -> U2 if T2 is a subtype of T1 and U1 is a subtype of U2
- some rules for ‘throws’ functions

I think there might be some tricky cases with associated type inference. We should be careful not to break any existing code.

Also a related proposal would be to allow enum cases to witness static method requirements, and methods to witness property requirements of function type and vice versa, but these seem less useful.

Code for matching witnesses is in TypeCheckProtocol.cpp, and override matching is elsewhere (I think Decl.cpp in lib/AST/)? Take a look if you’re curious about what the rules are today.

Slava

···

On Mar 7, 2016, at 11:06 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

If it’s an obvious omission, is it better to have it as a bug than a formal proposal?

On 08 Mar 2016, at 00:16, Joe Groff via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Mar 7, 2016, at 8:12 AM, Mark Anders via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Consider the following (you can paste it in a Playground to see the error):

class Node { }
class Containable : Node{}

protocol Refers {
    var to : Node {get}
}

class Link : Refers {
    var to : Node
    init(n : Node) {
        to = n
    }
}

class Contains : Refers {
    var to : Containable
    init(c : Containable) {
        to = c
    }
}

This currently does not work because it seems that to adopt a protocol, the type of protocol var must match exactly.

It would be great if objects could be said to adopt a protocol if the type of the var is the type or a derived type.
This would allow me to treat the structure in a type safe way (i.e. only a Containable can have a Contains relationship),
while me to have a set of Refers and iterate through each Node.

Is there a reason why the type must match exactly? Or could protocols be enhanced to to allow matching
derived types, similar to assignment and func parameter rules?

No fundamental reason, this just isn't something we've had time to implement. It should be straightforward to support.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Mark Anders) #8

Slava/Joe, thanks for clarifying the issue and Slava for providing more depth on what needs to happen. Like David, I was unsure if this was just a bug or an enhancement.

Out of curiosity, you mention some related issues, and I just ran into something similar and wonder if it’s related too.

class A {}
class B : A {}

var arrayOfA = [A]()
let arrayOfB : [B] = [B()]
arrayOfA.appendContentsOf(arrayOfB)

This fails to compile, though doing arrayOfA.append(B()) works as I would expect.

Is it related? In general, it seems that Swift’s type system is stricter than I would expect.

Do these types of issues seem in-scope for Swift 3?

Mark

···

On March 8, 2016 at 1:22:13 AM, Slava Pestov via swift-evolution (swift-evolution@swift.org) wrote:

I would suggest a formal proposal because its a bit involved I think. It should ideally match the subtyping rules, so

- A matches B if A is a subclass of B
- A matches P if A conforms to P
- A matches A?
- A! matches A? and vice versa
- T1 -> U1 matches T2 -> U2 if T2 is a subtype of T1 and U1 is a subtype of U2
- some rules for ‘throws’ functions

I think there might be some tricky cases with associated type inference. We should be careful not to break any existing code.

Also a related proposal would be to allow enum cases to witness static method requirements, and methods to witness property requirements of function type and vice versa, but these seem less useful.

Code for matching witnesses is in TypeCheckProtocol.cpp, and override matching is elsewhere (I think Decl.cpp in lib/AST/)? Take a look if you’re curious about what the rules are today.

Slava

On Mar 7, 2016, at 11:06 PM, David Hart via swift-evolution <swift-evolution@swift.org> wrote:

If it’s an obvious omission, is it better to have it as a bug than a formal proposal?

On 08 Mar 2016, at 00:16, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Mar 7, 2016, at 8:12 AM, Mark Anders via swift-evolution <swift-evolution@swift.org> wrote:

Consider the following (you can paste it in a Playground to see the error):

class Node { }
class Containable : Node{}

protocol Refers {
var to : Node {get}
}

class Link : Refers {
var to : Node
init(n : Node) {
to = n
}
}

class Contains : Refers {
var to : Containable
init(c : Containable) {
to = c
}
}

This currently does not work because it seems that to adopt a protocol, the type of protocol var must match exactly.

It would be great if objects could be said to adopt a protocol if the type of the var is the type or a derived type.
This would allow me to treat the structure in a type safe way (i.e. only a Containable can have a Contains relationship),
while me to have a set of Refers and iterate through each Node.

Is there a reason why the type must match exactly? Or could protocols be enhanced to to allow matching
derived types, similar to assignment and func parameter rules?

No fundamental reason, this just isn't something we've had time to implement. It should be straightforward to support.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Joe Groff) #9

This one seems like a plain bug to me, since it should be possible to convert arrayOfB to [A] and hand the [A] off to appendContentsOf.

-Joe

···

On Mar 8, 2016, at 1:15 PM, Mark Anders via swift-evolution <swift-evolution@swift.org> wrote:

Slava/Joe, thanks for clarifying the issue and Slava for providing more depth on what needs to happen. Like David, I was unsure if this was just a bug or an enhancement.

Out of curiosity, you mention some related issues, and I just ran into something similar and wonder if it’s related too.

class A {}
class B : A {}

var arrayOfA = [A]()
let arrayOfB : [B] = [B()]
arrayOfA.appendContentsOf(arrayOfB)

This fails to compile, though doing arrayOfA.append(B()) works as I would expect.


(Mark Anders) #10

Thanks Joe, bug submitted!

Mark

···

On March 8, 2016 at 2:27:57 PM, Joe Groff (jgroff@apple.com) wrote:

On Mar 8, 2016, at 1:15 PM, Mark Anders via swift-evolution <swift-evolution@swift.org> wrote:

Slava/Joe, thanks for clarifying the issue and Slava for providing more depth on what needs to happen. Like David, I was unsure if this was just a bug or an enhancement.

Out of curiosity, you mention some related issues, and I just ran into something similar and wonder if it’s related too.

class A {}
class B : A {}

var arrayOfA = [A]()
let arrayOfB : [B] = [B()]
arrayOfA.appendContentsOf(arrayOfB)

This fails to compile, though doing arrayOfA.append(B()) works as I would expect.

This one seems like a plain bug to me, since it should be possible to convert arrayOfB to [A] and hand the [A] off to appendContentsOf.

-Joe