Files
swift-mirror/test/SILOptimizer/lifetime_dependence/lifetime_dependence_closure.swift
Erik Eckstein 3243f87204 tests: remove the lit feature swift_in_compiler
Tests require the compiler built with SwiftCompilerSources since a long time.
So this lit feature was redundant anyway. We can remove it.
2026-06-30 14:47:56 +02:00

287 lines
9.3 KiB
Swift

// RUN: %target-swift-frontend %s -emit-sil \
// RUN: -o /dev/null \
// RUN: -verify \
// RUN: -sil-verify-all \
// RUN: -disable-availability-checking \
// RUN: -module-name test \
// RUN: -enable-experimental-feature Lifetimes
// REQUIRES: swift_feature_Lifetimes
struct NCInt: ~Copyable {
var value: Int
init(_ value: Int) { self.value = value }
}
struct NEInt : ~Escapable {
let value: Int
@_lifetime(borrow borrowed)
init(borrowed: borrowing NCInt) {
self.value = borrowed.value
}
}
extension NCInt {
var neInt: NEInt {
borrowing get {
NEInt(borrowed: self)
}
}
}
func ncint_get_neint_mutable_local() {
var ncInt = NCInt(731)
do {
// Begin read access ncInt
var neInt = ncInt.neInt
// Capture neInt in a nonescapable closure.
assert(ncInt.value == neInt.value)
// End read access ncInt
// Begin read access ncInt
// Fully reassign neInt
neInt = ncInt.neInt
// Destroy neInt
// End read access ncInt
}
ncInt.value = 1
}
struct NE: ~Escapable {
let p: UnsafeRawPointer?
public func condition() -> Bool {
return true
}
}
func getLocalPointer() -> UnsafeRawPointer? {
return nil
}
func takePicker(picker: @_lifetime(copy ne0, copy ne1) (_ ne0: NE, _ ne1: NE) -> NE) {
let x = NE(p: getLocalPointer())
let y = NE(p: getLocalPointer())
_ = picker(x, y)
}
func takeOnePicker(picker: @_lifetime(copy ne0) (_ ne0: NE, NE) -> NE) {
let x = NE(p: getLocalPointer())
let y = NE(p: getLocalPointer())
_ = picker(x, y)
}
func takeCapturePicker(picker: /* DEFAULT: @_lifetime(captures, copy ne0, copy ne1) */ (_ ne0: NE, _ ne1: NE) -> NE) {
let x = NE(p: getLocalPointer())
let y = NE(p: getLocalPointer())
_ = picker(x, y)
}
func takeMutator(mutator: @_lifetime(ne: copy ne) (_ ne: inout NE) -> ()) {
var ne = NE(p: getLocalPointer())
mutator(&ne)
let _ = ne
}
func takeCaptureMutator(mutator: @_lifetime(ne: captures, copy ne) (_ ne: inout NE) -> ()) {
var ne = NE(p: getLocalPointer())
mutator(&ne)
let _ = ne
}
func testClosureLifetimes(cond: Bool) {
takePicker { ne0, ne1 in ne0 } // OK
takePicker { ne0, ne1 in ne1 } // OK
takePicker { ne0, ne1 in if cond { return ne0 } else { return ne1 } } // OK
// Several of the following error cases should start passing with no source
// changes once closure context lifetimes are implemented. Some may require
// explicit lifetime annotations; for those, add an extra test case with the
// annotation.
// OK, ne2 is captured but its lifetime is not related
let ne2 = NE(p: getLocalPointer())
takePicker { ne0, ne1 in
if ne2.condition() { return ne0 } else { return ne1 }
}
let ne3 = NE(p: getLocalPointer())
// expected-error@-1{{lifetime-dependent variable 'ne3' escapes its scope}}
// expected-note@-2{{it depends on a closure capture; this is not yet supported}}
takePicker { ne0, ne1 in ne3 }
// expected-note@-1{{this use causes the lifetime-dependent value to escape}}
var ne4 = NE(p: getLocalPointer()) // expected-error{{lifetime-dependent variable 'ne4' escapes its scope}}
takePicker { ne0, ne1 in
ne4 = NE(p: getLocalPointer()) // expected-note{{it depends on the lifetime of this parent value}}
return ne0 // expected-note{{this use causes the lifetime-dependent value to escape}}
}
let _ = ne4
let ne5 = NE(p: getLocalPointer()) // expected-note{{it depends on a closure capture; this is not yet supported}}
takePicker { ne0, ne1 in
let ne = ne5 // expected-error{{lifetime-dependent variable 'ne' escapes its scope}}
return ne // expected-note{{this use causes the lifetime-dependent value to escape}}
}
var ne6 = NE(p: getLocalPointer()) // expected-error{{lifetime-dependent variable 'ne6' escapes its scope}}
takePicker { ne0, ne1 in // expected-note{{it depends on the lifetime of argument 'ne1'}}
ne6 = ne1
return ne0 // expected-note{{this use causes the lifetime-dependent value to escape}}
}
let _ = ne6
takeMutator { ne0 in // OK
let neLocal = ne0
ne0 = neLocal
}
let ne8 = NE(p: getLocalPointer()) // expected-note{{it depends on a closure capture; this is not yet supported}}
takeMutator { ne0 in // expected-error{{lifetime-dependent variable 'ne0' escapes its scope}}
ne0 = ne8
} // expected-note{{this use causes the lifetime-dependent value to escape}}
takeOnePicker { ne0, ne1 in ne0 } // OK
takeOnePicker { ne0, ne1 in ne1 }
// expected-error@-1{{lifetime-dependent variable 'ne1' escapes its scope}}
// expected-note@-2{{it depends on the lifetime of argument 'ne1'}}
// expected-note@-3{{this use causes the lifetime-dependent value to escape}}
takeOnePicker { ne0, ne1 in
// expected-error@-1{{lifetime-dependent variable 'ne1' escapes its scope}}
// expected-note@-2{{it depends on the lifetime of argument 'ne1'}}
if cond {
return ne0
} else {
return ne1 // expected-note{{this use causes the lifetime-dependent value to escape}}
}
}
// Callbacks with captures dependencies.
takeCapturePicker { ne0, ne1 in ne0 } // OK
takeCapturePicker { ne0, ne1 in ne1 } // OK
let ne9 = NE(p: getLocalPointer())
takeCapturePicker { ne0, ne1 in ne9 } // OK
takeCaptureMutator { ne0 in // OK
let neLocal = ne0
ne0 = neLocal
}
let ne10 = NE(p: getLocalPointer())
takeCaptureMutator { ne0 in // OK
ne0 = ne10
}
}
// MARK: Methods with lifetime dependencies
func transfer(ne: NE) -> NE { ne }
struct S {
static func staticTransfer(ne: NE) -> NE { ne }
@_lifetime(borrow ne)
static func staticTransferBorrowingNE(ne: NE) -> NE { ne }
func transfer(ne: NE) -> NE { ne }
@_lifetime(borrow self, copy ne)
func transferDependingOnSelf(ne: NE) -> NE { ne }
}
do {
typealias Transfer = @_lifetime(copy ne) (_ ne: NE) -> NE
let _ = transfer // OK
let _: (NE) -> NE = transfer // OK
let _: Transfer = transfer // OK
let _: Transfer = S.staticTransfer // OK, method is static
// Non-static methods are not supported yet
let s = S()
let _: Transfer = s.transfer
// expected-error@-1{{lifetime-dependent value escapes its scope}}
// expected-note@-2{{it depends on the lifetime of argument 'ne'}}
// expected-note@-3{{this use causes the lifetime-dependent value to escape}}
let _: Transfer = s.transferDependingOnSelf
// expected-error@-1{{lifetime-dependent value escapes its scope}}
// expected-note@-2{{it depends on the lifetime of argument 'ne'}}
// expected-note@-3{{this use causes the lifetime-dependent value to escape}}
// expected-error@-4{{lifetime-dependent value escapes its scope}}
// expected-note@-5{{it depends on a closure capture; this is not yet supported}}
// expected-note@-6{{this use causes the lifetime-dependent value to escape}}
}
// ~Escapable function types
@_lifetime(borrow body)
func borrowClosureNE(body: /* @_lifetime(captures) */ () -> NE) -> NE {
body()
}
func callBorrowClosureNE(ne: NE) -> NE {
// expected-error@-1{{lifetime-dependent variable 'ne' escapes its scope}}
// expected-note@-2{{it depends on the lifetime of argument 'ne'}}
let neo = borrowClosureNE { ne }
// expected-error@-1{{lifetime-dependent variable 'neo' escapes its scope}}
// expected-note@-2{{this use causes the lifetime-dependent value to escape}}
// expected-note@-3{{it depends on the lifetime of this parent value}}
return neo // expected-note{{this use causes the lifetime-dependent value to escape}}
}
@_lifetime(copy body)
func copyClosureNE(body: () -> NE) -> NE {
body()
}
func callCopyClosureNE(ne: NE) -> NE {
let neo = copyClosureNE { ne }
return neo
}
// =============================================================================
// easily recognizable patterns involving the capture of an array's span
func closure_array_int(_: [Int], _ f: ()->Int) -> Int { f() }
func testCaptureSpanReturnElement(array: consuming [Int]) -> Int {
let span = array.span
return closure_array_int(array) { span[0] }
}
func closure_consuming_array_int(_: consuming [Int], _ f: ()->Int) -> Int { f() }
func testConsumeAndCaptureSpanReturnElement(array: consuming [Int]) -> Int {
// expected-error@-1{{'array' used after consume}}
let span = array.span // expected-note{{conflicting access is here}}
return closure_consuming_array_int(array) { span[0] }
// expected-error@-1{{overlapping accesses to 'array', but deinitialization requires exclusive access}}
// expected-note@-2{{consumed here}}
}
func closure_span(_ f: ()->Span<Int>) -> Span<Int> { f() }
func testArgSpanCaptureReturnSpan(array: [Int]) -> Span<Int> {
let span = array.span
return closure_span { span }
}
@_lifetime(immortal)
func testConsumingArgSpanCaptureReturnSpan(array: consuming [Int]) -> Span<Int> {
let span = array.span // expected-note{{it depends on this scoped access to variable 'array'}}
return closure_span { span }
// expected-error@-1{{lifetime-dependent value escapes its scope}}
// expected-note@-2{{this use causes the lifetime-dependent value to escape}}
}
@_lifetime(immortal)
func testLocalSpanCaptureReturnSpan() -> Span<Int> {
let array = [1,2,3] // expected-note{{it depends on the lifetime of variable 'array'}}
let span = array.span
return closure_span { span }
// expected-error@-1{{lifetime-dependent value escapes its scope}}
// expected-note@-2{{this use causes the lifetime-dependent value to escape}}
}