Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 76 additions & 74 deletions docs/ReferenceGuides/LifetimeAnnotation.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,58 +18,73 @@ The `@lifetime` annotation is enforced both in the body of the function and at e

## Default lifetimes

The Swift 6.2 compiler provided default `@_lifetime` behavior whenever it can do so without ambiguity. Often, despite ambiguity, an "obvious" default exists, but we wanted to introduce defaults slowly after developers have enough experience to inform discussion about them. This document tracks the current state of the implementation as it progresses from the original 6.2 implementation. Corresponding tests are in `test/Sema/lifetime_depend_infer.swift`; searching for "DEFAULT:" highlights the rules defined below...
The Swift 6.2 compiler provided default `@_lifetime` behavior whenever it can do so without ambiguity. Often, despite ambiguity, an obvious default exists, but we wanted to introduce defaults slowly after developers have enough experience to inform discussion about them. This document tracks the current state of the implementation as it progresses from the original 6.2 implementation. Corresponding tests are in `test/Sema/lifetime_depend_infer.swift`; searching for "DEFAULT:" highlights the rules defined below...

### Single parameter default rule
### Same-type default lifetime

Given a function or method that returns a non-Escapable result:
Given a function declaration:

- Default to `@_lifetime(<scope> a)` for a `~Escapable` result on functions with a single parameter `a`.
`func foo(..., a: A, ...) -> R { ... }`

- Default to `@_lifetime(<scope> self)` for a `~Escapable` result on methods with no parameters.
Where `R: ~Escapable`, `A == R`, and `a` is not an `inout` parameter, default to `@_lifetime(copy a)`.
For non-mutating methods, the same rule applies to implicit `Self` parameter.

| Type of parameter | default |
| (`a` or `self`) | lifetime dependency |
| ----------------- | ------------------------------ |
| `Escapable` | `@_lifetime(borrow param)`[^1] |
| `inout Escapable` | `@_lifetime(&param)`[^1] |
| `~Escapable` | none[^2] |
This handles the obvious cases in which both the parameter and result are `~Escapable`. For example:

[^1]: When the parameter is `BitwiseCopyable`, such as an integer or unsafe pointer, the single parameter default rule applies to function parameters but not to the implicit `self` parameter. Depending on a `BitwiseCopyable` value is a convenience for APIs that construct span-like values from an `UnsafePointer` passed as an argument. This creates a dependency on a local copy of the pointer variable with subtle semantics. User-defined `BitwiseCopyable` structs should generally avoid such subtle lifetime dependencies. If needed, the author of the data type should explicitly opt into them.
```swift
extension Span {
/* DEFAULT: @_lifetime(copy self) */
func extracting(droppingLast k: Int) -> Self { ... }
}
```

[^2]: When the single parameter is also `~Escapable`, the result must depend on it, but the dependency may either be scoped (`borrow` or `&`) or it may be copied (`copy`). `copy` is the obvious choice when the parameter and result are the same type, but it is not always correct. Furthermore, a lifetime dependency can only be copied from a generic type when result as the same generic type. This case is therefore handled by same-type default lifetime (discussed below) rather than as a default `@_lifetime` rule.
#### Generic same-type default lifetime

Examples:
The same-type default lifetime rule described above is convenient for nominal types but essential for generic type parameters.

```swift
struct A: Escapable {
let obj: AnyObject // ~BitwiseCopyable
}
struct NE: ~Escapable {...}
Given a generic function declaration:

/* DEFAULT: @_lifetime(borrow a) */
func oneParam_NEResult(a: A) -> NE
`func foo<A, R>(..., a: A, ...) -> R { ... }`

/* DEFAULT: @_lifetime(&a) */
func oneInoutParam_NEResult(a: inout A) -> NE
The same-type default lifetime rule applies to the types in the function declaration's generic context just as it did for nominal types in the previous example. So, again, if type resolution determines `R: ~Escapable` and `A == R`, then `@_lifetime(copy a)` will be default.

extension A /* Self: Escapable */ {
/* DEFAULT: @_lifetime(borrow self) */
func noParam_NEResult() -> NE
Unlike nominal types, the programmer is not allowed to explicitly declare a lifetime dependency, `@_lifetime(copy
a)`, unless the argument and result types are equivalent (`A == R`). Copying a lifetime dependency from one value to another requires that both values are non-Escapable. Generic types are conditionally non-Escapable (their lifetime dependencies are type-erased), so type equivalence is the only way to ensure that both values are non-Escapable under the same conditions.

/* DEFAULT: @_lifetime(&self) */
mutating func mutating_noParam_NEResult() -> NE
Here we see how same-type lifetime requirement applies to type substitution and associated types:

```swift
protocol P {
associatedtype T: ~Escapable
}

protocol Q {
associatedtype U: ~Escapable
}

struct S<A: P, B: Q> {
/* OK: @_lifetime(copy a) is valid and default */
func foo(a: A.T) -> B.U where A.T == B.U
}
```

### Implicit initializer and setter defaults
Note that lifetime dependencies are resolved at function declaration time, which determines the function's type. The generic context at the point of function invocation is not considered. For example, the following declaration of `foo` is invalid, because it's argument and results types don't match at the point of declaration, even though the argument and result do have the same type when invoked inside `bar`:

An implicit setter of a `~Escapable` stored property defaults to `@_lifetime(self: copy self, copy newValue)`. This is always correct because the setter simply assigns the stored property to the newValue. Assigning a `~Escapable` variable copies the lifetime dependency.
```swift
struct S<T: ~Escapable, U: ~Escapable> {
static func foo(a: T) -> U // ERROR: missing lifetime dependency
}

Similarly, an implicit initializer of a non-Escapable struct defaults to `@_lifetime(self: copy arg)` if all of the initializer arguments are `~Escapable`. This is equivalent to assigning each `~Escapable` stored property. If, however, any initializer arguments are `Escapable`, then no default lifetime is provided unless it is the sole argument, in which case the single parameter rule applies.
/* OK: @_lifetime(copy a) is valid and default */
func bar<T: ~Escapable>(a: T) -> T {
S<T, T>.foo(a: a) // The same-type rule is satisfied in this context, but 'foo's declaration is invalid.
}
```

### `inout` parameter default rule

The following `inout` parameter default rule directly follows from the general same-type default rule above. We descrive it as a separate rule here only because the behavior is important and would otherwise be nonobvious:

- Default to `@_lifetime(a: copy a)` for all `inout` parameters where `a` is `~Escapable`.

- Default to `@_lifetime(self: copy self)` on `mutating` methods where `self` is `~Escapable`.
Expand Down Expand Up @@ -122,63 +137,50 @@ extension NE /* Self: ~Escapable */ {
}
```

## Same-type default lifetime (unimplemented)

Given a function declaration:

`func foo(..., a: A, ...) -> R { ... }`

Where `R: ~Escapable` and `A == R`, default to `@_lifetime(copy a)`.
For methods, the same rule applies to implicit `Self` parameter.

This handles the obvious cases in which both the parameter and result are `~Escapable`. For example:

```swift
extension Span {
/* DEFAULT: @_lifetime(copy self) */
func extracting(droppingLast k: Int) -> Self { ... }
}
```
### Single parameter default rule

### Generic same-type default lifetime (unimplemented)
Given a function or method that returns a non-Escapable result, if that result's dependency does not have a same-type default:

The same-type default lifetime rule described above is convenient for nominal types but essential for generic type parameters.
- Default to `@_lifetime(<scope> a)` for a `~Escapable` result on functions with a single parameter `a`.

Given a generic function declaration:
- Default to `@_lifetime(<scope> self)` for a `~Escapable` result on methods with no parameters.

`func foo<A, R>(..., a: A, ...) -> R { ... }`
| Type of parameter | default |
| (`a` or `self`) | lifetime dependency |
| ----------------- | ------------------------------ |
| `Escapable` | `@_lifetime(borrow param)`[^1] |
| `inout Escapable` | `@_lifetime(&param)`[^1] |
| `~Escapable` | none[^2] |

The same-type default lifetime rule applies to the types in the function declaration's generic context just as it did for nominal types in the previous example. So, again, if type resolution determines `R: ~Escapable` and `A == R`, then `@_lifetime(copy a)` will be default.
[^1]: When the parameter is `BitwiseCopyable`, such as an integer or unsafe pointer, the single parameter default rule applies to function parameters but not to the implicit `self` parameter. Depending on a `BitwiseCopyable` value is a convenience for APIs that construct span-like values from an `UnsafePointer` passed as an argument. This creates a dependency on a local copy of the pointer variable with subtle semantics. User-defined `BitwiseCopyable` structs should generally avoid such subtle lifetime dependencies. If needed, the author of the data type should explicitly opt into them.

Unlike nominal types, the programmer is not allowed to explicitly declare a lifetime dependency, `@_lifetime(copy
a)`, unless the argument and result types are equivalent (`A == R`). Copying a lifetime dependency from one value to another requires that both values are non-Escapable. Generic types are conditionally non-Escapable (their lifetime dependencies are type-erased), so type equivalence is the only way to ensure that both values are non-Escapable under the same conditions.
[^2]: When the single parameter is also `~Escapable`, the result must depend on it, but the dependency may either be scoped (`borrow` or `&`) or it may be copied (`copy`). `copy` is the obvious choice when the parameter and result are the same type, but it is not always correct. Furthermore, a lifetime dependency can only be copied from a generic type when result as the same generic type. This case is therefore handled by same-type default lifetime (discussed below) rather than as a default `@_lifetime` rule.

Here we see how same-type lifetime requirement applies to type substitution and associated types:
Examples:

```swift
protocol P {
associatedtype T: ~Escapable
struct A: Escapable {
let obj: AnyObject // ~BitwiseCopyable
}
struct NE: ~Escapable {...}

protocol Q {
associatedtype U: ~Escapable
}
/* DEFAULT: @_lifetime(borrow a) */
func oneParam_NEResult(a: A) -> NE

struct S<A: P, B: Q> {
/* OK: @_lifetime(copy a) is valid and default */
func foo(a: A.T) -> B.U where A.T == B.U
/* DEFAULT: @_lifetime(&a) */
func oneInoutParam_NEResult(a: inout A) -> NE

extension A /* Self: Escapable */ {
/* DEFAULT: @_lifetime(borrow self) */
func noParam_NEResult() -> NE

/* DEFAULT: @_lifetime(&self) */
mutating func mutating_noParam_NEResult() -> NE
}
```

Note that lifetime dependencies are resolved at function declaration time, which determines the function's type. The generic context at the point of function invocation is not considered. For example, the following declaration of `foo` is invalid, because it's argument and results types don't match at the point of declaraion, even though the argument and result do have the same type when invoked inside `bar`:
### Implicit initializer and setter defaults

```swift
struct S<T: ~Escapable, U: ~Escapable> {
static func foo(a: T) -> U // ERROR: missing lifetime dependency
}
An implicit setter of a `~Escapable` stored property defaults to `@_lifetime(self: copy self, copy newValue)`. This is always correct because the setter simply assigns the stored property to the newValue. Assigning a `~Escapable` variable copies the lifetime dependency.

/* OK: @_lifetime(copy a) is valid and default */
func bar<T: ~Escapable>(a: T) -> T {
S<T, T>.foo(a: a) // The same-type rule is satisfied in this context, but 'foo's declaration is invalid.
}
```
Similarly, an implicit initializer of a non-Escapable struct defaults to `@_lifetime(self: copy arg)` if all of the initializer arguments are `~Escapable`. This is equivalent to assigning each `~Escapable` stored property. If, however, any initializer arguments are `Escapable`, then no default lifetime is provided unless it is the sole argument, in which case the single parameter rule applies.
53 changes: 52 additions & 1 deletion lib/AST/LifetimeDependence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,8 @@ class LifetimeDependenceChecker {

// Infer non-Escapable results.
if (isDiagnosedNonEscapable(getResultOrYield())) {
inferNonEscapableResultOnSameTypeParam();

if (isInit() && isImplicitOrSIL()) {
inferImplicitInit();
} else if (hasImplicitSelfParam()) {
Expand All @@ -1105,6 +1107,45 @@ class LifetimeDependenceChecker {
inferInoutParams();
}

// Infer a dependency to the ~Escapable result from all parameters of the same
// type.
void inferNonEscapableResultOnSameTypeParam() {
// The function declaration's substituted result type.
CanType resultTy = getResultOrYield()->getCanonicalType();

TargetDeps *targetDeps = depBuilder.getInferredTargetDeps(resultIndex);
if (!targetDeps)
return;

// Ignore mutating self. An 'inout' modifier effectively makes the parameter
// a different type for lifetime inference.
if (hasImplicitSelfParam() && !afd->getImplicitSelfDecl()->isInOut()) {
Type selfTy = afd->mapTypeIntoEnvironment(dc->getSelfInterfaceType());
if (selfTy->getCanonicalType() == resultTy) {
targetDeps->inheritIndices.set(selfIndex);
}
}

unsigned paramIndex = 0;
for (auto *param : *afd->getParameters()) {
SWIFT_DEFER { paramIndex++; };

// Ignore 'inout' parameters. An 'inout' modifier effectively makes the
// parameter a different type for lifetime inference. An 'inout' parameter
// defaults to being the source and target of a self-dependency, as
// covered by the 'inout' rule.
if (param->isInOut())
continue;

CanType paramTy = afd->mapTypeIntoEnvironment(
param->getInterfaceType())->getCanonicalType();
if (paramTy != resultTy)
continue;

targetDeps->inheritIndices.set(paramIndex);
}
}

// Infer dependence for an accessor whose non-escapable result depends on
// self. This includes _read and _modify.
//
Expand All @@ -1117,7 +1158,8 @@ class LifetimeDependenceChecker {
bool nonEscapableSelf = isDiagnosedNonEscapable(dc->getSelfTypeInContext());
if (nonEscapableSelf && accessor->getImplicitSelfDecl()->isInOut()) {
// First, infer the dependency of the inout non-Escapable 'self'. This may
// result in two inferred dependencies for accessors.
// result in two inferred dependencies for accessors (one targetting
// selfIndex here, and one targetting resultIndex below).
inferMutatingAccessor(accessor);
}
// Handle synthesized wrappers...
Expand Down Expand Up @@ -1255,6 +1297,9 @@ class LifetimeDependenceChecker {
if (!resultDeps)
return; // .sil implicit initializers may have been annotated.

if (!resultDeps->empty())
return; // same-type inferrence applied; don't issue diagnostics.

unsigned paramIndex = 0;
for (auto *param : *afd->getParameters()) {
SWIFT_DEFER { paramIndex++; };
Expand Down Expand Up @@ -1293,6 +1338,9 @@ class LifetimeDependenceChecker {
if (!resultDeps)
return;

if (!resultDeps->empty())
return; // same-type inferrence applied; don't issue diagnostics.

bool nonEscapableSelf = isDiagnosedNonEscapable(dc->getSelfTypeInContext());
// Do not infer the result's dependence when the method is mutating and
// 'self' is non-Escapable. Independently, a missing dependence on inout
Expand Down Expand Up @@ -1359,6 +1407,9 @@ class LifetimeDependenceChecker {
if (!resultDeps)
return;

if (!resultDeps->empty())
return; // same-type inferrence applied; don't issue diagnostics.

// Strict inference only handles a single escapable parameter,
// which is an unambiguous borrow dependence.
if (afd->getParameters()->size() == 0) {
Expand Down
4 changes: 2 additions & 2 deletions test/Parse/lifetime_attr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ func derive(_ ne1: NE, _ ne2: NE) -> NE {
}

@_lifetime // expected-error{{expected '(' after lifetime dependence specifier}}
func testMissingLParenError(_ ne: NE) -> NE { // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}}
func testMissingLParenError(_ ne: NE) -> NE {
ne
}

@_lifetime() // expected-error{{expected 'copy', 'borrow', or '&' followed by an identifier, index or 'self' in lifetime dependence specifier}}
func testMissingDependence(_ ne: NE) -> NE { // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}}
func testMissingDependence(_ ne: NE) -> NE {
ne
}

Expand Down
Loading