You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Lifetime dependence is lost when an address-only ~Escapable value is stored into an Optional #93414
Actual: the first command compiles at -Onone and -O; at -Onone it prints 1, then 9 after the owner is mutated, then the contents of a freed buffer (0 here). The second command refuses the two direct shapes as expected (direct, and the optional parameter assigned straight to the optional variable), so an unwrapped and re-wrapped optional gets through where the plain assignment doesn't.
The shape that matters in practice: a non-frozen public ~Escapable type from a library built with -enable-library-evolution is address-only in its clients, and owner.withView { view in kept = view } compiles there whether or not the client enables the feature; against a @frozen type, or the same library built without library evolution, it's refused. In the SIL of the accepted closure the store goes through alloc_stack $Optional<View>, init_enum_data_addr, copy_addr, inject_enum_addr and a copy_addr [take] into the captured box, where the refused direct store is a single copy_addr.
// Swift 6.4 (swiftlang-6.4.0.34.1): an address-only ~Escapable value escapes its borrow through an Optional.
// swiftc -enable-experimental-feature Lifetimes repro.swift -o repro && ./repro
// swiftc -enable-experimental-feature Lifetimes -DNEGATIVE repro.swift (the shapes the compiler refuses)
struct View:~Escapable {letspan:Span<UInt8>lettag:Any=() // address-only, as a non-frozen public type from a library-evolution build is in its clients; drop this line and every shape below is refused
@_lifetime(copy span) init(_ span: Span<UInt8>){self.span = span }varfirst:UInt8{span[0]}}structOwner{varbytes:[UInt8]=[1,2,3,4]varview:View{@_lifetime(borrow self)get{View(bytes.span)}}func withView(_ body:(View)->Void){body(view)}func withOptionalView(_ body:(View?)->Void){body(view)}}func escape(){varowner=Owner()varkept:View?=nil
owner.withView{ view in kept = view } // compiles: the view outlives the lend
print("kept view after the lend:", kept!.first) // 1
owner.bytes[0]=9 // the owner is mutated while the view is held
print("kept view after the owner changed:", kept!.first) // 9: the view still aliases the owner
owner.withOptionalView{ view iniflet view { kept = view }} // compiles as well
do{letlocal=Owner(); kept = local.view } // compiles as well, no closure involved
print("kept view of a dead owner:", kept!.first) // reads freed memory
}escape()#if NEGATIVEfunc refused(_ owner:Owner){vardirect:View= owner.view
owner.withView{ view in direct = view } // error: lifetime-dependent variable 'direct' escapes its scope
varkept:View?=nil
owner.withOptionalView{ view in kept = view } // error: lifetime-dependent variable 'kept' escapes its scope
print(direct.first, kept!.first)}#endif
Expected behavior
Expected: the three stores into kept in escape() are refused with error: lifetime-dependent variable 'kept' escapes its scope, as they are when the tag field is removed from View (a loadable type) or when kept is a non-optional View.
Activity