Skip to content

[nonescapable] remove '@_lifetime' requirement on implicit accessors #82404

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 4 commits into from
Jun 23, 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
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ extension MarkDependenceAddrInst : OnoneSimplifiable, SILCombineSimplifiable {

private extension MarkDependenceInstruction {
var isRedundant: Bool {
if base.type.isObject && base.type.isTrivial(in: base.parentFunction) {
if base.type.isObject && base.type.isTrivial(in: base.parentFunction)
&& !(base.definingInstruction is BeginApplyInst) {
// Sometimes due to specialization/builtins, we can get a mark_dependence whose base is a trivial
// typed object. Trivial values live forever. Therefore the mark_dependence does not have a meaning.
// begin_apply is a special case. A dependency on the token is limited to the coroutine scope (ideally, the token
// would have a non-trivial type like $Builtin.Token).
//
// Note: the mark_dependence is still needed for lifetime diagnostics. So it's important that this
// simplification does not run before the lifetime diagnostic pass.
return true
Expand Down
128 changes: 106 additions & 22 deletions lib/AST/LifetimeDependence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,7 @@ class LifetimeDependenceChecker {
return false;
}

// Infer ambiguous cases for backward compatibility.
bool useLazyInference() const {
return isInterfaceFile()
|| ctx.LangOpts.EnableExperimentalLifetimeDependenceInference;
Expand Down Expand Up @@ -873,7 +874,7 @@ class LifetimeDependenceChecker {
// Infer non-Escapable results.
if (isDiagnosedNonEscapable(getResultOrYield())) {
if (hasImplicitSelfParam()) {
// Methods and accessors that return or yield a non-Escapable value.
// Methods that return a non-Escapable value.
inferNonEscapableResultOnSelf();
return;
}
Expand Down Expand Up @@ -910,31 +911,42 @@ class LifetimeDependenceChecker {
return true;
}

// Infer method dependence: result depends on self. This includes _modify.
// Infer method dependence of result on self for
// methods, getters, and _modify accessors.
void inferNonEscapableResultOnSelf() {
Type selfTypeInContext = dc->getSelfTypeInContext();
if (selfTypeInContext->hasError()) {
return;
}
bool nonEscapableSelf = isDiagnosedNonEscapable(selfTypeInContext);

// Avoid diagnosing inference on mutating methods when 'self' is
// non-Escapable. The inout 'self' also needs an inferred dependence on
// itself. This will be diagnosed when checking for missing dependencies.
if (nonEscapableSelf && afd->getImplicitSelfDecl()->isInOut()) {
if (auto accessor = dyn_cast<AccessorDecl>(afd)) {
inferMutatingAccessor(accessor);
bool nonEscapableSelf = isDiagnosedNonEscapable(selfTypeInContext);
if (auto accessor = dyn_cast<AccessorDecl>(afd)) {
if (isImplicitOrSIL() || useLazyInference()) {
if (nonEscapableSelf && afd->getImplicitSelfDecl()->isInOut()) {
// Implicit accessors that return or yield a non-Escapable value may
// infer dependency on both self and result.
inferMutatingAccessor(accessor);
}
// Infer the result dependency on self based on the kind of accessor
// that is wrapped by this synthesized accessors.
if (auto dependenceKind =
getAccessorDependence(accessor, selfTypeInContext)) {
pushDeps(createDeps(resultIndex).add(selfIndex, *dependenceKind));
}
return;
}
// Explicit accessors are inferred the same way as regular methods.
}
// Do infer the result of a mutating method when 'self' is
// non-Escapable. The missing dependence on inout 'self' will be diagnosed
// later anyway, so an explicit annotation will still be needed.
if (nonEscapableSelf && afd->getImplicitSelfDecl()->isInOut()) {
return;
}

// Methods with parameters only apply to lazy inference.
if (!useLazyInference() && afd->getParameters()->size() > 0) {
return;
}
// Allow inference for implicit getters, which simply return a stored,
// property, and for implicit _read/_modify, which cannot be defined
// explicitly alongside a regular getter.
if (!useLazyInference() && !isImplicitOrSIL()) {
// Require explicit @_lifetime(borrow self) for UnsafePointer-like self.
if (!nonEscapableSelf && isBitwiseCopyable(selfTypeInContext, ctx)) {
Expand All @@ -950,9 +962,12 @@ class LifetimeDependenceChecker {
return;
}
}
// Infer based on ownership if possible for either explicit accessors or
// methods as long as they pass preceding ambiguity checks.
auto kind = inferLifetimeDependenceKind(
selfTypeInContext, afd->getImplicitSelfDecl()->getValueOwnership());
if (!kind) {
// Special diagnostic for an attempt to depend on a consuming parameter.
diagnose(returnLoc,
diag::lifetime_dependence_cannot_infer_scope_ownership,
"self", diagnosticQualifier());
Expand All @@ -961,6 +976,8 @@ class LifetimeDependenceChecker {
pushDeps(createDeps(resultIndex).add(selfIndex, *kind));
}

// Infer the kind of dependence that makes sense for reading or writing a
// stored property (for getters or initializers).
std::optional<LifetimeDependenceKind>
inferLifetimeDependenceKind(Type sourceType, ValueOwnership ownership) {
if (!sourceType->isEscapable()) {
Expand All @@ -974,8 +991,9 @@ class LifetimeDependenceChecker {
auto loweredOwnership = ownership != ValueOwnership::Default
? ownership
: getLoweredOwnership(afd);
if (loweredOwnership != ValueOwnership::Shared &&
loweredOwnership != ValueOwnership::InOut) {
// It is impossible to depend on a consumed Escapable value (unless it is
// BitwiseCopyable as checked above).
if (loweredOwnership == ValueOwnership::Owned) {
return std::nullopt;
}
return LifetimeDependenceKind::Scope;
Expand Down Expand Up @@ -1125,7 +1143,10 @@ class LifetimeDependenceChecker {
// Handle implicit setters before diagnosing mutating methods. This
// does not include global accessors, which have no implicit 'self'.
if (auto accessor = dyn_cast<AccessorDecl>(afd)) {
inferMutatingAccessor(accessor);
// Explicit setters require explicit lifetime dependencies.
if (isImplicitOrSIL() || useLazyInference()) {
inferMutatingAccessor(accessor);
}
return;
}
if (afd->getParameters()->size() > 0) {
Expand All @@ -1142,12 +1163,29 @@ class LifetimeDependenceChecker {
LifetimeDependenceKind::Inherit));
}

// Infer a mutating accessor's non-Escapable 'self' dependencies.
void inferMutatingAccessor(AccessorDecl *accessor) {
// Infer dependence for an accessor whose non-escapable result depends on
// self. This includes _read and _modify.
void inferAccessor(AccessorDecl *accessor, Type selfTypeInContext) {
// Explicit accessors require explicit lifetime dependencies.
if (!isImplicitOrSIL() && !useLazyInference()) {
// Explicit setters require explicit lifetime dependencies.
return;
}
bool nonEscapableSelf = isDiagnosedNonEscapable(selfTypeInContext);
if (nonEscapableSelf && afd->getImplicitSelfDecl()->isInOut()) {
// First, infer the dependency on the inout non-Escapable self. This may
// result in two inferred dependencies for accessors.
inferMutatingAccessor(accessor);
}
// Infer the result dependency on self based on the kind of accessor that
// is wrapped by this synthesized accessors.
if (auto dependenceKind =
getAccessorDependence(accessor, selfTypeInContext)) {
pushDeps(createDeps(resultIndex).add(selfIndex, *dependenceKind));
}
}

// Infer a mutating accessor's non-Escapable 'self' dependencies.
void inferMutatingAccessor(AccessorDecl *accessor) {
switch (accessor->getAccessorKind()) {
case AccessorKind::Read:
case AccessorKind::Read2:
Expand All @@ -1164,8 +1202,6 @@ class LifetimeDependenceChecker {
// _modify for them even though it won't be emitted.
pushDeps(createDeps(selfIndex).add(selfIndex,
LifetimeDependenceKind::Inherit));
pushDeps(createDeps(resultIndex).add(selfIndex,
LifetimeDependenceKind::Scope));
break;
case AccessorKind::Set: {
const unsigned newValIdx = 0;
Expand All @@ -1185,7 +1221,7 @@ class LifetimeDependenceChecker {
// dependency, so the setter cannot depend on 'newValue'.
if (!paramTypeInContext->isEscapable()) {
targetDeps = std::move(targetDeps)
.add(newValIdx, LifetimeDependenceKind::Inherit);
.add(newValIdx, LifetimeDependenceKind::Inherit);
}
pushDeps(std::move(targetDeps));
break;
Expand All @@ -1207,6 +1243,54 @@ class LifetimeDependenceChecker {
}
}

// Implicit accessors must be consistent with the accessor that they
// wrap. Otherwise, the sythesized implementation will report a diagnostic
// error.
std::optional<LifetimeDependenceKind>
getAccessorDependence(AccessorDecl *accessor, Type selfTypeInContext) {
std::optional<AccessorKind> wrappedAccessorKind = std::nullopt;
switch (accessor->getAccessorKind()) {
case AccessorKind::Read:
case AccessorKind::Read2:
case AccessorKind::Modify:
case AccessorKind::Modify2:
// read/modify are syntesized as calls to the getter.
wrappedAccessorKind = AccessorKind::Get;
break;
case AccessorKind::Get:
// getters are synthesized as access to a stored property.
break;
default:
// Unknown synthesized accessor.
// Setters are handled in inferMutatingAccessor() because they don't
// return a value.
return std::nullopt;
}
if (wrappedAccessorKind) {
auto *var = cast<VarDecl>(accessor->getStorage());
for (auto *wrappedAccessor : var->getAllAccessors()) {
if (wrappedAccessor->isImplicit())
continue;
if (wrappedAccessor->getAccessorKind() == wrappedAccessorKind) {
if (auto deps = wrappedAccessor->getLifetimeDependencies()) {
for (auto &dep : *deps) {
if (dep.getTargetIndex() != resultIndex)
continue;
if (dep.checkInherit(selfIndex))
return LifetimeDependenceKind::Inherit;
if (dep.checkScope(selfIndex))
return LifetimeDependenceKind::Scope;
}
}
}
}
}
// Either a Get or Modify without any wrapped accessor. Handle these like a
// read of the stored property.
return inferLifetimeDependenceKind(
selfTypeInContext, afd->getImplicitSelfDecl()->getValueOwnership());
}

// Infer 'inout' parameter dependency when the only parameter is
// non-Escapable.
//
Expand Down
Loading