Skip to content

Commit 8789a68

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 376ce05 commit 8789a68

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
@@ -8374,6 +8374,9 @@ ERROR(lifetime_dependence_cannot_infer_kind, none,
83748374
ERROR(lifetime_dependence_cannot_infer_scope_ownership, none,
83758375
"cannot borrow the lifetime of '%0', which has consuming ownership on %1",
83768376
(StringRef, StringRef))
8377+
ERROR(lifetime_dependence_cannot_infer_implicit_init, none,
8378+
"cannot infer implicit initialization lifetime. Add an initializer with "
8379+
"'@_lifetime(...)' for each parameter the result depends on", ())
83778380

83788381
//------------------------------------------------------------------------------
83798382
// MARK: Lifetime Dependence Experimental Inference

lib/AST/LifetimeDependence.cpp

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,9 +1064,13 @@ class LifetimeDependenceChecker {
10641064
return LifetimeDependenceKind::Scope;
10651065
}
10661066

1067-
// Infer implicit initialization. The dependence kind can be inferred, similar
1068-
// to an implicit setter, because the implementation is simply an assignment
1069-
// to stored property.
1067+
// Infer implicit initialization. A non-Escapable initializer parameter can
1068+
// always be inferred, similar to an implicit setter, because the
1069+
// implementation is simply an assignment to stored property. Escapable
1070+
// parameters are ambiguous: they may either be borrowed or
1071+
// non-dependent. non-Escapable types often have incidental integer fields
1072+
// that are unrelated to lifetime. Avoid inferring any dependency on Escapable
1073+
// parameters unless it is the (unambiguously borrowed) sole parameter.
10701074
void inferImplicitInit() {
10711075
auto *afd = cast<AbstractFunctionDecl>(decl);
10721076
if (afd->getParameters()->size() == 0) {
@@ -1086,15 +1090,28 @@ class LifetimeDependenceChecker {
10861090
if (paramTypeInContext->hasError()) {
10871091
return;
10881092
}
1093+
if (!paramTypeInContext->isEscapable()) {
1094+
// An implicitly initialized non-Escapable value always copies its
1095+
// dependency.
1096+
targetDeps = std::move(targetDeps).add(paramIndex,
1097+
LifetimeDependenceKind::Inherit);
1098+
continue;
1099+
}
1100+
if (afd->getParameters()->size() > 1 && !useLazyInference()) {
1101+
diagnose(param->getLoc(),
1102+
diag::lifetime_dependence_cannot_infer_implicit_init);
1103+
return;
1104+
}
1105+
// A single Escapable parameter must be borrowed.
10891106
auto kind = inferLifetimeDependenceKind(paramTypeInContext,
10901107
param->getValueOwnership());
10911108
if (!kind) {
10921109
diagnose(returnLoc,
10931110
diag::lifetime_dependence_cannot_infer_scope_ownership,
10941111
param->getParameterName().str(), diagnosticQualifier());
1095-
return;
10961112
}
1097-
targetDeps = std::move(targetDeps).add(paramIndex, *kind);
1113+
targetDeps = std::move(targetDeps).add(paramIndex,
1114+
LifetimeDependenceKind::Scope);
10981115
}
10991116
pushDeps(std::move(targetDeps));
11001117
}

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)