From f39359eb3501cf33cedcacc2fe5deb8ad37ca7fb Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Thu, 15 Jan 2026 18:44:20 +0000 Subject: [PATCH 1/8] LifetimeDependence: Fix issues from earlier refactor This addresses feedback from #86560: - Remove redundant collectParameterInfo call - Consistent naming of ParamInfo variables - Explicit type name instead of decltype Also add nullptr checks on afd, as appropriate. --- lib/AST/LifetimeDependence.cpp | 102 ++++++++++++++++----------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index 2d653fa9d4590..789d4c3067786 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -452,7 +452,7 @@ class LifetimeDependenceChecker { // A parameter corresponding to the implicit self declaration of // the function, if it has one. Otherwise, std::nullopt. - std::optional implicitSelfParam; + std::optional implicitSelfParamInfo; LifetimeDependenceBuilder depBuilder; @@ -506,8 +506,8 @@ class LifetimeDependenceChecker { } const ParamInfo &getParamForIndex(unsigned paramIndex) { - if (implicitSelfParam && paramIndex == implicitSelfParam->index) - return *implicitSelfParam; + if (implicitSelfParamInfo && paramIndex == implicitSelfParamInfo->index) + return *implicitSelfParamInfo; assert(paramIndex < parameterInfos.size() && "unexpected result index"); return parameterInfos[paramIndex]; @@ -517,8 +517,9 @@ class LifetimeDependenceChecker { if (paramOrResultIndex == resultIndex) return resultInterfaceType; - if (implicitSelfParam && paramOrResultIndex == implicitSelfParam->index) - return implicitSelfParam->getInterfaceType(); + if (implicitSelfParamInfo && + paramOrResultIndex == implicitSelfParamInfo->index) + return implicitSelfParamInfo->getInterfaceType(); return parameterInfos[paramOrResultIndex].getInterfaceType(); } @@ -527,8 +528,9 @@ class LifetimeDependenceChecker { if (paramOrResultIndex == resultIndex) return resultTy; - if (implicitSelfParam && paramOrResultIndex == implicitSelfParam->index) - return implicitSelfParam->typeInContext; + if (implicitSelfParamInfo && + paramOrResultIndex == implicitSelfParamInfo->index) + return implicitSelfParamInfo->typeInContext; return parameterInfos[paramOrResultIndex].typeInContext; } @@ -557,23 +559,20 @@ class LifetimeDependenceChecker { LifetimeDependenceChecker(AbstractFunctionDecl *afd) : lifetimeEntries(collectLifetimeEntries(afd->getAttrs())), parameterInfos(collectParameterInfo(afd->getParameters(), afd)), - afd(afd), - ctx(afd->getDeclContext()->getASTContext()), + afd(afd), ctx(afd->getDeclContext()->getASTContext()), sourceFile(afd->getParentSourceFile()), - escapableDecl( - ctx.getProtocol( + escapableDecl(ctx.getProtocol( swift::getKnownProtocolKind(InvertibleProtocolKind::Escapable))), genericEnv(afd->getGenericEnvironment()), resultIndex(getResultIndex(afd)), resultInterfaceType(getResultOrYieldInterface(afd)), resultTy(afd->mapTypeIntoEnvironment(resultInterfaceType)), returnLoc(getReturnLoc(afd)), - implicitSelfParam(getSelfParamInfo(afd)), + implicitSelfParamInfo(getSelfParamInfo(afd)), depBuilder(/*sourceIndexCap*/ resultIndex), - isImplicit(afd->isImplicit()), - isInit(isa(afd)), + isImplicit(afd->isImplicit()), isInit(isa(afd)), hasUnsafeNonEscapableResult( - afd->getAttrs().hasAttribute()) {} + afd->getAttrs().hasAttribute()) {} std::optional> currentDependencies() const { @@ -581,7 +580,7 @@ class LifetimeDependenceChecker { } std::optional> checkFuncDecl() { - assert(isa(afd) || isa(afd)); + assert(nullptr != afd && (isa(afd) || isa(afd))); assert(depBuilder.empty()); // Handle Builtins first because, even though Builtins require @@ -695,7 +694,7 @@ class LifetimeDependenceChecker { // the extra formal self parameter, a dependency targeting the formal result // index would incorrectly target the SIL metatype parameter. bool hasImplicitSelfParam() const { - return !isInit && implicitSelfParam.has_value(); + return !isInit && implicitSelfParamInfo.has_value(); } // In SIL, implicit initializers and accessors become explicit. @@ -746,11 +745,11 @@ class LifetimeDependenceChecker { return qualifier; } } - if (implicitSelfParam.has_value()) { + if (implicitSelfParamInfo.has_value()) { if (isInit) { return "an initializer"; } - if (implicitSelfParam->param.isInOut()) { + if (implicitSelfParamInfo->param.isInOut()) { return "a mutating method"; } return "a method"; @@ -779,14 +778,14 @@ class LifetimeDependenceChecker { if (!hasImplicitSelfParam()) { return; } - if (!implicitSelfParam->param.isInOut()) { + if (!implicitSelfParamInfo->param.isInOut()) { return; } - if (!isDiagnosedNonEscapable(implicitSelfParam->typeInContext)) { + if (!isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext)) { return; } - if (!depBuilder.hasTargetDeps(implicitSelfParam->index)) { - ctx.Diags.diagnose(implicitSelfParam->loc, diagID, + if (!depBuilder.hasTargetDeps(implicitSelfParamInfo->index)) { + ctx.Diags.diagnose(implicitSelfParamInfo->loc, diagID, {StringRef(diagnosticQualifier())}); } } @@ -846,13 +845,13 @@ class LifetimeDependenceChecker { // Inferrence helper. bool isCompatibleWithOwnership(LifetimeDependenceKind kind, - ParamInfo const ¶m) const { + ParamInfo const ¶mInfo) const { if (kind == LifetimeDependenceKind::Inherit) { return true; } - auto paramType = param.typeInContext; - auto loweredOwnership = getLoweredOwnership(param.param); + auto paramType = paramInfo.typeInContext; + auto loweredOwnership = getLoweredOwnership(paramInfo.param); // Lifetime dependence always propagates through temporary BitwiseCopyable // values, even if the dependence is scoped. if (isBitwiseCopyable(paramType, ctx)) { @@ -1007,7 +1006,7 @@ class LifetimeDependenceChecker { diag::lifetime_dependence_invalid_self_in_init); return nullptr; } - return &implicitSelfParam.value(); + return &implicitSelfParamInfo.value(); } } } @@ -1054,17 +1053,17 @@ class LifetimeDependenceChecker { // Get the value ownership of param if it is non-default. Otherwise, compute // the lowered value ownership. The supplied Param must be a member of - // parameters or implicitSelfParam.value(). + // parameters or implicitSelfParamInfo.value(). ValueOwnership getLoweredOwnership(Param const ¶m) const { auto const ownership = param.getValueOwnership(); if (ownership != ValueOwnership::Default) return ownership; - if (isa(afd)) { + if (nullptr != afd && isa(afd)) { return ValueOwnership::Owned; } if (auto *ad = dyn_cast_or_null(afd)) { - auto const isSelfParameter = implicitSelfParam.has_value() && - ¶m == &(implicitSelfParam->param); + auto const isSelfParameter = implicitSelfParamInfo.has_value() && + ¶m == &(implicitSelfParamInfo->param); if (ad->getAccessorKind() == AccessorKind::Set) { return isSelfParameter ? ValueOwnership::InOut : ValueOwnership::Owned; } @@ -1224,9 +1223,10 @@ class LifetimeDependenceChecker { // Ignore mutating self. An 'inout' modifier effectively makes the parameter // a different type for lifetime inference. - if (hasImplicitSelfParam() && !implicitSelfParam->param.isInOut()) { - if (implicitSelfParam->typeInContext->getCanonicalType() == canResultTy) { - targetDeps->inheritIndices.set(implicitSelfParam->index); + if (hasImplicitSelfParam() && !implicitSelfParamInfo->param.isInOut()) { + if (implicitSelfParamInfo->typeInContext->getCanonicalType() == + canResultTy) { + targetDeps->inheritIndices.set(implicitSelfParamInfo->index); } } @@ -1259,7 +1259,7 @@ class LifetimeDependenceChecker { return; } bool nonEscapableSelf = - isDiagnosedNonEscapable(implicitSelfParam->typeInContext); + isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext); if (nonEscapableSelf && accessor->getImplicitSelfDecl()->isInOut()) { // First, infer the dependency of the inout non-Escapable 'self'. This may // result in two inferred dependencies for accessors (one targetting @@ -1273,7 +1273,7 @@ class LifetimeDependenceChecker { // 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, implicitSelfParam->index, + depBuilder.inferDependency(resultIndex, implicitSelfParamInfo->index, *dependenceKind); } } @@ -1302,7 +1302,7 @@ class LifetimeDependenceChecker { if (paramTypeInContext->hasError()) { return; } - depBuilder.inferInoutDependency(implicitSelfParam->index); + depBuilder.inferInoutDependency(implicitSelfParamInfo->index); // The 'newValue' dependence kind must match the getter's dependence kind // because the generated '_modify' accessor composes the getter's result @@ -1310,7 +1310,7 @@ class LifetimeDependenceChecker { // Escapable then the getter does not have any lifetime dependency, so the // setter cannot depend on 'newValue'. if (!paramTypeInContext->isEscapable()) { - depBuilder.inferDependency(implicitSelfParam->index, newValIdx, + depBuilder.inferDependency(implicitSelfParamInfo->index, newValIdx, LifetimeDependenceKind::Inherit); } break; @@ -1322,7 +1322,7 @@ class LifetimeDependenceChecker { // 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(implicitSelfParam->index); + depBuilder.inferInoutDependency(implicitSelfParamInfo->index); } break; default: @@ -1367,9 +1367,9 @@ class LifetimeDependenceChecker { for (auto &dep : *deps) { if (dep.getTargetIndex() != resultIndex) continue; - if (dep.checkInherit(implicitSelfParam->index)) + if (dep.checkInherit(implicitSelfParamInfo->index)) return LifetimeDependenceKind::Inherit; - if (dep.checkScope(implicitSelfParam->index)) + if (dep.checkScope(implicitSelfParamInfo->index)) return LifetimeDependenceKind::Scope; } } @@ -1378,7 +1378,7 @@ class LifetimeDependenceChecker { } // Either a Get or Modify without any wrapped accessor. Handle these like a // read of the stored property. - return inferLifetimeDependenceKind(*implicitSelfParam); + return inferLifetimeDependenceKind(*implicitSelfParamInfo); } // Infer implicit initialization. A non-Escapable initializer parameter can @@ -1445,26 +1445,26 @@ class LifetimeDependenceChecker { return; // same-type inferrence applied; don't issue diagnostics. bool nonEscapableSelf = - isDiagnosedNonEscapable(implicitSelfParam->typeInContext); + isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext); // 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 && implicitSelfParam->param.isInOut()) { + if (nonEscapableSelf && implicitSelfParamInfo->param.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() && + if (!(nullptr != afd && isa(afd)) && !useLazyInference() && parameterInfos.size() > 0) { return; } if (!useLazyInference() && !isImplicitOrSIL()) { // Require explicit @_lifetime(borrow self) for UnsafePointer-like self. if (!nonEscapableSelf && - isBitwiseCopyable(implicitSelfParam->typeInContext, ctx)) { + isBitwiseCopyable(implicitSelfParamInfo->typeInContext, ctx)) { diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_bitwisecopyable, diagnosticQualifier(), "self"); @@ -1479,7 +1479,7 @@ class LifetimeDependenceChecker { } // Infer based on ownership if possible for either explicit accessors or // methods as long as they pass preceding ambiguity checks. - auto kind = inferLifetimeDependenceKind(*implicitSelfParam); + auto kind = inferLifetimeDependenceKind(*implicitSelfParamInfo); if (!kind) { // Special diagnostic for an attempt to depend on a consuming parameter. diagnose(returnLoc, @@ -1487,7 +1487,7 @@ class LifetimeDependenceChecker { "self", diagnosticQualifier()); return; } - resultDeps->addIfNew(implicitSelfParam->index, *kind); + resultDeps->addIfNew(implicitSelfParamInfo->index, *kind); } // Infer result dependence on a function or intitializer parameter. @@ -1602,15 +1602,15 @@ class LifetimeDependenceChecker { if (!hasImplicitSelfParam()) return; - if (!isDiagnosedNonEscapable(implicitSelfParam->typeInContext)) + if (!isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext)) return; assert(!isInit && "class initializers have Escapable self"); - if (!implicitSelfParam->param.isInOut()) + if (!implicitSelfParamInfo->param.isInOut()) return; // Assume that a mutating method does not depend on its parameters. - depBuilder.inferInoutDependency(implicitSelfParam->index); + depBuilder.inferInoutDependency(implicitSelfParamInfo->index); } // Infer @_lifetime(param: copy param) for 'inout' non-Escapable parameters. From fbb2da15268966fb4ba549b6d59f8f17bf9b64a0 Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Tue, 13 Jan 2026 17:52:14 +0000 Subject: [PATCH 2/8] Serialization: Add LifetimeDependenceInfo.isFromAnnotation to module format The isFromAnnotation flag is set if and only if the lifetime originates from a @lifetime or @_lifetime annotation in the source program. isFromAnnotation==false means that the lifetime dependence checker would infer the same lifetime if the Swift type or decl was printed without an annotation for that dependency. More specifically, it means that the depenence was inferred by the lifetime dependence checker. Some dependencies on imported C/C++ decls are "inferred", but they either correspond to explicit lifetime information in the source (smart pointers, lifetimebound attribute) or are likely to differ from what the dependence checker would infer. As such, we set the flag to true for all of them. --- include/swift/AST/LifetimeDependence.h | 50 ++++++++++++++++++++------ include/swift/SIL/SILBridging.h | 1 + include/swift/SIL/SILBridgingImpl.h | 8 +++-- lib/AST/LifetimeDependence.cpp | 29 ++++++++------- lib/ClangImporter/ImportDecl.cpp | 17 +++++---- lib/SIL/IR/SILFunctionType.cpp | 23 ++++++------ lib/Serialization/Deserialization.cpp | 8 +++-- lib/Serialization/ModuleFormat.h | 3 +- lib/Serialization/Serialization.cpp | 2 +- 9 files changed, 94 insertions(+), 47 deletions(-) diff --git a/include/swift/AST/LifetimeDependence.h b/include/swift/AST/LifetimeDependence.h index 7b501920d6cc0..257cb7bcbf58d 100644 --- a/include/swift/AST/LifetimeDependence.h +++ b/include/swift/AST/LifetimeDependence.h @@ -190,23 +190,28 @@ class LifetimeEntry final class LifetimeDependenceInfo { IndexSubset *inheritLifetimeParamIndices; IndexSubset *scopeLifetimeParamIndices; - llvm::PointerIntPair - addressableParamIndicesAndImmortal; + // The outer bool is the "isFromAnnotation" bit. The inner one is the + // "isImmortal" bit. + llvm::PointerIntPair, 1, bool> + addressableParamIndicesAndImmortalAndFromAnnotation; IndexSubset *conditionallyAddressableParamIndices; unsigned targetIndex; public: + /// Fully-initialized dependence info. LifetimeDependenceInfo(IndexSubset *inheritLifetimeParamIndices, IndexSubset *scopeLifetimeParamIndices, unsigned targetIndex, bool isImmortal, - // set during SIL type lowering - IndexSubset *addressableParamIndices = nullptr, - IndexSubset *conditionallyAddressableParamIndices = nullptr) + bool isFromAnnotation, + IndexSubset *addressableParamIndices, + IndexSubset *conditionallyAddressableParamIndices) : inheritLifetimeParamIndices(inheritLifetimeParamIndices), scopeLifetimeParamIndices(scopeLifetimeParamIndices), - addressableParamIndicesAndImmortal(addressableParamIndices, isImmortal), - conditionallyAddressableParamIndices(conditionallyAddressableParamIndices), + addressableParamIndicesAndImmortalAndFromAnnotation( + {addressableParamIndices, isImmortal}, isFromAnnotation), + conditionallyAddressableParamIndices( + conditionallyAddressableParamIndices), targetIndex(targetIndex) { ASSERT(this->isImmortal() || inheritLifetimeParamIndices || scopeLifetimeParamIndices); @@ -238,6 +243,18 @@ class LifetimeDependenceInfo { } } + /// Partially-initialized dependence info, with addressable & conditionally + /// addressable parameter indices unset for now. + LifetimeDependenceInfo(IndexSubset *inheritLifetimeParamIndices, + IndexSubset *scopeLifetimeParamIndices, + unsigned targetIndex, bool isImmortal, + bool isFromAnnotation) + : LifetimeDependenceInfo(inheritLifetimeParamIndices, + scopeLifetimeParamIndices, targetIndex, + isImmortal, isFromAnnotation, + // set during SIL type lowering + nullptr, nullptr) {} + operator bool() const { return !empty(); } bool empty() const { @@ -245,7 +262,18 @@ class LifetimeDependenceInfo { scopeLifetimeParamIndices == nullptr; } - bool isImmortal() const { return addressableParamIndicesAndImmortal.getInt(); } + bool isImmortal() const { + return addressableParamIndicesAndImmortalAndFromAnnotation.getPointer() + .getInt(); + } + + /// Whether this lifetime dependence corresponds to a @lifetime annotation in + /// the source program (Swift or SIL). Such dependencies are likely to differ + /// from the default (inferred) ones, so they must be included when printing a + /// Swift function's type. + bool isFromAnnotation() const { + return addressableParamIndicesAndImmortalAndFromAnnotation.getInt(); + } unsigned getTargetIndex() const { return targetIndex; } @@ -256,7 +284,8 @@ class LifetimeDependenceInfo { return scopeLifetimeParamIndices != nullptr; } bool hasAddressableParamIndices() const { - return addressableParamIndicesAndImmortal.getPointer() != nullptr; + return addressableParamIndicesAndImmortalAndFromAnnotation.getPointer() + .getPointer() != nullptr; } unsigned getParamIndicesLength() const { @@ -282,7 +311,8 @@ class LifetimeDependenceInfo { /// not only on the value, but the memory location of a particular instance /// of the value. IndexSubset *getAddressableIndices() const { - return addressableParamIndicesAndImmortal.getPointer(); + return addressableParamIndicesAndImmortalAndFromAnnotation.getPointer() + .getPointer(); } /// Return the set of parameters which may have addressable dependencies /// depending on the type of the parameter. diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index 391fae79ff6a2..f892e7e742dae 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -198,6 +198,7 @@ struct BridgedLifetimeDependenceInfo { swift::IndexSubset *_Nullable conditionallyAddressableParamIndices; SwiftUInt targetIndex; bool immortal; + bool fromAnnotation; BRIDGED_INLINE BridgedLifetimeDependenceInfo(swift::LifetimeDependenceInfo info); diff --git a/include/swift/SIL/SILBridgingImpl.h b/include/swift/SIL/SILBridgingImpl.h index e0717fffbe670..d547a28299198 100644 --- a/include/swift/SIL/SILBridgingImpl.h +++ b/include/swift/SIL/SILBridgingImpl.h @@ -171,13 +171,15 @@ BridgedParameterInfo BridgedParameterInfoArray::at(SwiftInt parameterIndex) cons // BridgedLifetimeDependenceInfo //===----------------------------------------------------------------------===// -BridgedLifetimeDependenceInfo::BridgedLifetimeDependenceInfo(swift::LifetimeDependenceInfo info) +BridgedLifetimeDependenceInfo::BridgedLifetimeDependenceInfo( + swift::LifetimeDependenceInfo info) : inheritLifetimeParamIndices(info.getInheritIndices()), scopeLifetimeParamIndices(info.getScopeIndices()), addressableParamIndices(info.getAddressableIndices()), conditionallyAddressableParamIndices( - info.getConditionallyAddressableIndices()), - targetIndex(info.getTargetIndex()), immortal(info.isImmortal()) {} + info.getConditionallyAddressableIndices()), + targetIndex(info.getTargetIndex()), immortal(info.isImmortal()), + fromAnnotation(info.isFromAnnotation()) {} SwiftInt BridgedLifetimeDependenceInfoArray::count() const { return lifetimeDependenceInfoArray.unbridged().size(); diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index 789d4c3067786..85707eb9d6159 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -194,7 +194,8 @@ std::string LifetimeDependenceInfo::getString() const { } void LifetimeDependenceInfo::Profile(llvm::FoldingSetNodeID &ID) const { - ID.AddBoolean(addressableParamIndicesAndImmortal.getInt()); + ID.AddBoolean(isImmortal()); + ID.AddBoolean(isFromAnnotation()); ID.AddInteger(targetIndex); if (inheritLifetimeParamIndices) { ID.AddInteger((uint8_t)LifetimeDependenceKind::Inherit); @@ -204,11 +205,11 @@ void LifetimeDependenceInfo::Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger((uint8_t)LifetimeDependenceKind::Scope); scopeLifetimeParamIndices->Profile(ID); } - if (addressableParamIndicesAndImmortal.getPointer()) { + if (hasAddressableParamIndices()) { ID.AddBoolean(true); - addressableParamIndicesAndImmortal.getPointer()->Profile(ID); + getAddressableIndices()->Profile(ID); } else { - ID.AddBoolean(false); + ID.AddBoolean(false); } } @@ -235,7 +236,7 @@ void LifetimeDependenceInfo::getConcatenatedData( pushData(scopeLifetimeParamIndices); } if (hasAddressableParamIndices()) { - pushData(addressableParamIndicesAndImmortal.getPointer()); + pushData(getAddressableIndices()); } } @@ -337,8 +338,8 @@ struct LifetimeDependenceBuilder { } TargetDeps *createAnnotatedTargetDeps(unsigned targetIndex) { - auto iterAndInserted = depsArray.try_emplace(targetIndex, true, - sourceIndexCap); + auto iterAndInserted = + depsArray.try_emplace(targetIndex, true, sourceIndexCap); if (!iterAndInserted.second) return nullptr; @@ -394,10 +395,11 @@ struct LifetimeDependenceBuilder { ASSERT(!deps.isImmortal && "cannot combine immortal lifetime with parameter dependency"); } - lifetimeDependencies.push_back(LifetimeDependenceInfo{ + lifetimeDependencies.push_back(LifetimeDependenceInfo( /*inheritLifetimeParamIndices*/ inheritIndices, /*scopeLifetimeParamIndices*/ scopeIndices, targetIndex, - /*isImmortal*/ deps.isImmortal}); + /*isImmortal*/ deps.isImmortal, + /*isFromAnnotation*/ deps.hasAnnotation)); } if (lifetimeDependencies.empty()) { return std::nullopt; @@ -1809,10 +1811,10 @@ static std::optional checkSILTypeModifiers( } case LifetimeDescriptor::DescriptorKind::Named: { assert(source.isImmortal()); - return LifetimeDependenceInfo(/*inheritLifetimeParamIndices*/ nullptr, - /*scopeLifetimeParamIndices*/ nullptr, - targetIndex, - /*isImmortal*/ true); + return LifetimeDependenceInfo( + /*inheritLifetimeParamIndices*/ nullptr, + /*scopeLifetimeParamIndices*/ nullptr, targetIndex, + /*isImmortal*/ true, /*isFromAnnotation*/ true); } default: llvm_unreachable("SIL can only have ordered or immortal lifetime " @@ -1829,6 +1831,7 @@ static std::optional checkSILTypeModifiers( : nullptr, targetIndex, /*isImmortal*/ false, + /*isFromAnnotation*/ true, addressableLifetimeParamIndices.any() ? IndexSubset::get(ctx, addressableLifetimeParamIndices) : nullptr, diff --git a/lib/ClangImporter/ImportDecl.cpp b/lib/ClangImporter/ImportDecl.cpp index 51e0192019901..76bc796b32439 100644 --- a/lib/ClangImporter/ImportDecl.cpp +++ b/lib/ClangImporter/ImportDecl.cpp @@ -2102,7 +2102,8 @@ namespace { } SmallVector lifetimeDependencies; LifetimeDependenceInfo immortalLifetime(nullptr, nullptr, resultIndex, - /*isImmortal*/ true); + /*isImmortal*/ true, + /*isFromAnnotation*/ true); lifetimeDependencies.push_back(immortalLifetime); Impl.SwiftContext.evaluator.cacheOutput( LifetimeDependenceInfoRequest{fd}, @@ -4216,7 +4217,7 @@ namespace { lifetimeDependencies.push_back(LifetimeDependenceInfo( nullptr, IndexSubset::get(Impl.SwiftContext, dependenciesOfRet), returnIdx, - /*isImmortal*/ false)); + /*isImmortal*/ false, /*isFromAnnotation*/ true)); Impl.SwiftContext.evaluator.cacheOutput( LifetimeDependenceInfoRequest{result}, Impl.SwiftContext.AllocateCopy(lifetimeDependencies)); @@ -4285,7 +4286,8 @@ namespace { SmallVector lifetimeDependencies; LifetimeDependenceInfo immortalLifetime(nullptr, nullptr, 0, - /*isImmortal*/ true); + /*isImmortal*/ true, + /*isFromAnnotation*/ true); if (hasUnsafeAPIAttr(decl) && !isEscapable(decl->getReturnType())) { lifetimeDependencies.push_back(immortalLifetime); Impl.SwiftContext.evaluator.cacheOutput( @@ -4373,8 +4375,11 @@ namespace { cast(decl)->getThisType()->getPointeeType()); for (auto& [idx, inheritedDepVec]: inheritedArgDependences) { - lifetimeDependencies.push_back(LifetimeDependenceInfo(inheritedDepVec.any() ? IndexSubset::get(Impl.SwiftContext, - inheritedDepVec): nullptr, nullptr, idx, /*isImmortal=*/false)); + lifetimeDependencies.push_back(LifetimeDependenceInfo( + inheritedDepVec.any() + ? IndexSubset::get(Impl.SwiftContext, inheritedDepVec) + : nullptr, + nullptr, idx, /*isImmortal=*/false, /*isFromAnnotation=*/true)); } if (inheritLifetimeParamIndicesForReturn.any() || @@ -4389,7 +4394,7 @@ namespace { scopedLifetimeParamIndicesForReturn) : nullptr, returnIdx, - /*isImmortal*/ false)); + /*isImmortal*/ false, /*isFromAnnotation*/ true)); else if (auto *ctordecl = dyn_cast(decl)) { // Assume default constructed view types have no dependencies. if (ctordecl->isDefaultConstructor() && diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index 5f3636dfcf6d3..df3fe77fb0871 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -2957,8 +2957,10 @@ static CanSILFunctionType getSILFunctionType( = [&](const LifetimeDependenceInfo &formalDeps, unsigned target) -> LifetimeDependenceInfo { if (formalDeps.isImmortal()) { - return LifetimeDependenceInfo(nullptr, nullptr, - target, /*immortal*/ true); + return LifetimeDependenceInfo( + nullptr, nullptr, target, + /*immortal*/ true, + /*fromAnnotation*/ formalDeps.isFromAnnotation()); } auto lowerIndexSet = [&](IndexSubset *formal) -> IndexSubset * { @@ -2992,8 +2994,10 @@ static CanSILFunctionType getSILFunctionType( // entirely (such as if they were of `()` type), then there is effectively // no dependency, leaving behind an immortal value. if (!inheritIndicesSet && !scopeIndicesSet) { - return LifetimeDependenceInfo(nullptr, nullptr, target, - /*immortal*/ true); + return LifetimeDependenceInfo( + nullptr, nullptr, target, + /*immortal*/ true, + /*fromAnnotation*/ formalDeps.isFromAnnotation()); } SmallBitVector addressableDeps = scopeIndicesSet @@ -3009,12 +3013,11 @@ static CanSILFunctionType getSILFunctionType( IndexSubset *condAddressableSet = condAddressableDeps.any() ? IndexSubset::get(TC.Context, condAddressableDeps) : nullptr; - - return LifetimeDependenceInfo(inheritIndicesSet, - scopeIndicesSet, - target, /*immortal*/ false, - addressableSet, - condAddressableSet); + + return LifetimeDependenceInfo( + inheritIndicesSet, scopeIndicesSet, target, /*immortal*/ false, + /*fromAnnotation*/ formalDeps.isFromAnnotation(), addressableSet, + condAddressableSet); }; // Lower parameter dependencies. for (unsigned i = 0; i < parameterMap.size(); ++i) { diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 197e7738ddc2b..0ad85efa772b8 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -9563,12 +9563,13 @@ ModuleFile::maybeReadLifetimeDependence() { unsigned targetIndex; unsigned paramIndicesLength; bool isImmortal; + bool isFromAnnotation; bool hasInheritLifetimeParamIndices; bool hasScopeLifetimeParamIndices; bool hasAddressableParamIndices; ArrayRef lifetimeDependenceData; LifetimeDependenceLayout::readRecord( - scratch, targetIndex, paramIndicesLength, isImmortal, + scratch, targetIndex, paramIndicesLength, isImmortal, isFromAnnotation, hasInheritLifetimeParamIndices, hasScopeLifetimeParamIndices, hasAddressableParamIndices, lifetimeDependenceData); @@ -9604,8 +9605,9 @@ ModuleFile::maybeReadLifetimeDependence() { hasScopeLifetimeParamIndices ? IndexSubset::get(ctx, scopeLifetimeParamIndices) : nullptr, - targetIndex, isImmortal, + targetIndex, isImmortal, isFromAnnotation, hasAddressableParamIndices ? IndexSubset::get(ctx, addressableParamIndices) - : nullptr); + : nullptr, + nullptr); } diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index 47741ff33fd00..61295b365d77f 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 983; // @reparented inherited entry in support for Reparenting feature +const uint16_t SWIFTMODULE_VERSION_MINOR = 984; // @_lifetime attribute isFromAnnotation flag /// A standard hash seed used for all string hashes in a serialized module. /// @@ -2366,6 +2366,7 @@ namespace decls_block { BCVBR<4>, // targetIndex BCVBR<4>, // paramIndicesLength BCFixed<1>, // isImmortal + BCFixed<1>, // isFromAnnotation BCFixed<1>, // hasInheritLifetimeParamIndices BCFixed<1>, // hasScopeLifetimeParamIndices BCFixed<1>, // hasAddressableParamIndices diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index d9fb420ef2cf7..68e2c8c8e2c3e 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -2780,7 +2780,7 @@ void Serializer::writeLifetimeDependencies( LifetimeDependenceLayout::emitRecord( Out, ScratchRecord, abbrCode, info.getTargetIndex(), info.getParamIndicesLength(), info.isImmortal(), - info.hasInheritLifetimeParamIndices(), + info.isFromAnnotation(), info.hasInheritLifetimeParamIndices(), info.hasScopeLifetimeParamIndices(), info.hasAddressableParamIndices(), paramIndices); paramIndices.clear(); From b2c79211af84ed773f78270125a1e6ce49422b28 Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Mon, 19 Jan 2026 12:44:55 +0000 Subject: [PATCH 3/8] Types: + AnyFunctionType::hasExplicitLifetimeDependencies --- include/swift/AST/Types.h | 4 ++++ lib/AST/Type.cpp | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/include/swift/AST/Types.h b/include/swift/AST/Types.h index f444897fb1656..370c561978bd2 100644 --- a/include/swift/AST/Types.h +++ b/include/swift/AST/Types.h @@ -3790,6 +3790,10 @@ class AnyFunctionType : public TypeBase { return Bits.AnyFunctionType.HasLifetimeDependencies; } + /// Type has lifetime dependencies derived from explicit @_lifetime + /// attributes. + bool hasExplicitLifetimeDependencies() const; + ClangTypeInfo getClangTypeInfo() const; ClangTypeInfo getCanonicalClangTypeInfo() const; diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index cf8cb872cf180..fa5b1ad231fd2 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -4218,6 +4218,14 @@ CanType ProtocolCompositionType::getMinimalCanonicalType() const { return result.subst(existentialSig.Generalization)->getCanonicalType(); } +bool AnyFunctionType::hasExplicitLifetimeDependencies() const { + return hasLifetimeDependencies() && + llvm::any_of(getLifetimeDependencies(), + [](const LifetimeDependenceInfo &dep) { + return dep.isFromAnnotation(); + }); +} + ClangTypeInfo AnyFunctionType::getClangTypeInfo() const { switch (getKind()) { case TypeKind::Function: From 805d8e9e260664171355e808d75f6e239481ae17 Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Mon, 19 Jan 2026 12:49:37 +0000 Subject: [PATCH 4/8] Add language feature ClosureLifetimes --- include/swift/AST/TypeAttr.def | 1 + include/swift/Basic/Features.def | 1 + lib/AST/FeatureSet.cpp | 50 ++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/include/swift/AST/TypeAttr.def b/include/swift/AST/TypeAttr.def index 9aa07d1ff0160..d34858e35a16b 100644 --- a/include/swift/AST/TypeAttr.def +++ b/include/swift/AST/TypeAttr.def @@ -62,6 +62,7 @@ SIMPLE_TYPE_ATTR(reparented, Reparented) SIMPLE_TYPE_ATTR(unchecked, Unchecked) SIMPLE_TYPE_ATTR(preconcurrency, Preconcurrency) SIMPLE_TYPE_ATTR(unsafe, Unsafe) +TYPE_ATTR(_lifetime, Lifetime) SIMPLE_TYPE_ATTR(_local, Local) SIMPLE_TYPE_ATTR(_noMetadata, NoMetadata) TYPE_ATTR(_opaqueReturnTypeOf, OpaqueReturnTypeOf) diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index 912e9c0e8edb5..cee8e41c33be6 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -278,6 +278,7 @@ LANGUAGE_FEATURE(InoutLifetimeDependence, 0, "Support @_lifetime(&)") SUPPRESSIBLE_LANGUAGE_FEATURE(NonexhaustiveAttribute, 487, "Nonexhaustive Enums") LANGUAGE_FEATURE(ModuleSelector, 491, "Module selectors (`Module::name` syntax)") LANGUAGE_FEATURE(BuiltinConcurrencyStackNesting, 0, "Concurrency Stack Nesting Builtins") +LANGUAGE_FEATURE(ClosureLifetimes, 0, "Support @_lifetime on function types") // TEMPORARY: Never use this, because it is only meant to allow us to model // suppression of @c in Swift interfaces as a temporary measure. diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 79574ae1000f1..efaaaa724a765 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -233,6 +233,56 @@ static bool usesFeatureLifetimes(Decl *decl) { return false; } +static bool hasLifetimeDependencies(Type type) { + if (auto *aft = type->getAs()) { + return aft->hasExplicitLifetimeDependencies(); + } + return false; +} + +/// Search for any types within decl with lifetime dependencies. Ignore +/// lifetimes on AbstractFunctionDecl decls, since those are supported by the +/// Lifetimes feature, not ClosureLifetimes. +static bool findClosureLifetimes(Decl *decl) { + // Search for any Decl that uses a type with lifetime dependencies, possibly + // restricting this to explicit dependencies. + class ClosureLifetimesWalker : public ASTWalker { + + public: + bool useFound = false; + + PreWalkAction walkToDeclPre(Decl *D) override { + if (auto *afd = dyn_cast(D)) { + // Check the parameters and result, but not the AFD itself, since + // lifetimes on AFDs are supported by the Lifetimes feature. + Type resultType = + afd->getInterfaceType()->getAs()->getResult(); + useFound = resultType.findIf(hasLifetimeDependencies); + return Action::StopIf(useFound); + } + + // Check for lifetime dependence info on param and type decls. + if (isa(D) || isa(D)) { + useFound = usesTypeMatching(D, hasLifetimeDependencies); + return Action::StopIf(useFound); + } + + // Any other Decl kinds are irrelevant. + return Action::SkipChildren(); + } + }; + + ClosureLifetimesWalker walker; + decl->walk(walker); + return walker.useFound; +} + +static bool usesFeatureClosureLifetimes(Decl *decl) { + // This will find function types with lifetimes & closures with lifetimes, + // since it walks the AST rooted at decl, checking types. + return findClosureLifetimes(decl); +} + static bool usesFeatureInoutLifetimeDependence(Decl *decl) { auto hasInoutLifetimeDependence = [](Decl *decl) { for (auto attr : decl->getAttrs().getAttributes()) { From e401b1a72069cea9bf601116cd722ed9237e0d95 Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Tue, 6 Jan 2026 17:31:16 +0000 Subject: [PATCH 5/8] [AST] Support multiple of the same attribute per type --- include/swift/AST/Attr.h | 11 ++++ lib/Sema/TypeCheckType.cpp | 112 ++++++++++++++++++++++++++----------- 2 files changed, 90 insertions(+), 33 deletions(-) diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 5bf834c7b0a0f..273ee323a37e9 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -4274,6 +4274,17 @@ class alignas(1 << AttrAlignInBits) TypeAttribute SWIFT_DEBUG_DUMPER(dump()); void print(ASTPrinter &Printer, const PrintOptions &Options) const; + + /// Returns true if multiple instances of an attribute kind + /// can appear on a type. + static constexpr bool allowMultipleAttributes(TypeAttrKind TK) { + switch (TK) { + case swift::TypeAttrKind::Lifetime: + return true; + default: + return false; + } + } }; class AtTypeAttrBase : public TypeAttribute { diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index b482e57325e66..0e3b86dee9bf1 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -2468,7 +2468,7 @@ namespace { CallerIsolatedTypeRepr *nonisolatedNonsendingAttr; llvm::TinyPtrVector customAttrs; - EnumMap typeAttrs; + EnumMap> typeAttrs; llvm::SmallBitVector claimedCustomAttrs; FixedBitSet claimedTypeAttrs; @@ -2515,33 +2515,35 @@ namespace { claimedCustomAttrs.set(it - customAttrs.begin()); } - TypeAttribute *getWithoutClaiming(TypeAttrKind attrKind) { + ArrayRef getWithoutClaiming(TypeAttrKind attrKind) { auto it = typeAttrs.find(attrKind); if (it != typeAttrs.end()) { return *it; } else { - return nullptr; + return {}; } } /// Claim the attribute matching the given representative kind. /// It will not be diagnosed as unused. - TypeAttribute *claim(TypeAttrKind attrKind) { + ArrayRef claim(TypeAttrKind attrKind) { assert(getRepresentative(attrKind) == attrKind); auto it = typeAttrs.find(attrKind); if (it != typeAttrs.end()) { claimedTypeAttrs.insert(it - typeAttrs.begin()); return *it; } else { - return nullptr; + return {}; } } /// Claim all attributes for which the given function returns true. void claimAllWhere(ContextualTypeAttrResolver resolver) { size_t i = 0; - for (TypeAttribute *attr : typeAttrs) { - if (resolver(attr)) + for (auto &attrVector : typeAttrs) { + // Only claim the attribute if the resolver matches for every instance + // of it. + if (llvm::all_of(attrVector, resolver)) claimedTypeAttrs.insert(i); i++; } @@ -2551,8 +2553,10 @@ namespace { /// but process them in reverse source order. void reversedClaimAllWhere(ContextualTypeAttrResolver resolver) { for (size_t i = typeAttrs.size(); i > 0; --i) { - TypeAttribute *attr = typeAttrs.begin()[i - 1]; - if (resolver(attr)) + auto &attrVector = typeAttrs.begin()[i - 1]; + // Only claim the attribute if the resolver matches for every instance + // of it. + if (llvm::all_of(attrVector, resolver)) claimedTypeAttrs.insert(i - 1); } } @@ -2584,33 +2588,66 @@ namespace { } }; - template - AttrClass *claim(TypeAttrSet &attrs) { - auto attr = attrs.claim(AttrClass::StaticKind); - return cast_or_null(attr); + template + auto claim(TypeAttrSet &attrs) { + auto attrVector = attrs.claim(Kind); + if constexpr (!TypeAttribute::allowMultipleAttributes(Kind)) { + return attrVector.empty() ? nullptr : attrVector.front(); + } else { + return attrVector; + } } template - AttrClass *claim(TypeAttrSet *attrs) { - return (attrs ? claim(*attrs) : nullptr); + auto claim(TypeAttrSet &attrs) { + ArrayRef attrVector = attrs.claim(AttrClass::StaticKind); + if constexpr (!TypeAttribute::allowMultipleAttributes( + AttrClass::StaticKind)) { + return attrVector.empty() ? nullptr + : cast_or_null(attrVector.front()); + } else { + // The type attributes in attrVector are all instances of the type + // attribute that corresponds to the claimed type attribute kind (i.e. + // AttrClass) so casting the data pointer should always be safe. Since we + // cannot statically prove this, perform a paranoid check. + if (llvm::all_of(attrVector, [](TypeAttribute *attr) { + return isa(attr); + })) { + return ArrayRef( + reinterpret_cast(attrVector.data()), + attrVector.size()); + } else { + return ArrayRef{}; + } + } } template - AttrClass *getWithoutClaiming(TypeAttrSet &attrs) { - auto attr = attrs.getWithoutClaiming(AttrClass::StaticKind); - return cast_or_null(attr); + auto claim(TypeAttrSet *attrs) { + return (attrs ? claim(*attrs) + : decltype(claim(*attrs)){}); } template - std::enable_if_t, AttrClass *> - getWithoutClaiming(TypeAttrSet *attrs) { - return (attrs ? getWithoutClaiming(*attrs) : nullptr); + auto getWithoutClaiming(TypeAttrSet &attrs) { + auto attrVector = attrs.getWithoutClaiming(AttrClass::StaticKind); + if constexpr (!TypeAttribute::allowMultipleAttributes( + AttrClass::StaticKind)) { + return attrVector.empty() ? nullptr + : cast_or_null(attrVector.front()); + } else { + return attrVector; + } } template - std::enable_if_t, - CallerIsolatedTypeRepr *> - getWithoutClaiming(TypeAttrSet *attrs) { + auto getWithoutClaiming(TypeAttrSet *attrs) { + return (attrs ? getWithoutClaiming(*attrs) + : decltype(getWithoutClaiming(*attrs)){}); + } + + template <> + auto getWithoutClaiming(TypeAttrSet *attrs) { return attrs ? attrs->getNonisolatedNonsendingAttr() : nullptr; } } // end anonymous namespace @@ -3282,8 +3319,15 @@ void TypeAttrSet::accumulate(ArrayRef attrs) { auto insertResult = typeAttrs.insert(representativeKind, typeAttr); if (insertResult.second) continue; + // If an attribute with the same kind already exists, only add this one if + // there can be more than one. + if (TypeAttribute::allowMultipleAttributes(representativeKind)) { + insertResult.first->push_back(typeAttr); + continue; + } + // Dignose the conflict. - TypeAttribute *previousAttr = *insertResult.first; + TypeAttribute *previousAttr = insertResult.first->front(); diagnoseConflict(representativeKind, previousAttr, typeAttr); } @@ -3340,11 +3384,12 @@ void TypeAttrSet::diagnoseUnclaimed(const TypeResolution &resolution, // Type attributes size_t i = 0; - for (auto attr : typeAttrs) { + for (auto const &attrVector : typeAttrs) { if (claimedTypeAttrs.contains(i)) continue; i++; - diagnoseUnclaimed(attr, resolution, options, resolvedType); + for (auto attr : attrVector) + diagnoseUnclaimed(attr, resolution, options, resolvedType); } } @@ -3517,7 +3562,7 @@ TypeResolver::resolveAttributedType(TypeRepr *repr, TypeResolutionOptions option // These are the total type transforms. Type ty; - if (auto attr = attrs.claim(TAR_TypeTransformer)) { + if (auto attr = claim(attrs)) { if (auto opaqueAttr = dyn_cast(attr)) { ty = resolveOpaqueReturnType(repr, opaqueAttr->getMangledName(), opaqueAttr->getIndex(), @@ -3531,8 +3576,8 @@ TypeResolver::resolveAttributedType(TypeRepr *repr, TypeResolutionOptions option // The SIL metatype attributes are basically total type transforms, too. // TODO: this should really be restricted to lowered types - } else if (auto attr = isSILSourceFile() - ? attrs.claim(TAR_SILMetatype) : nullptr) { + } else if (auto attr = + isSILSourceFile() ? claim(attrs) : nullptr) { ty = resolveSILMetatype(repr, options, attr); // Okay, propagate attributes down to specific resolvers. @@ -4617,7 +4662,7 @@ NeverNullType TypeResolver::resolveSILFunctionType(FunctionTypeRepr *repr, bool hasError = false; auto coroutineKind = SILCoroutineKind::None; - if (auto coroAttr = attrs ? attrs->claim(TAR_SILCoroutine) : nullptr) { + if (auto coroAttr = attrs ? claim(*attrs) : nullptr) { assert(isa(coroAttr) || isa(coroAttr) || isa(coroAttr)); @@ -4637,7 +4682,8 @@ NeverNullType TypeResolver::resolveSILFunctionType(FunctionTypeRepr *repr, } ParameterConvention callee = ParameterConvention::Direct_Unowned; - if (auto calleeAttr = attrs ? attrs->claim(TAR_SILCalleeConvention) : nullptr) { + if (auto calleeAttr = + attrs ? claim(*attrs) : nullptr) { assert(isa(calleeAttr) || isa(calleeAttr)); callee = (isa(calleeAttr) @@ -5054,7 +5100,7 @@ bool TypeResolver::resolveSingleSILResult( return false; } - if (auto conventionAttr = attrs.claim(TAR_SILValueConvention)) { + if (auto conventionAttr = claim(attrs)) { switch (conventionAttr->getKind()) { #define ERROR(ATTR, CONVENTION) \ case TypeAttrKind::ATTR: \ From 7b9db389848e141a1a196e946954f68b41bf9ccc Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Mon, 10 Nov 2025 17:47:22 +0000 Subject: [PATCH 6/8] LifetimeDependence: Support function types To a function type's lifetimes, a base version of the type is first created with no lifetime dependence info. This is then passed to the dependence checker, and the resulting dependencies are added to it. It would be possible to do this analysis by passing just the parameter list and result type (which are available before the type is created), but this approach lets us avoid dealing with a header inclusion cycle between Types.h, ExtInfo.h, and LifetimeDependence.h, since it does not require AnyFunctionType::Param to be defined in LifetimeDependence.h. --- include/swift/AST/ASTBridging.h | 7 ++ include/swift/AST/Attr.h | 13 +++ include/swift/AST/DiagnosticsSema.def | 2 + include/swift/AST/LifetimeDependence.h | 8 ++ include/swift/AST/Ownership.h | 2 +- lib/AST/Attr.cpp | 8 ++ lib/AST/Bridging/TypeAttributeBridging.cpp | 8 ++ lib/AST/LifetimeDependence.cpp | 108 +++++++++++++++++++++ lib/AST/TypeCheckRequests.cpp | 1 + lib/ASTGen/Sources/ASTGen/TypeAttrs.swift | 17 ++++ lib/Parse/ParseDecl.cpp | 16 +++ lib/Sema/TypeCheckType.cpp | 49 ++++++++-- 12 files changed, 228 insertions(+), 11 deletions(-) diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index 68a173916c950..19c9a8ced9321 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -2574,6 +2574,13 @@ BridgedDifferentiableTypeAttr BridgedDifferentiableTypeAttr_createParsed( swift::SourceLoc nameLoc, swift::SourceRange parensRange, BridgedDifferentiabilityKind cKind, swift::SourceLoc kindLoc); +SWIFT_NAME("BridgedLifetimeTypeAttr.createParsed(_:atLoc:nameLoc:" + "parensRange:entry:)") +BridgedLifetimeTypeAttr BridgedLifetimeTypeAttr_createParsed( + BridgedASTContext cContext, swift::SourceLoc atLoc, + swift::SourceLoc nameLoc, swift::SourceRange parensRange, + BridgedLifetimeEntry entry); + SWIFT_NAME("BridgedIsolatedTypeAttr.createParsed(_:atLoc:nameLoc:parensRange:" "isolationKind:isolationKindLoc:)") BridgedIsolatedTypeAttr BridgedIsolatedTypeAttr_createParsed( diff --git a/include/swift/AST/Attr.h b/include/swift/AST/Attr.h index 273ee323a37e9..c6e6d75719acc 100644 --- a/include/swift/AST/Attr.h +++ b/include/swift/AST/Attr.h @@ -4394,6 +4394,19 @@ class DifferentiableTypeAttr void printImpl(ASTPrinter &printer, const PrintOptions &options) const; }; +class LifetimeTypeAttr : public SimpleTypeAttrWithArgs { + LifetimeEntry *entry; + +public: + LifetimeTypeAttr(SourceLoc atLoc, SourceLoc kwLoc, SourceRange parens, + LifetimeEntry *entry) + : SimpleTypeAttr(atLoc, kwLoc, parens), entry(entry) {} + + LifetimeEntry *getLifetimeEntry() const { return entry; } + + void printImpl(ASTPrinter &printer, const PrintOptions &options) const; +}; + class OpaqueReturnTypeOfTypeAttr : public SimpleTypeAttrWithArgs { Located MangledName; diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index e0c9777435fa9..9ebb721e99d74 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8511,6 +8511,8 @@ ERROR(lifetime_target_requires_nonescapable, none, "invalid lifetime dependence on an Escapable %0", (StringRef)) NOTE(lifetime_escapable_source_requires_escapable_note, none, "use '@_lifetime(%0%1)' instead", (StringRef, StringRef)) +ERROR(lifetime_dependence_unknown_type, none, + "lifetime dependence checking failed due to unknown %0 type", (StringRef)) //------------------------------------------------------------------------------ // MARK: Lifetime Dependence Requirements diff --git a/include/swift/AST/LifetimeDependence.h b/include/swift/AST/LifetimeDependence.h index 257cb7bcbf58d..47e43de4b7bb6 100644 --- a/include/swift/AST/LifetimeDependence.h +++ b/include/swift/AST/LifetimeDependence.h @@ -31,8 +31,10 @@ namespace swift { class AbstractFunctionDecl; +class AnyFunctionType; class FunctionTypeRepr; class LifetimeDependentTypeRepr; +class LifetimeTypeAttr; class SILParameterInfo; class SILResultInfo; @@ -351,6 +353,12 @@ class LifetimeDependenceInfo { getFromSIL(FunctionTypeRepr *funcRepr, ArrayRef params, ArrayRef results, DeclContext *dc); + /// Builds LifetimeDependenceInfo from a function type. + static std::optional> + getFromAST(FunctionTypeRepr *funcRepr, AnyFunctionType *funcType, + ArrayRef lifetimeAttributes, DeclContext *dc, + GenericEnvironment *env); + bool operator==(const LifetimeDependenceInfo &other) const { return this->isImmortal() == other.isImmortal() && this->getTargetIndex() == other.getTargetIndex() && diff --git a/include/swift/AST/Ownership.h b/include/swift/AST/Ownership.h index 9ec8ab6b4ba6f..1f458cf03cbee 100644 --- a/include/swift/AST/Ownership.h +++ b/include/swift/AST/Ownership.h @@ -153,7 +153,7 @@ ValueOwnership asValueOwnership(ParameterOwnership o); static inline llvm::StringRef getOwnershipSpelling(ValueOwnership ownership) { switch (ownership) { case ValueOwnership::Default: - return ""; + return "default"; case ValueOwnership::InOut: return "inout"; case ValueOwnership::Shared: diff --git a/lib/AST/Attr.cpp b/lib/AST/Attr.cpp index 6eb1d9d7208cc..8b2bb176d7da9 100644 --- a/lib/AST/Attr.cpp +++ b/lib/AST/Attr.cpp @@ -238,6 +238,14 @@ void DifferentiableTypeAttr::printImpl(ASTPrinter &printer, printer.printStructurePost(PrintStructureKind::BuiltinAttribute); } +void LifetimeTypeAttr::printImpl(ASTPrinter &printer, + const PrintOptions &options) const { + printer.callPrintStructurePre(PrintStructureKind::BuiltinAttribute); + printer.printAttrName("@_lifetime"); + printer << entry->getString(); + printer.printStructurePost(PrintStructureKind::BuiltinAttribute); +} + void ConventionTypeAttr::printImpl(ASTPrinter &printer, const PrintOptions &options) const { printer.callPrintStructurePre(PrintStructureKind::BuiltinAttribute); diff --git a/lib/AST/Bridging/TypeAttributeBridging.cpp b/lib/AST/Bridging/TypeAttributeBridging.cpp index 975dacf3ce928..34acabf269579 100644 --- a/lib/AST/Bridging/TypeAttributeBridging.cpp +++ b/lib/AST/Bridging/TypeAttributeBridging.cpp @@ -69,6 +69,14 @@ BridgedDifferentiableTypeAttr BridgedDifferentiableTypeAttr_createParsed( atLoc, nameLoc, parensRange, {unbridged(cKind), kindLoc}); } +BridgedLifetimeTypeAttr BridgedLifetimeTypeAttr_createParsed( + BridgedASTContext cContext, swift::SourceLoc atLoc, + swift::SourceLoc nameLoc, swift::SourceRange parensRange, + BridgedLifetimeEntry entry) { + return new (cContext.unbridged()) + LifetimeTypeAttr(atLoc, nameLoc, parensRange, entry.unbridged()); +} + BridgedIsolatedTypeAttr BridgedIsolatedTypeAttr_createParsed( BridgedASTContext cContext, SourceLoc atLoc, SourceLoc nameLoc, SourceRange parensRange, BridgedIsolatedTypeAttrIsolationKind cIsolation, diff --git a/lib/AST/LifetimeDependence.cpp b/lib/AST/LifetimeDependence.cpp index 85707eb9d6159..f4bfc8369787a 100644 --- a/lib/AST/LifetimeDependence.cpp +++ b/lib/AST/LifetimeDependence.cpp @@ -17,6 +17,7 @@ #include "swift/AST/ConformanceLookup.h" #include "swift/AST/Decl.h" #include "swift/AST/DiagnosticsSema.h" +#include "swift/AST/GenericEnvironment.h" #include "swift/AST/Module.h" #include "swift/AST/ParameterList.h" #include "swift/AST/SourceFile.h" @@ -32,6 +33,20 @@ using namespace swift; +/// Determine whether Type t is "unknown", meaning we cannot safely determine +/// whether it is Escapable by calling TypeBase::isEscapable. +static bool isTypeUnknown(Type t) { + // These types would hit an assertion in + // TypeBase::computeInvertibleConformances. + if (t->hasUnboundGenericType() || t->hasTypeParameter()) + return true; + // This type would hit an assertion in checkRequirements. + if (t->hasTypeVariable()) + return true; + + return false; +} + std::string LifetimeDescriptor::getString() const { switch (kind) { case DescriptorKind::Named: { @@ -576,11 +591,90 @@ class LifetimeDependenceChecker { hasUnsafeNonEscapableResult( afd->getAttrs().hasAttribute()) {} + LifetimeDependenceChecker(FunctionTypeRepr *funcRepr, + AnyFunctionType *funcType, + ArrayRef lifetimeAttrs, + DeclContext *dc, GenericEnvironment *env) + : afd(nullptr), ctx(funcType->getASTContext()), + sourceFile(dc->getParentSourceFile()), + resultIndex(funcType->getParams().size()), + returnLoc(funcRepr->getResultTypeRepr()->getLoc()), + implicitSelfParamInfo(std::nullopt), depBuilder(resultIndex), + isImplicit(false), isInit(false), hasUnsafeNonEscapableResult(false) { + for (auto functionLifetimeEntry : lifetimeAttrs) + lifetimeEntries.push_back(functionLifetimeEntry->getLifetimeEntry()); + + // We only ever use the second names of function type parameters for + // lifetimes. + auto const params = funcType->getParams(); + auto const argReprs = funcRepr->getArgsTypeRepr()->getElements(); + + resultTy = + GenericEnvironment::mapTypeIntoEnvironment(env, funcType->getResult()); + + assert(params.size() == argReprs.size()); + for (unsigned paramIndex = 0; paramIndex < params.size(); ++paramIndex) { + auto const ¶meter = params[paramIndex]; + auto const &arg = argReprs[paramIndex]; + parameterInfos.push_back( + {parameter, paramIndex, + // If an argument has no second name, use the location of its type. + arg.SecondNameLoc.isValid() ? arg.SecondNameLoc : arg.Type->getLoc(), + GenericEnvironment::mapTypeIntoEnvironment( + env, parameter.getPlainType())}); + } + } + std::optional> currentDependencies() const { return depBuilder.initializeDependenceInfoArray(ctx); } + /// Perform lifetime dependence checks for a function type. + std::optional> checkFuncType() { + // Check if the function type contains any types for which we cannot + // determine Escapability. We cannot perform lifetime inference for such + // types, or check the correctness of lifetime annotations on them, so bail + // out. Emit diagnostics if there are any lifetime attributes. + // + // We could make this more granular, only bailing out if a lifetime source + // or target contains an unbound generic, but these cases seem too niche to + // be worth the effort. + // + // Even if there were no explicit lifetime entries, we still need to + // diagnose failed inference if a parameter or the result was ~Escapable. + const auto isNonEscapableSafe = [](Type t) { + return !isTypeUnknown(t) && isDiagnosedNonEscapable(t); + }; + const bool shouldDiagnose = + !lifetimeEntries.empty() || + llvm::any_of(parameterInfos, + [&](const ParamInfo ¶mInfo) { + return isNonEscapableSafe(paramInfo.typeInContext); + }) || + isNonEscapableSafe(resultTy); + bool unknownTypeFound = false; + for (const auto ¶mInfo : parameterInfos) { + if (isTypeUnknown(paramInfo.typeInContext)) { + unknownTypeFound = true; + if (shouldDiagnose) + diagnose(paramInfo.loc, diag::lifetime_dependence_unknown_type, + "parameter"); + } + } + if (isTypeUnknown(resultTy)) { + unknownTypeFound = true; + if (shouldDiagnose) + diagnose(returnLoc, diag::lifetime_dependence_unknown_type, "result"); + } + + if (unknownTypeFound) + return std::nullopt; + + return checkCommon(); + } + + /// Perform lifetime dependence checks for a function declaration. std::optional> checkFuncDecl() { assert(nullptr != afd && (isa(afd) || isa(afd))); assert(depBuilder.empty()); @@ -593,6 +687,10 @@ class LifetimeDependenceChecker { return currentDependencies(); } + return checkCommon(); + } + + std::optional> checkCommon() { if (!ctx.LangOpts.hasFeature(Feature::LifetimeDependence) && !ctx.LangOpts.hasFeature(Feature::Lifetimes) && !ctx.SourceMgr.isImportMacroGeneratedLoc(returnLoc)) { @@ -1716,6 +1814,16 @@ LifetimeDependenceInfo::get(ValueDecl *decl) { return LifetimeDependenceChecker::checkEnumElementDecl(eed); } +std::optional> +LifetimeDependenceInfo::getFromAST( + FunctionTypeRepr *funcRepr, AnyFunctionType *funcType, + ArrayRef lifetimeAttributes, DeclContext *dc, + GenericEnvironment *env) { + return LifetimeDependenceChecker(funcRepr, funcType, lifetimeAttributes, dc, + env) + .checkFuncType(); +} + void LifetimeDependenceInfo::dump() const { llvm::errs() << "target: " << getTargetIndex() << '\n'; if (isImmortal()) { diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index 5d402544766e2..a23467f04a720 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -2769,6 +2769,7 @@ void LifetimeDependenceInfoRequest::cacheResult( } auto *eed = cast(decl); eed->LazySemanticInfo.NoLifetimeDependenceInfo = 1; + return; } decl->getASTContext().evaluator.cacheNonEmptyOutput(*this, std::move(result)); diff --git a/lib/ASTGen/Sources/ASTGen/TypeAttrs.swift b/lib/ASTGen/Sources/ASTGen/TypeAttrs.swift index eedfcd0ffaece..4faf5e2d5b79d 100644 --- a/lib/ASTGen/Sources/ASTGen/TypeAttrs.swift +++ b/lib/ASTGen/Sources/ASTGen/TypeAttrs.swift @@ -85,6 +85,9 @@ extension ASTGenVisitor { case .Differentiable: return (self.generateDifferentiableTypeAttr(attribute: node)?.asTypeAttribute) .map(BridgedTypeOrCustomAttr.typeAttr(_:)) + case .Lifetime: + return (self.generateLifetimeTypeAttr(attribute: node)?.asTypeAttribute) + .map(BridgedTypeOrCustomAttr.typeAttr(_:)) case .OpaqueReturnTypeOf: return (self.generateOpaqueReturnTypeOfTypeAttr(attribute: node)?.asTypeAttribute) .map(BridgedTypeOrCustomAttr.typeAttr(_:)) @@ -247,6 +250,20 @@ extension ASTGenVisitor { kindLoc: differentiabilityLoc ) } + + func generateLifetimeTypeAttr(attribute node: AttributeSyntax) -> BridgedLifetimeTypeAttr? { + guard let entry = self.generateLifetimeEntry(attribute: node) else { + return nil + } + + return .createParsed( + self.ctx, + atLoc: self.generateSourceLoc(node.atSign), + nameLoc: self.generateSourceLoc(node.attributeName), + parensRange: self.generateAttrParensRange(attribute: node), + entry: entry + ) + } func generateIsolatedTypeAttr(attribute node: AttributeSyntax) -> BridgedIsolatedTypeAttr? { let isolationKindLoc = self.generateSourceLoc(node.arguments) diff --git a/lib/Parse/ParseDecl.cpp b/lib/Parse/ParseDecl.cpp index 49a1ef2124166..e5f629231bf9b 100644 --- a/lib/Parse/ParseDecl.cpp +++ b/lib/Parse/ParseDecl.cpp @@ -5076,6 +5076,22 @@ ParserStatus Parser::parseTypeAttribute(TypeOrCustomAttr &result, return makeParserSuccess(); } + case TypeAttrKind::Lifetime: { + const auto entryResult = parseLifetimeEntry(AtLoc); + if (entryResult.isNull()) { + return makeParserError(); + } + + auto *entry = entryResult.get(); + + if (!justChecking) { + result = new (Context) LifetimeTypeAttr( + AtLoc, attrLoc, SourceRange(entry->getStartLoc(), entry->getEndLoc()), + entryResult.get()); + } + return makeParserSuccess(); + } + case TypeAttrKind::Convention: { ConventionTypeAttr *convention = nullptr; if (parseConventionAttributeInternal(AtLoc, attrLoc, convention, diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index 0e3b86dee9bf1..90e60f02d1fbb 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -4572,21 +4572,50 @@ NeverNullType TypeResolver::resolveASTFunctionType( .build(); // SIL uses polymorphic function types to resolve overloaded member functions. + AnyFunctionType *aft; if (auto genericSig = repr->getGenericSignature()) { - return GenericFunctionType::get(genericSig, params, outputTy, extInfo); + aft = GenericFunctionType::get(genericSig, params, outputTy, extInfo); + } else { + + auto fnTy = FunctionType::get(params, outputTy, extInfo); + if (fnTy->hasError()) + return fnTy; + + if (TypeChecker::diagnoseInvalidFunctionType(fnTy, repr->getLoc(), repr, + getDeclContext(), + resolution.getStage())) + return ErrorType::get(fnTy); + + aft = fnTy; } - auto fnTy = FunctionType::get(params, outputTy, extInfo); - - if (fnTy->hasError()) - return fnTy; + auto const lifetimeAttributes = claim(attrs); - if (TypeChecker::diagnoseInvalidFunctionType(fnTy, repr->getLoc(), repr, - getDeclContext(), - resolution.getStage())) - return ErrorType::get(fnTy); + // Lifetime dependence inference has to map parameter and result types into + // the generic environment of the DeclContext, so it must request the generic + // signature of the type. In order to prevent cycles in request evaluation, we + // defer lifetime dependence checking until the Interface type resolution + // stage. Since the analysis depends on the type's generic environment, it can + // only run after a type has already been produced. + if (!ctx.LangOpts.hasFeature(Feature::Lifetimes) && + !lifetimeAttributes.empty()) { + diagnose(lifetimeAttributes[0]->getAttrLoc(), + diag::requires_experimental_feature, "@_lifetime", false, + Feature::Lifetimes.getName()); + return ErrorType::get(getASTContext()); + } + + if (inStage(TypeResolutionStage::Interface)) { + if (auto const resolvedLifetimeDependence = + LifetimeDependenceInfo::getFromAST( + repr, aft, lifetimeAttributes, getDeclContext(), + resolution.getGenericSignature().getGenericEnvironment())) { + aft = aft->withExtInfo(aft->getExtInfo().withLifetimeDependencies( + *resolvedLifetimeDependence)); + } + } - return fnTy; + return aft; } NeverNullType TypeResolver::resolveSILBoxType(SILBoxTypeRepr *repr, From a366c4765a7b73451b2216dc2a11150e6c7ec52f Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Thu, 8 Jan 2026 17:22:48 +0000 Subject: [PATCH 7/8] [AST] Print function type LifetimeDependenceInfo using parameter labels We fall back to indices when labels are not available, but labels are preferable, because they readable, stable, and preferred by the lifetime dependencies proposal. --- include/swift/AST/ASTPrinter.h | 11 +- include/swift/AST/LifetimeDependence.h | 6 + lib/AST/ASTPrinter.cpp | 181 ++++++++++++++++++++++--- 3 files changed, 179 insertions(+), 19 deletions(-) diff --git a/include/swift/AST/ASTPrinter.h b/include/swift/AST/ASTPrinter.h index ea24c38f3cab7..2db30a672375b 100644 --- a/include/swift/AST/ASTPrinter.h +++ b/include/swift/AST/ASTPrinter.h @@ -368,7 +368,9 @@ class ASTPrinter { return printedClangDecl.insert(d).second; } - void printLifetimeDependence( + /// Print lifetimeDependence as a SIL lifetime attribute, attached to a + /// parameter or result of a function. + void printSILLifetimeDependence( std::optional lifetimeDependence) { if (!lifetimeDependence.has_value()) { return; @@ -380,10 +382,15 @@ class ASTPrinter { ArrayRef lifetimeDependencies, unsigned index) { if (auto lifetimeDependence = getLifetimeDependenceFor(lifetimeDependencies, index)) { - printLifetimeDependence(*lifetimeDependence); + printSILLifetimeDependence(*lifetimeDependence); } } + /// Print lifetimeDependence as a Swift lifetime attribute. + void + printSwiftLifetimeDependence(LifetimeDependenceInfo const &lifetimeDependence, + ArrayRef params); + private: virtual void anchor(); }; diff --git a/include/swift/AST/LifetimeDependence.h b/include/swift/AST/LifetimeDependence.h index 47e43de4b7bb6..e46775f205fa8 100644 --- a/include/swift/AST/LifetimeDependence.h +++ b/include/swift/AST/LifetimeDependence.h @@ -339,6 +339,12 @@ class LifetimeDependenceInfo { && scopeLifetimeParamIndices->contains(index); } + /// Get a string representation of this LifetimeDependenceInfo suitable for + /// printing in SIL. The target is not included, since this is determined by + /// the position of the attribute in SIL. + /// + /// For printing function type lifetimes in Swift, see + /// ASTPrinter::printSwiftLifetimeDependence. std::string getString() const; void Profile(llvm::FoldingSetNodeID &ID) const; void getConcatenatedData(SmallVectorImpl &concatenatedData) const; diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index d4f53b82d72f9..197a247a529fc 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -504,6 +504,108 @@ bool TypeTransformContext::isPrintingSynthesizedExtension() const { return !Decl.isNull(); } +/// Get a string representation of the parameter at params[index]. This will be +/// the label or internal label of a normal parameter, or the string form of +/// index. We cannot currently print lifetimes with indices in swiftinterface +/// files because SwiftSyntax cannot parse lifetime entries that use them. +static std::string +getLifetimeDependenceIdentifier(unsigned index, + ArrayRef params) { + // TODO: Print "self" for the self parameter. Requires a way to determine if a + // type is the interface type of an instance method. + if (index < params.size() && params[index].hasInternalLabel()) { + return params[index].getInternalLabel().get(); + } + + return std::to_string(index); +} + +/// Get a string representation for the list of sources of the given +/// LifetimeDependenceInfo, if they can be printed. +/// +/// See getLifetimeDependenceIdentifier. +static std::string getLifetimeDependenceInfoSourceListString( + LifetimeDependenceInfo const &info, + ArrayRef params) { + + std::string lifetimeDependenceString = ""; + auto addressable = info.getAddressableIndices(); + auto condAddressable = info.getConditionallyAddressableIndices(); + + auto getSourceString = [&](IndexSubset *bitvector, + StringRef kind) -> std::string { + std::string result; + bool isFirstSetBit = true; + for (unsigned i = 0; i < bitvector->getCapacity(); i++) { + if (bitvector->contains(i)) { + if (!isFirstSetBit) { + result += ", "; + } + result += kind; + if (addressable && addressable->contains(i)) { + result += "address "; + } else if (condAddressable && condAddressable->contains(i)) { + result += "address_for_deps "; + } + // Print source labels for explicit lifetime annotations. Otherwise, + // print the index. Indices should ideally never be used in Swift + // lifetime annotations, especially in module interfaces. + result += getLifetimeDependenceIdentifier(i, params); + isFirstSetBit = false; + } + } + return result; + }; + auto inheritLifetimeParamIndices = info.getInheritIndices(); + if (inheritLifetimeParamIndices) { + assert(!inheritLifetimeParamIndices->isEmpty()); + lifetimeDependenceString += + getSourceString(inheritLifetimeParamIndices, "copy "); + } + if (auto scopeLifetimeParamIndices = info.getScopeIndices()) { + assert(!scopeLifetimeParamIndices->isEmpty()); + if (inheritLifetimeParamIndices) { + lifetimeDependenceString += ", "; + } + lifetimeDependenceString += + getSourceString(scopeLifetimeParamIndices, "borrow "); + } + if (info.isImmortal()) { + lifetimeDependenceString += "immortal"; + } + return lifetimeDependenceString; +} + +/// Get a string representation for this dependence info as a Swift lifetime +/// attribute (for a type or decl). The target and sources are referred to by +/// their labels if possible (see getLifetimeDependenceIdentifier). +static std::string +getLifetimeDependenceInfoSwiftString(LifetimeDependenceInfo const &info, + ArrayRef params) { + std::string lifetimeDependenceString = "@_lifetime("; + + // Only print the target if it is a parameter or self. + const auto resultIndex = params.size(); + const auto targetIndex = info.getTargetIndex(); + if (targetIndex != resultIndex) { + lifetimeDependenceString += + getLifetimeDependenceIdentifier(targetIndex, params); + lifetimeDependenceString += ": "; + } + + lifetimeDependenceString += + getLifetimeDependenceInfoSourceListString(info, params); + lifetimeDependenceString += ") "; + return lifetimeDependenceString; +} + +void ASTPrinter::printSwiftLifetimeDependence( + LifetimeDependenceInfo const &lifetimeDependence, + ArrayRef params) { + + *this << getLifetimeDependenceInfoSwiftString(lifetimeDependence, params); +} + void ASTPrinter::anchor() {} void ASTPrinter::printIndent() { @@ -6755,6 +6857,17 @@ class TypePrinter : public TypeVisitorhasLifetimeDependencies()) { + ArrayRef params = fnType->getParams(); + + for (const auto &lifetimeDependence : info.getLifetimeDependencies()) { + if (lifetimeDependence.isFromAnnotation()) { + Printer.printSwiftLifetimeDependence(lifetimeDependence, params); + } + } + } + SmallString<64> buf; switch (Options.PrintFunctionRepresentationAttrs) { case PrintOptions::FunctionRepresentationMode::None: @@ -6940,8 +7053,16 @@ class TypePrinter : public TypeVisitor Params, bool printLabels, + ArrayRef Params, bool printExternalLabels, + bool printAllLabels, ArrayRef lifetimeDependencies) { Printer << "("; @@ -6955,16 +7076,23 @@ class TypePrinter : public TypeVisitorhasExplicitLifetimeDependencies(); + } + void visitFunctionType(FunctionType *T, NonRecursivePrintOptions nrOptions) { Printer.callPrintStructurePre(PrintStructureKind::FunctionType); @@ -7002,7 +7138,10 @@ class TypePrinter : public TypeVisitorgetParams(), /*printLabels*/ false, + visitAnyFunctionTypeParams(T->getParams(), + /*printExternalLabels*/ false, + /*printAllLabels*/ + shouldPrintLabelsForLifetimeAttributes(T), T->getLifetimeDependencies()); if (T->hasExtInfo()) { @@ -7028,7 +7167,10 @@ class TypePrinter : public TypeVisitorgetLifetimeDependenceForResult()); + if (Options.PrintInSILBody) { + Printer.printLifetimeDependenceAt(T->getLifetimeDependencies(), + T->getParams().size()); + } Printer.callPrintStructurePre(PrintStructureKind::FunctionReturnType); T->getResult().print(Printer, Options); @@ -7063,7 +7205,9 @@ class TypePrinter : public TypeVisitorgetParams(), /*printLabels*/ true, + visitAnyFunctionTypeParams(T->getParams(), + /*printExternalLabels*/ true, /*printAllLabels*/ + shouldPrintLabelsForLifetimeAttributes(T), T->getLifetimeDependencies()); if (T->hasExtInfo()) { @@ -7085,8 +7229,10 @@ class TypePrinter : public TypeVisitor "; - Printer.printLifetimeDependenceAt(T->getLifetimeDependencies(), - T->getParams().size()); + if (Options.PrintInSILBody) { + Printer.printLifetimeDependenceAt(T->getLifetimeDependencies(), + T->getParams().size()); + } Printer.callPrintStructurePre(PrintStructureKind::FunctionReturnType); T->getResult().print(Printer, Options); @@ -7191,7 +7337,7 @@ class TypePrinter : public TypeVisitor "; - Printer.printLifetimeDependence(T->getLifetimeDependenceForResult()); + Printer.printSILLifetimeDependence(T->getLifetimeDependenceForResult()); bool parenthesizeResults = mustParenthesizeResults(T); if (parenthesizeResults) @@ -7835,12 +7981,13 @@ void AnyFunctionType::printParams(ArrayRef Params, printParams(Params, Printer, PO); } void AnyFunctionType::printParams(ArrayRef Params, - ASTPrinter &Printer, - const PrintOptions &PO) { - // TODO: Handle lifetime dependence printing here + ASTPrinter &Printer, const PrintOptions &PO) { + // Function type lifetimes are printed as part of the ExtInfo, not the + // parameter list, so we do not need to print them here. TypePrinter(Printer, PO) .visitAnyFunctionTypeParams(Params, - /*printLabels*/ true, {}); + /*printExternalLabels*/ true, + /*printAllLabels*/ false, {}); } std::string @@ -8029,7 +8176,7 @@ void SILParameterInfo::print( } if (lifetimeDependence) { - Printer.printLifetimeDependence(*lifetimeDependence); + Printer.printSILLifetimeDependence(*lifetimeDependence); } if (options.contains(SILParameterInfo::ImplicitLeading)) { From 6aa55785f0cde88735500d05a5b734b9698e0493 Mon Sep 17 00:00:00 2001 From: Aidan Hall Date: Wed, 26 Nov 2025 15:24:28 +0000 Subject: [PATCH 8/8] LifetimeDependence: Tests for function type attribute Includes: - rdar://166912068 test case - Nested type SIL tests - rdar://160894371 test - Many more --- .../lifetime_underscored_dependence.swift | 27 +++ ...lifetime_underscored_dependence_test.swift | 42 ++++ ...licit_lifetime_dependence_specifiers.swift | 40 ++++ test/SIL/implicit_lifetime_dependence.swift | 16 ++ .../lifetime_dependence_param_fail.swift | 35 ++++ .../verify_diagnostics.swift | 7 - test/Sema/lifetime_attr_nofeature.swift | 5 + test/Sema/lifetime_dependence_functype.swift | 188 +++++++++++++++++- 8 files changed, 350 insertions(+), 10 deletions(-) diff --git a/test/ModuleInterface/Inputs/lifetime_underscored_dependence.swift b/test/ModuleInterface/Inputs/lifetime_underscored_dependence.swift index 9fe204db4b5ec..0f3712714b111 100644 --- a/test/ModuleInterface/Inputs/lifetime_underscored_dependence.swift +++ b/test/ModuleInterface/Inputs/lifetime_underscored_dependence.swift @@ -89,3 +89,30 @@ public struct RigidArray : ~Copyable { } } +// Function types + +@inlinable +@_lifetime(copy ne0) +public func takeCopier(f: @_lifetime(io: copy io) @_lifetime(copy inview) (_ inview: consuming AnotherView, _ io: inout AnotherView) -> AnotherView, ne0: consuming AnotherView, ne1: inout AnotherView) -> AnotherView { + let ne2 = f(ne0, &ne1) + return ne2 +} + +@inlinable +public func takeCopierUnannotated(f: (consuming AnotherView) -> AnotherView) {} + +public typealias ExplicitNestedType = @_lifetime(copy ne0) @_lifetime(ne1: copy ne0) ((AnotherView) -> AnotherView, _ ne0: consuming AnotherView, _ ne1: inout AnotherView) -> AnotherView + +@inlinable +public func takeExplicitNestedType(f: ExplicitNestedType) {} + + +@inlinable +public func returnableCopier(_ aView: AnotherView) -> AnotherView { + return aView +} + +@inlinable +public func returnCopier() -> @_lifetime(copy a) (_ a: AnotherView) -> AnotherView { + return returnableCopier +} diff --git a/test/ModuleInterface/lifetime_underscored_dependence_test.swift b/test/ModuleInterface/lifetime_underscored_dependence_test.swift index 4e6aeba094aa7..662ba3bb94911 100644 --- a/test/ModuleInterface/lifetime_underscored_dependence_test.swift +++ b/test/ModuleInterface/lifetime_underscored_dependence_test.swift @@ -123,3 +123,45 @@ import lifetime_underscored_dependence // CHECK: } // CHECK: #endif // CHECK: } + +// CHECK: #if compiler(>=5.3) && $ClosureLifetimes +// CHECK-NEXT: #if compiler(>=5.3) && $Lifetimes +// CHECK-NEXT: @_lifetime(copy ne0) +// CHECK-NEXT: @inlinable public func takeCopier(f: @_lifetime(io: copy io) @_lifetime(copy inview) (_ inview: consuming lifetime_underscored_dependence.AnotherView, _ io: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView, ne0: consuming lifetime_underscored_dependence.AnotherView, ne1: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView { +// CHECK-NEXT: let ne2 = f(ne0, &ne1) +// CHECK-NEXT: return ne2 +// CHECK-NEXT: } +// CHECK-NEXT: #else +// CHECK-NEXT: @lifetime(copy ne0) +// CHECK-NEXT: @inlinable public func takeCopier(f: @_lifetime(io: copy io) @_lifetime(copy inview) (_ inview: consuming lifetime_underscored_dependence.AnotherView, _ io: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView, ne0: consuming lifetime_underscored_dependence.AnotherView, ne1: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView { +// CHECK-NEXT: let ne2 = f(ne0, &ne1) +// CHECK-NEXT: return ne2 +// CHECK-NEXT: } +// CHECK-NEXT: #endif +// CHECK-NEXT: #endif + +// CHECK: @inlinable public func takeCopierUnannotated(f: (consuming lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView) {} + +// CHECK: #if compiler(>=5.3) && $ClosureLifetimes +// CHECK-NEXT: public typealias ExplicitNestedType = @_lifetime(copy ne0) @_lifetime(ne1: copy ne0) ((lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView, _ ne0: consuming lifetime_underscored_dependence.AnotherView, _ ne1: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView +// CHECK-NEXT: #endif + +// CHECK: #if compiler(>=5.3) && $ClosureLifetimes +// CHECK-NEXT: @inlinable public func takeExplicitNestedType(f: @_lifetime(copy ne0) @_lifetime(ne1: copy ne0) ((lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView, _ ne0: consuming lifetime_underscored_dependence.AnotherView, _ ne1: inout lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView) {} +// CHECK-NEXT: #endif + +// CHECK: #if compiler(>=5.3) && $Lifetimes +// CHECK-NEXT: @inlinable public func returnableCopier(_ aView: lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView { +// CHECK-NEXT: return aView +// CHECK-NEXT: } +// CHECK-NEXT: #else +// CHECK-NEXT: @inlinable public func returnableCopier(_ aView: lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView { +// CHECK-NEXT: return aView +// CHECK-NEXT: } +// CHECK-NEXT: #endif + +// CHECK: #if compiler(>=5.3) && $ClosureLifetimes +// CHECK-NEXT: @inlinable public func returnCopier() -> @_lifetime(copy a) (_ a: lifetime_underscored_dependence.AnotherView) -> lifetime_underscored_dependence.AnotherView { +// CHECK-NEXT: return returnableCopier +// CHECK-NEXT: } +// CHECK-NEXT: #endif diff --git a/test/SIL/explicit_lifetime_dependence_specifiers.swift b/test/SIL/explicit_lifetime_dependence_specifiers.swift index 2393028abf162..5dd906e119946 100644 --- a/test/SIL/explicit_lifetime_dependence_specifiers.swift +++ b/test/SIL/explicit_lifetime_dependence_specifiers.swift @@ -8,6 +8,46 @@ import Builtin +struct NE: ~Escapable {} + +// Function type lifetime dependencies are printed as they appeared in Swift. +// Internal labels are preserved when a function type has explicit lifetime +// dependence information, since these may be used to refer to the sources and +// targets of lifetimes. + +// CHECK-LABEL: typealias LabelledNE2NE = @_lifetime(copy ne) (_ ne: NE) -> NE +typealias LabelledNE2NE = @_lifetime(copy ne) (_ ne: NE) -> NE +// CHECK-LABEL: typealias InferredNE2NE = (NE) -> NE +typealias InferredNE2NE = (NE) -> NE +// CHECK-LABEL: typealias InferredLabelledNE2NE = (NE) -> NE +typealias InferredLabelledNE2NE = (_ ne: NE) -> NE + +// CHECK-LABEL: typealias NamedLifetimeType = @_lifetime(copy ne) (_ ne: NE, _ ne2: NE) -> NE +typealias NamedLifetimeType = @_lifetime(copy ne) (_ ne: NE, _ ne2: NE) -> NE +// CHECK-LABEL: typealias InferredLifetimeType = (NE, NE) -> NE +typealias InferredLifetimeType = (_ ne: NE, NE) -> NE +// CHECK-LABEL: typealias ImmortalLifetimeType = @_lifetime(immortal) (_ ne: NE, _ ne2: NE) -> NE +typealias ImmortalLifetimeType = @_lifetime(immortal) (_ ne: NE, _ ne2: NE) -> NE + +// CHECK-LABEL: typealias NoLifetimeType = (Int) -> Int +typealias NoLifetimeType = (_ x: Int) -> Int + +// CHECK: typealias MixedLifetimeType = @_lifetime(copy ne0) (_ ne0: NE, _ neio: inout NE) -> NE +typealias MixedLifetimeType = @_lifetime(copy ne0) (_ ne0: NE, _ neio: inout NE) -> NE +// CHECK: typealias NestedLifetimeType = @_lifetime(neo: copy ne0) (_ nei: inout NE, _ ne0: NE, _ neo: inout NE) -> (NE) -> NE +typealias NestedLifetimeType = @_lifetime(neo: copy ne0) (_ nei: inout NE, _ ne0: NE, _ neo: inout NE) -> (_ ne1: NE) -> NE + +// CHECK-LABEL: typealias NestedType = @_lifetime(copy ne2) @_lifetime(ne3: copy ne2) (@_lifetime(copy ne0) @_lifetime(ne1: copy ne1) (_ ne0: NE, _ ne1: inout NE) -> NE, _ ne2: consuming NE, _ ne3: inout NE) -> NE +typealias NestedType = + @_lifetime(copy ne2) @_lifetime(ne3: copy ne2) + (@_lifetime(copy ne0) @_lifetime(ne1: copy ne1) (_ ne0: NE, _ ne1: inout NE) -> NE, + _ ne2: consuming NE, _ ne3: inout NE) -> NE + +// CHECK-LABEL: func takeNestedType(f: @_lifetime(copy ne2) @_lifetime(ne3: copy ne2) (@_lifetime(copy ne0) @_lifetime(ne1: copy ne1) (_ ne0: NE, _ ne1: inout NE) -> NE, _ ne2: consuming NE, _ ne3: inout NE) -> NE) + +// CHECK-LABEL: sil hidden @$s39explicit_lifetime_dependence_specifiers14takeNestedType1fyAA2NEVA2E_AEztXE_AEnAEztXE_tF : $@convention(thin) (@guaranteed @noescape @callee_guaranteed (@guaranteed @noescape @callee_guaranteed (@guaranteed NE, @lifetime(copy 1) @inout NE) -> @lifetime(copy 0) @owned NE, @owned NE, @lifetime(copy 1) @inout NE) -> @lifetime(copy 1) @owned NE) -> () { +func takeNestedType(f: NestedType) {} + struct BufferView : ~Escapable { let ptr: UnsafeRawBufferPointer // CHECK-LABEL: sil hidden @$s39explicit_lifetime_dependence_specifiers10BufferViewVyACSWcfC : $@convention(method) (UnsafeRawBufferPointer, @thin BufferView.Type) -> @lifetime(borrow 0) @owned BufferView { diff --git a/test/SIL/implicit_lifetime_dependence.swift b/test/SIL/implicit_lifetime_dependence.swift index 9774c26df922e..1bcec61eef7f5 100644 --- a/test/SIL/implicit_lifetime_dependence.swift +++ b/test/SIL/implicit_lifetime_dependence.swift @@ -5,6 +5,16 @@ // REQUIRES: swift_feature_Lifetimes +struct NE: ~Escapable {} + +// CHECK-LABEL: typealias ImplicitNestedType = ((NE, inout NE) -> NE, consuming NE, inout NE) -> NE +typealias ImplicitNestedType = ((NE, inout NE) -> NE, consuming NE, inout NE) -> NE + +// CHECK-LABEL: func takeImplicitNestedType(f: ((NE, inout NE) -> NE, consuming NE, inout NE) -> NE) + +// CHECK-LABEL: sil hidden @$s28implicit_lifetime_dependence22takeImplicitNestedType1fyAA2NEVA2E_AEztXE_AEnAEztXE_tF : $@convention(thin) (@guaranteed @noescape @callee_guaranteed (@guaranteed @noescape @callee_guaranteed (@guaranteed NE, @lifetime(copy 1) @inout NE) -> @lifetime(copy 0) @owned NE, @owned NE, @lifetime(copy 2) @inout NE) -> @lifetime(copy 1) @owned NE) -> () { +func takeImplicitNestedType(f: ImplicitNestedType) {} + struct BufferView : ~Escapable { let ptr: UnsafeRawBufferPointer let c: Int @@ -228,3 +238,9 @@ public struct OuterNE: ~Escapable { self.inner1 = value } } + +// rdar://160894371 (Infer @_lifetime(param: copy param) for inout closure arguments) +// CHECK-LABEL: sil hidden @$s28implicit_lifetime_dependence20inoutClosureArgument1fyyAA2NEVzXE_tF : $@convention(thin) (@guaranteed @noescape @callee_guaranteed (@lifetime(copy 0) @inout NE) -> ()) -> () { +// CHECK-LABEL: } // end sil function '$s28implicit_lifetime_dependence20inoutClosureArgument1fyyAA2NEVzXE_tF' +func inoutClosureArgument(f: (inout NE) -> ()) { +} diff --git a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_param_fail.swift b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_param_fail.swift index dc12135660ae4..ad10c885ac7ee 100644 --- a/test/SILOptimizer/lifetime_dependence/lifetime_dependence_param_fail.swift +++ b/test/SILOptimizer/lifetime_dependence/lifetime_dependence_param_fail.swift @@ -103,3 +103,38 @@ func bvcons_capture_escapelet(bv: consuming BV) -> ()->Int { // expected-error * let closure = { bv.c } return closure } + +// Higher-Order Function Tests + +@_lifetime(other: copy bv) +func take_assign_inout_copy(bv: BV, other: inout BV, f: @_lifetime(other: copy bv) (_ bv: BV, _ other: inout BV) -> ()) { + f(bv, &other) // OK +} + +@_lifetime(other: borrow bv) +func take_assign_inout_borrow(bv: BV, other: inout BV, f: @_lifetime(other: borrow bv) (_ bv: BV, _ other: inout BV) -> ()) { + f(bv, &other) // OK +} + +@_lifetime(bv: copy bv) +@_lifetime(other: copy bv) +func take_bvmut_assign_inout(bv: inout BV, other: inout BV, + f: @_lifetime(bv: copy bv) @_lifetime(other: copy bv) + (_ bv: inout BV, _ other: inout BV) -> ()) { + f(&bv, &other) // OK +} + +struct NE2: ~Escapable {} + +@_lifetime(oNE: copy ne) +func succeed_transfer_ne(ne: consuming NE2, oNE: inout NE2, f: (NE2) -> NE2) { + oNE = f(ne) // OK +} + +func fail_smuggle_ne(oNE: inout NE2, f: (NE2) -> NE2) { + // expected-error @-1 {{lifetime-dependent variable 'oNE' escapes its scope}} + let ne = NE2() + // expected-note @-1 {{it depends on the lifetime of this parent value}} + oNE = f(ne) + // expected-note @+1 {{this use causes the lifetime-dependent value to escape}} +} diff --git a/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift b/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift index bac45aa706381..7405bc231d9e2 100644 --- a/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift +++ b/test/SILOptimizer/lifetime_dependence/verify_diagnostics.swift @@ -237,13 +237,6 @@ func testIndirectNonForwardedResult(arg1: GNE, arg2: GNE) -> GNE { forward(arg2) // expected-note {{this use causes the lifetime-dependent value to escape}} } -func testIndirectClosureResult(f: () -> GNE) -> GNE { - f() - // expected-error @-1{{lifetime-dependent variable '$return_value' escapes its scope}} - // expected-note @-3{{it depends on the lifetime of argument '$return_value'}} - // expected-note @-3{{this use causes the lifetime-dependent value to escape}} -} - // ============================================================================= // Coroutines // ============================================================================= diff --git a/test/Sema/lifetime_attr_nofeature.swift b/test/Sema/lifetime_attr_nofeature.swift index a86d46ec238b5..68a935e46f227 100644 --- a/test/Sema/lifetime_attr_nofeature.swift +++ b/test/Sema/lifetime_attr_nofeature.swift @@ -13,3 +13,8 @@ func f_inout_infer(a: inout MutableRawSpan) {} // DEFAULT OK func f_inout_no_infer(a: inout MutableRawSpan, b: RawSpan) {} // DEFAULT OK +typealias DeriveType = @_lifetime(copy ne) (_ ne: NE) -> NE // expected-error{{'@_lifetime' attribute is only valid when experimental feature Lifetimes is enabled}} + +typealias InoutInferType = (inout MutableRawSpan) -> Void // DEFAULT OK + +typealias InoutNoInferType = (inout MutableRawSpan, RawSpan) -> Void // DEFAULT OK diff --git a/test/Sema/lifetime_dependence_functype.swift b/test/Sema/lifetime_dependence_functype.swift index ee704ac9292ab..871af3d61b9c7 100644 --- a/test/Sema/lifetime_dependence_functype.swift +++ b/test/Sema/lifetime_dependence_functype.swift @@ -24,12 +24,17 @@ func applyAnnotatedTransfer(ne: NE, @_lifetime(0) transfer: (NE) -> NE) -> NE { } @_lifetime(copy ne) -func applyTransfer(ne: NE, transfer: (NE) -> NE) -> NE { +func applyCorrectlyAnnotatedTransfer(ne: NE, transfer: @_lifetime(copy ne) (_ ne: NE) -> NE) -> NE { // OK + transfer(ne) +} + +@_lifetime(copy ne) +func applyTransfer(ne: NE, transfer: (NE) -> NE) -> NE { // OK, copy 0 inferred transfer(ne) } func testTransfer(nc: consuming NC) { - let transferred = applyTransfer(ne: nc.ne, transfer: transfer) // expected-error{{does not conform to expected type 'Escapable'}} e/xpected-error{{cannot convert value of type '(NE) -> @_lifetime(copy 0) NE' to expected argument type '(NE) -> NE'}} + let transferred = applyTransfer(ne: nc.ne, transfer: transfer) // OK _ = consume nc _ = transfer(transferred) @@ -45,8 +50,185 @@ func applyBorrow(nc: borrowing NC, borrow: (borrowing NC) -> NE) -> NE { } func testBorrow(nc: consuming NC) { - let borrowed = applyBorrow(nc: nc, borrow: borrow) // expected-error{{does not conform to expected type 'Escapable'}} ex/pected-error{{cannot convert value of type '(borrowing NC) -> @_lifetime(borrow 0) NE' to expected argument type '(borrowing NC) -> NE}} + let borrowed = applyBorrow(nc: nc, borrow: borrow) // OK _ = consume nc _ = transfer(borrowed) } +// Tests adapted from lifetime_attr.swift for function types. +class Klass {} +typealias InvalidAttrOnNonExistingParamType = @_lifetime(copy nonexisting) (_ ne: NE) -> NE // expected-error{{invalid parameter name specified 'nonexisting'}} + +typealias InvalidAttrOnNonExistingSelfType = @_lifetime(copy self) (_ ne: NE) -> NE // expected-error{{invalid lifetime dependence specifier on non-existent self}} + +typealias InvalidAttrOnNonExistingParamIndexType = @_lifetime(2) (_ ne: NE) -> NE // expected-error{{invalid parameter index specified '2'}} + +typealias InvalidDuplicateLifetimeDependenceType = @_lifetime(copy ne, borrow ne) (_ ne: borrowing NE) -> NE // expected-error{{duplicate lifetime dependence specifier}} + +typealias InvalidDependenceConsumeKlassType = @_lifetime(borrow x) (_ x: consuming Klass) -> NE // expected-error{{invalid use of borrow dependence with consuming ownership}} + +typealias InvalidDependenceBorrowKlassType = @_lifetime(&x) (_ x: borrowing Klass) -> NE // expected-error{{invalid use of & dependence with borrowing ownership}} + // expected-note @-1{{use '@_lifetime(borrow x)' instead}} + +typealias InvalidDependenceInoutKlassType = @_lifetime(borrow x) (_ x: inout Klass) -> NE // expected-error{{invalid use of borrow dependence with inout ownership}} + // expected-note @-1{{use '@_lifetime(&x)' instead}} + +typealias InvalidDependenceConsumeIntType = @_lifetime(borrow x) (_ x: consuming Int) -> NE // OK + +typealias InvalidDependenceBorrowIntType = @_lifetime(&x) (_ x: borrowing Int) -> NE // expected-error{{invalid use of & dependence with borrowing ownership}} + // expected-note @-1{{use '@_lifetime(borrow x)' instead}} + +typealias InvalidDependenceInoutIntType = @_lifetime(borrow x) (_ x: inout Int) -> NE // expected-error{{invalid use of borrow dependence with inout ownership}} + // expected-note @-1{{use '@_lifetime(&x)' instead}} + +typealias InvalidTargetType = + @_lifetime(result: copy source1) + @_lifetime(result: copy source2) // expected-error{{invalid duplicate target lifetime dependencies on function}} + (_ result: inout NE, _ source1: consuming NE, _ source2: consuming NE) -> () + +typealias InvalidSourceType = + @_lifetime(result: copy source) + @_lifetime(result: borrow source) // expected-error{{invalid duplicate target lifetime dependencies on function}} + (_ result: inout NE, _ source: consuming NE) -> () + +typealias ImmortalConflictType = @_lifetime(immortal) (_ immortal: Int) -> NE // expected-error{{conflict between the parameter name and 'immortal' contextual keyword}} + +typealias TestParameterDepType = @_lifetime(span: borrow holder) (_ holder: AnyObject, _ span: Span) -> () // expected-error{{lifetime-dependent parameter 'span' must be 'inout'}} + +typealias InoutLifetimeDependenceType = @_lifetime(&ne) (_ ne: inout NE) -> NE + +typealias DependOnEscapableType1 = @_lifetime(copy k) (_ k: inout Klass) -> NE // expected-error{{cannot copy the lifetime of an Escapable type}} + // expected-note@-1{{use '@_lifetime(&k)' instead}} + +typealias DependOnEscapableType2 = @_lifetime(copy k) (_ k: borrowing Klass) -> NE // expected-error{{cannot copy the lifetime of an Escapable type}} + // expected-note@-1{{use '@_lifetime(borrow k)' instead}} + +typealias DependOnEscapableType3 = @_lifetime(copy k) (_ k: consuming Klass) -> NE // expected-error{{cannot copy the lifetime of an Escapable type}} + // expected-note@-1{{use '@_lifetime(borrow k)' instead}} + +typealias GetIntType1 = @_lifetime(inValue) (_ inValue: Int) -> Int // expected-error{{invalid lifetime dependence on an Escapable result}} + +typealias GetIntType2 = @_lifetime(outValue: borrow inValue) (_ outValue: inout Int, _ inValue: Int) -> () // expected-error{{invalid lifetime dependence on an Escapable target}} + +typealias GetGenericEscapableType = @_lifetime(inValue) (_ inValue: T) -> T // expected-error{{invalid lifetime dependence on an Escapable result}} + +typealias GetGenericEscapableType2 = + @_lifetime(outValue: borrow inValue) // expected-error{{invalid lifetime dependence on an Escapable target}} + (_ outValue: inout T, _ inValue: T) -> () + +typealias GetGenericNonEscapableType2 = + @_lifetime(borrow inValue) (_ inValue: borrowing T) -> T // OK + +typealias GetGenericCorrectType = + @_lifetime(outValue: borrow inValue) (_ outValue: inout T, _ inValue: borrowing T) -> () // OK + +@_lifetime(outValue: copy inValue) // OK +func getGeneric(_ outValue: inout T, _ inValue: borrowing T) { // expected-note{{in call to function 'getGeneric'}} + outValue = inValue +} + +@_lifetime(outValue: borrow inValue) // OK +func getGeneric2(_ outValue: inout T, _ /* borrowing inferred */ inValue: T) { + outValue = inValue +} + +@_lifetime(outValueI: immortal) // OK +func getImmortalNE(_ outValueI: inout NE, _ inValueI: borrowing NE) { + outValueI = NE() +} + +@_lifetime(o: borrow i) // OK +func takeGetGenericAndArgs(f: @_lifetime(outValue: borrow inValue) + (_ outValue: inout T, _ inValue: borrowing T) -> (), o: inout T, i: T) { + f(&o, i) +} + +do { + let x = NE() + var y = NE() + takeGetGenericAndArgs(f: getGeneric, o: &y, i: x) + // expected-error@-1{{cannot convert value of type '(inout T, borrowing T) -> ()' to expected argument type '@_lifetime(outValue: borrow inValue) (_ outValue: inout NE, _ inValue: borrowing NE) -> ()'}} + // expected-error@-2{{generic parameter 'T' could not be inferred}} +} +do { + let x = NE() + var y = NE() + takeGetGenericAndArgs(f: getGeneric2, o: &y, i: x) // OK +} +do { + let x = NE() + var y = NE() + takeGetGenericAndArgs(f: getImmortalNE, o: &y, i: x) + // expected-error@-1{{cannot convert value of type '@_lifetime(0: immortal) (inout NE, borrowing NE) -> ()' to expected argument type '@_lifetime(outValue: borrow inValue) (_ outValue: inout NE, _ inValue: borrowing NE) -> ()'}} +} +do { + let _ = transfer // OK + let _: (NE) -> NE = transfer // OK + let _: @_lifetime(copy ne) (_ ne: NE) -> NE = transfer // OK +} + +// rdar://166912068 (Incorrect error when passing a local function with a non-escapable parameter) +struct NEWithSpan: ~Escapable { + var span: RawSpan + + @_lifetime(copy span) + init(span: RawSpan) { + self.span = span + } +} +func takeBody(body: (inout NEWithSpan) -> Void) {} +func checkNestedFunctions() { + func doIt(ne: inout NEWithSpan) {} + takeBody(body: doIt) +} + +// Bail-out cases where lifetime dependence checking cannot run. +struct CNE: ~Escapable { + let ne: T + @_lifetime(copy ne) + init(ne: T) { + self.ne = ne + } +} + +@_lifetime(borrow cne) +func copyCNE(cne: CNE) -> CNE { + return cne +} + +public let UnboundGenericParamFunctionType : (CNE) -> CNE = copyCNE // expected-error{{lifetime dependence checking failed due to unknown parameter type}} + // expected-error@-1{{value of type 'CNE' does not conform to specified type 'Escapable'}} +public let UnboundGenericParamFunctionTypeAnnotated : @_lifetime(borrow cne) (_ cne: CNE) -> CNE = copyCNE // expected-error{{lifetime dependence checking failed due to unknown parameter type}} + // expected-error@-1{{value of type 'CNE' does not conform to specified type 'Escapable'}} +public let UnboundGenericResultFunctionType : (CNE) -> CNE = copyCNE // expected-error{{lifetime dependence checking failed due to unknown result type}} + // expected-error@-1{{value of type 'CNE' does not conform to specified type 'Escapable'}} +public let UnboundGenericResultFunctionTypeAnnotated : @_lifetime(borrow cne) (_ cne: CNE) -> CNE = copyCNE // expected-error{{lifetime dependence checking failed due to unknown result type}} + // expected-error@-1{{value of type 'CNE' does not conform to specified type 'Escapable'}} + +public let TypeParameterParamFunctionType = copyCNE as (_) -> CNE // expected-error{{lifetime dependence checking failed due to unknown parameter type}} + // expected-error@-1{{failed to produce diagnostic for expression}} +public let TypeParameterParamFunctionTypeAnnotated = copyCNE as @_lifetime(borrow cne) (_ cne: _) -> CNE // expected-error{{lifetime dependence checking failed due to unknown parameter type}} + // expected-error@-1{{failed to produce diagnostic for expression}} +public let TypeParameterResultFunctionType = copyCNE as (CNE) -> _ // expected-error{{lifetime dependence checking failed due to unknown result type}} + // expected-error@-1{{failed to produce diagnostic for expression}} +public let TypeParameterResultFunctionTypeAnnotated = copyCNE as @_lifetime(borrow cne) (_ cne: CNE) -> _ // expected-error{{lifetime dependence checking failed due to unknown result type}} + // expected-error@-1{{failed to produce diagnostic for expression}} + +// Closure Context Dependence Tests + +// This case should pass after we add support for dependencies on the closure context. +// +// We had to move it out of +// SILOptimizer/lifetime_dependence/verify_diagnostics.swift because it causes +// an error during type checking with the current version of function type +// lifetime checking, preventing the SIL diagnostic checks that file tests from +// running. +// +// TODO: Add more diagnostic tests when implementing closure context +// dependencies, including SILOptimizer diagnostic tests such as the one this +// originated as. +func testIndirectClosureResult(f: () -> CNE) -> CNE { + // expected-error @-1{{a function with a ~Escapable result needs a parameter to depend on}} + // expected-note @-2{{'@_lifetime(immortal)' can be used to indicate that values produced by this initializer have no lifetime dependencies}} + f() +}