Skip to content

[6.2] Disable surprising lifetime inference of implicit initializers #82473

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 25, 2025
Merged
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
3 changes: 3 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -8294,6 +8294,9 @@ ERROR(lifetime_dependence_cannot_infer_kind, none,
ERROR(lifetime_dependence_cannot_infer_scope_ownership, none,
"cannot borrow the lifetime of '%0', which has consuming ownership on %1",
(StringRef, StringRef))
ERROR(lifetime_dependence_cannot_infer_implicit_init, none,
"cannot infer implicit initialization lifetime. Add an initializer with "
"'@_lifetime(...)' for each parameter the result depends on", ())

//------------------------------------------------------------------------------
// MARK: Lifetime Dependence Experimental Inference
Expand Down
27 changes: 22 additions & 5 deletions lib/AST/LifetimeDependence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1046,9 +1046,13 @@ class LifetimeDependenceChecker {
return LifetimeDependenceKind::Scope;
}

// Infer implicit initialization. The dependence kind can be inferred, similar
// to an implicit setter, because the implementation is simply an assignment
// to stored property.
// Infer implicit initialization. A non-Escapable initializer parameter can
// always be inferred, similar to an implicit setter, because the
// implementation is simply an assignment to stored property. Escapable
// parameters are ambiguous: they may either be borrowed or
// non-dependent. non-Escapable types often have incidental integer fields
// that are unrelated to lifetime. Avoid inferring any dependency on Escapable
// parameters unless it is the (unambiguously borrowed) sole parameter.
void inferImplicitInit() {
auto *afd = cast<AbstractFunctionDecl>(decl);
if (afd->getParameters()->size() == 0) {
Expand All @@ -1068,15 +1072,28 @@ class LifetimeDependenceChecker {
if (paramTypeInContext->hasError()) {
return;
}
if (!paramTypeInContext->isEscapable()) {
// An implicitly initialized non-Escapable value always copies its
// dependency.
targetDeps = std::move(targetDeps).add(paramIndex,
LifetimeDependenceKind::Inherit);
continue;
}
if (afd->getParameters()->size() > 1 && !useLazyInference()) {
diagnose(param->getLoc(),
diag::lifetime_dependence_cannot_infer_implicit_init);
return;
}
// A single Escapable parameter must be borrowed.
auto kind = inferLifetimeDependenceKind(paramTypeInContext,
param->getValueOwnership());
if (!kind) {
diagnose(returnLoc,
diag::lifetime_dependence_cannot_infer_scope_ownership,
param->getParameterName().str(), diagnosticQualifier());
return;
}
targetDeps = std::move(targetDeps).add(paramIndex, *kind);
targetDeps = std::move(targetDeps).add(paramIndex,
LifetimeDependenceKind::Scope);
}
pushDeps(std::move(targetDeps));
}
Expand Down
2 changes: 1 addition & 1 deletion test/Sema/lifetime_attr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func immortalConflict(_ immortal: Int) -> NE { // expected-error{{conflict betwe
}

do {
struct Test: ~Escapable {
struct Test: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
var v1: Int
var v2: NE
}
Expand Down
36 changes: 36 additions & 0 deletions test/Sema/lifetime_depend_infer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -577,3 +577,39 @@ struct NonEscapableMutableSelf: ~Escapable {
@_lifetime(&self)
mutating func mutatingMethodOneParamBorrow(_: NE) {}
}

// =============================================================================
// Initializers
// =============================================================================

struct NE_Int: ~Escapable {
let i: Int
}

struct NE_C: ~Escapable { // expected-error{{cannot borrow the lifetime of 'c', which has consuming ownership on an implicit initializer}}
let c: C
}

struct NE_C_Int: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
let c: C
let i: Int
}

struct NE_Int_Int: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
let i: Int
let j: Int
}

struct NE_NE: ~Escapable {
let ne: NE
}

struct NE_NE_Int: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
let ne: NE
let i: Int
}

struct NE_NE_C: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}}
let ne: NE
let c: C
}