Skip to content

Commit 78e8b5a

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) (cherry picked from commit 8789a68)
1 parent b8d0749 commit 78e8b5a

File tree

4 files changed

+62
-6
lines changed

4 files changed

+62
-6
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8294,6 +8294,9 @@ ERROR(lifetime_dependence_cannot_infer_kind, none,
82948294
ERROR(lifetime_dependence_cannot_infer_scope_ownership, none,
82958295
"cannot borrow the lifetime of '%0', which has consuming ownership on %1",
82968296
(StringRef, StringRef))
8297+
ERROR(lifetime_dependence_cannot_infer_implicit_init, none,
8298+
"cannot infer implicit initialization lifetime. Add an initializer with "
8299+
"'@_lifetime(...)' for each parameter the result depends on", ())
82978300

82988301
//------------------------------------------------------------------------------
82998302
// MARK: Lifetime Dependence Experimental Inference

lib/AST/LifetimeDependence.cpp

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,9 +1046,13 @@ class LifetimeDependenceChecker {
10461046
return LifetimeDependenceKind::Scope;
10471047
}
10481048

1049-
// Infer implicit initialization. The dependence kind can be inferred, similar
1050-
// to an implicit setter, because the implementation is simply an assignment
1051-
// to stored property.
1049+
// Infer implicit initialization. A non-Escapable initializer parameter can
1050+
// always be inferred, similar to an implicit setter, because the
1051+
// implementation is simply an assignment to stored property. Escapable
1052+
// parameters are ambiguous: they may either be borrowed or
1053+
// non-dependent. non-Escapable types often have incidental integer fields
1054+
// that are unrelated to lifetime. Avoid inferring any dependency on Escapable
1055+
// parameters unless it is the (unambiguously borrowed) sole parameter.
10521056
void inferImplicitInit() {
10531057
auto *afd = cast<AbstractFunctionDecl>(decl);
10541058
if (afd->getParameters()->size() == 0) {
@@ -1068,15 +1072,28 @@ class LifetimeDependenceChecker {
10681072
if (paramTypeInContext->hasError()) {
10691073
return;
10701074
}
1075+
if (!paramTypeInContext->isEscapable()) {
1076+
// An implicitly initialized non-Escapable value always copies its
1077+
// dependency.
1078+
targetDeps = std::move(targetDeps).add(paramIndex,
1079+
LifetimeDependenceKind::Inherit);
1080+
continue;
1081+
}
1082+
if (afd->getParameters()->size() > 1 && !useLazyInference()) {
1083+
diagnose(param->getLoc(),
1084+
diag::lifetime_dependence_cannot_infer_implicit_init);
1085+
return;
1086+
}
1087+
// A single Escapable parameter must be borrowed.
10711088
auto kind = inferLifetimeDependenceKind(paramTypeInContext,
10721089
param->getValueOwnership());
10731090
if (!kind) {
10741091
diagnose(returnLoc,
10751092
diag::lifetime_dependence_cannot_infer_scope_ownership,
10761093
param->getParameterName().str(), diagnosticQualifier());
1077-
return;
10781094
}
1079-
targetDeps = std::move(targetDeps).add(paramIndex, *kind);
1095+
targetDeps = std::move(targetDeps).add(paramIndex,
1096+
LifetimeDependenceKind::Scope);
10801097
}
10811098
pushDeps(std::move(targetDeps));
10821099
}

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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,39 @@ struct NonEscapableMutableSelf: ~Escapable {
577577
@_lifetime(&self)
578578
mutating func mutatingMethodOneParamBorrow(_: NE) {}
579579
}
580+
581+
// =============================================================================
582+
// Initializers
583+
// =============================================================================
584+
585+
struct NE_Int: ~Escapable {
586+
let i: Int
587+
}
588+
589+
struct NE_C: ~Escapable { // expected-error{{cannot borrow the lifetime of 'c', which has consuming ownership on an implicit initializer}}
590+
let c: C
591+
}
592+
593+
struct NE_C_Int: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
594+
let c: C
595+
let i: Int
596+
}
597+
598+
struct NE_Int_Int: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
599+
let i: Int
600+
let j: Int
601+
}
602+
603+
struct NE_NE: ~Escapable {
604+
let ne: NE
605+
}
606+
607+
struct NE_NE_Int: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
608+
let ne: NE
609+
let i: Int
610+
}
611+
612+
struct NE_NE_C: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
613+
let ne: NE
614+
let c: C
615+
}

0 commit comments

Comments
 (0)