From ada46a0cf05ca2e44dbce6c31c23776711c86673 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 25 Aug 2025 10:28:08 -0700 Subject: [PATCH 1/6] Lifetime inference: restructure to support multiple dependencies. Restructure the inference logic to allow the same function declaration to independently annotate or infer different lifetime targets. For example: @_lifetime(borrow a) /* DEFAULT: @_lifetime(b: copy b) func f(a: A, b: inout B) -> NE The fact that we prevented this was somewhat accidental and surprising. This restructuring simplifies the code but also gives us much more control over the inference logic. Fixes: rdar://159288750 ("A function cannot have a ~Escapable 'inout' parameter in addition to other ~Escapable parameters" is not actionable) --- include/swift/AST/LifetimeDependence.h | 2 +- lib/AST/LifetimeDependence.cpp | 1205 ++++++++++++------------ 2 files changed, 624 insertions(+), 583 deletions(-) diff --git a/include/swift/AST/LifetimeDependence.h b/include/swift/AST/LifetimeDependence.h index ada4f9d54b4c6..987cbc41d9b42 100644 --- a/include/swift/AST/LifetimeDependence.h +++ b/include/swift/AST/LifetimeDependence.h @@ -218,7 +218,7 @@ class LifetimeDependenceInfo { addressableParamIndicesAndImmortal(addressableParamIndices, isImmortal), conditionallyAddressableParamIndices(conditionallyAddressableParamIndices), targetIndex(targetIndex) { - assert(this->isImmortal() || inheritLifetimeParamIndices || + ASSERT(this->isImmortal() || inheritLifetimeParamIndices || scopeLifetimeParamIndices); ASSERT(!inheritLifetimeParamIndices || !inheritLifetimeParamIndices->isEmpty()); diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index c3313aba6562e..0e248e6bf19a1 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -25,6 +25,7 @@ #include "swift/Basic/Defer.h" #include "swift/Basic/Range.h" #include "swift/Basic/SourceManager.h" +#include "llvm/ADT/MapVector.h" namespace swift { @@ -210,6 +211,9 @@ static bool isBitwiseCopyable(Type type, ASTContext &ctx) { if (!bitwiseCopyableProtocol) { return false; } + if (type->hasError()) + return false; + return (bool)checkConformance(type, bitwiseCopyableProtocol); } @@ -261,7 +265,143 @@ void LifetimeDependenceInfo::getConcatenatedData( } } +// Temporary data structure for building target dependencies. Used by the +// LifetimeDependenceChecker. +struct LifetimeDependenceBuilder { + struct TargetDeps { + SmallBitVector inheritIndices; + SmallBitVector scopeIndices; + bool hasAnnotation; + bool isImmortal = false; + + TargetDeps(bool hasAnnotation, unsigned capacity) + : inheritIndices(capacity), scopeIndices(capacity), + hasAnnotation(hasAnnotation) {} + + bool empty() const { + return !(isImmortal || inheritIndices.any() || scopeIndices.any()); + } + + void addIfNew(unsigned sourceIndex, LifetimeDependenceKind kind) { + // Some inferrence rules may attempt to add an inherit dependency after a + // scope dependency (accessor wrapper + getter method). + if (isImmortal || inheritIndices[sourceIndex] + || scopeIndices[sourceIndex]) { + return; + } + switch (kind) { + case LifetimeDependenceKind::Inherit: + inheritIndices.set(sourceIndex); + break; + case LifetimeDependenceKind::Scope: + scopeIndices.set(sourceIndex); + break; + } + } + }; + + const unsigned sourceIndexCap; + + LifetimeDependenceBuilder(int sourceIndexCap) + : sourceIndexCap(sourceIndexCap) {} + + llvm::SmallMapVector depsArray; + +public: + // True if the builder is uninitialized. This may, however, be false even if + // all TargetDeps are themselves empty. + bool empty() const { return depsArray.empty(); } + + // Return TargetDeps for 'targetIndex' if it has at least one source + // dependency. + const TargetDeps *getTargetDepsOrNull(unsigned targetIndex) const { + auto iter = depsArray.find(targetIndex); + if (iter != depsArray.end() && !iter->second.empty()) { + return &iter->second; + } + return nullptr; + } + + bool hasTargetDeps(unsigned targetIndex) const { + return getTargetDepsOrNull(targetIndex) != nullptr; + } + + TargetDeps *createAnnotatedTargetDeps(unsigned targetIndex) { + auto iterAndInserted = depsArray.try_emplace(targetIndex, true, + sourceIndexCap); + if (!iterAndInserted.second) + return nullptr; + + return &iterAndInserted.first->second; + } + + // Check this before diagnosing any broken inference to avoid diagnosing a + // target that has an explicit annotation. + TargetDeps *getInferredTargetDeps(unsigned targetIndex) { + auto iter = depsArray.try_emplace(targetIndex, false, sourceIndexCap).first; + auto &deps = iter->second; + return deps.hasAnnotation ? nullptr : &deps; + } + + void inferDependency(unsigned targetIndex, unsigned sourceIndex, + LifetimeDependenceKind kind) { + auto targetDeps = getInferredTargetDeps(targetIndex); + if (!targetDeps) + return; + targetDeps->addIfNew(sourceIndex, kind); + } + + void inferInoutDependency(unsigned paramIndex) { + inferDependency(paramIndex, paramIndex, LifetimeDependenceKind::Inherit); + } + + // Allocate LifetimeDependenceInfo in the ASTContext. Initialize it by + // copying heap-allocated TargetDeps fields into ASTContext allocations + // (e.g. convert SmallBitVector to IndexSubset). + std::optional> + initializeDependenceInfoArray(ASTContext &ctx) const { + if (depsArray.empty()) { + return std::nullopt; + } + // Inference might attempt to infer a target, but fail leaving the source + // indices empty. + SmallVector lifetimeDependencies; + for (auto &idxAndDeps : depsArray) { + unsigned targetIndex = idxAndDeps.first; + auto &deps = idxAndDeps.second; + if (deps.empty()) + continue; + + IndexSubset *inheritIndices = nullptr; + if (deps.inheritIndices.any()) { + inheritIndices = IndexSubset::get(ctx, deps.inheritIndices); + ASSERT(!deps.isImmortal + && "cannot combine immortal lifetime with parameter dependency"); + } + IndexSubset *scopeIndices = nullptr; + if (deps.scopeIndices.any()) { + scopeIndices = IndexSubset::get(ctx, deps.scopeIndices); + ASSERT(!deps.isImmortal + && "cannot combine immortal lifetime with parameter dependency"); + } + lifetimeDependencies.push_back(LifetimeDependenceInfo{ + /*inheritLifetimeParamIndices*/ inheritIndices, + /*scopeLifetimeParamIndices*/ scopeIndices, targetIndex, + /*isImmortal*/ deps.isImmortal}); + } + if (lifetimeDependencies.empty()) { + return std::nullopt; + } + return ctx.AllocateCopy(lifetimeDependencies); + } +}; + +/// Diagnostics for ~Escpable types in function signatures. This lowers +/// @_lifetime attributes to the SILFunction's lifetime dependencies and +/// implements the lifetime inferrence rules. class LifetimeDependenceChecker { + using TargetDeps = LifetimeDependenceBuilder::TargetDeps; + ValueDecl *decl; DeclContext *dc; @@ -276,43 +416,53 @@ class LifetimeDependenceChecker { // represent the function result. unsigned resultIndex = ~0; - SmallVector lifetimeDependencies; + LifetimeDependenceBuilder depBuilder; // True if lifetime diganostics have already been performed. Avoids redundant // diagnostics, and allows bypassing diagnostics for special cases. bool performedDiagnostics = false; +public: + static int getResultIndex(AbstractFunctionDecl *afd) { + return afd->hasImplicitSelfDecl() ? (afd->getParameters()->size() + 1) + : afd->getParameters()->size(); + } + + static int getResultIndex(EnumElementDecl *eed) { + auto *paramList = eed->getParameterList(); + return paramList ? paramList->size() + 1 : 1; + } + public: LifetimeDependenceChecker(AbstractFunctionDecl *afd) - : decl(afd), dc(afd->getDeclContext()), ctx(dc->getASTContext()) { + : decl(afd), dc(afd->getDeclContext()), ctx(dc->getASTContext()), + resultIndex(getResultIndex(afd)), + depBuilder(/*sourceIndexCap*/ resultIndex) { + auto resultTypeRepr = afd->getResultTypeRepr(); returnLoc = resultTypeRepr ? resultTypeRepr->getLoc() : afd->getLoc(); if (afd->hasImplicitSelfDecl()) { selfIndex = afd->getParameters()->size(); - resultIndex = selfIndex + 1; - } else { - resultIndex = afd->getParameters()->size(); } } LifetimeDependenceChecker(EnumElementDecl *eed) - : decl(eed), dc(eed->getDeclContext()), ctx(dc->getASTContext()) { - auto *paramList = eed->getParameterList(); - resultIndex = paramList ? eed->getParameterList()->size() + 1 : 1; + : decl(eed), dc(eed->getDeclContext()), ctx(dc->getASTContext()), + resultIndex(getResultIndex(eed)), + depBuilder(/*sourceIndexCap*/ resultIndex) { + + selfIndex = resultIndex - 1; } std::optional> currentDependencies() const { - if (lifetimeDependencies.empty()) { - return std::nullopt; - } - return decl->getASTContext().AllocateCopy(lifetimeDependencies); + return depBuilder.initializeDependenceInfoArray(ctx); } std::optional> checkFuncDecl() { assert(isa(decl) || isa(decl)); - assert(lifetimeDependencies.empty()); + assert(depBuilder.empty()); auto *afd = cast(decl); // Handle Builtins first because, even though Builtins require @@ -328,9 +478,9 @@ class LifetimeDependenceChecker { && !ctx.SourceMgr.isImportMacroGeneratedLoc(returnLoc)) { // Infer inout dependencies without requiring a feature flag. On - // returning, 'lifetimeDependencies' contains any inferred - // dependencies. This does not issue any diagnostics because any invalid - // usage should generate a missing feature flag diagnostic instead. + // returning, 'depBuilder' contains any inferred dependencies. This does + // not issue any diagnostics because using unsupported lifetime features + // may generate a different diagnostic when the feature flag is disabled. inferInoutParams(); diagnoseMissingResultDependencies( @@ -344,12 +494,14 @@ class LifetimeDependenceChecker { } if (afd->getAttrs().hasAttribute()) { - return checkAttribute(); + initializeAttributeDeps(); + if (performedDiagnostics) + return std::nullopt; } // Methods or functions with @_unsafeNonescapableResult do not require // lifetime annotation and do not infer any lifetime dependency. if (afd->getAttrs().hasAttribute()) { - return std::nullopt; + return currentDependencies(); } inferOrDiagnose(); @@ -388,10 +540,8 @@ class LifetimeDependenceChecker { return std::nullopt; } - auto resultIndex = params->size() + /*selfType*/ 1; - auto capacity = resultIndex + 1; - SmallBitVector inheritIndices(capacity); - SmallVector lifetimeDependencies; + TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); + ASSERT(resultDeps && "enum declaration has a lifetime attribute"); // Add all indices of ~Escapable parameters as lifetime dependence sources. for (size_t i = 0; i < params->size(); i++) { @@ -399,18 +549,9 @@ class LifetimeDependenceChecker { if (!isDiagnosedNonEscapable(paramType)) { continue; } - inheritIndices.set(i); - } - if (inheritIndices.none()) { - return std::nullopt; + resultDeps->inheritIndices.set(i); } - auto lifetimeDependenceInfo = LifetimeDependenceInfo( - IndexSubset::get(eed->getASTContext(), inheritIndices), nullptr, - resultIndex, - /*isImmortal*/ false); - lifetimeDependencies.push_back(lifetimeDependenceInfo); - - return eed->getASTContext().AllocateCopy(lifetimeDependencies); + return currentDependencies(); } protected: @@ -422,10 +563,13 @@ class LifetimeDependenceChecker { return ctx.Diags.diagnose(Loc, ID, std::move(Args)...); } + // Issue a diagnostic for an @_lifetime attribute. This does not set + // 'performedDiagnostics', so other lifetime diagnosics are raised as if the + // attribute was not present. template InFlightDiagnostic - diagnose(const Decl *decl, Diag id, - typename detail::PassArgument::type... args) { + diagnoseAttr(const Decl *decl, Diag id, + typename detail::PassArgument::type... args) { return ctx.Diags.diagnose(decl, Diagnostic(id, std::move(args)...)); } @@ -480,6 +624,10 @@ class LifetimeDependenceChecker { || ctx.LangOpts.EnableExperimentalLifetimeDependenceInference; } + // ========================================================================== + // MARK: Catch-all diagnostics for missing attributes and inferrence rules. + // ========================================================================== + std::string diagnosticQualifier() const { auto *afd = cast(decl); if (afd->isImplicit()) { @@ -512,10 +660,7 @@ class LifetimeDependenceChecker { if (!isDiagnosedNonEscapable(getResultOrYield())) { return; } - if (llvm::none_of(lifetimeDependencies, - [&](LifetimeDependenceInfo dep) { - return dep.getTargetIndex() == resultIndex; - })) { + if (!depBuilder.hasTargetDeps(resultIndex)) { ctx.Diags.diagnose(returnLoc, diagID, {StringRef(diagnosticQualifier())}); } @@ -537,10 +682,7 @@ class LifetimeDependenceChecker { if (!isDiagnosedNonEscapable(dc->getSelfTypeInContext())) { return; } - if (llvm::none_of(lifetimeDependencies, - [&](LifetimeDependenceInfo dep) { - return dep.getTargetIndex() == selfIndex; - })) { + if (!depBuilder.hasTargetDeps(selfIndex)) { ctx.Diags.diagnose(selfDecl->getLoc(), diagID, {StringRef(diagnosticQualifier())}); } @@ -558,10 +700,7 @@ class LifetimeDependenceChecker { afd->mapTypeIntoContext(param->getInterfaceType()))) { continue; } - if (llvm::none_of(lifetimeDependencies, - [&](LifetimeDependenceInfo dep) { - return dep.getTargetIndex() == paramIndex; - })) { + if (!depBuilder.hasTargetDeps(paramIndex)) { ctx.Diags.diagnose(param->getLoc(), diagID, {StringRef(diagnosticQualifier()), param->getName().str()}); @@ -575,6 +714,7 @@ class LifetimeDependenceChecker { } } + // Attribute parsing helper. bool isCompatibleWithOwnership(ParsedLifetimeDependenceKind kind, ParamDecl *param, bool isInterfaceFile = false) const { @@ -606,6 +746,7 @@ class LifetimeDependenceChecker { return loweredOwnership == ValueOwnership::InOut; } + // Inferrence helper. bool isCompatibleWithOwnership(LifetimeDependenceKind kind, ParamDecl *param) const { if (kind == LifetimeDependenceKind::Inherit) { @@ -628,59 +769,6 @@ class LifetimeDependenceChecker { loweredOwnership == ValueOwnership::InOut; } - struct TargetDeps { - unsigned targetIndex; - SmallBitVector inheritIndices; - SmallBitVector scopeIndices; - - TargetDeps(unsigned targetIndex, unsigned capacity) - : targetIndex(targetIndex), inheritIndices(capacity), - scopeIndices(capacity) {} - - TargetDeps &&add(unsigned sourceIndex, LifetimeDependenceKind kind) && { - switch (kind) { - case LifetimeDependenceKind::Inherit: - inheritIndices.set(sourceIndex); - break; - case LifetimeDependenceKind::Scope: - scopeIndices.set(sourceIndex); - break; - } - return std::move(*this); - } - }; - - TargetDeps createDeps(unsigned targetIndex) { - auto *afd = cast(decl); - unsigned capacity = afd->hasImplicitSelfDecl() - ? (afd->getParameters()->size() + 1) - : afd->getParameters()->size(); - return TargetDeps(targetIndex, capacity); - } - - // Allocate LifetimeDependenceInfo in the ASTContext and push it onto - // lifetimeDependencies. - void pushDeps(const TargetDeps &&deps) { - assert(llvm::none_of(lifetimeDependencies, - [&](LifetimeDependenceInfo dep) { - return dep.getTargetIndex() == deps.targetIndex; - })); - IndexSubset *inheritIndices = nullptr; - if (deps.inheritIndices.any()) { - inheritIndices = IndexSubset::get(ctx, deps.inheritIndices); - } - IndexSubset *scopeIndices = nullptr; - if (deps.scopeIndices.any()) { - scopeIndices = IndexSubset::get(ctx, deps.scopeIndices); - } - lifetimeDependencies.push_back( - LifetimeDependenceInfo{ - /*inheritLifetimeParamIndices*/ inheritIndices, - /*scopeLifetimeParamIndices*/ scopeIndices, - deps.targetIndex, - /*isImmortal*/ false}); - } - Type getResultOrYield() const { auto *afd = cast(decl); if (auto *accessor = dyn_cast(afd)) { @@ -700,6 +788,10 @@ class LifetimeDependenceChecker { return afd->mapTypeIntoContext(resultType); } + // ========================================================================== + // MARK: @_lifetime attribute semantics + // ========================================================================== + std::optional getDependenceKindFromDescriptor(LifetimeDescriptor descriptor, ParamDecl *paramDecl) { @@ -835,252 +927,125 @@ class LifetimeDependenceChecker { } } - std::optional> checkAttribute() { + // Initialize 'depBuilder' based on the function's @_lifetime attributes. + void initializeAttributeDeps() { auto *afd = cast(decl); - SmallVector lifetimeDependencies; - llvm::SmallSet lifetimeDependentTargets; auto lifetimeAttrs = afd->getAttrs().getAttributes(); for (auto attr : lifetimeAttrs) { - auto lifetimeDependenceInfo = - checkAttributeEntry(attr->getLifetimeEntry()); - if (!lifetimeDependenceInfo.has_value()) { - return std::nullopt; - } - auto targetIndex = lifetimeDependenceInfo->getTargetIndex(); - if (lifetimeDependentTargets.contains(targetIndex)) { - // TODO: Diagnose at the source location of the @lifetime attribute with - // duplicate target. - diagnose(afd->getLoc(), diag::lifetime_dependence_duplicate_target); - } - lifetimeDependentTargets.insert(targetIndex); - lifetimeDependencies.push_back(*lifetimeDependenceInfo); - } - - return afd->getASTContext().AllocateCopy(lifetimeDependencies); - } - - std::optional - checkAttributeEntry(LifetimeEntry *entry) { - auto *afd = cast(decl); - auto capacity = afd->hasImplicitSelfDecl() - ? (afd->getParameters()->size() + 1) - : afd->getParameters()->size(); - - SmallBitVector inheritIndices(capacity); - SmallBitVector scopeIndices(capacity); - - auto updateLifetimeIndices = [&](LifetimeDescriptor descriptor, - unsigned paramIndexToSet, - LifetimeDependenceKind lifetimeKind) { - if (inheritIndices.test(paramIndexToSet) || - scopeIndices.test(paramIndexToSet)) { - diagnose(descriptor.getLoc(), - diag::lifetime_dependence_duplicate_param_id); - return true; - } - if (lifetimeKind == LifetimeDependenceKind::Inherit) { - inheritIndices.set(paramIndexToSet); + LifetimeEntry *entry = attr->getLifetimeEntry(); + auto targetDescriptor = entry->getTargetDescriptor(); + unsigned targetIndex; + if (targetDescriptor.has_value()) { + auto targetDeclAndIndex = getParamDeclFromDescriptor(*targetDescriptor); + if (!targetDeclAndIndex.has_value()) { + return; + } + // TODO: support dependencies on non-inout parameters. + if (!targetDeclAndIndex->first->isInOut()) { + diagnoseAttr(targetDeclAndIndex->first, + diag::lifetime_parameter_requires_inout, + targetDescriptor->getString()); + } + if (isDiagnosedEscapable( + targetDeclAndIndex->first->getTypeInContext())) { + diagnose(targetDescriptor->getLoc(), + diag::lifetime_target_requires_nonescapable, "target"); + } + targetIndex = targetDeclAndIndex->second; } else { - assert(lifetimeKind == LifetimeDependenceKind::Scope); - scopeIndices.set(paramIndexToSet); - } - return false; - }; - - auto targetDescriptor = entry->getTargetDescriptor(); - unsigned targetIndex; - if (targetDescriptor.has_value()) { - auto targetDeclAndIndex = getParamDeclFromDescriptor(*targetDescriptor); - if (!targetDeclAndIndex.has_value()) { - return std::nullopt; - } - // TODO: support dependencies on non-inout parameters. - if (!targetDeclAndIndex->first->isInOut()) { - diagnose(targetDeclAndIndex->first, - diag::lifetime_parameter_requires_inout, - targetDescriptor->getString()); - } - if (isDiagnosedEscapable(targetDeclAndIndex->first->getTypeInContext())) { - diagnose(targetDescriptor->getLoc(), - diag::lifetime_target_requires_nonescapable, "target"); - } - targetIndex = targetDeclAndIndex->second; - } else { - if (isDiagnosedEscapable(getResultOrYield())) { - diagnose(entry->getLoc(), diag::lifetime_target_requires_nonescapable, - "result"); - } - targetIndex = afd->hasImplicitSelfDecl() - ? afd->getParameters()->size() + 1 - : afd->getParameters()->size(); - } - - for (auto source : entry->getSources()) { - if (source.isImmortal()) { - auto immortalParam = - std::find_if(afd->getParameters()->begin(), - afd->getParameters()->end(), [](ParamDecl *param) { - return param->getName().nonempty() - && strcmp(param->getName().get(), "immortal") == 0; - }); - if (immortalParam != afd->getParameters()->end()) { - diagnose(*immortalParam, - diag::lifetime_dependence_immortal_conflict_name); - return std::nullopt; + if (isDiagnosedEscapable(getResultOrYield())) { + diagnose(entry->getLoc(), diag::lifetime_target_requires_nonescapable, + "result"); } - return LifetimeDependenceInfo(nullptr, nullptr, targetIndex, - /*isImmortal*/ true); - } - - auto paramDeclAndIndex = getParamDeclFromDescriptor(source); - if (!paramDeclAndIndex.has_value()) { - return std::nullopt; - } - auto *param = paramDeclAndIndex->first; - unsigned sourceIndex = paramDeclAndIndex->second; - auto lifetimeKind = getDependenceKindFromDescriptor(source, param); - if (!lifetimeKind.has_value()) { - return std::nullopt; - } - if (lifetimeKind == LifetimeDependenceKind::Scope - && param->isInOut() - && sourceIndex == targetIndex) { - diagnose(source.getLoc(), - diag::lifetime_dependence_cannot_use_parsed_borrow_inout); - ctx.Diags.diagnose(source.getLoc(), - diag::lifetime_dependence_cannot_infer_inout_suggest, - param->getName().str()); - - return std::nullopt; + targetIndex = afd->hasImplicitSelfDecl() + ? afd->getParameters()->size() + 1 + : afd->getParameters()->size(); + } + TargetDeps *deps = depBuilder.createAnnotatedTargetDeps(targetIndex); + if (deps == nullptr) { + diagnose(attr->getLocation(), + diag::lifetime_dependence_duplicate_target); + return; } - bool hasError = - updateLifetimeIndices(source, sourceIndex, *lifetimeKind); - if (hasError) { - return std::nullopt; + for (auto source : entry->getSources()) { + initializeDescriptorDeps(targetIndex, *deps, source); } } - - return LifetimeDependenceInfo( - inheritIndices.any() ? IndexSubset::get(ctx, inheritIndices) : nullptr, - scopeIndices.any() ? IndexSubset::get(ctx, scopeIndices) : nullptr, - targetIndex, /*isImmortal*/ false); } - // On returning, 'lifetimeDependencies' contains any inferred dependencies and - // 'performedDiagnostics' indicates whether any specific diagnostics were - // issued. - void inferOrDiagnose() { - // Infer non-Escapable results. - if (isDiagnosedNonEscapable(getResultOrYield())) { - if (hasImplicitSelfParam()) { - // Methods that return a non-Escapable value. - inferNonEscapableResultOnSelf(); + // Initialize TargetDeps based on the function's @_lifetime attributes. + void initializeDescriptorDeps(unsigned targetIndex, + TargetDeps &deps, + LifetimeDescriptor source) { + auto *afd = cast(decl); + if (source.isImmortal()) { + // Record the immortal dependency even if it is invalid to suppress other diagnostics. + deps.isImmortal = true; + auto immortalParam = std::find_if( + afd->getParameters()->begin(), afd->getParameters()->end(), + [](ParamDecl *param) { + return param->getName().nonempty() + && strcmp(param->getName().get(), "immortal") == 0; + }); + if (immortalParam != afd->getParameters()->end()) { + diagnoseAttr(*immortalParam, + diag::lifetime_dependence_immortal_conflict_name); return; } - if (isInit() && isImplicitOrSIL()) { - inferImplicitInit(); - return; + if (deps.inheritIndices.any() || deps.scopeIndices.any()) { + diagnoseAttr(*immortalParam, diag::lifetime_dependence_immortal_alone); } - // Regular functions and initializers that return a non-Escapable value. - inferNonEscapableResultOnParam(); return; } - // Infer mutating non-Escapable methods (excluding initializers). - inferMutatingSelf(); - - // Infer inout parameters. - inferInoutParams(); - } - - /// If the current function is a mutating method and 'self' is non-Escapable, - /// return 'self's ParamDecl. - bool isMutatingNonEscapableSelf() { - auto *afd = cast(decl); - if (!hasImplicitSelfParam()) - return false; - - if (!isDiagnosedNonEscapable(dc->getSelfTypeInContext())) - return false; - - assert(!isInit() && "class initializers have Escapable self"); - auto *selfDecl = afd->getImplicitSelfDecl(); - if (!selfDecl->isInOut()) - return false; - - return true; - } - - // Infer method dependence of result on self for - // methods, getters, and _modify accessors. - void inferNonEscapableResultOnSelf() { - auto *afd = cast(decl); - Type selfTypeInContext = dc->getSelfTypeInContext(); - if (selfTypeInContext->hasError()) { + auto paramDeclAndIndex = getParamDeclFromDescriptor(source); + if (!paramDeclAndIndex.has_value()) { return; } - - bool nonEscapableSelf = isDiagnosedNonEscapable(selfTypeInContext); - auto accessor = dyn_cast(afd); - if (accessor) { - 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 not infer the result's dependence when the method is mutating and - // 'self' is non-Escapable. Independently, a missing dependence on inout - // 'self' will be diagnosed. Since an explicit annotation will be needed for - // 'self', we also require the method's result to have an explicit - // annotation. - if (nonEscapableSelf && afd->getImplicitSelfDecl()->isInOut()) { + auto *param = paramDeclAndIndex->first; + unsigned sourceIndex = paramDeclAndIndex->second; + auto lifetimeKind = getDependenceKindFromDescriptor(source, param); + if (!lifetimeKind.has_value()) { return; } - // Methods with parameters only apply to lazy inference. This does not - // include accessors because a subscript's index is assumed not to be the - // source of the result's dependency. - if (!accessor && !useLazyInference() && afd->getParameters()->size() > 0) { + if (lifetimeKind == LifetimeDependenceKind::Scope && param->isInOut() + && sourceIndex == targetIndex) { + diagnose(source.getLoc(), + diag::lifetime_dependence_cannot_use_parsed_borrow_inout); + ctx.Diags.diagnose(source.getLoc(), + diag::lifetime_dependence_cannot_infer_inout_suggest, + param->getName().str()); + return; } - if (!useLazyInference() && !isImplicitOrSIL()) { - // Require explicit @_lifetime(borrow self) for UnsafePointer-like self. - if (!nonEscapableSelf && isBitwiseCopyable(selfTypeInContext, ctx)) { - diagnose(returnLoc, - diag::lifetime_dependence_cannot_infer_bitwisecopyable, - diagnosticQualifier(), "self"); - return; - } - // Require explicit @_lifetime(copy or borrow) for non-Escapable self. - if (nonEscapableSelf) { - diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_kind, - diagnosticQualifier(), "self"); - return; - } + addDescriptorIndices(deps, source, sourceIndex, *lifetimeKind); + } + + void addDescriptorIndices(TargetDeps &deps, LifetimeDescriptor descriptor, + unsigned paramIndexToSet, + LifetimeDependenceKind lifetimeKind) { + if (deps.isImmortal) { + diagnose(descriptor.getLoc(), diag::lifetime_dependence_immortal_alone); + return; } - // Infer based on ownership if possible for either explicit accessors or - // methods as long as they pass preceding ambiguity checks. - auto kind = inferLifetimeDependenceKind(afd->getImplicitSelfDecl()); - if (!kind) { - // Special diagnostic for an attempt to depend on a consuming parameter. - diagnose(returnLoc, - diag::lifetime_dependence_cannot_infer_scope_ownership, - "self", diagnosticQualifier()); + if (deps.inheritIndices.test(paramIndexToSet) + || deps.scopeIndices.test(paramIndexToSet)) { + diagnose(descriptor.getLoc(), + diag::lifetime_dependence_duplicate_param_id); return; } - pushDeps(createDeps(resultIndex).add(selfIndex, *kind)); + if (lifetimeKind == LifetimeDependenceKind::Inherit) { + deps.inheritIndices.set(paramIndexToSet); + } else { + assert(lifetimeKind == LifetimeDependenceKind::Scope); + deps.scopeIndices.set(paramIndexToSet); + } } + // ========================================================================== + // MARK: Inferrence rules + // ========================================================================== + // Infer the kind of dependence that makes sense for reading or writing a // stored property (for getters or initializers). std::optional @@ -1107,58 +1072,278 @@ class LifetimeDependenceChecker { return LifetimeDependenceKind::Scope; } - // 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(decl); - if (afd->getParameters()->size() == 0) { - // Empty ~Escapable types can be implicitly initialized without any - // dependencies. In SIL, implicit initializers become explicit. Set - // performedDiagnostics here to bypass normal dependence checking without - // raising an error. - performedDiagnostics = true; - return; - } - auto targetDeps = createDeps(resultIndex); - unsigned paramIndex = 0; - for (auto *param : *afd->getParameters()) { - SWIFT_DEFER { paramIndex++; }; - Type paramTypeInContext = - afd->mapTypeIntoContext(param->getInterfaceType()); - 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(param); - if (!kind) { - diagnose(returnLoc, - diag::lifetime_dependence_cannot_infer_scope_ownership, - param->getParameterName().str(), diagnosticQualifier()); - } - targetDeps = std::move(targetDeps).add(paramIndex, - LifetimeDependenceKind::Scope); + // On returning, 'depBuilder' contains any inferred dependencies and + // 'performedDiagnostics' indicates whether any specific diagnostics were + // issued. + void inferOrDiagnose() { + if (auto accessor = dyn_cast(decl)) { + inferAccessor(accessor); + // Aside from the special cases handled above, accessors are considered + // regular methods... } - pushDeps(std::move(targetDeps)); - } - // Infer result dependence on a function or intitializer parameter. + // Infer non-Escapable results. + if (isDiagnosedNonEscapable(getResultOrYield())) { + if (isInit() && isImplicitOrSIL()) { + inferImplicitInit(); + } else if (hasImplicitSelfParam()) { + // Methods that return a non-Escapable value - single parameter + // default rule. + inferNonEscapableResultOnSelf(); + } else { + // Regular functions and initializers that return a non-Escapable value + // - single parameter default rule. + inferNonEscapableResultOnParam(); + } + } + + // Infer mutating non-Escapable methods (excluding initializers) - + // `inout` parameter default rule. + inferMutatingSelf(); + + // Infer inout parameters - `inout` parameter default rule. + inferInoutParams(); + } + + // Infer dependence for an accessor whose non-escapable result depends on + // self. This includes _read and _modify. + // + // Any accessors not handled here will be handled like a normal method. + void inferAccessor(AccessorDecl *accessor) { + if (!hasImplicitSelfParam()) { + // global accessors have no 'self'. + return; + } + bool nonEscapableSelf = isDiagnosedNonEscapable(dc->getSelfTypeInContext()); + if (nonEscapableSelf && accessor->getImplicitSelfDecl()->isInOut()) { + // First, infer the dependency of the inout non-Escapable 'self'. This may + // result in two inferred dependencies for accessors. + inferMutatingAccessor(accessor); + } + // Handle synthesized wrappers... + if (!isImplicitOrSIL() && !useLazyInference()) + return; + + // Infer the result dependency of the result or yielded value on 'self' + // based on the kind of accessor called by this wrapper accessor. + if (auto dependenceKind = getImplicitAccessorResultDependence(accessor)) { + depBuilder.inferDependency(resultIndex, selfIndex, *dependenceKind); + } + } + + // Infer a mutating accessor's non-Escapable 'self' dependencies. + void inferMutatingAccessor(AccessorDecl *accessor) { + switch (accessor->getAccessorKind()) { + case AccessorKind::Read: + case AccessorKind::Read2: + case AccessorKind::Modify: + case AccessorKind::Modify2: + // '_read' and '_modify' are inferred like regular methods. The yielded + // value depends on the single 'self' parameter. Additionally, '_modify' + // infers 'self' as an 'inout' parameter. + // + // The caller of _modify will ensure that the modified 'self', passed as + // 'inout', depends on any value stored to the yielded address. + // + // Note that the AST generates a _modify for stored properties even though + // it won't be emitted. + break; + case AccessorKind::Set: { + const unsigned newValIdx = 0; + auto *afd = cast(decl); + auto *param = afd->getParameters()->get(newValIdx); + Type paramTypeInContext = + afd->mapTypeIntoContext(param->getInterfaceType()); + if (paramTypeInContext->hasError()) { + return; + } + depBuilder.inferInoutDependency(selfIndex); + + // The 'newValue' dependence kind must match the getter's dependence kind + // because the generated '_modify' accessor composes the getter's result + // with the setter's 'newValue'. In particular, if the getter's result is + // Escapable then the getter does not have any lifetime dependency, so the + // setter cannot depend on 'newValue'. + if (!paramTypeInContext->isEscapable()) { + depBuilder.inferDependency(selfIndex, newValIdx, + LifetimeDependenceKind::Inherit); + } + break; + } + case AccessorKind::MutableAddress: + if (useLazyInference()) { + // Assume that a mutating method does not depend on its parameters. + // Currently only for backward interface compatibility. Even though this + // is the only useful dependence (a borrow of self is possible but not + // useful), explicit annotation is required for now to confirm that the + // mutated self cannot depend on anything stored at this address. + depBuilder.inferInoutDependency(selfIndex); + } + break; + default: + // Unknown mutating accessor. + break; + } + } + + // Implicit accessors must be consistent with the accessor that they + // wrap. Otherwise, the sythesized implementation will report a diagnostic + // error. + std::optional + getImplicitAccessorResultDependence(AccessorDecl *accessor) { + if (!isDiagnosedNonEscapable(getResultOrYield())) + return std::nullopt; + + std::optional 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(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(accessor->getImplicitSelfDecl()); + } + + // 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(decl); + if (afd->getParameters()->size() == 0) { + // Empty ~Escapable types can be implicitly initialized without any + // dependencies. In SIL, implicit initializers become explicit. Set + // performedDiagnostics here to bypass normal dependence checking without + // raising an error. + performedDiagnostics = true; + return; + } + TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); + if (!resultDeps) + return; // .sil implicit initializers may have been annotated. + + unsigned paramIndex = 0; + for (auto *param : *afd->getParameters()) { + SWIFT_DEFER { paramIndex++; }; + Type paramTypeInContext = + afd->mapTypeIntoContext(param->getInterfaceType()); + if (paramTypeInContext->hasError()) { + return; + } + if (!paramTypeInContext->isEscapable()) { + // An implicitly initialized non-Escapable value always copies its + // dependency. + resultDeps->addIfNew(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(param); + if (!kind) { + diagnose(returnLoc, + diag::lifetime_dependence_cannot_infer_scope_ownership, + param->getParameterName().str(), diagnosticQualifier()); + } + resultDeps->addIfNew(paramIndex, LifetimeDependenceKind::Scope); + } + } + + // Infer method dependence of result on self for methods, getters, and _modify + // accessors. Implements the single-parameter rule for methods and accessors + // accessors (ignoring the subscript index parameter). + void inferNonEscapableResultOnSelf() { + auto *afd = cast(decl); + + TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); + if (!resultDeps) + return; + + bool nonEscapableSelf = isDiagnosedNonEscapable(dc->getSelfTypeInContext()); + // Do not infer the result's dependence when the method is mutating and + // 'self' is non-Escapable. Independently, a missing dependence on inout + // 'self' will be diagnosed. Since an explicit annotation will be needed for + // 'self', we also require the method's result to have an explicit + // annotation. + if (nonEscapableSelf && afd->getImplicitSelfDecl()->isInOut()) { + return; + } + // Methods with parameters only apply to lazy inference. This does not + // include accessors because a subscript's index is assumed not to be the + // source of the result's dependency. + if (!isa(afd) && !useLazyInference() + && afd->getParameters()->size() > 0) { + return; + } + if (!useLazyInference() && !isImplicitOrSIL()) { + // Require explicit @_lifetime(borrow self) for UnsafePointer-like self. + if (!nonEscapableSelf + && isBitwiseCopyable(dc->getSelfTypeInContext(), ctx)) { + diagnose(returnLoc, + diag::lifetime_dependence_cannot_infer_bitwisecopyable, + diagnosticQualifier(), "self"); + return; + } + // Require explicit @_lifetime(copy or borrow) for non-Escapable self. + if (nonEscapableSelf) { + diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_kind, + diagnosticQualifier(), "self"); + return; + } + } + // Infer based on ownership if possible for either explicit accessors or + // methods as long as they pass preceding ambiguity checks. + auto kind = inferLifetimeDependenceKind(afd->getImplicitSelfDecl()); + if (!kind) { + // Special diagnostic for an attempt to depend on a consuming parameter. + diagnose(returnLoc, + diag::lifetime_dependence_cannot_infer_scope_ownership, + "self", diagnosticQualifier()); + return; + } + resultDeps->addIfNew(selfIndex, *kind); + } + + // Infer result dependence on a function or intitializer parameter. + // Implements the single-parameter rule for functions. // // Note: for implicit initializers with parameters, consider inferring // Inherit dependency for each non-Escapable parameter. This would be @@ -1175,6 +1360,9 @@ class LifetimeDependenceChecker { if (useLazyInference()) { return lazillyInferNonEscapableResultOnParam(); } + TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); + if (!resultDeps) + return; // Strict inference only handles a single escapable parameter, // which is an unambiguous borrow dependence. @@ -1209,13 +1397,17 @@ class LifetimeDependenceChecker { param->getParameterName().str(), diagnosticQualifier()); return; } - pushDeps(createDeps(resultIndex).add(/*paramIndex*/ 0, kind)); + resultDeps->addIfNew(/*paramIndex*/ 0, kind); } // Lazy inference for .swiftinterface backward compatibility and // experimentation. Inference cases can be added but not removed. void lazillyInferNonEscapableResultOnParam() { auto *afd = cast(decl); + TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); + if (!resultDeps) + return; + std::optional candidateParamIndex; std::optional candidateLifetimeKind; unsigned paramIndex = 0; @@ -1254,170 +1446,29 @@ class LifetimeDependenceChecker { diagnosticQualifier()); return; } - pushDeps(createDeps(resultIndex).add(*candidateParamIndex, - *candidateLifetimeKind)); + resultDeps->addIfNew(*candidateParamIndex, *candidateLifetimeKind); } // Infer a mutating 'self' dependency when 'self' is non-Escapable and the // result is 'void'. void inferMutatingSelf() { auto *afd = cast(decl); - if (!isMutatingNonEscapableSelf()) { - return; - } - // Handle implicit setters before diagnosing mutating methods. This - // does not include global accessors, which have no implicit 'self'. - if (auto accessor = dyn_cast(afd)) { - // Explicit setters require explicit lifetime dependencies. - if (isImplicitOrSIL() || useLazyInference()) { - inferMutatingAccessor(accessor); - } - return; - } - if (afd->getParameters()->size() > 0) { - if (useLazyInference()) { - // Assume that a mutating method does not depend on its parameters. - // This is unsafe but needed because some MutableSpan APIs snuck into - // the standard library interface without specifying dependencies. - pushDeps(createDeps(selfIndex).add(selfIndex, - LifetimeDependenceKind::Inherit)); - } + if (!hasImplicitSelfParam()) return; - } - pushDeps(createDeps(selfIndex).add(selfIndex, - LifetimeDependenceKind::Inherit)); - } - // Infer dependence for an accessor whose non-escapable result depends on - // self. This includes _read and _modify. - void inferAccessor(AccessorDecl *accessor, Type selfTypeInContext) { - auto *afd = cast(decl); - if (!isImplicitOrSIL() && !useLazyInference()) { + if (!isDiagnosedNonEscapable(dc->getSelfTypeInContext())) 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: - // An implicit _read/read accessor is generated when a mutating getter is - // declared. Emit the same lifetime dependencies as an implicit _modify. - case AccessorKind::Modify: - case AccessorKind::Modify2: - // A _modify's yielded value depends on self. The _modify dependency in - // the opposite direction (self depends on the modified value) is not - // recorded. The caller of _modify ensures that the modified 'self', - // passed as 'inout', depends on any value stored to the yielded address. - // - // This is required for stored properties because the AST generates a - // _modify for them even though it won't be emitted. - pushDeps(createDeps(selfIndex).add(selfIndex, - LifetimeDependenceKind::Inherit)); - break; - case AccessorKind::Set: { - const unsigned newValIdx = 0; - auto *afd = cast(decl); - auto *param = afd->getParameters()->get(newValIdx); - Type paramTypeInContext = - afd->mapTypeIntoContext(param->getInterfaceType()); - if (paramTypeInContext->hasError()) { - return; - } - auto targetDeps = - createDeps(selfIndex).add(selfIndex, LifetimeDependenceKind::Inherit); + assert(!isInit() && "class initializers have Escapable self"); + auto *selfDecl = afd->getImplicitSelfDecl(); + if (!selfDecl->isInOut()) + return; - // The 'newValue' dependence kind must match the getter's dependence kind - // because generated the implementation '_modify' accessor composes the - // getter's result with the setter's 'newValue'. In particular, if the - // result type is Escapable then the getter does not have any lifetime - // dependency, so the setter cannot depend on 'newValue'. - if (!paramTypeInContext->isEscapable()) { - targetDeps = std::move(targetDeps) - .add(newValIdx, LifetimeDependenceKind::Inherit); - } - pushDeps(std::move(targetDeps)); - break; - } - case AccessorKind::MutableAddress: - if (useLazyInference()) { - // Assume that a mutating method does not depend on its parameters. - // Currently only for backward interface compatibility. Even though this - // is the only useful dependence (a borrow of self is possible but not - // useful), explicit annotation is required for now to confirm that the - // mutated self cannot depend on anything stored at this address. - pushDeps(createDeps(selfIndex).add(selfIndex, - LifetimeDependenceKind::Inherit)); - } - break; - default: - // Unknown mutating accessor. - break; - } + // Assume that a mutating method does not depend on its parameters. + depBuilder.inferInoutDependency(selfIndex); } - // Implicit accessors must be consistent with the accessor that they - // wrap. Otherwise, the sythesized implementation will report a diagnostic - // error. - std::optional - getAccessorDependence(AccessorDecl *accessor, Type selfTypeInContext) { - std::optional 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(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(accessor->getImplicitSelfDecl()); - } - - // Infer 'inout' parameter dependency when the only parameter is - // non-Escapable. + // Infer @_lifetime(param: copy param) for 'inout' non-Escapable parameters. // // This supports the common case in which the user of a non-Escapable type, // such as MutableSpan, wants to modify the span's contents without modifying @@ -1427,48 +1478,38 @@ class LifetimeDependenceChecker { // MutableSpan method could update the underlying unsafe pointer and forget to // declare a dependence on the incoming pointer. // - // Disallowing other non-Escapable parameters rules out the easy mistake of - // programmers attempting to trivially reassign the inout parameter. There's - // is no way to rule out the possibility that they derive another - // non-Escapable value from an Escapable parameteter. So they can still write - // the following and will get a lifetime diagnostic: + // This also allows programmers to make the easy mistake reassign the inout + // parameter to another parameter: + // + // func reassign(s: inout MutableSpan, a: MutableSpan) { + // s = a + // } + // + // But, even if that case were disallowed, they may derive another + // non-Escapable value from an Escapable parameteter: // // func reassign(s: inout MutableSpan, a: [Int]) { // s = a.mutableSpan // } // + // In either case, a diagnostics on the `reassign` function's implementation + // will catch the invalid reassignment. The only real danger is when the + // implementation is uses unsafe constructs. + // // Do not issue any diagnostics. This inference is triggered even when the // feature is disabled! void inferInoutParams() { auto *afd = cast(decl); - if (isMutatingNonEscapableSelf()) { - return; - } - std::optional candidateParamIndex; - bool hasNonEscapableParameter = false; - if (hasImplicitSelfParam() - && isDiagnosedNonEscapable(dc->getSelfTypeInContext())) { - hasNonEscapableParameter = true; - } for (unsigned paramIndex : range(afd->getParameters()->size())) { auto *param = afd->getParameters()->get(paramIndex); - if (isDiagnosedNonEscapable( - afd->mapTypeIntoContext(param->getInterfaceType()))) { - if (param->isInOut()) { - if (hasNonEscapableParameter) - return; - candidateParamIndex = paramIndex; - continue; - } - if (candidateParamIndex) - return; - - hasNonEscapableParameter = true; + if (!isDiagnosedNonEscapable( + afd->mapTypeIntoContext(param->getInterfaceType()))) { + continue; } - } - if (candidateParamIndex) { - pushDeps(createDeps(*candidateParamIndex).add( - *candidateParamIndex, LifetimeDependenceKind::Inherit)); + if (!param->isInOut()) + continue; + + depBuilder.inferInoutDependency(paramIndex); } } @@ -1486,17 +1527,14 @@ class LifetimeDependenceChecker { afd->mapTypeIntoContext(param->getInterfaceType()))) { return; } - pushDeps(createDeps(paramIndex).add(paramIndex, - LifetimeDependenceKind::Inherit)); + depBuilder.inferInoutDependency(paramIndex); } void inferBuiltin() { auto *afd = cast(decl); // Normal inout parameter inference works for most generic Builtins. inferUnambiguousInoutParams(); - if (!lifetimeDependencies.empty()) { - return; - } + const DeclName &name = afd->getName(); if (name.isSpecial()) { return; @@ -1510,16 +1548,15 @@ class LifetimeDependenceChecker { ctx.getIdentifier(getBuiltinName(BuiltinValueKind::InjectEnumTag))) { // ignore the tag parameter const unsigned inoutIdx = 0; - pushDeps(createDeps(inoutIdx).add(inoutIdx, - LifetimeDependenceKind::Inherit)); + depBuilder.inferInoutDependency(inoutIdx); } else if (id == ctx.getIdentifier( getBuiltinName(BuiltinValueKind::ConvertUnownedUnsafeToGuaranteed))) { const unsigned baseIdx = 0; const unsigned inoutIdx = 1; - pushDeps(createDeps(inoutIdx) - .add(inoutIdx, LifetimeDependenceKind::Inherit) - .add(baseIdx, LifetimeDependenceKind::Scope)); + depBuilder.inferInoutDependency(inoutIdx); + depBuilder.inferDependency(inoutIdx, baseIdx, + LifetimeDependenceKind::Scope); } } }; @@ -1533,9 +1570,32 @@ LifetimeDependenceInfo::get(ValueDecl *decl) { return LifetimeDependenceChecker(eed).checkEnumElementDecl(); } +void LifetimeDependenceInfo::dump() const { + llvm::errs() << "target: " << getTargetIndex() << '\n'; + if (isImmortal()) { + llvm::errs() << " immortal\n"; + } + if (auto scoped = getScopeIndices()) { + llvm::errs() << " scoped: "; + scoped->dump(); + } + if (auto inherited = getInheritIndices()) { + llvm::errs() << " inherited: "; + inherited->dump(); + } + if (auto addressable = getAddressableIndices()) { + llvm::errs() << " addressable: "; + addressable->dump(); + } +} + +// ============================================================================= +// SIL parsing support +// ============================================================================= + // This implements the logic for SIL type descriptors similar to source-level -// logic in LifetimeDependenceChecker::checkAttributeEntry(). The SIL context is -// substantially different from Sema. +// logic in LifetimeDependenceChecker::initializeAttributeDeps(). The SIL +// context is substantially different from Sema. static std::optional checkSILTypeModifiers( LifetimeDependentTypeRepr *lifetimeDependentRepr, unsigned targetIndex, ArrayRef params, DeclContext *dc) { @@ -1668,23 +1728,4 @@ LifetimeDependenceInfo::getFromSIL(FunctionTypeRepr *funcRepr, return dc->getASTContext().AllocateCopy(lifetimeDependencies); } -void LifetimeDependenceInfo::dump() const { - llvm::errs() << "target: " << getTargetIndex() << '\n'; - if (isImmortal()) { - llvm::errs() << " immortal\n"; - } - if (auto scoped = getScopeIndices()) { - llvm::errs() << " scoped: "; - scoped->dump(); - } - if (auto inherited = getInheritIndices()) { - llvm::errs() << " inherited: "; - inherited->dump(); - } - if (auto addressable = getAddressableIndices()) { - llvm::errs() << " addressable: "; - addressable->dump(); - } -} - } // namespace swift From 37acd5781eadc654ea3d077dd828702bc2c6014d Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 2 Sep 2025 21:06:32 -0700 Subject: [PATCH 2/6] Infer @_lifetime for mutating methods without experimental feature Infer @_lifetime(self: copy self) for mutating methods without requiring the experimental Lifetimes feature to be disabled. Treatment of mutating 'self' is now consistent with other 'inout' parameters. Example: extension MutableSpan { mutating func mutatingMethod() {...} } --- lib/AST/LifetimeDependence.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index 0e248e6bf19a1..eeda2655e016c 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -481,6 +481,7 @@ class LifetimeDependenceChecker { // returning, 'depBuilder' contains any inferred dependencies. This does // not issue any diagnostics because using unsupported lifetime features // may generate a different diagnostic when the feature flag is disabled. + inferMutatingSelf(); inferInoutParams(); diagnoseMissingResultDependencies( From ed9283e1ad3ec72caa8ce7d9aba8c60b29dd0a53 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Wed, 3 Sep 2025 15:18:55 -0700 Subject: [PATCH 3/6] [NFC] drive-by comment --- SwiftCompilerSources/Sources/SIL/ApplySite.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/SwiftCompilerSources/Sources/SIL/ApplySite.swift b/SwiftCompilerSources/Sources/SIL/ApplySite.swift index 0e8a818d5df93..8f092a13f700f 100644 --- a/SwiftCompilerSources/Sources/SIL/ApplySite.swift +++ b/SwiftCompilerSources/Sources/SIL/ApplySite.swift @@ -72,6 +72,7 @@ public struct ApplyOperandConventions : Collection { calleeArgumentIndex(ofOperandIndex: operandIndex)!] } + // If the specified parameter is a dependency target, return its dependency sources. public subscript(parameterDependencies operandIndex: Int) -> FunctionConvention.LifetimeDependencies? { return calleeArgumentConventions[parameterDependencies: From bbdadac8fefaaf7b6752d8801b40c05d5dd555aa Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Fri, 29 Aug 2025 00:12:24 -0700 Subject: [PATCH 4/6] Document @_lifetime annotation --- docs/ReferenceGuides/LifetimeAnnotation.md | 184 +++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 docs/ReferenceGuides/LifetimeAnnotation.md diff --git a/docs/ReferenceGuides/LifetimeAnnotation.md b/docs/ReferenceGuides/LifetimeAnnotation.md new file mode 100644 index 0000000000000..ec7b79537edb0 --- /dev/null +++ b/docs/ReferenceGuides/LifetimeAnnotation.md @@ -0,0 +1,184 @@ +# @_lifetime annotation + +## Introduction + +`@_lifetime` annotations are now available under `-enable-experimental-features Lifetimes`. The feature proposal is documented in [PR: Lifetime dependencies #2750](https://github.com/swiftlang/swift-evolution/pull/2750). + +To summarize the basic syntax: functions require a `@_lifetime` annotation when they return a non-Escapable type, either as the function result, or as an `inout` parameter. The annotation syntax pattern is `@_lifetime(target: source)` where `target` is a function output and `source` is an input. If `target:` is omitted, then it is assumed to be the function's result. + +` ::= [borrow|&|copy]` + +`borrow` creates a new borrow scope that guarantees exclusive read access to the caller's `source` argument over all uses of `target`. + +`&` creates a new borrow scope that guarantees exclusive write access to the caller's `source` argument over all uses of `target`. + +`copy` copies the lifetime constraints on the caller's `source` argument to `target`. + +The `@lifetime` annotation is enforced both in the body of the function and at each call site. For both `borrow` and `&` scoped dependencies, the function's implementation guarantees that `target` is valid as long as `source` is alive, and each caller of the function guarantees that `source` will outlive `target`. For `copy` dependencies, the function's implementation guarantees that all constraints on `target` are copied from `source`, and the caller propagates all lifetime constraints on `source` to all uses of `target`. + +## Default lifetimes + +The Swift 6.2 compiler provided default `@_lifetime` behavior whenever it can do so without ambiguity. Often, despite ambiguity, an "obvious" default exists, but we wanted to introduce defaults slowly after developers have enough experience to inform discussion about them. This document tracks the current state of the implementation as it progresses from the original 6.2 implementation. Corresponding tests are in `test/Sema/lifetime_depend_infer.swift`; searching for "DEFAULT:" highlights the rules defined below... + +### Single parameter default rule + +Given a function or method that returns a non-Escapable result: + +- Default to `@_lifetime( a)` for a `~Escapable` result on functions with a single parameter `a`. + +- Default to `@_lifetime( self)` for a `~Escapable` result on methods with no parameters. + +| Type of parameter | default | +| (`a` or `self`) | lifetime dependency | +| ----------------- | ------------------------------ | +| `Escapable` | `@_lifetime(borrow param)`[^1] | +| `inout Escapable` | `@_lifetime(¶m)`[^1] | +| `~Escapable` | none[^2] | + +[^1]: When the parameter is `BitwiseCopyable`, such as an integer or unsafe pointer, the single parameter default rule applies to function parameters but not to the implicit `self` parameter. Depending on a `BitwiseCopyable` value is a convenience for APIs that construct span-like values from an `UnsafePointer` passed as an argument. This creates a dependency on a local copy of the pointer variable with subtle semantics. User-defined `BitwiseCopyable` structs should generally avoid such subtle lifetime dependencies. If needed, the author of the data type should explicitly opt into them. + +[^2]: When the single parameter is also `~Escapable`, the result must depend on it, but the dependency may either be scoped (`borrow` or `&`) or it may be copied (`copy`). `copy` is the obvious choice when the parameter and result are the same type, but it is not always correct. Furthermore, a lifetime dependency can only be copied from a generic type when result as the same generic type. This case is therefore handled by same-type default lifetime (discussed below) rather than as a default `@_lifetime` rule. + +Examples: + +```swift +struct A: Escapable { + let obj: AnyObject // ~BitwiseCopyable +} +struct NE: ~Escapable {...} + +/* DEFAULT: @_lifetime(borrow a) */ +func oneParam_NEResult(a: A) -> NE + +/* DEFAULT: @_lifetime(&a) */ +func oneInoutParam_NEResult(a: inout A) -> NE + +extension A /* Self: Escapable */ { + /* DEFAULT: @_lifetime(borrow self) */ + func noParam_NEResult() -> NE + + /* DEFAULT: @_lifetime(&self) */ + mutating func mutating_noParam_NEResult() -> NE +} +``` + +### Implicit initializer and setter defaults + +An implicit setter of a `~Escapable` stored property defaults to `@_lifetime(self: copy self, copy newValue)`. This is always correct because the setter simply assigns the stored property to the newValue. Assigning a `~Escapable` variable copies the lifetime dependency. + +Similarly, an implicit initializer of a non-Escapable struct defaults to `@_lifetime(self: copy arg)` if all of the initializer arguments are `~Escapable`. This is equivalent to assigning each `~Escapable` stored property. If, however, any initializer arguments are `Escapable`, then no default lifetime is provided unless it is the sole argument, in which case the single parameter rule applies. + +### `inout` parameter default rule + +- Default to `@_lifetime(a: copy a)` for all `inout` parameters where `a` is `~Escapable`. + +- Default to `@_lifetime(self: copy self)` on `mutating` methods where `self` is `~Escapable`. + +#### Examples + +```swift +struct A: Escapable { + let obj: AnyObject // ~BitwiseCopyable +} +struct NE: ~Escapable {...} + +/* DEFAULT: @_lifetime(a: copy a) */ +func inoutNEParam_void(_: inout NE) -> () + +/* DEFAULT: @_lifetime(a: copy a) */ +/* DEFAULT: @_lifetime(b: copy b) */ +func inoutNEParam_inoutNEParam_void(a: inout NE, b: inout NE) -> () + +/* DEFAULT: @_lifetime(ne: copy ne) */ +@_lifetime(&ne) +func inoutNEParam_NEResult(ne: inout NE) -> NE + +extension A /* Self: Escapable */ { + /* DEFAULT: @_lifetime(ne: copy NE) */ + func inoutNEParam_void(a: inout ) -> () + + /* DEFAULT: @_lifetime(ne: copy NE) */ + mutating func mutating_inoutNEParam_void() -> () + + /* DEFAULT: @_lifetime(ne: copy NE) */ + @_lifetime(&self) + func inoutNEParam_NEResult(ne: inout NE) -> NE +} + +extension NE /* Self: ~Escapable */ { + /* DEFAULT: @_lifetime(self: copy self) */ + mutating func mutating_noParam_void() -> () + + /* DEFAULT: @_lifetime(self: copy self) */ + mutating func mutating_oneParam_void(_: NE) -> () + + /* DEFAULT: @_lifetime(self: copy self) */ + /* DEFAULT: @_lifetime(ne: copy ne) */ + mutating func mutating_inoutParam_void(ne: inout NE) -> () + + /* DEFAULT: @_lifetime(self: copy self) */ + @_lifetime(&self) + mutating func mutating_noParam_NEResult() -> NE +} +``` + +## Same-type default lifetime (unimplemented) + +Given a function declaration: + +`func foo(..., a: A, ...) -> R { ... }` + +Where `R: ~Escapable` and `A == R`, default to `@_lifetime(copy a)`. +For methods, the same rule applies to implicit `Self` parameter. + +This handles the obvious cases in which both the parameter and result are `~Escapable`. For example: + +```swift +extension Span { + /* DEFAULT: @_lifetime(copy self) */ + func extracting(droppingLast k: Int) -> Self { ... } +} +``` + +### Generic same-type default lifetime (unimplemented) + +The same-type default lifetime rule described above is convenient for nominal types but essential for generic type parameters. + +Given a generic function declaration: + +`func foo(..., a: A, ...) -> R { ... }` + +The same-type default lifetime rule applies to the types in the function declaration's generic context just as it did for nominal types in the previous example. So, again, if type resolution determines `R: ~Escapable` and `A == R`, then `@_lifetime(copy a)` will be default. + +Unlike nominal types, the programmer is not allowed to explicitly declare a lifetime dependency, `@_lifetime(copy +a)`, unless the argument and result types are equivalent (`A == R`). Copying a lifetime dependency from one value to another requires that both values are non-Escapable. Generic types are conditionally non-Escapable (their lifetime dependencies are type-erased), so type equivalence is the only way to ensure that both values are non-Escapable under the same conditions. + +Here we see how same-type lifetime requirement applies to type substitution and associated types: + +```swift +protocol P { + associatedtype T: ~Escapable +} + +protocol Q { + associatedtype U: ~Escapable +} + +struct S { + /* OK: @_lifetime(copy a) is valid and default */ + func foo(a: A.T) -> B.U where A.T == B.U +} +``` + +Note that lifetime dependencies are resolved at function declaration time, which determines the function's type. The generic context at the point of function invocation is not considered. For example, the following declaration of `foo` is invalid, because it's argument and results types don't match at the point of declaraion, even though the argument and result do have the same type when invoked inside `bar`: + +```swift +struct S { + static func foo(a: T) -> U // ERROR: missing lifetime dependency +} + +/* OK: @_lifetime(copy a) is valid and default */ +func bar(a: T) -> T { + S.foo(a: a) // The same-type rule is satisfied in this context, but 'foo's declaration is invalid. +} +``` From 84a4b328ec0a43e8bddce263e5a7c66fabe974a5 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Mon, 25 Aug 2025 10:27:41 -0700 Subject: [PATCH 5/6] Lifetime inference test case improvements. Cleanup the tests so we can cross reference them with the documentation. Update the tests to allow multiple annotations and defaults. --- docs/ReferenceGuides/LifetimeAnnotation.md | 4 +- test/Sema/lifetime_depend_infer.swift | 523 ++++++++++-------- .../Sema/lifetime_depend_infer_defaults.swift | 299 ++++++++++ 3 files changed, 595 insertions(+), 231 deletions(-) create mode 100644 test/Sema/lifetime_depend_infer_defaults.swift diff --git a/docs/ReferenceGuides/LifetimeAnnotation.md b/docs/ReferenceGuides/LifetimeAnnotation.md index ec7b79537edb0..5e399d268b8e3 100644 --- a/docs/ReferenceGuides/LifetimeAnnotation.md +++ b/docs/ReferenceGuides/LifetimeAnnotation.md @@ -28,9 +28,9 @@ Given a function or method that returns a non-Escapable result: - Default to `@_lifetime( self)` for a `~Escapable` result on methods with no parameters. -| Type of parameter | default | +| Type of parameter | default | | (`a` or `self`) | lifetime dependency | -| ----------------- | ------------------------------ | +| ----------------- | ------------------------------ | | `Escapable` | `@_lifetime(borrow param)`[^1] | | `inout Escapable` | `@_lifetime(¶m)`[^1] | | `~Escapable` | none[^2] | diff --git a/test/Sema/lifetime_depend_infer.swift b/test/Sema/lifetime_depend_infer.swift index 80fa667fb02a1..5b0865bbd2c9b 100644 --- a/test/Sema/lifetime_depend_infer.swift +++ b/test/Sema/lifetime_depend_infer.swift @@ -3,8 +3,12 @@ // REQUIRES: swift_feature_Lifetimes -// Coverage testing for LifetimeDependence inferrence logic. The tests are grouped according to the design of -// LifetimeDependenceChecker. +// Coverage testing for LifetimeDependence inferrence logic. The tests are sorted and grouped according to +// docs/ReferenceGuides/LifetimeAnnotation.md. To find the cases that cover the default lifetime +// rules described in the documentation, search for DEFAULT. +// +// Each default case is also defined in Sema/lifetime_depend_infer_defaults.swift to check that the function type has +// the correct dependencies. class C {} @@ -18,160 +22,316 @@ struct NEImmortal: ~Escapable { struct MutNE: ~Copyable & ~Escapable {} // ============================================================================= -// Handle non-Escapable results with 'self' +// Single parameter default rule for functions // ============================================================================= -struct NonEscapableSelf: ~Escapable { - func methodNoParam() -> NonEscapableSelf { self } // expected-error{{cannot infer the lifetime dependence scope on a method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} +func noParam_NEResult() -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result needs a parameter to depend on}} +// expected-note@-1{{'@_lifetime(immortal)' can be used to indicate that values produced by this initializer have no lifetime dependencies}} - @_lifetime(self) // expected-error{{cannot infer the lifetime dependence scope on a method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} - func methodNoParamLifetime() -> NonEscapableSelf { self } +@_lifetime(immortal) +func noParamImmortal_NEResult() -> NEImmortal { NEImmortal() } // OK - @_lifetime(copy self) // OK - func methodNoParamCopy() -> NonEscapableSelf { self } +/* DEFAULT: @_lifetime(borrow i) */ +func oneTrivialParam_NEResult(i: Int) -> NEImmortal { NEImmortal() } - @_lifetime(borrow self) // OK - func methodNoParamBorrow() -> NonEscapableSelf { self } +/* DEFAULT: @_lifetime(borrow c) */ +func oneParam_NEResult(c: C) -> NEImmortal { NEImmortal() } - mutating func mutatingMethodNoParam() -> NonEscapableSelf { self } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)'}} - // expected-error@-1{{a mutating method with a ~Escapable 'self' requires '@_lifetime(self: ...)'}} +/* DEFAULT: @_lifetime(borrow c) */ +@_lifetime(c) +func oneParamLifetime_NEResult(c: C) -> NEImmortal { NEImmortal() } - @_lifetime(self) // expected-error{{cannot infer the lifetime dependence scope on a mutating method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} - mutating func mutatingMethodNoParamLifetime() -> NonEscapableSelf { self } +/* DEFAULT: @_lifetime(borrow c) */ +func oneParamBorrow_NEResult(c: borrowing C) -> NEImmortal { NEImmortal() } // OK - @_lifetime(copy self) // OK - mutating func mutatingMethodNoParamCopy() -> NonEscapableSelf { self } +/* DEFAULT: @_lifetime(borrow c) */ +@_lifetime(c) +func oneParamBorrowLifetime_NEResult(c: borrowing C) -> NEImmortal { NEImmortal() } // OK - @_lifetime(&self) // OK - mutating func mutatingMethodNoParamBorrow() -> NonEscapableSelf { self } +func oneParamConsume_NEResult(c: consuming C) -> NEImmortal { NEImmortal() } // expected-error{{cannot borrow the lifetime of 'c', which has consuming ownership on a function}} - func methodOneParam(_: Int) -> NonEscapableSelf { self } // expected-error{{a method with a ~Escapable result requires '@_lifetime(...)'}} +@_lifetime(c) // expected-error{{invalid lifetime dependence on an Escapable value with consuming ownership}} +func oneParamConsumeLifetime_NEResult(c: consuming C) -> NEImmortal { NEImmortal() } - @_lifetime(self) // expected-error{{cannot infer the lifetime dependence scope on a method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} - func methodOneParamLifetime(_: Int) -> NonEscapableSelf { self } +/* DEFAULT: @_lifetime(&c) */ +func oneInoutParam_NEResult(c: inout C) -> NEImmortal { NEImmortal() } // OK - @_lifetime(copy self) // OK - func methodOneParamCopy(_: Int) -> NonEscapableSelf { self } +/* DEFAULT: @_lifetime(&c) */ +@_lifetime(c) +func oneParamInoutLifetime_NEResult(c: inout C) -> NEImmortal { NEImmortal() } // OK - @_lifetime(borrow self) // OK - func methodOneParamBorrow(_: Int) -> NonEscapableSelf { self } +func twoParams_NEResult(c: C, _: Int) -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} - mutating func mutatingMethodOneParam(_: Int) -> NonEscapableSelf { self } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)'}} - // expected-error@-1{{a mutating method with a ~Escapable 'self' requires '@_lifetime(self: ...)'}} +@_lifetime(c) +func twoParamsLifetime_NEResult(c: C, _: Int) -> NEImmortal { NEImmortal() } - @_lifetime(self) // expected-error{{cannot infer the lifetime dependence scope on a mutating method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} - mutating func mutatingMethodOneParamLifetime(_: Int) -> NonEscapableSelf { self } +func twoParamsConsume_NEResult(c: consuming C, _: Int) -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} - @_lifetime(copy self) // OK - mutating func mutatingMethodOneParamCopy(_: Int) -> NonEscapableSelf { self } +func twoParamsBorrow_NEResult(c: borrowing C, _: Int) -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} - @_lifetime(&self) // OK - mutating func mutatingMethodOneParamBorrow(_: Int) -> NonEscapableSelf { self } +func twoParamsInout_NEResult(c: inout C, _: Int) -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} + +func neParam_NEResult(ne: NE) -> NE { ne } // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} + +@_lifetime(ne) // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} +func neParamLifetime_NEResult(ne: NE) -> NE { ne } + +func neParamBorrow_NEResult(ne: borrowing NE) -> NE { ne } // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} + +@_lifetime(ne) // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} +func neParamBorrowLifetime_NEResult(ne: borrowing NE) -> NE { ne } + +func neParamConsume_NEResult(ne: consuming NE) -> NE { ne } // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} + +@_lifetime(ne) // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} +func neParamConsumeLifetime_NEResult(ne: consuming NE) -> NE { ne } + +func neParam_IntParam_NEResult(ne: NE, _:Int) -> NE { ne } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} + +func inoutParam_inoutParam_NEResult(a: inout C, b: inout C) -> NEImmortal { NEImmortal() } +// expected-error@-1{{a function with a ~Escapable result requires '@_lifetime(...)'}} + + +// ============================================================================= +// Single parameter default rule for methods +// ============================================================================= + +struct EscapableNonTrivialSelf { + let c: C + + init(c: C) { self.c = c } + + /* DEFAULT: @_lifetime(borrow self) */ + func noParam_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(borrow self) */ + @_lifetime(self) + func noParamLifetime_NEResult() -> NEImmortal { NEImmortal() } + + @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(borrow self)' instead}} + func noParamCopy_NEResult() -> NEImmortal { NEImmortal() } + + @_lifetime(borrow self) + func noParamBorrow_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(&self) */ + mutating func mutating_noParam_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(&self) */ + @_lifetime(self) + mutating func mutating_noParamLifetime_NEResult() -> NEImmortal { NEImmortal() } + + @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(&self)' instead}} + mutating func mutating_noParamCopy_NEResult() -> NEImmortal { NEImmortal() } + + @_lifetime(&self) + mutating func mutating_noParamBorrow_NEResult() -> NEImmortal { NEImmortal() } + + func oneParam_NEResult(_: Int) -> NEImmortal { NEImmortal() } // expected-error{{a method with a ~Escapable result requires '@_lifetime(...)'}} + + @_lifetime(self) + func oneParamLifetime_NEResult(_: Int) -> NEImmortal { NEImmortal() } + + @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(borrow self)' instead}} + func oneParamCopy_NEResult(_: Int) -> NEImmortal { NEImmortal() } + + @_lifetime(borrow self) + func oneParamBorrow_NEResult(_: Int) -> NEImmortal { NEImmortal() } + + mutating func mutating_oneParam_NEResult(_: Int) -> NEImmortal { NEImmortal() } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)'}} + + /* DEFAULT: @_lifetime(borrow self) */ + @_lifetime(self) + mutating func mutating_oneParamLifetime_NEResult(_: Int) -> NEImmortal { NEImmortal() } + + @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(&self)' instead}} + mutating func mutating_oneParamCopy_NEResult(_: Int) -> NEImmortal { NEImmortal() } + + @_lifetime(&self) + mutating func mutating_oneParamBorrow_NEResult(_: Int) -> NEImmortal { NEImmortal() } } struct EscapableTrivialSelf { - func methodNoParam() -> NEImmortal { NEImmortal() } // expected-error{{cannot infer lifetime dependence on a method because 'self' is BitwiseCopyable}} + func noParam_NEResult() -> NEImmortal { NEImmortal() } // expected-error{{cannot infer lifetime dependence on a method because 'self' is BitwiseCopyable}} + /* DEFAULT: @_lifetime(borrow self) */ @_lifetime(self) // OK - func methodNoParamLifetime() -> NEImmortal { NEImmortal() } + func noParamLifetime_NEResult() -> NEImmortal { NEImmortal() } @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(borrow self)' instead}} - func methodNoParamCopy() -> NEImmortal { NEImmortal() } + func noParamCopy_NEResult() -> NEImmortal { NEImmortal() } @_lifetime(borrow self) // OK - func methodNoParamBorrow() -> NEImmortal { NEImmortal() } + func noParamBorrow_NEResult() -> NEImmortal { NEImmortal() } - func mutatingMethodNoParam() -> NEImmortal { NEImmortal() } // expected-error{{cannot infer lifetime dependence on a method because 'self' is BitwiseCopyable}} + func mutatingMethodNoParam_NEResult() -> NEImmortal { NEImmortal() } // expected-error{{cannot infer lifetime dependence on a method because 'self' is BitwiseCopyable}} + /* DEFAULT: @_lifetime(borrow self) */ @_lifetime(self) // OK - mutating func mutatingMethodNoParamLifetime() -> NEImmortal { NEImmortal() } + mutating func mutatingMethodNoParamLifetime_NEResult() -> NEImmortal { NEImmortal() } @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(&self)' instead}} - mutating func mutatingMethodNoParamCopy() -> NEImmortal { NEImmortal() } + mutating func mutatingMethodNoParamCopy_NEResult() -> NEImmortal { NEImmortal() } @_lifetime(&self) - mutating func mutatingMethodNoParamBorrow() -> NEImmortal { NEImmortal() } + mutating func mutatingMethodNoParamBorrow_NEResult() -> NEImmortal { NEImmortal() } - func methodOneParam(_: Int) -> NEImmortal { NEImmortal() } // expected-error{{a method with a ~Escapable result requires '@_lifetime(...)'}} + func oneParam_NEResult_NEResult(_: Int) -> NEImmortal { NEImmortal() } // expected-error{{a method with a ~Escapable result requires '@_lifetime(...)'}} + /* DEFAULT: @_lifetime(borrow self) */ @_lifetime(self) - func methodOneParamLifetime(_: Int) -> NEImmortal { NEImmortal() } + func oneParamLifetime_NEResult(_: Int) -> NEImmortal { NEImmortal() } @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(borrow self)' instead}} - func methodOneParamCopy(_: Int) -> NEImmortal { NEImmortal() } + func oneParamCopy_NEResult(_: Int) -> NEImmortal { NEImmortal() } @_lifetime(borrow self) - func methodOneParamBorrow(_: Int) -> NEImmortal { NEImmortal() } + func oneParamBorrow_NEResult(_: Int) -> NEImmortal { NEImmortal() } - mutating func mutatingMethodOneParam(_: Int) -> NEImmortal { NEImmortal() } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)'}} + mutating func mutating_oneParam_NEResult(_: Int) -> NEImmortal { NEImmortal() } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)'}} + /* DEFAULT: @_lifetime(borrow self) */ @_lifetime(self) - mutating func mutatingMethodOneParamLifetime(_: Int) -> NEImmortal { NEImmortal() } + mutating func mutating_oneParamLifetime_NEResult(_: Int) -> NEImmortal { NEImmortal() } @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(&self)' instead}} - mutating func mutatingMethodOneParamCopy(_: Int) -> NEImmortal { NEImmortal() } + mutating func mutating_oneParamCopy_NEResult(_: Int) -> NEImmortal { NEImmortal() } @_lifetime(&self) - mutating func mutatingMethodOneParamBorrow(_: Int) -> NEImmortal { NEImmortal() } + mutating func mutating_oneParamBorrow_NEResult(_: Int) -> NEImmortal { NEImmortal() } } -struct EscapableNonTrivialSelf { - let c: C +struct NonEscapableSelf: ~Escapable { + func noParam_NEResult() -> NonEscapableSelf { self } // expected-error{{cannot infer the lifetime dependence scope on a method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} - init(c: C) { self.c = c } + @_lifetime(self) // expected-error{{cannot infer the lifetime dependence scope on a method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} + func noParamLifetime_NEResult() -> NonEscapableSelf { self } - func methodNoParam() -> NEImmortal { NEImmortal() } + @_lifetime(copy self) // OK + func noParamCopy_NEResult_NEResult() -> NonEscapableSelf { self } - @_lifetime(self) - func methodNoParamLifetime() -> NEImmortal { NEImmortal() } + @_lifetime(borrow self) // OK + func noParamBorrow_NEResult() -> NonEscapableSelf { self } - @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(borrow self)' instead}} - func methodNoParamCopy() -> NEImmortal { NEImmortal() } + mutating func mutating_noParam_NEResult() -> NonEscapableSelf { self } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)'}} - @_lifetime(borrow self) - func methodNoParamBorrow() -> NEImmortal { NEImmortal() } + @_lifetime(self) // expected-error{{cannot infer the lifetime dependence scope on a mutating method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} + mutating func mutating_noParamLifetime_NEResult() -> NonEscapableSelf { self } - mutating func mutatingMethodNoParam() -> NEImmortal { NEImmortal() } + @_lifetime(copy self) // OK + mutating func mutating_noParamCopy_NEResult() -> NonEscapableSelf { self } - func methodInoutNonEscapableParam(_: inout NE) {} + @_lifetime(&self) // OK + mutating func mutating_noParamBorrow_NEResult() -> NonEscapableSelf { self } - mutating func mutatingMethodInoutNonEscapableParam(_: inout NE) {} + func oneParam_NEResult(_: Int) -> NonEscapableSelf { self } // expected-error{{a method with a ~Escapable result requires '@_lifetime(...)'}} - @_lifetime(self) - mutating func mutatingMethodNoParamLifetime() -> NEImmortal { NEImmortal() } + @_lifetime(self) // expected-error{{cannot infer the lifetime dependence scope on a method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} + func oneParamLifetime_NEResult(_: Int) -> NonEscapableSelf { self } - @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(&self)' instead}} - mutating func mutatingMethodNoParamCopy() -> NEImmortal { NEImmortal() } + @_lifetime(copy self) // OK + func oneParamCopy_NEResult(_: Int) -> NonEscapableSelf { self } - @_lifetime(&self) - mutating func mutatingMethodNoParamBorrow() -> NEImmortal { NEImmortal() } + @_lifetime(borrow self) // OK + func oneParamBorrow_NEResult(_: Int) -> NonEscapableSelf { self } - func methodOneParam(_: Int) -> NEImmortal { NEImmortal() } // expected-error{{a method with a ~Escapable result requires '@_lifetime(...)'}} + mutating func mutating_oneParam_NEResult(_: Int) -> NonEscapableSelf { self } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)'}} - @_lifetime(self) - func methodOneParamLifetime(_: Int) -> NEImmortal { NEImmortal() } + @_lifetime(self) // expected-error{{cannot infer the lifetime dependence scope on a mutating method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} + mutating func mutating_oneParamLifetime_NEResult(_: Int) -> NonEscapableSelf { self } - @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(borrow self)' instead}} - func methodOneParamCopy(_: Int) -> NEImmortal { NEImmortal() } + @_lifetime(copy self) // OK + mutating func mutating_oneParamCopy_NEResult(_: Int) -> NonEscapableSelf { self } - @_lifetime(borrow self) - func methodOneParamBorrow(_: Int) -> NEImmortal { NEImmortal() } + @_lifetime(&self) // OK + mutating func mutating_oneParamBorrow_NEResult(_: Int) -> NonEscapableSelf { self } + + mutating func mutating_inoutParam_NEResult(a: inout NE) -> NEImmortal { NEImmortal() } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)'}} +} + +// ============================================================================= +// inout parameter default rule for functions +// ============================================================================= + +/* DEFAULT: @_lifetime(ne: copy ne) */ +func inoutNEParam_void(ne: inout NE) {} // OK + +/* DEFAULT: @_lifetime(0: copy 0) */ +func inoutNEParam_NEParam_void(_: inout NE, _: NE) {} // OK + +/* DEFAULT: @_lifetime(0: copy 0) */ +/* DEFAULT: @_lifetime(1: copy 1) */ +func inoutParam_inoutNEParam_void(_: inout NE, _: inout NE) {} // OK - mutating func mutatingMethodOneParam(_: Int) -> NEImmortal { NEImmortal() } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)'}} +func inoutNEParam_NEResult(ne: inout NE) -> NE { ne } // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} +/* DEFAULT: @_lifetime(ne: copy ne) */ +@_lifetime(&ne) +func inoutNEParam_NEResult_Lifetime(ne: inout NE) -> NE { ne } + +// ============================================================================= +// inout parameter default rule for methods +// ============================================================================= + +extension EscapableNonTrivialSelf { + /* DEFAULT: @_lifetime(ne: copy ne) */ + func inoutNEParam_void(ne: inout NE) {} + + /* DEFAULT: @_lifetime(ne: copy ne) */ + mutating func mutating_inoutNEParam_void(ne: inout NE) {} + + /* DEFAULT: @_lifetime(ne: copy NE) */ + @_lifetime(&ne) + func inoutNEParam_NEResult_Lifetime(ne: inout NE) -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(ne: copy NE) */ @_lifetime(self) - mutating func mutatingMethodOneParamLifetime(_: Int) -> NEImmortal { NEImmortal() } + func inoutNEParam_NEResult_LifetimeSelf(ne: inout NE) -> NEImmortal { NEImmortal() } +} - @_lifetime(copy self) // expected-error{{cannot copy the lifetime of an Escapable type, use '@_lifetime(&self)' instead}} - mutating func mutatingMethodOneParamCopy(_: Int) -> NEImmortal { NEImmortal() } +struct NonEscapableMutableSelf: ~Escapable { + // This is unambiguous: inout 'self' needs a dependency, and it can't be a borrow dependency because the original + // value is consumed. + /* DEFAULT: @_lifetime(self: copy self) */ + mutating func mutating_noParam_void() {} // OK + + @_lifetime(self: self) // expected-error{{cannot infer the lifetime dependence scope on a mutating method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} + mutating func mutating_lifetime_noParam_void() {} + + @_lifetime(self: copy self) // OK + mutating func mutating_copyLifetime_noParam_void() {} + + @_lifetime(self: &self) // expected-error{{invalid use of inout dependence on the same inout parameter}} + // expected-note @-1{{use '@_lifetime(self: copy self) to forward the inout dependency}} + mutating func mutating_inoutLifetime_noParam_void() {} + + /* DEFAULT: @_lifetime(self: copy self) */ + mutating func mutating_oneParam_void(_: NE) {} + + @_lifetime(self: self) // expected-error{{cannot infer the lifetime dependence scope on a mutating method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} + mutating func mutating_lifetime_oneParam(_: NE) {} + + @_lifetime(self: copy self) // OK + mutating func mutating_copyLifetime_oneParam(_: NE) {} + + /* DEFAULT: @_lifetime(self: copy self) */ + /* DEFAULT: @_lifetime(a: copy a) */ + mutating func mutating_inoutParam_void(ne: inout NE) {} + + mutating func mutating_noParam_NEResult_noLifetime() -> NEImmortal { NEImmortal() } // expected-error{{a mutating method with a ~Escapable result requires '@_lifetime(...)}} + /* DEFAULT: @_lifetime(self: copy Self) */ @_lifetime(&self) - mutating func mutatingMethodOneParamBorrow(_: Int) -> NEImmortal { NEImmortal() } + mutating func mutating_noParam_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(self: copy Self) */ + /* DEFAULT: @_lifetime(ne: copy NE) */ + @_lifetime(&self) + mutating func mutating_inoutNEParam_NEResult(ne: inout NE) -> NEImmortal { NEImmortal() } } // ============================================================================= -// Handle non-Escapable results which must depend on a parameter -// (for initializers and stand-alone functions) +// non-Escapable Initialization // ============================================================================= // An implicit initializer illegally consumes its nontrivial parameter. @@ -223,71 +383,56 @@ struct NonescapableInoutInitializers: ~Escapable { init(ne: inout NE) { c = C() } // expected-error{{cannot infer the lifetime dependence scope on an initializer with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} } -func noParam() -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result needs a parameter to depend on}} -// expected-note@-1{{'@_lifetime(immortal)' can be used to indicate that values produced by this initializer have no lifetime dependencies}} - -@_lifetime(immortal) -func noParamImmortal() -> NEImmortal { NEImmortal() } // OK - -func oneParam(c: C) -> NEImmortal { NEImmortal() } - -@_lifetime(c) -func oneParamLifetime(c: C) -> NEImmortal { NEImmortal() } - -func oneParamConsume(c: consuming C) -> NEImmortal { NEImmortal() } // expected-error{{cannot borrow the lifetime of 'c', which has consuming ownership on a function}} - -@_lifetime(c) // expected-error{{invalid lifetime dependence on an Escapable value with consuming ownership}} -func oneParamConsumeLifetime(c: consuming C) -> NEImmortal { NEImmortal() } - -func oneParamBorrow(c: borrowing C) -> NEImmortal { NEImmortal() } // OK - -@_lifetime(c) -func oneParamBorrowLifetime(c: borrowing C) -> NEImmortal { NEImmortal() } // OK - -func oneParamInout(c: inout C) -> NEImmortal { NEImmortal() } // OK - -@_lifetime(c) -func oneParamInoutLifetime(c: inout C) -> NEImmortal { NEImmortal() } // OK - -func twoParams(c: C, _: Int) -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} - -@_lifetime(c) -func twoParamsLifetime(c: C, _: Int) -> NEImmortal { NEImmortal() } - -func twoParamsConsume(c: consuming C, _: Int) -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} - -func twoParamsBorrow(c: borrowing C, _: Int) -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} - -func twoParamsInout(c: inout C, _: Int) -> NEImmortal { NEImmortal() } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} - -func neParam(ne: NE) -> NE { ne } // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} - -@_lifetime(ne) // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} -func neParamLifetime(ne: NE) -> NE { ne } - -func neParamBorrow(ne: borrowing NE) -> NE { ne } // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} +// ============================================================================= +// Aggregate initialization +// ============================================================================= -@_lifetime(ne) // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} -func neParamBorrowLifetime(ne: borrowing NE) -> NE { ne } +// Motivation: 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. +// This 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 +// +struct CountedSpan: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}} + let span: Span + let i: Int +} -func neParamConsume(ne: consuming NE) -> NE { ne } // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} +struct NE_Int: ~Escapable { + let i: Int +} -@_lifetime(ne) // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} -func neParamConsumeLifetime(ne: consuming NE) -> NE { ne } +struct NE_C: ~Escapable { // expected-error{{cannot borrow the lifetime of 'c', which has consuming ownership on an implicit initializer}} + let c: C +} -func neParamInout(ne: inout NE) -> NE { ne } // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} +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 +} -@_lifetime(ne) // expected-error{{cannot infer the lifetime dependence scope on a function with a ~Escapable parameter, specify '@_lifetime(borrow ne)' or '@_lifetime(copy ne)'}} -func neParamInoutLifetime(ne: inout NE) -> NE { ne } +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 +} -func neTwoParam(ne: NE, _:Int) -> NE { ne } // expected-error{{a function with a ~Escapable result requires '@_lifetime(...)'}} +struct NE_NE: ~Escapable { + let ne: NE +} -func voidInoutOneParam(_: inout NE) {} // OK +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 +} -func voidInoutTwoParams(_: inout NE, _: Int) {} // OK +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 +} // ============================================================================= -// Handle Accessors: +// Accessors: // // 'get', '_read', and '_modify' are inferred as methods that return ~Escpable results dependent on 'self' // @@ -300,6 +445,7 @@ struct Accessors { let c: C var neComputed: NEImmortal { + /* DEFAULT: @_lifetime(borrow self) */ get { // OK NEImmortal() } @@ -309,10 +455,12 @@ struct Accessors { } var neYielded: NEImmortal { + /* DEFAULT: @_lifetime(borrow self) */ _read { // OK yield NEImmortal() } + /* DEFAULT: @_lifetime(borrow self) */ _modify { // OK var ne = NEImmortal() yield &ne @@ -321,6 +469,7 @@ struct Accessors { // Synthesized _modify... subscript(_ index: Int) -> NEImmortal { + /* DEFAULT: @_lifetime(borrow self) */ get { // OK NEImmortal() } @@ -372,7 +521,8 @@ struct NonescapableSelfAccessors: ~Escapable { ne } - set { // expected-error{{a mutating method with a ~Escapable 'self' requires '@_lifetime(self: ...)'}} + // DEFAULT: '@_lifetime(self: copy self, copy newValue) + set { ne = newValue } } @@ -469,7 +619,8 @@ struct NoncopyableSelfAccessors: ~Copyable & ~Escapable { ne } - set { // expected-error{{a mutating method with a ~Escapable 'self' requires '@_lifetime(self: ...)'}} + // DEFAULT: '@_lifetime(self: copy self, copy newValue) + set { ne = newValue } } @@ -558,95 +709,9 @@ struct NoncopyableSelfAccessors: ~Copyable & ~Escapable { } } -// ============================================================================= -// Handle mutating methods with no return value -// ============================================================================= - -struct NonEscapableMutableSelf: ~Escapable { - // This is unambiguous: inout 'self' needs a dependency, and it can't be a borrow dependency because the original - // value is consumed. - /* @_lifetime(self: copy self) */ - mutating func mutatingMethodNoParam() {} // OK - - @_lifetime(self: self) // expected-error{{cannot infer the lifetime dependence scope on a mutating method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} - mutating func mutatingMethodNoParamLifetime() {} - - @_lifetime(self: copy self) // OK - mutating func mutatingMethodNoParamCopy() {} - - @_lifetime(self: &self) // expected-error{{invalid use of inout dependence on the same inout parameter}} - // expected-note @-1{{use '@_lifetime(self: copy self) to forward the inout dependency}} - mutating func mutatingMethodNoParamBorrow() {} - - mutating func mutatingMethodOneParam(_: NE) {} // expected-error{{a mutating method with a ~Escapable 'self' requires '@_lifetime(self: ...)'}} - - @_lifetime(self: self) // expected-error{{cannot infer the lifetime dependence scope on a mutating method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} - mutating func mutatingMethodOneParamLifetime(_: NE) {} - - @_lifetime(self: copy self) // OK - mutating func mutatingMethodOneParamCopy(_: NE) {} - - @_lifetime(self: copy self) // OK - mutating func mutatingMethodOneParamBorrow(_: NE) {} -} - -// ============================================================================= -// Initializers -// ============================================================================= - -// Motivation: 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. -// This 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 -// -struct CountedSpan: ~Escapable { // expected-error{{cannot infer implicit initialization lifetime. Add an initializer with '@_lifetime(...)' for each parameter the result depends on}} - let span: Span - let i: Int -} - -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 -} - -// ============================================================================= -// Handle common mistakes with inout parameter annotations -// ============================================================================= - -// Unable to infer an 'inout' dependency. Provide valid guidance. -// -func f_inout_no_infer(a: inout MutNE, b: NE) {} -// expected-error @-1{{a function with a ~Escapable 'inout' parameter requires '@_lifetime(a: ...)'}} -// expected-note @-2{{use '@_lifetime(a: copy a) to forward the inout dependency}} +// ================================================================================== +// Common mistakes with inout parameter annotations requiring custom diagnostics... +// ================================================================================== // Invalid keyword for the dependence kind. // diff --git a/test/Sema/lifetime_depend_infer_defaults.swift b/test/Sema/lifetime_depend_infer_defaults.swift new file mode 100644 index 0000000000000..e69e351c2debd --- /dev/null +++ b/test/Sema/lifetime_depend_infer_defaults.swift @@ -0,0 +1,299 @@ +// RUN: %target-swift-emit-silgen -enable-experimental-feature Lifetimes -primary-file %s | %FileCheck %s +// +// -primary-file is required to synthesize accessors. + +// REQUIRES: swift_feature_Lifetimes + +// Check that all the DEFAULT cases in Sema/lifetime_depend_infer_defaults.swift generate the correct function +// signature. Checking the SILGen output seems like the easiest way. + +class C {} + +struct NE: ~Escapable {} + +struct NEImmortal: ~Escapable { + @_lifetime(immortal) + init() {} +} + +struct MutNE: ~Copyable & ~Escapable {} + +// ============================================================================= +// Single parameter default rule for functions +// ============================================================================= + +/* DEFAULT: @_lifetime(borrow i) */ +// CHECK: @$s30lifetime_depend_infer_defaults24oneTrivialParam_NEResult1iAA10NEImmortalVSi_tF : $@convention(thin) (Int) -> @lifetime(borrow 0) @owned NEImmortal +func oneTrivialParam_NEResult(i: Int) -> NEImmortal { NEImmortal() } + +/* DEFAULT: @_lifetime(borrow c) */ +// CHECK: @$s30lifetime_depend_infer_defaults17oneParam_NEResult1cAA10NEImmortalVAA1CC_tF : $@convention(thin) (@guaranteed C) -> @lifetime(borrow 0) @owned NEImmortal +func oneParam_NEResult(c: C) -> NEImmortal { NEImmortal() } + +/* DEFAULT: @_lifetime(borrow c) */ +// CHECK: @$s30lifetime_depend_infer_defaults25oneParamLifetime_NEResult1cAA10NEImmortalVAA1CC_tF : $@convention(thin) (@guaranteed C) -> @lifetime(borrow 0) @owned NEImmortal +@_lifetime(c) +func oneParamLifetime_NEResult(c: C) -> NEImmortal { NEImmortal() } + +/* DEFAULT: @_lifetime(borrow c) */ +// CHECK: @$s30lifetime_depend_infer_defaults23oneParamBorrow_NEResult1cAA10NEImmortalVAA1CC_tF : $@convention(thin) (@guaranteed C) -> @lifetime(borrow 0) @owned NEImmortal +func oneParamBorrow_NEResult(c: borrowing C) -> NEImmortal { NEImmortal() } // OK + +/* DEFAULT: @_lifetime(borrow c) */ +// CHECK: @$s30lifetime_depend_infer_defaults31oneParamBorrowLifetime_NEResult1cAA10NEImmortalVAA1CC_tF : $@convention(thin) (@guaranteed C) -> @lifetime(borrow 0) @owned NEImmortal +@_lifetime(c) +func oneParamBorrowLifetime_NEResult(c: borrowing C) -> NEImmortal { NEImmortal() } // OK + +/* DEFAULT: @_lifetime(&c) */ +// CHECK: @$s30lifetime_depend_infer_defaults22oneInoutParam_NEResult1cAA10NEImmortalVAA1CCz_tF : $@convention(thin) (@inout C) -> @lifetime(borrow 0) @owned NEImmortal +func oneInoutParam_NEResult(c: inout C) -> NEImmortal { NEImmortal() } // OK + +/* DEFAULT: @_lifetime(&c) */ +// CHECK: @$s30lifetime_depend_infer_defaults30oneParamInoutLifetime_NEResult1cAA10NEImmortalVAA1CCz_tF : $@convention(thin) (@inout C) -> @lifetime(borrow 0) @owned NEImmortal +@_lifetime(c) +func oneParamInoutLifetime_NEResult(c: inout C) -> NEImmortal { NEImmortal() } // OK + +// ============================================================================= +// Single parameter default rule for methods +// ============================================================================= + +struct EscapableNonTrivialSelf { + let c: C + + init(c: C) { self.c = c } + + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults23EscapableNonTrivialSelfV16noParam_NEResultAA10NEImmortalVyF : $@convention(method) (@guaranteed EscapableNonTrivialSelf) -> @lifetime(borrow 0) @owned NEImmortal + func noParam_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults23EscapableNonTrivialSelfV24noParamLifetime_NEResultAA10NEImmortalVyF : $@convention(method) (@guaranteed EscapableNonTrivialSelf) -> @lifetime(borrow 0) @owned NEImmortal + @_lifetime(self) + func noParamLifetime_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(&self) */ + // CHECK: @$s30lifetime_depend_infer_defaults23EscapableNonTrivialSelfV25mutating_noParam_NEResultAA10NEImmortalVyF : $@convention(method) (@inout EscapableNonTrivialSelf) -> @lifetime(borrow 0) @owned NEImmortal + mutating func mutating_noParam_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(&self) */ + // CHECK: @$s30lifetime_depend_infer_defaults23EscapableNonTrivialSelfV33mutating_noParamLifetime_NEResultAA10NEImmortalVyF : $@convention(method) (@inout EscapableNonTrivialSelf) -> @lifetime(borrow 0) @owned NEImmortal + @_lifetime(self) + mutating func mutating_noParamLifetime_NEResult() -> NEImmortal { NEImmortal() } +} + +struct EscapableTrivialSelf { + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults20EscapableTrivialSelfV24noParamLifetime_NEResultAA10NEImmortalVyF : $@convention(method) (EscapableTrivialSelf) -> @lifetime(borrow 0) @owned NEImmortal + @_lifetime(self) // OK + func noParamLifetime_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults20EscapableTrivialSelfV38mutatingMethodNoParamLifetime_NEResultAA10NEImmortalVyF : $@convention(method) (@inout EscapableTrivialSelf) -> @lifetime(borrow 0) @owned NEImmortal + @_lifetime(self) // OK + mutating func mutatingMethodNoParamLifetime_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults20EscapableTrivialSelfV25oneParamLifetime_NEResultyAA10NEImmortalVSiF : $@convention(method) (Int, EscapableTrivialSelf) -> @lifetime(borrow 1) @owned NEImmortal + @_lifetime(self) + func oneParamLifetime_NEResult(_: Int) -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults20EscapableTrivialSelfV34mutating_oneParamLifetime_NEResultyAA10NEImmortalVSiF : $@convention(method) (Int, @inout EscapableTrivialSelf) -> @lifetime(borrow 1) @owned NEImmortal + @_lifetime(self) + mutating func mutating_oneParamLifetime_NEResult(_: Int) -> NEImmortal { NEImmortal() } +} + +// ============================================================================= +// inout parameter default rule for functions +// ============================================================================= + +/* DEFAULT: @_lifetime(ne: copy ne) */ +// CHECK: @$s30lifetime_depend_infer_defaults17inoutNEParam_void2neyAA2NEVz_tF : $@convention(thin) (@lifetime(copy 0) @inout NE) -> () +func inoutNEParam_void(ne: inout NE) {} // OK + +/* DEFAULT: @_lifetime(0: copy 0) */ +// CHECK: @$s30lifetime_depend_infer_defaults013inoutNEParam_F5_voidyyAA2NEVz_ADtF : $@convention(thin) (@lifetime(copy 0) @inout NE, @guaranteed NE) -> () +func inoutNEParam_NEParam_void(_: inout NE, _: NE) {} // OK + +/* DEFAULT: @_lifetime(0: copy 0) */ +/* DEFAULT: @_lifetime(1: copy 1) */ +// CHECK: @$s30lifetime_depend_infer_defaults011inoutParam_E12NEParam_voidyyAA2NEVz_ADztF : $@convention(thin) (@lifetime(copy 0) @inout NE, @lifetime(copy 1) @inout NE) -> () +func inoutParam_inoutNEParam_void(_: inout NE, _: inout NE) {} // OK + +/* DEFAULT: @_lifetime(ne: copy ne) */ +// CHECK: @$s30lifetime_depend_infer_defaults30inoutNEParam_NEResult_Lifetime2neAA2NEVAEz_tF : $@convention(thin) (@lifetime(copy 0) @inout NE) -> @lifetime(borrow 0) @owned NE +@_lifetime(&ne) +func inoutNEParam_NEResult_Lifetime(ne: inout NE) -> NE { ne } + +// ============================================================================= +// inout parameter default rule for methods +// ============================================================================= + +extension EscapableNonTrivialSelf { + /* DEFAULT: @_lifetime(ne: copy ne) */ + // CHECK: @$s30lifetime_depend_infer_defaults23EscapableNonTrivialSelfV17inoutNEParam_void2neyAA2NEVz_tF : $@convention(method) (@lifetime(copy 0) @inout NE, @guaranteed EscapableNonTrivialSelf) -> () + func inoutNEParam_void(ne: inout NE) {} + + /* DEFAULT: @_lifetime(ne: copy ne) */ + // CHECK: @$s30lifetime_depend_infer_defaults23EscapableNonTrivialSelfV26mutating_inoutNEParam_void2neyAA2NEVz_tF : $@convention(method) (@lifetime(copy 0) @inout NE, @inout EscapableNonTrivialSelf) -> () + mutating func mutating_inoutNEParam_void(ne: inout NE) {} + + /* DEFAULT: @_lifetime(ne: copy NE) */ + // CHECK: @$s30lifetime_depend_infer_defaults23EscapableNonTrivialSelfV30inoutNEParam_NEResult_Lifetime2neAA10NEImmortalVAA2NEVz_tF : $@convention(method) (@lifetime(copy 0) @inout NE, @guaranteed EscapableNonTrivialSelf) -> @lifetime(borrow 0) @owned NEImmortal + @_lifetime(&ne) + func inoutNEParam_NEResult_Lifetime(ne: inout NE) -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(ne: copy NE) */ + // CHECK: @$s30lifetime_depend_infer_defaults23EscapableNonTrivialSelfV030inoutNEParam_NEResult_LifetimeH02neAA10NEImmortalVAA2NEVz_tF : $@convention(method) (@lifetime(copy 0) @inout NE, @guaranteed EscapableNonTrivialSelf) -> @lifetime(borrow 1) @owned NEImmortal + @_lifetime(self) + func inoutNEParam_NEResult_LifetimeSelf(ne: inout NE) -> NEImmortal { NEImmortal() } +} + +struct NonEscapableMutableSelf: ~Escapable { + // This is unambiguous: inout 'self' needs a dependency, and it can't be a borrow dependency because the original + // value is consumed. + /* DEFAULT: @_lifetime(self: copy self) */ + // CHECK: @$s30lifetime_depend_infer_defaults23NonEscapableMutableSelfV21mutating_noParam_voidyyF : $@convention(method) (@lifetime(copy 0) @inout NonEscapableMutableSelf) -> () + mutating func mutating_noParam_void() {} // OK + + /* DEFAULT: @_lifetime(self: copy self) */ + // CHECK: @$s30lifetime_depend_infer_defaults23NonEscapableMutableSelfV22mutating_oneParam_voidyyAA2NEVF : $@convention(method) (@guaranteed NE, @lifetime(copy 1) @inout NonEscapableMutableSelf) -> () + mutating func mutating_oneParam_void(_: NE) {} + + /* DEFAULT: @_lifetime(self: copy self) */ + /* DEFAULT: @_lifetime(a: copy a) */ + // CHECK: @$s30lifetime_depend_infer_defaults23NonEscapableMutableSelfV24mutating_inoutParam_void2neyAA2NEVz_tF : $@convention(method) (@lifetime(copy 0) @inout NE, @lifetime(copy 1) @inout NonEscapableMutableSelf) -> () + mutating func mutating_inoutParam_void(ne: inout NE) {} + + /* DEFAULT: @_lifetime(self: copy Self) */ + // CHECK: @$s30lifetime_depend_infer_defaults23NonEscapableMutableSelfV25mutating_noParam_NEResultAA10NEImmortalVyF : $@convention(method) (@lifetime(copy 0) @inout NonEscapableMutableSelf) -> @lifetime(borrow 0) @owned NEImmortal + @_lifetime(&self) + mutating func mutating_noParam_NEResult() -> NEImmortal { NEImmortal() } + + /* DEFAULT: @_lifetime(self: copy Self) */ + /* DEFAULT: @_lifetime(ne: copy NE) */ + // CHECK: @$s30lifetime_depend_infer_defaults23NonEscapableMutableSelfV30mutating_inoutNEParam_NEResult2neAA10NEImmortalVAA2NEVz_tF : $@convention(method) (@lifetime(copy 0) @inout NE, @lifetime(copy 1) @inout NonEscapableMutableSelf) -> @lifetime(borrow 1) @owned NEImmortal + @_lifetime(&self) + mutating func mutating_inoutNEParam_NEResult(ne: inout NE) -> NEImmortal { NEImmortal() } +} + +// ============================================================================= +// Accessors: +// +// 'get', '_read', and '_modify' are inferred as methods that return ~Escpable results dependent on 'self' +// +// 'set' is only inferred when implicit. This allows for the declaration of non-Escapable stored properties. Knowing +// that the implicit setter assigns a stored property is sufficient for the compiler to assume Inherit dependency on +// both 'self' and 'newValue'. A full assignment would not need the 'self' dependency. +// ============================================================================= + +struct Accessors { + let c: C + + var neComputed: NEImmortal { + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults9AccessorsV10neComputedAA10NEImmortalVvg : $@convention(method) (@guaranteed Accessors) -> @lifetime(borrow 0) @owned NEImmortal + get { // OK + NEImmortal() + } + + // CHECK: @$s30lifetime_depend_infer_defaults9AccessorsV10neComputedAA10NEImmortalVvs : $@convention(method) (@owned NEImmortal, @inout Accessors) -> () + set { // OK (no dependency) + } + } + + var neYielded: NEImmortal { + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults9AccessorsV9neYieldedAA10NEImmortalVvr : $@yield_once @convention(method) (@guaranteed Accessors) -> @lifetime(borrow 0) @yields @guaranteed NEImmortal + _read { // OK + yield NEImmortal() + } + + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults9AccessorsV9neYieldedAA10NEImmortalVvM : $@yield_once @convention(method) (@inout Accessors) -> @lifetime(borrow 0) @yields @inout NEImmortal + _modify { // OK + var ne = NEImmortal() + yield &ne + } + } + + subscript(_ index: Int) -> NEImmortal { + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults9AccessorsVyAA10NEImmortalVSicig : $@convention(method) (Int, @guaranteed Accessors) -> @lifetime(borrow 1) @owned NEImmortal + get { // OK + NEImmortal() + } + + // CHECK: @$s30lifetime_depend_infer_defaults9AccessorsVyAA10NEImmortalVSicis : $@convention(method) (@owned NEImmortal, Int, @inout Accessors) -> () + set { // OK (no dependency) + } + + // Synthesized modify... + /* DEFAULT: @_lifetime(borrow self) */ + // CHECK: @$s30lifetime_depend_infer_defaults9AccessorsVyAA10NEImmortalVSiciM : $@yield_once @convention(method) (Int, @inout Accessors) -> @lifetime(borrow 1) @yields @inout NEImmortal + } +} + +struct TrivialAccessors { + let p: UnsafeRawPointer + + // The implicit _read/_modify accessors must be inferred. They cannot be written explicitly because a getter is + // already defined. + var neComputed: NEImmortal { + @_lifetime(borrow self) + get { // OK + NEImmortal() + } + + // CHECK: @$s30lifetime_depend_infer_defaults16TrivialAccessorsV10neComputedAA10NEImmortalVvs : $@convention(method) (@owned NEImmortal, @inout TrivialAccessors) -> () + set { // OK (no dependency) + } + + // synthesized modify + // CHECK: @$s30lifetime_depend_infer_defaults16TrivialAccessorsV10neComputedAA10NEImmortalVvM : $@yield_once @convention(method) (@inout TrivialAccessors) -> @lifetime(borrow 0) @yields @inout NEImmortal + } +} + +struct NonescapableSelfAccessors: ~Escapable { + var ne: NE + + @_lifetime(immortal) + init() { + ne = NE() + } + + var neComputed: NE { + @_lifetime(copy self) + get { + ne + } + // DEFAULT: '@_lifetime(self: copy self, copy newValue) + // CHECK: @$s30lifetime_depend_infer_defaults25NonescapableSelfAccessorsV10neComputedAA2NEVvs : $@convention(method) (@owned NE, @lifetime(copy 0, copy 1) @inout NonescapableSelfAccessors) -> () + set { + ne = newValue + } + + // synthesized modify. + // CHECK: @$s30lifetime_depend_infer_defaults25NonescapableSelfAccessorsV10neComputedAA2NEVvM : $@yield_once @convention(method) (@lifetime(copy 0) @inout NonescapableSelfAccessors) -> @lifetime(copy 0) @yields @inout NE + } +} + +struct NoncopyableSelfAccessors: ~Copyable & ~Escapable { + var ne: NE + + var neComputed: NE { + @_lifetime(copy self) + get { + ne + } + // DEFAULT: '@_lifetime(self: copy self, copy newValue) + // CHECK: @$s30lifetime_depend_infer_defaults24NoncopyableSelfAccessorsV10neComputedAA2NEVvs : $@convention(method) (@owned NE, @lifetime(copy 0, copy 1) @inout NoncopyableSelfAccessors) -> () + set { + ne = newValue + } + + // synthesized modify. + // CHECK: @$s30lifetime_depend_infer_defaults24NoncopyableSelfAccessorsV10neComputedAA2NEVvM : $@yield_once @convention(method) (@lifetime(copy 0) @inout NoncopyableSelfAccessors) -> @lifetime(copy 0) @yields @inout NE + } + +} From 91ab0b79bf6a5df852680929a4a84f193f560e06 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 2 Sep 2025 17:45:37 -0700 Subject: [PATCH 6/6] Update unit tests for improved inference rules. --- test/SIL/implicit_lifetime_dependence.swift | 2 +- .../lifetime_dependence_borrow.swift | 10 ---------- .../lifetime_dependence_diagnostics.swift | 2 +- .../lifetime_dependence_scope_fixup.swift | 4 ++-- test/Sema/lifetime_attr.swift | 13 ++++++++++--- test/Sema/lifetime_attr_nofeature.swift | 5 ++--- test/Sema/lifetime_depend_nofeature.swift | 17 ++++++++--------- 7 files changed, 24 insertions(+), 29 deletions(-) diff --git a/test/SIL/implicit_lifetime_dependence.swift b/test/SIL/implicit_lifetime_dependence.swift index 077e03f476bca..9774c26df922e 100644 --- a/test/SIL/implicit_lifetime_dependence.swift +++ b/test/SIL/implicit_lifetime_dependence.swift @@ -88,7 +88,7 @@ struct Wrapper : ~Escapable { _read { yield _view } -// CHECK: sil hidden @$s28implicit_lifetime_dependence7WrapperV4viewAA10BufferViewVvM : $@yield_once @convention(method) (@inout Wrapper) -> @lifetime(borrow 0) @yields @inout BufferView { +// CHECK: sil hidden @$s28implicit_lifetime_dependence7WrapperV4viewAA10BufferViewVvM : $@yield_once @convention(method) (@lifetime(copy 0) @inout Wrapper) -> @lifetime(borrow 0) @yields @inout BufferView { @_lifetime(&self) _modify { yield &_view diff --git a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_borrow.swift b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_borrow.swift index f57c86f20521c..f9959f638b6d7 100644 --- a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_borrow.swift +++ b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_borrow.swift @@ -8,16 +8,6 @@ // REQUIRES: swift_in_compiler // REQUIRES: swift_feature_Lifetimes -@_unsafeNonescapableResult -@_lifetime(copy source) -internal func _overrideLifetime< - T: ~Copyable & ~Escapable, U: ~Copyable & ~Escapable ->( - _ dependent: consuming T, copying source: borrowing U -) -> T { - dependent -} - // Some container-ish thing. struct CN: ~Copyable { let p: UnsafeRawPointer diff --git a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_diagnostics.swift b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_diagnostics.swift index 61dc495c4fe28..a5c1981322301 100644 --- a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_diagnostics.swift +++ b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_diagnostics.swift @@ -30,7 +30,7 @@ public struct NEInt: ~Escapable { var i: Int // Test yielding an address. - // CHECK-LABEL: sil hidden @$s4test5NEIntV5iprop{{.*}}vM : $@yield_once @convention(method) (@inout NEInt) -> @lifetime(borrow 0) @yields @inout NEInt + // CHECK-LABEL: sil hidden @$s4test5NEIntV5iprop{{.*}}vM : $@yield_once @convention(method) (@lifetime(copy 0) @inout NEInt) -> @lifetime(borrow 0) @yields @inout NEInt // CHECK: bb0(%0 : $*NEInt): // CHECK: [[A:%.*]] = begin_access [modify] [static] %0 : $*NEInt // CHECK: yield [[A]] : $*NEInt, resume bb1, unwind bb2 diff --git a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_scope_fixup.swift b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_scope_fixup.swift index 81f51c292e3a3..900998edb52b3 100644 --- a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_scope_fixup.swift +++ b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_scope_fixup.swift @@ -280,12 +280,12 @@ func testPointeeDependenceOnMutablePointer(p: UnsafePointer) { // CHECK: [[VAR:%.*]] = alloc_stack [lexical] [var_decl] $MutableView, var, name "span", type $MutableView // CHECK: apply %{{.*}}(%0, %{{.*}}) : $@convention(method) (UnsafeMutableRawBufferPointer, @thin MutableView.Type) -> @lifetime(borrow 0) @owned MutableView // CHECK: [[ACCESS1:%.*]] = begin_access [modify] [static] [[VAR]] : $*MutableView -// CHECK: apply %{{.*}}(%{{.*}}) : $@convention(method) (@inout MutableView) -> @lifetime(borrow 0) @owned MutableView +// CHECK: apply %{{.*}}(%{{.*}}) : $@convention(method) (@lifetime(copy 0) @inout MutableView) -> @lifetime(borrow 0) @owned MutableView // CHECK: [[LD1:%.*]] = load %{{.*}} : $*MutableView // CHECK: apply %{{.*}}([[LD1]]) : $@convention(thin) (@guaranteed MutableView) -> () // CHECK: end_access [[ACCESS1]] : $*MutableView // CHECK: [[ACCESS2:%.*]] = begin_access [modify] [static] [[VAR]] : $*MutableView -// CHECK: apply %{{.*}}(%{{.*}}) : $@convention(method) (@inout MutableView) -> @lifetime(borrow 0) @owned MutableView +// CHECK: apply %{{.*}}(%{{.*}}) : $@convention(method) (@lifetime(copy 0) @inout MutableView) -> @lifetime(borrow 0) @owned MutableView // CHECK: [[LD2:%.*]] = load %{{.*}} : $*MutableView // CHECK: apply %{{.*}}([[LD2]]) : $@convention(thin) (@guaranteed MutableView) -> () // CHECK: end_access [[ACCESS2]] : $*MutableView diff --git a/test/Sema/lifetime_attr.swift b/test/Sema/lifetime_attr.swift index f2b6d89b9021f..d0212792f106a 100644 --- a/test/Sema/lifetime_attr.swift +++ b/test/Sema/lifetime_attr.swift @@ -63,9 +63,15 @@ func invalidDependenceInoutInt(_ x: inout Int) -> NE { NE() } -@_lifetime(result: copy source) -@_lifetime(result: borrow source) // TODO: display error here -func invalidTarget(_ result: inout NE, _ source: consuming NE) { // expected-error{{invalid duplicate target lifetime dependencies on function}} +@_lifetime(result: copy source1) // expected-error{{invalid duplicate target lifetime dependencies on function}} +@_lifetime(result: copy source2) +func invalidTarget(_ result: inout NE, _ source1: consuming NE, _ source2: consuming NE) { + result = source1 +} + +@_lifetime(result: copy source) // expected-error{{invalid duplicate target lifetime dependencies on function}} +@_lifetime(result: borrow source) +func invalidSource(_ result: inout NE, _ source: consuming NE) { result = source } @@ -123,6 +129,7 @@ struct Wrapper : ~Escapable { } @_lifetime(self: &self) nonmutating _modify {// expected-error{{lifetime-dependent parameter 'self' must be 'inout'}} + // expected-error@-1{{cannot infer the lifetime dependence scope on a method with a ~Escapable parameter, specify '@_lifetime(borrow self)' or '@_lifetime(copy self)'}} } } diff --git a/test/Sema/lifetime_attr_nofeature.swift b/test/Sema/lifetime_attr_nofeature.swift index ea3637ef2f5a6..a86d46ec238b5 100644 --- a/test/Sema/lifetime_attr_nofeature.swift +++ b/test/Sema/lifetime_attr_nofeature.swift @@ -9,8 +9,7 @@ func derive(_ ne: NE) -> NE { // expected-error{{a function cannot return a ~Esc ne } -func f_inout_infer(a: inout MutableRawSpan) {} +func f_inout_infer(a: inout MutableRawSpan) {} // DEFAULT OK -func f_inout_no_infer(a: inout MutableRawSpan, b: RawSpan) {} -// expected-error @-1{{a function cannot have a ~Escapable 'inout' parameter 'a' in addition to other ~Escapable parameters}} +func f_inout_no_infer(a: inout MutableRawSpan, b: RawSpan) {} // DEFAULT OK diff --git a/test/Sema/lifetime_depend_nofeature.swift b/test/Sema/lifetime_depend_nofeature.swift index d3d7d71ba5216..8ee667a8f1dbd 100644 --- a/test/Sema/lifetime_depend_nofeature.swift +++ b/test/Sema/lifetime_depend_nofeature.swift @@ -11,20 +11,20 @@ struct EmptyNonEscapable: ~Escapable {} // expected-error{{an implicit initializ // Don't allow non-Escapable return values. func neReturn(span: RawSpan) -> RawSpan { span } // expected-error{{a function cannot return a ~Escapable result}} -func neInout(span: inout RawSpan) {} // OK +func neInout(span: inout RawSpan) {} // DEFAULT OK -func neInoutNEParam(span: inout RawSpan, _: RawSpan) {} // expected-error{{a function cannot have a ~Escapable 'inout' parameter 'span'}} +func neInoutNEParam(span: inout RawSpan, _: RawSpan) {} // DEFAULT OK struct S { func neReturn(span: RawSpan) -> RawSpan { span } // expected-error{{a method cannot return a ~Escapable result}} func neInout(span: inout RawSpan) {} // OK - func neInoutNEParam(span: inout RawSpan, _: RawSpan) {} // expected-error{{a method cannot have a ~Escapable 'inout' parameter 'span'}} + func neInoutNEParam(span: inout RawSpan, _: RawSpan) {} // DEFAULT OK mutating func mutatingNEInout(span: inout RawSpan) {} // OK - mutating func mutatingNEInoutParam(span: inout RawSpan, _: RawSpan) {} // expected-error{{a mutating method cannot have a ~Escapable 'inout' parameter 'span'}} + mutating func mutatingNEInoutParam(span: inout RawSpan, _: RawSpan) {} // DEFAULT OK } class C { @@ -36,16 +36,15 @@ class C { extension MutableSpan { func method() {} // OK - mutating func mutatingMethod() {} // expected-error{{a mutating method cannot have a ~Escapable 'self'}} + mutating func mutatingMethod() {} // DEFAULT OK func neReturn(span: RawSpan) -> RawSpan { span } // expected-error{{a method cannot return a ~Escapable result}} - func neInout(span: inout RawSpan) {} // expected-error{{a method cannot have a ~Escapable 'inout' parameter 'span'}} + func neInout(span: inout RawSpan) {} // DEFAULT OK - mutating func mutatingNEInout(span: inout RawSpan) {} // expected-error{{a mutating method cannot have a ~Escapable 'self'}} - // expected-error@-1{{a mutating method cannot have a ~Escapable 'inout' parameter 'span'}} + mutating func mutatingNEInout(span: inout RawSpan) {} // DEFAULT OK } extension Span { - mutating func mutate() {} // expected-error{{a mutating method cannot have a ~Escapable 'self'}} + mutating func mutate() {} // DEFAULT OK }