I also found some initializer parameters do not follow the borrowing-semantics rule
These initializers are the unsafe part of Span's implementation. Span's Escapable fields can be initialized without any lifetime dependency.
Similarly, with this example:
struct A: ~Escapable {
let value: Int
}
Direct assignment to A.value doesn't require any dependency because Int is Escapable. It's the same reason that this works:
extension A {
@_lifetime(immortal)
init() {
self.value = 3
}
}
The initializer of a type whose fields are all Escapable only specifies a lifetime requirement to enforce safety on the caller side. The non-Escapability of a data type like Span is first enforced in the code that calls into initializer.
In your next example, you are calling into an initializer with a borrowed value:
@_lifetime(borrow value)
init(_ value: consuming Int) { // ❌ lifetime-dependent variable 'self' escapes its scope
// note: it depends on the lifetime of variable 'value'
self.init(value: value)
}
Here, you're running into the strangeness of depending on a BitwiseCopyable value. The semantics of an integer value doesn't specify any ownership or lifetime. So, when you ask for that dependency, the compiler creates local borrow scope for the variable in the caller, giving it a sort of temporary lifetime. This makes it reasonably easy to build non-Escapable types from UnsafePointers. But it also tends to expose the compiler implementation in the form of overly strict diagnostics.
The diagnostic above is an artifact of the compiler. Since you're allowed to reassign value, it's internally modeled as a separate local variable that happens to be initialized with a copy of the argument. The call to self.init temporarily borrows that local variable. We could teach the compiler to look through the copied variable assignment in this case, it just doesn't fall out of the current implementation.
EDIT: I think borrowing dependency on a consuming value should be illegal:
@_lifetime(borrow value) // error: invalid use of borrow dependence with consuming ownership
func foo(_ value: consuming Int)
That defines away the confusing diagnostic above. This is already illegal for nontrivial types, but we seem to have a special handling for BitwiseCopyable, which probably isn't actually desirable.
EDIT #2: We allow this...
@_lifetime(borrow value)
func foo<T: BitwiseCopyable(_ value: consuming T)
As a special case because it's common to pass an UnsafePointer into a Span-like initializer. consuming is the default ownership for initializer parameters, and we don't want to force APIs to explicitly borrow a pointer (that wouldn't make sense). The fix here is to cleanup our implementation of ownership in SIL before performing lifetime diagnostics, then you won't end up with the above error 'self' escapes its scope.