When a function returns a non-escapable type that copies the lifetime dependencies of an input, the copy lifetime annotation is used:
struct NE: ~Escapable { }
@_lifetime(copy ne)
func copies(_ ne: borrowing NE) -> NE { return copy ne }
Such a function that returns a copy will still type-check, even if the lifetime annotation is replaced with borrow ne or &ne (if the parameter is changed to be taken as inout):
@_lifetime(borrow ne)
func stillCopies(_ ne: borrowing NE) -> NE {
return copy ne
// OR, equivalently:
// return copies(ne)
}
@_lifetime(&ne)
func stillCopies2(_ ne: inout NE) -> NE {
return copy ne
}
This makes sense—creating a scoped dependency on a borrow of ne necessarily implies that anything ne in turn depends on (i.e. the dependencies get copied above) is still alive as well.
This behavior led me to believe that there should be a subtyping relation between such function types, where the type of copies would be a subtype of the type of stillCopies, since any function with a copied dependence could be used as one with a scoped dependence. However, this is not the case:
// does NOT typecheck
// error: cannot convert value of type '@_lifetime(copy 0) (borrowing NE) -> NE' to specified type '@_lifetime(borrow ne) (_ ne: NE) -> NE'
let clos : @_lifetime(borrow ne) (_ ne: NE) -> NE = copies;
A similar error arises when attempting to conform to a protocol written with a @_lifetime(borrow ne) annotation using a conformance annotated as @_lifetime(copy ne). Note that these are positions where sub-lifetime annotations are permitted (for instance, a protocol annotated as @_lifetime(copy a, copy b) may have a conformance annotated with just @_lifetime(copy a)
Am I incorrect in thinking that this subtyping relationship should hold? Or would this be sound, and my above observations are merely limitations of the current implementation?