Skip to content

Commit 7abe222

Browse files
committed
Disable surprising lifetime inference of implicit initializers
Non-escapable struct definitions often have inicidental integer fields that are unrelated to lifetime. Without an explicit initializer, the compiler would infer these fields to be borrowed by the implicit intializer. struct CountedSpan: ~Escapable { let span: Span<Int> let i: Int /* infer: @Lifetime(copy span, borrow i) init(...) */ } This was done because - we always want to infer lifetimes of synthesized code if possible - inferring a borrow dependence is always conservative But this was the wrong decision because it inevitabely results in lifetime diagnostic errors elsewhere in the code that can't be tracked down at the use site: let span = CountedSpan(span: span, i: 3) // ERROR: span depends on the lifetime of this value Instead, force the author of the data type to specify whether the type actually depends on trivial fields or not. Such as: struct CountedSpan: ~Escapable { let span: Span<Int> let i: Int @Lifetime(copy span) init(...) { ... } } This fix enables stricter diagnostics, so we need it in 6.2. Fixes rdar://152130977 ([nonescapable] confusing diagnostic message when a synthesized initializer generates dependence on an Int parameter)
1 parent 76fd747 commit 7abe222

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8377,6 +8377,9 @@ ERROR(lifetime_dependence_cannot_infer_scope_ownership, none,
83778377
NOTE(lifetime_dependence_cannot_infer_inout_suggest, none,
83788378
"use '@_lifetime(%0: copy %0) to forward the inout dependency",
83798379
(StringRef))
8380+
ERROR(lifetime_dependence_cannot_infer_implicit_init, none,
8381+
"cannot infer implicit initialization lifetime. Add an initializer with "
8382+
"'@_lifetime(...)' for each parameter the result depends on", ())
83808383

83818384
//------------------------------------------------------------------------------
83828385
// MARK: Lifetime Dependence Experimental Inference

lib/AST/LifetimeDependence.cpp

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1075,9 +1075,13 @@ class LifetimeDependenceChecker {
10751075
return LifetimeDependenceKind::Scope;
10761076
}
10771077

1078-
// Infer implicit initialization. The dependence kind can be inferred, similar
1079-
// to an implicit setter, because the implementation is simply an assignment
1080-
// to stored property.
1078+
// Infer implicit initialization. A non-Escapable initializer parameter can
1079+
// always be inferred, similar to an implicit setter, because the
1080+
// implementation is simply an assignment to stored property. Escapable
1081+
// parameters are ambiguous: they may either be borrowed or
1082+
// non-dependent. non-Escapable types often have incidental integer fields
1083+
// that are unrelated to lifetime. Avoid inferring any dependency on Escapable
1084+
// parameters unless it is the (unambiguously borrowed) sole parameter.
10811085
void inferImplicitInit() {
10821086
auto *afd = cast<AbstractFunctionDecl>(decl);
10831087
if (afd->getParameters()->size() == 0) {
@@ -1097,15 +1101,28 @@ class LifetimeDependenceChecker {
10971101
if (paramTypeInContext->hasError()) {
10981102
return;
10991103
}
1104+
if (!paramTypeInContext->isEscapable()) {
1105+
// An implicitly initialized non-Escapable value always copies its
1106+
// dependency.
1107+
targetDeps = std::move(targetDeps).add(paramIndex,
1108+
LifetimeDependenceKind::Inherit);
1109+
continue;
1110+
}
1111+
if (afd->getParameters()->size() > 1 && !useLazyInference()) {
1112+
diagnose(param->getLoc(),
1113+
diag::lifetime_dependence_cannot_infer_implicit_init);
1114+
return;
1115+
}
1116+
// A single Escapable parameter must be borrowed.
11001117
auto kind = inferLifetimeDependenceKind(paramTypeInContext,
11011118
param->getValueOwnership());
11021119
if (!kind) {
11031120
diagnose(returnLoc,
11041121
diag::lifetime_dependence_cannot_infer_scope_ownership,
11051122
param->getParameterName().str(), diagnosticQualifier());
1106-
return;
11071123
}
1108-
targetDeps = std::move(targetDeps).add(paramIndex, *kind);
1124+
targetDeps = std::move(targetDeps).add(paramIndex,
1125+
LifetimeDependenceKind::Scope);
11091126
}
11101127
pushDeps(std::move(targetDeps));
11111128
}

test/Sema/lifetime_attr.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func immortalConflict(_ immortal: Int) -> NE { // expected-error{{conflict betwe
7575
}
7676

7777
do {
78-
struct Test: ~Escapable {
78+
struct Test: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
7979
var v1: Int
8080
var v2: NE
8181
}

test/Sema/lifetime_depend_infer.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,54 @@ struct NonEscapableMutableSelf: ~Escapable {
581581
mutating func mutatingMethodOneParamBorrow(_: NE) {}
582582
}
583583
584+
// =============================================================================
585+
// Initializers
586+
// =============================================================================
587+
588+
// Motivation: Non-escapable struct definitions often have inicidental integer fields that are unrelated to lifetime.
589+
// Without an explicit initializer, the compiler would infer these fields to be borrowed by the implicit intializer.
590+
// This inevitabely results in lifetime diagnostic errors elsewhere in the code that can't be tracked down at the use
591+
// site:
592+
//
593+
// let span = CountedSpan(span: span, i: 3) // ERROR: span depends on the lifetime of this value
594+
//
595+
struct CountedSpan: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
596+
let span: Span<Int>
597+
let i: Int
598+
}
599+
600+
struct NE_Int: ~Escapable {
601+
let i: Int
602+
}
603+
604+
struct NE_C: ~Escapable { // expected-error{{cannot borrow the lifetime of 'c', which has consuming ownership on an implicit initializer}}
605+
let c: C
606+
}
607+
608+
struct NE_C_Int: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
609+
let c: C
610+
let i: Int
611+
}
612+
613+
struct NE_Int_Int: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
614+
let i: Int
615+
let j: Int
616+
}
617+
618+
struct NE_NE: ~Escapable {
619+
let ne: NE
620+
}
621+
622+
struct NE_NE_Int: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
623+
let ne: NE
624+
let i: Int
625+
}
626+
627+
struct NE_NE_C: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
628+
let ne: NE
629+
let c: C
630+
}
631+
584632
// =============================================================================
585633
// Handle common mistakes with inout parameter annotations
586634
// =============================================================================

0 commit comments

Comments
 (0)