//===--- LifetimeDependence.cpp -----------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2024-2026 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #include "swift/AST/LifetimeDependence.h" #include "swift/AST/ASTContext.h" #include "swift/AST/ASTPrinter.h" #include "swift/AST/Builtins.h" #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/PackConformance.h" #include "swift/AST/ParameterList.h" #include "swift/AST/ProtocolConformance.h" #include "swift/AST/SourceFile.h" #include "swift/AST/Type.h" #include "swift/AST/TypeRepr.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/Defer.h" #include "swift/Basic/Range.h" #include "swift/Basic/SourceManager.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/MapVector.h" #define DEBUG_TYPE "LifetimeDependence" 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: { bool shouldEscape = escapeIdentifierInContext(getName(), PrintNameContext::Normal); if (shouldEscape) { return ("`" + getName().str() + "`").str(); } return getName().str().str(); } case DescriptorKind::Ordered: return std::to_string(getIndex()); case DescriptorKind::Self: return "self"; } llvm_unreachable("Invalid DescriptorKind"); } LifetimeEntry * LifetimeEntry::create(const ASTContext &ctx, SourceLoc startLoc, SourceLoc endLoc, ArrayRef sources, std::optional targetDescriptor) { unsigned size = totalSizeToAlloc(sources.size()); void *mem = ctx.Allocate(size, alignof(LifetimeEntry)); return new (mem) LifetimeEntry(startLoc, endLoc, sources, targetDescriptor); } std::string LifetimeEntry::getString() const { std::string result = "("; if (targetDescriptor.has_value()) { result += targetDescriptor->getString(); result += ": "; } bool firstElem = true; for (auto source : getSources()) { if (!firstElem) { result += ", "; } auto lifetimeKind = source.getParsedLifetimeDependenceKind(); auto kindString = getNameForParsedLifetimeDependenceKind(lifetimeKind); bool printSpace = (lifetimeKind == ParsedLifetimeDependenceKind::Borrow || lifetimeKind == ParsedLifetimeDependenceKind::Inherit); if (!kindString.empty()) { result += kindString; } if (printSpace) { result += " "; } result += source.getString(); firstElem = false; } result += ")"; return result; } namespace swift { std::optional getLifetimeDependenceFor(ArrayRef lifetimeDependencies, unsigned index) { for (auto dep : lifetimeDependencies) { if (dep.getTargetIndex() == index) { return dep; } } return std::nullopt; } bool filterEscapableLifetimeDependencies(GenericSignature sig, ArrayRef inputs, SmallVectorImpl &outputs, llvm::function_ref getSubstTargetType) { bool didRemoveLifetimeDependencies = false; for (auto &depInfo : inputs) { auto targetIndex = depInfo.getTargetIndex(); Type substTy = getSubstTargetType(targetIndex); // If the type still contains type variables we don't know whether we // can drop the dependency. if (substTy->hasTypeVariable()) continue; // Drop the dependency if the target type is Escapable. if (sig || !substTy->hasTypeParameter()) { if (substTy->isEscapable(sig)) { didRemoveLifetimeDependencies = true; continue; } } // Otherwise, keep the dependency. outputs.push_back(depInfo); } return didRemoveLifetimeDependencies; } StringRef getNameForParsedLifetimeDependenceKind(ParsedLifetimeDependenceKind kind) { switch (kind) { case ParsedLifetimeDependenceKind::Borrow: return "borrow"; case ParsedLifetimeDependenceKind::Inherit: return "copy"; case ParsedLifetimeDependenceKind::Inout: return "&"; default: return ""; } } } // namespace swift void LifetimeDependenceInfo::Profile(llvm::FoldingSetNodeID &ID) const { ID.AddBoolean(hasImmortalSpecifier()); ID.AddBoolean(isFromAnnotation()); ID.AddBoolean(hasCaptures()); ID.AddInteger(targetIndex); if (inheritLifetimeParamIndices) { ID.AddInteger((uint8_t)LifetimeDependenceKind::Inherit); inheritLifetimeParamIndices->Profile(ID); } if (scopeLifetimeParamIndices) { ID.AddInteger((uint8_t)LifetimeDependenceKind::Scope); scopeLifetimeParamIndices->Profile(ID); } if (hasAddressableParamIndices()) { ID.AddBoolean(true); getAddressableIndices()->Profile(ID); } else { ID.AddBoolean(false); } } void LifetimeDependenceInfo::getConcatenatedData( SmallVectorImpl &concatenatedData) const { auto pushData = [&](IndexSubset *paramIndices) { if (paramIndices == nullptr) { return; } assert(!paramIndices->isEmpty()); for (unsigned i = 0; i < paramIndices->getCapacity(); i++) { if (paramIndices->contains(i)) { concatenatedData.push_back(true); continue; } concatenatedData.push_back(false); } }; if (hasInheritLifetimeParamIndices()) { pushData(inheritLifetimeParamIndices); } if (hasScopeLifetimeParamIndices()) { pushData(scopeLifetimeParamIndices); } if (hasAddressableParamIndices()) { pushData(getAddressableIndices()); } } namespace { static bool isBitwiseCopyable(Type type, ASTContext &ctx) { auto *bitwiseCopyableProtocol = ctx.getProtocol(KnownProtocolKind::BitwiseCopyable); if (!bitwiseCopyableProtocol) { return false; } if (type->hasError()) return false; return (bool)checkConformance(type, bitwiseCopyableProtocol); } static bool isDiagnosedNonEscapable(Type type) { if (type->hasError()) { return false; } // FIXME: This check is temporary until rdar://139976667 is fixed. // ModuleType created with ModuleType::get methods are ~Copyable and // ~Escapable because the Copyable and Escapable conformance is not added to // them by default. if (type->is()) { return false; } return !type->isEscapable(); } static bool isDiagnosedEscapable(Type type) { if (type->hasError()) { return false; } return type->isEscapable(); } } // anonymous namespace namespace { enum class HasAnnotation { Annotated, Inferred }; enum class TargetKind { Inout, Result }; // Temporary data structure for building target dependencies. Used by the // LifetimeDependenceChecker. struct LifetimeDependenceBuilder { struct TargetDeps { SmallBitVector inheritIndices; SmallBitVector scopeIndices; TargetKind targetKind; LifetimeFlags flags; TargetDeps(HasAnnotation hasAnnotation, TargetKind targetKind, unsigned capacity) : inheritIndices(capacity), scopeIndices(capacity), targetKind(targetKind), flags(LifetimeFlags().withAnnotated(hasAnnotation == HasAnnotation::Annotated)) {} bool empty() const { return !(flags.hasImmortalSpecifier() || flags.hasCaptures() || inheritIndices.any() || scopeIndices.any()); } bool hasAnnotation() const { return flags.isFromAnnotation(); } bool isInout() const { return targetKind == TargetKind::Inout; } void addIfNew(unsigned sourceIndex, LifetimeDependenceKind kind) { // Some inferrence rules may attempt to add an inherit dependency after a // scope dependency (accessor wrapper + getter method). if (flags.hasImmortalSpecifier() || inheritIndices[sourceIndex] || scopeIndices[sourceIndex]) { return; } switch (kind) { case LifetimeDependenceKind::Inherit: inheritIndices.set(sourceIndex); break; case LifetimeDependenceKind::Scope: scopeIndices.set(sourceIndex); break; } } }; const unsigned resultIndex; llvm::SmallMapVector depsArray; LifetimeDependenceBuilder(unsigned resultIndex): resultIndex(resultIndex) {} public: // True if the builder is uninitialized. This may, however, be false even if // all TargetDeps are themselves empty. bool empty() const { return depsArray.empty(); } unsigned sourceIndexCap() const { return resultIndex; } TargetKind targetKindForIndex(unsigned targetIndex) const { return (targetIndex == resultIndex) ? TargetKind::Result : TargetKind::Inout; } // Return TargetDeps for 'targetIndex' if it has at least one source // dependency. const TargetDeps *getTargetDepsOrNull(unsigned targetIndex) const { auto iter = depsArray.find(targetIndex); if (iter != depsArray.end() && !iter->second.empty()) { return &iter->second; } return nullptr; } bool hasTargetDeps(unsigned targetIndex) const { return getTargetDepsOrNull(targetIndex) != nullptr; } TargetDeps *createAnnotatedTargetDeps(unsigned targetIndex) { auto iterAndInserted = depsArray.try_emplace(targetIndex, HasAnnotation::Annotated, targetKindForIndex(targetIndex), sourceIndexCap()); if (!iterAndInserted.second) return nullptr; return &iterAndInserted.first->second; } // Check this before diagnosing any broken inference to avoid diagnosing a // target that has an explicit annotation. TargetDeps *getInferredTargetDeps(unsigned targetIndex) { auto iter = depsArray.try_emplace(targetIndex, HasAnnotation::Inferred, targetKindForIndex(targetIndex), sourceIndexCap()).first; auto &deps = iter->second; return deps.hasAnnotation() ? nullptr : &deps; } void inferDependency(unsigned targetIndex, unsigned sourceIndex, LifetimeDependenceKind kind) { auto targetDeps = getInferredTargetDeps(targetIndex); if (!targetDeps) return; targetDeps->addIfNew(sourceIndex, kind); } void inferInoutDependency(unsigned paramIndex) { auto iter = depsArray.try_emplace(paramIndex, HasAnnotation::Inferred, TargetKind::Inout, sourceIndexCap()).first; // An immortal specifier erases any inferred inout dependency; // other annotations do not. if (!iter->second.flags.hasImmortalSpecifier()) { iter->second.addIfNew(paramIndex, LifetimeDependenceKind::Inherit); } } void inferImmortalResult() { auto targetDeps = getInferredTargetDeps(resultIndex); if (!targetDeps) return; targetDeps->flags.setImmortalSpecifier(true); } // Allocate LifetimeDependenceInfo in the ASTContext. Initialize it by // copying heap-allocated TargetDeps fields into ASTContext allocations // (e.g. convert SmallBitVector to IndexSubset). std::optional> initializeDependenceInfoArray(ASTContext &ctx) const { if (depsArray.empty()) { return std::nullopt; } // Inference might attempt to infer a target, but fail leaving the source // indices empty. SmallVector lifetimeDependencies; for (auto &idxAndDeps : depsArray) { unsigned targetIndex = idxAndDeps.first; auto &deps = idxAndDeps.second; if (deps.empty()) continue; IndexSubset *inheritIndices = nullptr; if (deps.inheritIndices.any()) { inheritIndices = IndexSubset::get(ctx, deps.inheritIndices); ASSERT( !deps.flags.hasImmortalSpecifier() || deps.isInout() && "cannot combine immortal lifetime with parameter dependency"); } IndexSubset *scopeIndices = nullptr; if (deps.scopeIndices.any()) { scopeIndices = IndexSubset::get(ctx, deps.scopeIndices); ASSERT( !deps.flags.hasImmortalSpecifier() || deps.isInout() && "cannot combine immortal lifetime with parameter dependency"); } lifetimeDependencies.push_back(LifetimeDependenceInfo( /*inheritLifetimeParamIndices*/ inheritIndices, /*scopeLifetimeParamIndices*/ scopeIndices, targetIndex, deps.flags)); } if (lifetimeDependencies.empty()) { return std::nullopt; } return ctx.AllocateCopy(lifetimeDependencies); } }; /// Diagnostics for ~Escpable types in function signatures. This lowers /// @_lifetime attributes to the SILFunction's lifetime dependencies and /// implements the lifetime inferrence rules. class LifetimeDependenceChecker { using TargetDeps = LifetimeDependenceBuilder::TargetDeps; using Param = AnyFunctionType::Param; SmallVector lifetimeEntries; struct ParamInfo { Param param; unsigned index; SourceLoc loc; Type typeInContext; Type getInterfaceType() const { return param.getPlainType(); } StringRef name() const { return param.getInternalLabel().str(); } }; SmallVector parameterInfos; /// The AbstractFunctionDecl, if one is being checked. Otherwise nullptr. AbstractFunctionDecl *_Nullable afd; ASTContext &ctx; // The source file the function being checked was declared in, if present. SourceFile const *_Nullable sourceFile; ProtocolDecl *escapableDecl; GenericEnvironment *_Nullable genericEnv; // 'resultIndex' is a pseudo-parameter-index used by LifetimeDependenceInfo to // represent the function result. const unsigned resultIndex; // The result or yield type of the function being checked in its generic // environment. Type resultTy; SourceLoc returnLoc; // A parameter corresponding to the implicit self declaration of // the function, if it has one. Otherwise, std::nullopt. std::optional implicitSelfParamInfo; LifetimeDependenceBuilder depBuilder; bool const isImplicit; bool const isInit; bool const hasUnsafeNonEscapableResult; // True if lifetime diganostics have already been performed. Avoids redundant // diagnostics, and allows bypassing diagnostics for special cases. bool performedDiagnostics = false; public: static unsigned getResultIndex(AbstractFunctionDecl *afd) { return afd->isInstanceMethod() ? (unsigned)(afd->getParameters()->size() + 1) : (unsigned)afd->getParameters()->size(); } static unsigned getResultIndex(EnumElementDecl *eed) { auto *paramList = eed->getParameterList(); return paramList ? (unsigned)(paramList->size() + 1) : 1; } static Type getResultOrYieldInterface(DeclContext *functionDC) { if (auto *accessor = dyn_cast(functionDC); accessor && accessor->isCoroutine()) { return accessor->getStorage()->getValueInterfaceType(); } if (auto fn = dyn_cast(functionDC)) { return fn->getResultInterfaceType(); } auto ctor = cast(functionDC); return ctor->getResultInterfaceType(); } static SourceLoc getReturnLoc(AbstractFunctionDecl *afd) { auto resultTypeRepr = afd->getResultTypeRepr(); return resultTypeRepr ? resultTypeRepr->getLoc() : afd->getLoc(); } static std::optional getSelfParamInfo(AbstractFunctionDecl *afd) { if (!afd->isInstanceMethod()) return std::nullopt; auto *selfDecl = afd->getImplicitSelfDecl(); if (!selfDecl) return std::nullopt; Type selfInterfaceType = selfDecl->toFunctionParam().getPlainType(); unsigned selfIndex = afd->getParameters()->size(); return ParamInfo{selfDecl->toFunctionParam(), selfIndex, selfDecl->getLoc(), afd->mapTypeIntoEnvironment(selfInterfaceType)}; } const ParamInfo &getParamForIndex(unsigned paramIndex) { if (implicitSelfParamInfo && paramIndex == implicitSelfParamInfo->index) return *implicitSelfParamInfo; assert(paramIndex < parameterInfos.size() && "unexpected result index"); return parameterInfos[paramIndex]; } Type getEnvTypeForIndex(unsigned paramOrResultIndex) { if (paramOrResultIndex == resultIndex) return resultTy; if (implicitSelfParamInfo && paramOrResultIndex == implicitSelfParamInfo->index) return implicitSelfParamInfo->typeInContext; return parameterInfos[paramOrResultIndex].typeInContext; } private: static auto collectDeclLifetimeEntries(DeclAttributes const &attrs) { decltype(lifetimeEntries) lifetimeEntries; for (auto attr : attrs.getAttributes()) { lifetimeEntries.push_back(attr->getLifetimeEntry()); } return lifetimeEntries; } static auto collectFunctionTypeLifetimeEntries( ArrayRef lifetimeAttrs) { decltype(lifetimeEntries) lifetimeEntries; for (auto *attr : lifetimeAttrs) { lifetimeEntries.push_back(attr->getLifetimeEntry()); } return lifetimeEntries; } static auto collectDeclParameterInfo(ParameterList const *params, DeclContext *DC) { decltype(parameterInfos) parameterInfos; for (auto [index, param] : enumerate(*params)) { parameterInfos.push_back( {param->toFunctionParam(), (unsigned)index, param->getLoc(), DC->mapTypeIntoEnvironment(param->getInterfaceType())}); } return parameterInfos; } static auto collectFunctionTypeParameterInfo(FunctionTypeRepr *funcRepr, AnyFunctionType *funcType, GenericEnvironment *env) { decltype(parameterInfos) parameterInfos; // We only ever use the second names of function type parameters for // lifetimes. ArrayRef params = funcType->getParams(); ArrayRef argReprs = funcRepr->getArgsTypeRepr()->getElements(); assert(params.size() == argReprs.size()); for (auto [index, param] : enumerate(params)) { auto const &arg = argReprs[index]; parameterInfos.push_back( {param, (unsigned)index, // If an argument has no second name, use the location of its type. arg.SecondNameLoc.isValid() ? arg.SecondNameLoc : arg.Type->getLoc(), GenericEnvironment::mapTypeIntoEnvironment( env, param.getPlainType())}); } return parameterInfos; } public: LifetimeDependenceChecker(AbstractFunctionDecl *afd) : lifetimeEntries(collectDeclLifetimeEntries(afd->getAttrs())), parameterInfos(collectDeclParameterInfo(afd->getParameters(), afd)), afd(afd), ctx(afd->getDeclContext()->getASTContext()), sourceFile(afd->getParentSourceFile()), escapableDecl(ctx.getProtocol( swift::getKnownProtocolKind(InvertibleProtocolKind::Escapable))), genericEnv(afd->getGenericEnvironment()), resultIndex(getResultIndex(afd)), resultTy(afd->mapTypeIntoEnvironment(getResultOrYieldInterface(afd))), returnLoc(getReturnLoc(afd)), implicitSelfParamInfo(getSelfParamInfo(afd)), depBuilder(resultIndex), isImplicit(afd->isImplicit()), isInit(isa(afd)), hasUnsafeNonEscapableResult( afd->getAttrs().hasAttribute()) {} LifetimeDependenceChecker(FunctionTypeRepr *funcRepr, AnyFunctionType *funcType, ArrayRef lifetimeAttrs, DeclContext *dc, GenericEnvironment *env) : lifetimeEntries(collectFunctionTypeLifetimeEntries(lifetimeAttrs)), parameterInfos( collectFunctionTypeParameterInfo(funcRepr, funcType, env)), afd(nullptr), ctx(funcType->getASTContext()), sourceFile(dc->getParentSourceFile()), escapableDecl(ctx.getProtocol( swift::getKnownProtocolKind(InvertibleProtocolKind::Escapable))), genericEnv(env), resultIndex(funcType->getParams().size()), resultTy( GenericEnvironment::mapTypeIntoEnvironment(env, funcType->getResult())), returnLoc(funcRepr->getResultTypeRepr()->getLoc()), implicitSelfParamInfo(std::nullopt), depBuilder(resultIndex), isImplicit(false), isInit(false), hasUnsafeNonEscapableResult(false) {} 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(isLifetimeForDecl() && (isa(afd) || isa(afd))); assert(depBuilder.empty()); // Handle Builtins first because, even though Builtins require // LifetimeDependence, we don't force the experimental feature // to be enabled when importing the Builtin module. if (afd->isImplicit() && afd->getModuleContext()->isBuiltinModule()) { inferBuiltin(); return currentDependencies(); } return checkCommon(); } std::optional> checkCommon() { if (!ctx.LangOpts.hasFeature(Feature::LifetimeDependence) && !ctx.LangOpts.hasFeature(Feature::Lifetimes) && !ctx.SourceMgr.isImportMacroGeneratedLoc(returnLoc)) { // Infer inout dependencies without requiring a feature flag. On // returning, 'depBuilder' contains any inferred dependencies. This does // not issue any diagnostics because using unsupported lifetime features // may generate a different diagnostic when the feature flag is disabled. inferMutatingSelf(); inferInoutParams(); diagnoseMissingResultDependencies( diag::lifetime_dependence_feature_required_return.ID); diagnoseMissingSelfDependencies( diag::lifetime_dependence_feature_required_mutating.ID); diagnoseMissingInoutDependencies( diag::lifetime_dependence_feature_required_inout.ID); return currentDependencies(); } if (!lifetimeEntries.empty()) { initializeAttributeDeps(); if (performedDiagnostics) return std::nullopt; } // Methods or functions with @_unsafeNonescapableResult do not require // lifetime annotation and do not infer any lifetime dependency. if (hasUnsafeNonEscapableResult) { return currentDependencies(); } inferOrDiagnose(); // If precise diagnostics were already issued, bypass // diagnoseMissingDependencies to avoid redundant diagnostics. if (!performedDiagnostics) { diagnoseMissingResultDependencies( diag::lifetime_dependence_cannot_infer_return.ID); diagnoseMissingSelfDependencies( diag::lifetime_dependence_cannot_infer_mutating.ID); diagnoseMissingInoutDependencies( diag::lifetime_dependence_cannot_infer_inout.ID); } return currentDependencies(); } static std::optional> checkEnumElementDecl(EnumElementDecl *eed) { auto const resultIndex = getResultIndex(eed); LifetimeDependenceBuilder depBuilder(resultIndex); auto *parentEnum = eed->getParentEnum(); auto enumType = parentEnum->mapTypeIntoEnvironment( parentEnum->getDeclaredInterfaceType()); // Add early bailout for imported enums. if (parentEnum->hasClangNode()) { return std::nullopt; } // Escapable enum, bailout. if (!isDiagnosedNonEscapable(enumType)) { return std::nullopt; } auto *params = eed->getParameterList(); // No payload, bailout. if (!params) { return std::nullopt; } TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); ASSERT(resultDeps && "enum declaration has a lifetime attribute"); // Add all indices of ~Escapable parameters as lifetime dependence sources. for (size_t i = 0; i < params->size(); i++) { auto paramType = params->get(i)->getTypeInContext(); if (!isDiagnosedNonEscapable(paramType)) { continue; } resultDeps->inheritIndices.set(i); } return depBuilder.initializeDependenceInfoArray(eed->getASTContext()); } protected: template InFlightDiagnostic diagnose( SourceLoc Loc, Diag ID, typename detail::PassArgument::type... Args) { performedDiagnostics = true; return ctx.Diags.diagnose(Loc, ID, std::move(Args)...); } // Is this lifetime information for an abstact function declaration (function, // constructor, or destructor) as opposed to a function type? bool isLifetimeForDecl() const { return afd != nullptr; } // For initializers, the implicit self parameter is ignored and instead shows // up as the result type. // // Note: Do not use this to reserve the self parameter index. // LifetimeDependenceInfo always reserves an extra formal parameter // index for hasImplicitSelfDecl(), even for initializers. During function // type lowering, it is mapped to the metatype parameter. Without reserving // the extra formal self parameter, a dependency targeting the formal result // index would incorrectly target the SIL metatype parameter. bool hasImplicitSelfParam() const { return !isInit && implicitSelfParamInfo.has_value(); } // In SIL, implicit initializers and accessors become explicit. bool isImplicitOrSIL() const { if (isImplicit) { return true; } // TODO: remove this check once SIL prints @lifetime. if (sourceFile) { // The AST printer makes implicit initializers explicit, but does not // print the @lifetime annotations. Until that is fixed, avoid // diagnosing this as an error. if (sourceFile->Kind == SourceFileKind::SIL) { return true; } } return false; } bool isInterfaceFile() const { // TODO: remove this check once all compilers that are rev-locked to the // stdlib print the 'copy' dependence kind in the interface (Aug '25) if (sourceFile && sourceFile->Kind == SourceFileKind::Interface) { return true; } return false; } // Infer ambiguous cases for backward compatibility. bool useLazyInference() const { return isInterfaceFile() || ctx.LangOpts.EnableExperimentalLifetimeDependenceInference; } // ========================================================================== // MARK: Catch-all diagnostics for missing attributes and inferrence rules. // ========================================================================== std::string diagnosticQualifier() const { if (isImplicit) { if (isInit) { return "an implicit initializer"; } if (auto *ad = dyn_cast_or_null(afd)) { std::string qualifier = "the '"; qualifier += accessorKindName(ad->getAccessorKind()); qualifier += "' accessor"; return qualifier; } } if (implicitSelfParamInfo.has_value()) { if (isInit) { return "an initializer"; } if (implicitSelfParamInfo->param.isInOut()) { return "a mutating method"; } return "a method"; } return "a function"; } // Ensure that dependencies exist for any return value or inout parameter that // needs one. Always runs before the checker completes if no other diagnostics // were issued. void diagnoseMissingResultDependencies(DiagID diagID) { if (!isDiagnosedNonEscapable(resultTy)) { return; } if (!depBuilder.hasTargetDeps(resultIndex)) { ctx.Diags.diagnose(returnLoc, diagID, {StringRef(diagnosticQualifier())}); } } // Ensure that dependencies exist for any mutating self value. Always runs // before the checker completes if no other diagnostics were issued. For // initializers, the inout self parameter is actually considered the result // type so is not handled here. void diagnoseMissingSelfDependencies(DiagID diagID) { if (!hasImplicitSelfParam()) { return; } if (!implicitSelfParamInfo->param.isInOut()) { return; } if (!isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext)) { return; } if (!depBuilder.hasTargetDeps(implicitSelfParamInfo->index)) { ctx.Diags.diagnose(implicitSelfParamInfo->loc, diagID, {StringRef(diagnosticQualifier())}); } } void diagnoseMissingInoutDependencies(DiagID diagID) { unsigned paramIndex = 0; for (auto ¶mInfo : parameterInfos) { SWIFT_DEFER { paramIndex++; }; if (!paramInfo.param.isInOut()) { continue; } if (!isDiagnosedNonEscapable(paramInfo.typeInContext)) { continue; } if (!depBuilder.hasTargetDeps(paramIndex)) { ctx.Diags.diagnose(paramInfo.loc, diagID, {StringRef(diagnosticQualifier()), paramInfo.name()}); if (diagID == diag::lifetime_dependence_cannot_infer_inout.ID) { ctx.Diags.diagnose( paramInfo.loc, diag::lifetime_dependence_cannot_infer_inout_suggest, paramInfo.name()); } } } } // ========================================================================== // MARK: attribute parsing and inference helpers // ========================================================================== // Attribute parsing helper. bool isCompatibleWithOwnership(ParsedLifetimeDependenceKind kind, Type paramType, ValueOwnership loweredOwnership, bool isInterfaceFile = false) const { if (kind == ParsedLifetimeDependenceKind::Inherit) { return true; } if (kind == ParsedLifetimeDependenceKind::Borrow) { // An owned/consumed BitwiseCopyable value can be effectively borrowed // because its lifetime can be indefinitely extended. if (loweredOwnership == ValueOwnership::Owned && isBitwiseCopyable(paramType, ctx)) { return true; } if (isInterfaceFile) { return loweredOwnership == ValueOwnership::Shared || loweredOwnership == ValueOwnership::InOut; } return loweredOwnership == ValueOwnership::Shared; } assert(kind == ParsedLifetimeDependenceKind::Inout); return loweredOwnership == ValueOwnership::InOut; } // Inferrence helper. bool isCompatibleWithOwnership(LifetimeDependenceKind kind, ParamInfo const ¶mInfo) const { if (kind == LifetimeDependenceKind::Inherit) { return true; } 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)) { return true; } assert(kind == LifetimeDependenceKind::Scope); return loweredOwnership == ValueOwnership::Shared || loweredOwnership == ValueOwnership::InOut; } // ========================================================================== // MARK: Same-type inference // ========================================================================== /// Is 'sourceEnvType' Escapable under any of the conformance requirements in /// 'targetReqs'? /// /// If true, we will infer a default dependency because a lifetime requirement /// in the source is always present in the target. The source may have /// additional lifetime requirements which are not copied to the /// target. Conversely, the target may depend on multiple sources. /// /// Example: /// /// struct NE1: ~Escapable {} /// struct NE2: ~Escapable {} /// func foo(arg: NE1?) -> NE1 // DEFAULT: @_lifetime(copy arg) /// func foo(arg: NE1?) -> NE2 // ERROR: missing annotation /// /// Invariant: hasSameTypeRequirement can only return true when /// hasGuaranteedLifetime is also true. /// bool hasSameTypeRequirement(Type sourceEnvType, const llvm::SmallDenseSet &targetReqs) { LLVM_DEBUG(llvm::dbgs() << "\nSource Type: " << sourceEnvType << "\n"); SmallVector sourceReqs; if (!collectRequiredTypesForNonEscapable(sourceEnvType, sourceReqs)) { return false; } if (sourceReqs.empty()) { // The source is unconditionally Escapable. return false; } LLVM_DEBUG(llvm::dbgs() << "\nSource reqs:\n"; for (auto sourceReq : sourceReqs) { sourceReq.dump(llvm::dbgs()); }); return llvm::any_of(sourceReqs, [&](Type sourceReq) { return targetReqs.contains(sourceReq); }); } bool collectRequiredTypesForNonEscapable(Type envType, SmallVectorImpl &inverseReqs) { if (envType->hasError()) { LLVM_DEBUG( llvm::dbgs() << "Error Type: " << envType << "\n"); return false; } auto confRef = lookupConformance(envType, escapableDecl); if (confRef.isInvalid()) { LLVM_DEBUG( llvm::dbgs() << "Outer non-Escapable Type: " << envType << "\n"); inverseReqs.push_back(envType); return true; } return collectRequiredTypesRecursively(confRef, inverseReqs); } bool collectRequiredTypesRecursively(ProtocolConformanceRef confRef, SmallVectorImpl &inverseReqs) { LLVM_DEBUG(llvm::dbgs() << "Collect for conformance:\n"; confRef.print(llvm::dbgs()); llvm::dbgs() << "\n"); if (confRef.isAbstract()) { // Abstract conformances unconditionally conform. return true; } if (confRef.isPack()) { // Parameters packs cannot yet suppress Escapable, so bailout. return false; /* TODO: PackType *packType = confRef.getPack()->getType(); for (auto subConfRef : confRef.getPack()->getPatternConformances()) { if (!collectRequiredTypesRecursively(subConfRef, inverseReqs)) { return false; } } return true; */ } if (confRef.isConcrete()) { ProtocolConformance *conformance = confRef.getConcrete(); switch (conformance->getKind()) { case ProtocolConformanceKind::Self: case ProtocolConformanceKind::Builtin: case ProtocolConformanceKind::Normal: // These types conform without requiring another type. return true; case ProtocolConformanceKind::Inherited: // InheritedConformance is not allowed for suppressible protocols. return true; case ProtocolConformanceKind::Specialized: // fall through to the recursive implementation. break; } SubstitutionMap subMap = conformance->getSubstitutionMap(); // Use the 'subMap' signature, not the conformance signature. GenericSignature subSig = subMap.getGenericSignature(); auto subConformances = subMap.getConformances(); for (auto &req : subSig.getRequirements()) { if (req.getKind() != RequirementKind::Conformance) continue; // GenericSignature's conformance Requirements line up with // subMap.getConformances(). ProtocolConformanceRef subConfRef = subConformances.front(); subConformances = subConformances.slice(1); if (subConfRef.isInvalid()) { Type envType = req.getFirstType().subst(subMap); LLVM_DEBUG(llvm::dbgs() << "Nested non-Escapable Type: " << envType << "\n"); inverseReqs.push_back(envType); continue; } if (subConfRef.getProtocol() ->isSpecificProtocol(KnownProtocolKind::Escapable)) { if (!collectRequiredTypesRecursively(subConfRef, inverseReqs)) { return false; } } } return true; } // unknown conformance kind return false; } // ========================================================================== // MARK: @_lifetime attribute semantics // ========================================================================== /// Resolve the dependency kind based on the descriptor syntax and check that /// it is consistent with parameter ownership. std::optional resolveSourceDescriptor(LifetimeDescriptor descriptor, ParamInfo const &sourceParam, unsigned targetIndex) { auto const loc = descriptor.getLoc(); auto const type = sourceParam.typeInContext; auto const parsedLifetimeKind = descriptor.getParsedLifetimeDependenceKind(); auto const loweredOwnership = getLoweredOwnership(sourceParam.param); switch (parsedLifetimeKind) { case ParsedLifetimeDependenceKind::Default: { // Infer copy dependence on @noescape function types by default. if (type->isNoEscape()) { return LifetimeDependenceKind::Inherit; } if (type->isEscapable()) { if (loweredOwnership == ValueOwnership::Shared || loweredOwnership == ValueOwnership::InOut) { return LifetimeDependenceKind::Scope; } diagnose( loc, diag::lifetime_dependence_cannot_use_default_escapable_consuming, getOwnershipSpelling(loweredOwnership)); return std::nullopt; } if (useLazyInference()) { return LifetimeDependenceKind::Inherit; } diagnose(loc, diag::lifetime_dependence_cannot_infer_kind, diagnosticQualifier(), descriptor.getString()); return std::nullopt; } case ParsedLifetimeDependenceKind::Borrow: LLVM_FALLTHROUGH; case ParsedLifetimeDependenceKind::Inout: { // @lifetime(borrow x) is valid only for borrowing parameters. // @lifetime(&x) is valid only for inout parameters. if (isCompatibleWithOwnership(parsedLifetimeKind, type, loweredOwnership, isInterfaceFile())) { return LifetimeDependenceKind::Scope; } diagnose(loc, diag::lifetime_dependence_parsed_borrow_with_ownership, getNameForParsedLifetimeDependenceKind(parsedLifetimeKind), getOwnershipSpelling(loweredOwnership)); switch (loweredOwnership) { case ValueOwnership::Shared: diagnose(loc, diag::lifetime_dependence_parsed_borrow_with_ownership_fix, "borrow ", descriptor.getString()); break; case ValueOwnership::InOut: diagnose(loc, diag::lifetime_dependence_parsed_borrow_with_ownership_fix, "&", descriptor.getString()); break; case ValueOwnership::Owned: case ValueOwnership::Default: break; } return std::nullopt; } case ParsedLifetimeDependenceKind::Inherit: { if (checkNonEscapableSource(descriptor, sourceParam, loweredOwnership)) return LifetimeDependenceKind::Inherit; return std::nullopt; } } } // @lifetime(copy x) is only valid if 'x' has a ~Escapable type. bool checkNonEscapableSource(LifetimeDescriptor descriptor, const ParamInfo &sourceParam, ValueOwnership ownership) { // Allow recompiling old interfaces with a newer compiler. if (isInterfaceFile()) return true; // @_hasUnsafeNonEscapableResult bypasses requires-escapable. // e.g. _overrideLifetime(_:, copying:) if (hasUnsafeNonEscapableResult) { return true; } // Does source have a guaranteed lifetime assuming the target is // non-Escapable? // Get the contextual source and target types. auto loc = descriptor.getLoc(); if (sourceParam.typeInContext->isEscapable()) { diagnose(loc, diag::lifetime_dependence_invalid_inherit_escapable_type); diagnose(loc, diag::lifetime_escapable_source_requires_escapable_note, (ownership == ValueOwnership::InOut) ? "&" : "borrow ", descriptor.getString()); return false; } return true; } // Finds the Param* and its index from a LifetimeDescriptor or returns // nullptr. ParamInfo const *getParamFromDescriptor(LifetimeDescriptor descriptor) { switch (descriptor.getDescriptorKind()) { case LifetimeDescriptor::DescriptorKind::Named: { const ParamInfo *candidate = llvm::find_if( parameterInfos, [name = descriptor.getName()](auto const ¶mInfo) { return paramInfo.param.getInternalLabel() == name; }); if (parameterInfos.end() == candidate) { diagnose(descriptor.getLoc(), diag::lifetime_dependence_invalid_param_name, descriptor.getName()); return nullptr; } return candidate; } case LifetimeDescriptor::DescriptorKind::Ordered: { auto paramIndex = descriptor.getIndex(); if (paramIndex >= parameterInfos.size()) { diagnose(descriptor.getLoc(), diag::lifetime_dependence_invalid_param_index, paramIndex); return nullptr; } return ¶meterInfos[paramIndex]; } case LifetimeDescriptor::DescriptorKind::Self: { if (!hasImplicitSelfParam()) { diagnose(descriptor.getLoc(), diag::lifetime_dependence_invalid_self_in_static); return nullptr; } if (isInit) { diagnose(descriptor.getLoc(), diag::lifetime_dependence_invalid_self_in_init); return nullptr; } return &implicitSelfParamInfo.value(); } } } // Initialize 'depBuilder' based on the function's @_lifetime attributes. void initializeAttributeDeps() { for (LifetimeEntry *entry : lifetimeEntries) { auto targetDescriptor = entry->getTargetDescriptor(); unsigned targetIndex; if (targetDescriptor.has_value()) { auto targetParam = getParamFromDescriptor(*targetDescriptor); if (!targetParam) { return; } // TODO: support dependencies on non-inout parameters. targetIndex = targetParam->index; if (!targetParam->param.isInOut()) { ctx.Diags.diagnose(targetParam->loc, diag::lifetime_parameter_requires_inout, targetDescriptor->getString()); } if (isDiagnosedEscapable(targetParam->typeInContext)) { diagnose(targetDescriptor->getLoc(), diag::lifetime_target_requires_nonescapable, "target"); } } else { if (isDiagnosedEscapable(resultTy)) { diagnose(entry->getLoc(), diag::lifetime_target_requires_nonescapable, "result"); } targetIndex = resultIndex; } TargetDeps *deps = depBuilder.createAnnotatedTargetDeps(targetIndex); if (deps == nullptr) { diagnose(entry->getLoc(), diag::lifetime_dependence_duplicate_target); return; } for (auto source : entry->getSources()) { initializeDescriptorDeps(targetIndex, *deps, source); } } } // 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 implicitSelfParamInfo.value(). ValueOwnership getLoweredOwnership(Param const ¶m) const { auto const ownership = param.getValueOwnership(); if (ownership != ValueOwnership::Default) return ownership; if (isLifetimeForDecl() && isa(afd)) { return ValueOwnership::Owned; } if (auto *ad = dyn_cast_or_null(afd)) { auto const isSelfParameter = implicitSelfParamInfo.has_value() && ¶m == &(implicitSelfParamInfo->param); if (ad->getAccessorKind() == AccessorKind::Set) { return isSelfParameter ? ValueOwnership::InOut : ValueOwnership::Owned; } if (isYieldingMutableAccessor(ad->getAccessorKind())) { assert(isSelfParameter); return ValueOwnership::InOut; } } return ValueOwnership::Shared; } // Initialize TargetDeps based on the function's @_lifetime attributes. void initializeDescriptorDeps(unsigned targetIndex, TargetDeps &deps, LifetimeDescriptor source) { // Find a parameter in parameterInfos with internal label 'keyword'. // If one exists, diagnose a conflict with the contextual keyword, and // return an iterator to it. Otherwise, return parameterInfos.end(). const auto findAndDiagnoseConflictingName = [&](const StringRef keyword) { auto conflictParam = llvm::find_if(parameterInfos, [&](auto const ¶mInfo) { return paramInfo.param.getInternalLabel().is(keyword); }); if (conflictParam != parameterInfos.end()) { ctx.Diags.diagnose( conflictParam->loc, diag::lifetime_dependence_contextual_keyword_conflict_name, keyword); } return conflictParam; }; if (source.isImmortalSpecifier()) { // Record the immortal dependency even if it is invalid to suppress other // diagnostics. deps.flags.setImmortalSpecifier(true); auto immortalParam = findAndDiagnoseConflictingName("immortal"); if (immortalParam != parameterInfos.end()) return; // @_lifetime(target: immortal, copy source) is allowed for inout targets. if (!deps.isInout()) { if (deps.inheritIndices.any() || deps.scopeIndices.any()) { ctx.Diags.diagnose(immortalParam->loc, diag::lifetime_dependence_immortal_alone); } } return; } if (!isLifetimeForDecl() && source.isCapturesSpecifier()) { // Record the closure context dependency for function types. deps.flags.setCaptures(true); findAndDiagnoseConflictingName( LifetimeDescriptor::CapturesContextSpecifier); return; } const ParamInfo *paramInfo = getParamFromDescriptor(source); if (!paramInfo) { return; } unsigned sourceIndex = paramInfo->index; auto lifetimeKind = resolveSourceDescriptor(source, *paramInfo, targetIndex); if (!lifetimeKind.has_value()) { return; } // Don't allow an 'inout' parameter to 'borrow' itself because it is useless // and an easy mistake when 'inout' was intended. if (lifetimeKind == LifetimeDependenceKind::Scope && paramInfo->param.isInOut() && sourceIndex == targetIndex) { diagnose(source.getLoc(), diag::lifetime_dependence_cannot_use_parsed_borrow_inout); ctx.Diags.diagnose(source.getLoc(), diag::lifetime_dependence_cannot_infer_inout_suggest, paramInfo->name()); return; } addDescriptorIndices(deps, source, sourceIndex, *lifetimeKind); } void addDescriptorIndices(LifetimeDependenceBuilder::TargetDeps &deps, LifetimeDescriptor descriptor, unsigned paramIndexToSet, LifetimeDependenceKind lifetimeKind) { // @_lifetime(target: immortal, copy source) is allowed for inout targets. if (deps.flags.hasImmortalSpecifier() && !deps.isInout()) { diagnose(descriptor.getLoc(), diag::lifetime_dependence_immortal_alone); return; } if (deps.inheritIndices.test(paramIndexToSet) || deps.scopeIndices.test(paramIndexToSet)) { diagnose(descriptor.getLoc(), diag::lifetime_dependence_duplicate_param_id); return; } if (lifetimeKind == LifetimeDependenceKind::Inherit) { deps.inheritIndices.set(paramIndexToSet); } else { assert(lifetimeKind == LifetimeDependenceKind::Scope); deps.scopeIndices.set(paramIndexToSet); } } // ========================================================================== // MARK: Inferrence rules // ========================================================================== // Infer the kind of dependence that makes sense for reading or writing a // stored property (for getters or initializers). std::optional inferLifetimeDependenceKind(ParamInfo const ¶mInfo) { Type paramType = paramInfo.typeInContext; if (!paramType->isEscapable()) { return LifetimeDependenceKind::Inherit; } // Lifetime dependence always propagates through temporary BitwiseCopyable // values, even if the dependence is scoped. if (isBitwiseCopyable(paramType, ctx)) { return LifetimeDependenceKind::Scope; } auto loweredOwnership = getLoweredOwnership(paramInfo.param); // It is impossible to depend on a consumed Escapable value (unless it is // BitwiseCopyable as checked above). if (loweredOwnership == ValueOwnership::Owned) { return std::nullopt; } return LifetimeDependenceKind::Scope; } // On returning, 'depBuilder' contains any inferred dependencies and // 'performedDiagnostics' indicates whether any specific diagnostics were // issued. void inferOrDiagnose() { if (auto accessor = dyn_cast_or_null(afd)) { inferAccessor(accessor); // Aside from the special cases handled above, accessors are considered // regular methods... } // Infer non-Escapable results. if (isDiagnosedNonEscapable(resultTy)) { if (isInit && isImplicitOrSIL()) { inferImplicitInit(); } else { // Apply the same-type rule before the single parameter rule. The // same-type rule does not trigger any diagnostics. inferNonEscapableResultOnSameTypeParam(); if (hasImplicitSelfParam()) { // Methods that return a non-Escapable value - single parameter // default rule. inferNonEscapableResultOnSelf(); } else if (isLifetimeForDecl()) { // Regular functions and initializers that return a non-Escapable // value - single parameter default rule. inferNonEscapableResultOnParam(); } else { // Function types - closure context default rule inferNonEscapingResultOnClosureContext(); } } } // Infer mutating non-Escapable methods (excluding initializers) - // `inout` parameter default rule. inferMutatingSelf(); // Infer inout parameters - `inout` parameter default rule. inferInoutParams(); } // Infer a dependency to the ~Escapable result from all parameters of the same // type. More generally, infer a dependency on any parameter type for which // Escapable conformance requires the result type to be Escapable. // // @_lifetime(copy a) // OK: Optional: Escapable requires T: Escapable // func foo(a: T?) -> T { // void inferNonEscapableResultOnSameTypeParam() { // Check that no @_lifetime annotation is present for the function result. TargetDeps *targetDeps = depBuilder.getInferredTargetDeps(resultIndex); if (!targetDeps) return; LLVM_DEBUG(llvm::dbgs() << "\nTarget Type: " << resultTy << "\n"); SmallVector targetReqList; if (!collectRequiredTypesForNonEscapable(resultTy, targetReqList)) { // Unable to evaluate conformance requirements. return; } if (targetReqList.empty()) { // The target is unconditionally Escapable. return; } LLVM_DEBUG(llvm::dbgs() << "\nTarget reqs:\n"; for (auto targetReq : targetReqList) { targetReq.dump(llvm::dbgs()); }); llvm::SmallDenseSet targetReqs; for (Type targetReq : targetReqList) { targetReqs.insert(targetReq); } // Ignore mutating self. An 'inout' modifier effectively makes the parameter // a different type for lifetime inference. if (hasImplicitSelfParam() && !implicitSelfParamInfo->param.isInOut()) { if (hasSameTypeRequirement(implicitSelfParamInfo->typeInContext, targetReqs)) { targetDeps->inheritIndices.set(implicitSelfParamInfo->index); } } unsigned paramIndex = 0; for (auto const ¶mInfo : parameterInfos) { SWIFT_DEFER { paramIndex++; }; // Ignore 'inout' parameters. An 'inout' modifier effectively makes the // parameter a different type for lifetime inference. An 'inout' parameter // defaults to being the source and target of a self-dependency, as // covered by the 'inout' rule. if (paramInfo.param.isInOut()) continue; if (hasSameTypeRequirement(paramInfo.typeInContext, targetReqs)) { targetDeps->inheritIndices.set(paramIndex); } } } // Infer dependence for an accessor whose non-escapable result depends on // self. This includes _read and _modify. // // Any accessors not handled here will be handled like a normal method. void inferAccessor(AccessorDecl *accessor) { if (!hasImplicitSelfParam()) { // Global accessors have no 'self'. Their result must be immortal. if (isDiagnosedNonEscapable(resultTy)) depBuilder.inferImmortalResult(); return; } bool nonEscapableSelf = 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 // selfIndex here, and one targetting resultIndex below). inferMutatingAccessor(accessor); } // Handle synthesized wrappers... if (!isImplicitOrSIL() && !useLazyInference()) return; // Infer the result dependency of the result or yielded value on 'self' // based on the kind of accessor called by this wrapper accessor. if (auto dependenceKind = getImplicitAccessorResultDependence(accessor)) { depBuilder.inferDependency(resultIndex, implicitSelfParamInfo->index, *dependenceKind); } } // Infer a mutating accessor's non-Escapable 'self' dependencies. void inferMutatingAccessor(AccessorDecl *accessor) { switch (accessor->getAccessorKind()) { case AccessorKind::Read: case AccessorKind::YieldingBorrow: case AccessorKind::Modify: case AccessorKind::YieldingMutate: // '_read' and '_modify' are inferred like regular methods. The yielded // value depends on the single 'self' parameter. Additionally, '_modify' // infers 'self' as an 'inout' parameter. // // The caller of _modify will ensure that the modified 'self', passed as // 'inout', depends on any value stored to the yielded address. // // Note that the AST generates a _modify for stored properties even though // it won't be emitted. break; case AccessorKind::Set: { const unsigned newValIdx = 0; auto const ¶mInfo = parameterInfos[newValIdx]; Type paramTypeInContext = paramInfo.typeInContext; if (paramTypeInContext->hasError()) { return; } 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 // with the setter's 'newValue'. In particular, if the getter's result is // Escapable then the getter does not have any lifetime dependency, so the // setter cannot depend on 'newValue'. if (!paramTypeInContext->isEscapable()) { depBuilder.inferDependency(implicitSelfParamInfo->index, newValIdx, LifetimeDependenceKind::Inherit); } break; } case AccessorKind::MutableAddress: if (useLazyInference()) { // Assume that a mutating method does not depend on its parameters. // Currently only for backward interface compatibility. Even though this // is the only useful dependence (a borrow of self is possible but not // useful), explicit annotation is required for now to confirm that the // mutated self cannot depend on anything stored at this address. depBuilder.inferInoutDependency(implicitSelfParamInfo->index); } break; default: // Unknown mutating accessor. break; } } // Implicit accessors must be consistent with the accessor that they // wrap. Otherwise, the sythesized implementation will report a diagnostic // error. std::optional getImplicitAccessorResultDependence(AccessorDecl *accessor) { if (!isDiagnosedNonEscapable(resultTy)) return std::nullopt; std::optional wrappedAccessorKind = std::nullopt; switch (accessor->getAccessorKind()) { case AccessorKind::Read: case AccessorKind::YieldingBorrow: case AccessorKind::Modify: case AccessorKind::YieldingMutate: // read/modify are syntesized as calls to the getter. wrappedAccessorKind = AccessorKind::Get; break; case AccessorKind::Get: // getters are synthesized as access to a stored property. break; default: // Unknown synthesized accessor. // Setters are handled in inferMutatingAccessor() because they don't // return a value. return std::nullopt; } if (wrappedAccessorKind) { auto *var = cast(accessor->getStorage()); for (auto *wrappedAccessor : var->getAllAccessors()) { if (wrappedAccessor->isImplicit()) continue; if (wrappedAccessor->getAccessorKind() == wrappedAccessorKind) { if (auto deps = wrappedAccessor->getLifetimeDependencies()) { for (auto &dep : *deps) { if (dep.getTargetIndex() != resultIndex) continue; if (dep.checkInherit(implicitSelfParamInfo->index)) return LifetimeDependenceKind::Inherit; if (dep.checkScope(implicitSelfParamInfo->index)) return LifetimeDependenceKind::Scope; } } } } } // Either a Get or Modify without any wrapped accessor. Handle these like a // read of the stored property. return inferLifetimeDependenceKind(*implicitSelfParamInfo); } // Infer implicit initialization. A non-Escapable initializer parameter can // always be inferred, similar to an implicit setter, because the // implementation is simply an assignment to stored property. Escapable // parameters are ambiguous: they may either be borrowed or // non-dependent. non-Escapable types often have incidental integer fields // that are unrelated to lifetime. Avoid inferring any dependency on Escapable // parameters unless it is the (unambiguously borrowed) sole parameter. void inferImplicitInit() { if (parameterInfos.size() == 0) { // Empty ~Escapable types can be implicitly initialized without any // dependencies. In SIL, implicit initializers become explicit. Set // performedDiagnostics here to bypass normal dependence checking without // raising an error. performedDiagnostics = true; return; } TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); if (!resultDeps) return; // .sil implicit initializers may have been annotated. if (!resultDeps->empty()) return; // same-type inferrence applied; don't issue diagnostics. unsigned paramIndex = 0; for (auto const ¶mInfo : parameterInfos) { SWIFT_DEFER { paramIndex++; }; Type paramTypeInContext = paramInfo.typeInContext; if (paramTypeInContext->hasError()) { return; } if (!paramTypeInContext->isEscapable()) { // An implicitly initialized non-Escapable value always copies its // dependency. resultDeps->addIfNew(paramIndex, LifetimeDependenceKind::Inherit); continue; } if (parameterInfos.size() > 1 && !useLazyInference()) { diagnose(paramInfo.loc, diag::lifetime_dependence_cannot_infer_implicit_init); return; } // A single Escapable parameter must be borrowed. auto kind = inferLifetimeDependenceKind(paramInfo); if (!kind) { diagnose( returnLoc, diag::lifetime_dependence_cannot_infer_scope_ownership, paramInfo.name(), diagnosticQualifier()); } resultDeps->addIfNew(paramIndex, LifetimeDependenceKind::Scope); } } // Infer method dependence of result on self for methods, getters, and _modify // accessors. Implements the single-parameter rule for methods and accessors // accessors (ignoring the subscript index parameter). void inferNonEscapableResultOnSelf() { TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); if (!resultDeps) return; if (!resultDeps->empty()) return; // same-type inferrence applied; don't issue diagnostics. bool nonEscapableSelf = 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 && 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 (!(isLifetimeForDecl() && isa(afd)) && !useLazyInference() && parameterInfos.size() > 0) { return; } if (!useLazyInference() && !isImplicitOrSIL()) { // Require explicit @_lifetime(borrow self) for UnsafePointer-like self. if (!nonEscapableSelf && isBitwiseCopyable(implicitSelfParamInfo->typeInContext, ctx)) { diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_bitwisecopyable, diagnosticQualifier(), "self"); return; } // Require explicit @_lifetime(copy or borrow) for non-Escapable self. if (nonEscapableSelf) { diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_kind, diagnosticQualifier(), "self"); return; } } // Infer based on ownership if possible for either explicit accessors or // methods as long as they pass preceding ambiguity checks. auto kind = inferLifetimeDependenceKind(*implicitSelfParamInfo); if (!kind) { // Special diagnostic for an attempt to depend on a consuming parameter. diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_scope_ownership, "self", diagnosticQualifier()); return; } resultDeps->addIfNew(implicitSelfParamInfo->index, *kind); } // Infer result dependence on a function or intitializer parameter. // Implements the single-parameter rule for functions. // // Note: for implicit initializers with parameters, consider inferring // Inherit dependency for each non-Escapable parameter. This would be // consistent with implicit stored property setters. This isn't done yet // because we also need to consider any Escapable parameters: either skip // inference if any exist, infer scoped dependency, or infer no // dependency. Implicit setters for Escapable properties are not inferred. void inferNonEscapableResultOnParam() { // This is only called when there is no 'self' argument that can be the // source of a dependence. assert(!hasImplicitSelfParam()); if (useLazyInference()) { return lazillyInferNonEscapableResultOnParam(); } TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); if (!resultDeps) return; if (!resultDeps->empty()) return; // same-type inferrence applied; don't issue diagnostics. // Strict inference only handles a single escapable parameter, // which is an unambiguous borrow dependence. if (parameterInfos.size() == 0) { diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_return_no_param, diagnosticQualifier()); diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_return_immortal); return; } if (parameterInfos.size() > 1) { // The usual diagnostic check is sufficient. return; } // Do not infer non-escapable dependence kind -- it is ambiguous, except for // noescape function types, for which we should always infer a copy dependence. auto const ¶mInfo = parameterInfos[0]; Type paramTypeInContext = paramInfo.typeInContext; if (paramTypeInContext->hasError()) { return; } if (!paramTypeInContext->isEscapable()) { if (paramTypeInContext->isNoEscape()) { resultDeps->addIfNew(/*paramIndex*/ 0, LifetimeDependenceKind::Inherit); return; } diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_kind, diagnosticQualifier(), paramInfo.name()); return; } auto kind = LifetimeDependenceKind::Scope; if (!isCompatibleWithOwnership(kind, paramInfo)) { diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_scope_ownership, paramInfo.name(), diagnosticQualifier()); return; } resultDeps->addIfNew(/*paramIndex*/ 0, kind); } // Lazy inference for .swiftinterface backward compatibility and // experimentation. Inference cases can be added but not removed. void lazillyInferNonEscapableResultOnParam() { TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); if (!resultDeps) return; std::optional candidateParamIndex; std::optional candidateLifetimeKind; unsigned paramIndex = 0; for (auto const ¶mInfo : parameterInfos) { SWIFT_DEFER { paramIndex++; }; Type paramTypeInContext = paramInfo.typeInContext; if (paramTypeInContext->hasError()) { return; } auto paramOwnership = paramInfo.param.getValueOwnership(); if (paramTypeInContext->isEscapable()) { if (isBitwiseCopyable(paramTypeInContext, ctx)) { continue; } if (paramOwnership == ValueOwnership::Default) { continue; } } candidateLifetimeKind = inferLifetimeDependenceKind(paramInfo); if (!candidateLifetimeKind) { continue; } if (candidateParamIndex) { diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_ambiguous_candidate, diagnosticQualifier()); return; } candidateParamIndex = paramIndex; } if (!candidateParamIndex) { diagnose(returnLoc, diag::lifetime_dependence_cannot_infer_no_candidates, diagnosticQualifier()); return; } resultDeps->addIfNew(*candidateParamIndex, *candidateLifetimeKind); } // Infer result dependence on the closure context for function types. void inferNonEscapingResultOnClosureContext() { assert(!isLifetimeForDecl() && "Only infer closure context dependence for function types"); TargetDeps *resultDeps = depBuilder.getInferredTargetDeps(resultIndex); if (!resultDeps) return; resultDeps->flags.setCaptures(true); } // Infer a mutating 'self' dependency when 'self' is non-Escapable and the // result is 'void'. void inferMutatingSelf() { if (!hasImplicitSelfParam()) return; if (!isDiagnosedNonEscapable(implicitSelfParamInfo->typeInContext)) return; assert(!isInit && "class initializers have Escapable self"); if (!implicitSelfParamInfo->param.isInOut()) return; // Assume that a mutating method does not depend on its parameters. depBuilder.inferInoutDependency(implicitSelfParamInfo->index); } // Infer @_lifetime(param: copy param) for 'inout' non-Escapable parameters. // // This supports the common case in which the user of a non-Escapable type, // such as MutableSpan, wants to modify the span's contents without modifying // the span value itself. It should be possible to use MutableSpan this way // without requiring any knowledge of lifetime annotations. The tradeoff is // that it makes authoring non-Escapable types less safe. For example, a // MutableSpan method could update the underlying unsafe pointer and forget to // declare a dependence on the incoming pointer. // // This also allows programmers to make the easy mistake reassign the inout // parameter to another parameter: // // func reassign(s: inout MutableSpan, a: MutableSpan) { // s = a // } // // But, even if that case were disallowed, they may derive another // non-Escapable value from an Escapable parameteter: // // func reassign(s: inout MutableSpan, a: [Int]) { // s = a.mutableSpan // } // // In either case, a diagnostics on the `reassign` function's implementation // will catch the invalid reassignment. The only real danger is when the // implementation is uses unsafe constructs. // // Do not issue any diagnostics. This inference is triggered even when the // feature is disabled! void inferInoutParams() { for (unsigned paramIndex : range(parameterInfos.size())) { auto const ¶mInfo = parameterInfos[paramIndex]; if (!isDiagnosedNonEscapable(paramInfo.typeInContext)) { continue; } if (!paramInfo.param.isInOut()) continue; depBuilder.inferInoutDependency(paramIndex); } } void inferUnambiguousInoutParams() { if (parameterInfos.size() != 1) { return; } const unsigned paramIndex = 0; auto const ¶mInfo = parameterInfos[paramIndex]; if (!paramInfo.param.isInOut()) { return; } if (!isDiagnosedNonEscapable(paramInfo.typeInContext)) { return; } depBuilder.inferInoutDependency(paramIndex); } void inferBuiltin() { // Only applicable to AbstractFunctionDecl. assert(nullptr != afd); // Normal inout parameter inference works for most generic Builtins. inferUnambiguousInoutParams(); const DeclName &name = afd->getName(); if (name.isSpecial()) { return; } // TODO: declare lifetime dependencies in Builtins.def. Until then, filter // the few that are not covered by general inference rules here. This is // safer than using a broader rule for implicit declarations. New Builtins // need to be considered as they are defined. auto id = name.getBaseIdentifier(); if (id == ctx.getIdentifier(getBuiltinName(BuiltinValueKind::InjectEnumTag))) { // ignore the tag parameter const unsigned inoutIdx = 0; depBuilder.inferInoutDependency(inoutIdx); } else if (id == ctx.getIdentifier( getBuiltinName(BuiltinValueKind::ConvertUnownedUnsafeToGuaranteed))) { const unsigned baseIdx = 0; const unsigned inoutIdx = 1; depBuilder.inferInoutDependency(inoutIdx); depBuilder.inferDependency(inoutIdx, baseIdx, LifetimeDependenceKind::Scope); } } }; } // anonymous namespace std::optional> LifetimeDependenceInfo::get(ValueDecl *decl) { if (auto *afd = dyn_cast(decl)) { return LifetimeDependenceChecker(afd).checkFuncDecl(); } auto *eed = cast(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(); } ArrayRef LifetimeDependenceInfo::uncurry( ASTContext &ctx, ArrayRef inner, unsigned numInnerParams, unsigned numOuterParams) { const unsigned numUncurriedParams = numInnerParams + numOuterParams; const auto uncurryIndices = [&](IndexSubset *indices) -> IndexSubset * { if (!indices) return nullptr; return indices->extendingCapacity(ctx, numUncurriedParams); }; SmallVector uncurried; // Process the inner dependencies for (auto innerDep : inner) { auto inherit = uncurryIndices(innerDep.getInheritIndices()); auto scope = uncurryIndices(innerDep.getScopeIndices()); auto addressable = uncurryIndices(innerDep.getAddressableIndices()); auto conditionallyAddressable = uncurryIndices(innerDep.getConditionallyAddressableIndices()); // The inner result's dependencies become the uncurried result's // dependencies. const auto targetIndex = (innerDep.getTargetIndex() == numInnerParams) ? numUncurriedParams : innerDep.getTargetIndex(); uncurried.push_back( LifetimeDependenceInfo(inherit, scope, targetIndex, addressable, conditionallyAddressable, innerDep.flags)); } return ctx.AllocateCopy(uncurried); } ArrayRef LifetimeDependenceInfo::partialApply( ASTContext &ctx, ArrayRef lifetimes, unsigned numFormalParams, unsigned numBoundParams) { if (numBoundParams == 0) return lifetimes; ASSERT(numBoundParams <= numFormalParams && "A partial application can only bind as many parameters as the " "function has."); // How many parameters the resulting closure will have. const unsigned numClosureParams = numFormalParams - numBoundParams; SmallVector curried; for (const auto &dep : lifetimes) { // Determine the new target index. unsigned targetIndex; if (dep.getTargetIndex() == numFormalParams) { // The target is the result. // Its index is the number of parameters. targetIndex = numClosureParams; } else if (dep.getTargetIndex() >= numClosureParams) { // The target is a captured parameter. // The resulting closure does not need a lifetime dependence entry for it. continue; } else { // The target is an uncaptured parameter. // Its index remains the same. targetIndex = dep.getTargetIndex(); } auto flags = dep.flags; const auto captureBoundParams = [&](IndexSubset *indices) -> IndexSubset * { if (!indices) return nullptr; ASSERT(indices->getCapacity() <= numFormalParams && "There should be at most 1 index per parameter. SIL functions " "cannot have " "an implicit self parameter."); auto bits = indices->getBitVector(); if (bits.find_last() >= int(numClosureParams)) { // One of the lifetime source parameters is bound by the partial_apply. // This becomes a captures dependence in the resulting closure. flags.setCaptures(true); } // Remove the indices of the captured parameters, leaving only those of // the closure parameters. if (bits.find_first() >= int(numClosureParams)) { // All lifetime sources are captured. The resulting empty list of // indices should be represented with a nullptr. return nullptr; } if (bits.size() > numClosureParams) bits.resize(numClosureParams); return IndexSubset::get(ctx, bits); }; auto inherit = captureBoundParams(dep.getInheritIndices()); auto scope = captureBoundParams(dep.getScopeIndices()); auto addressable = captureBoundParams(dep.getAddressableIndices()); auto conditionallyAddressable = captureBoundParams(dep.getConditionallyAddressableIndices()); curried.push_back(LifetimeDependenceInfo(inherit, scope, targetIndex, addressable, conditionallyAddressable, flags)); } // FIXME: Avoid allocating context memory for every partial apply. Instead, // cache a single uniqueLifetimeDependenceInfo array for each combination // of FunctionType + numBoundParams. return ctx.AllocateCopy(curried); } void LifetimeDependenceInfo::dump() const { llvm::errs() << "target: " << getTargetIndex() << '\n'; if (hasImmortalSpecifier()) { llvm::errs() << " immortal\n"; } if (auto scoped = getScopeIndices()) { llvm::errs() << " scoped: "; scoped->dump(); } if (auto inherited = getInheritIndices()) { llvm::errs() << " inherited: "; inherited->dump(); } if (auto addressable = getAddressableIndices()) { llvm::errs() << " addressable: "; addressable->dump(); } } // ============================================================================= // MARK: LifetimeDependentInterface // ============================================================================= LifetimeDependentInterface::LifetimeDependentInterface( AbstractFunctionDecl *afd, AnyFunctionType *interface) : interface(interface), implicitSelfType(afd->isInstanceMethod() ? std::optional(afd->getImplicitSelfDecl() ->toFunctionParam() .getPlainType()) : std::nullopt), // Instance methods' lifetime dependencies are attached to the outer type // of the method's interface (see LifetimeDependentInterface), so we must // get them from there, rather than the normal interface type. lifetimes(afd->isInstanceMethod() ? afd->getInterfaceType() ->castTo() ->getLifetimeDependencies() : interface->getLifetimeDependencies()) {} LifetimeDependentInterface::LifetimeDependentInterface(AnyFunctionType *type) : interface(type), implicitSelfType(std::nullopt), lifetimes(type->getLifetimeDependencies()) {} LifetimeDependentInterface LifetimeDependentInterface::fromValueDecl(ValueDecl *decl, AnyFunctionType *type) { if (auto *afd = dyn_cast(decl)) { return LifetimeDependentInterface(afd, type); } return LifetimeDependentInterface(type); } Type LifetimeDependentInterface::getSourceOrTargetType(unsigned index) const { const auto numParams = interface->getNumParams(); if (index < numParams) { return interface->getParams()[index].getPlainType(); } if (index == numParams) { return implicitSelfType ? *implicitSelfType : interface->getResult(); } if (implicitSelfType && index == numParams + 1) { return interface->getResult(); } llvm_unreachable("Invalid lifetime source or target index."); } bool LifetimeDependentInterface::canConvertTo( const LifetimeDependentInterface &other) const { // If from and to are the same array, they naturally match. This case should // be reasonably common because lifetime dependence info is canonicalized. if (lifetimes == other.lifetimes) { return true; } // Check each dependency target... return llvm::all_of(lifetimes, [&](const LifetimeDependenceInfo &target) { return canConvertTargetTo(target, other); }); } /// Determine whether type is "known" (see isTypeUnknown) and ~Escapable. static bool isTypeKnownAndEscapable(Type type) { if (isTypeUnknown(type)) return false; return type->isEscapable(); } bool LifetimeDependentInterface::canConvertTargetTo( const LifetimeDependenceInfo &fromDeps, const LifetimeDependentInterface &to) const { const auto targetIndex = fromDeps.getTargetIndex(); // Lifetime dependencies with Escapable targets, and 'copy' dependencies with // Escapable sources are always ignored (see Dependency type requirements in // LifetimeAnnotation.md). // // We only need to check if the target and sources are Escapable for the // lifetime dependence we are converting from (this), not the one we are // converting to (other). This is because we allow conversion to types with // additional dependencies: even if some of the dependencies of 'other' should // be ignored, they could not prevent convertibility unless 'this' had a // conflicting dependence (with the same source & target as one of 'other's // "ignorable" dependencies, but a different scope, copy vs borrow). In that // case, we would detect a mismatch regardless, due to the conflicting // dependence not being present in 'other'. const auto other = getLifetimeDependenceFor(to.lifetimes, targetIndex); // If the dependence target is Escapable, we ignore this lifetime dependence. if (isTypeKnownAndEscapable(getSourceOrTargetType(targetIndex))) return true; // The target must be the same. if (other && targetIndex != other->getTargetIndex()) { return false; } // Immortal lifetimes are the least restrictive, so only immortal lifetimes // can convert to them. if (other && other->isImmortal()) { return fromDeps.isImmortal(); } // Accordingly, immortal lifetimes can convert to any non-immortal lifetimes. if (fromDeps.isImmortal()) { return true; } // A dependence on closure captures can be added, but not removed. if (fromDeps.hasCaptures() && other && !other->hasCaptures()) { return false; } const auto isSubset = [&](IndexSubset *from, IndexSubset *to, bool ignoreEscapableSources = false) { // The empty set is a subset of every set, and every set is a subset of // itself. An empty set is represented with a nullptr. if (!from || from == to) return true; ASSERT(!from->isEmpty() && "Empty dependence source lists are represented with nullptr."); // The set 'from' is non-empty, so it cannot be a subset of an empty 'to'. if (!to) return false; if (!ignoreEscapableSources) { return from->isSubsetOf(to); } // Check whether from's set of (potentially) ~Escapable lifetime sources is // a subset of to's. return llvm::all_of(from->getIndices(), [&](const unsigned source) { return to->contains(source) || isTypeKnownAndEscapable(getSourceOrTargetType(source)); }); }; return // Ignore copy dependencies with an Escapable source if the // getSourceOrTargetType predicate is supplied. isSubset(fromDeps.getInheritIndices(), other ? other->getInheritIndices() : nullptr, /*ignoreEscapableSources=*/true) && isSubset(fromDeps.getScopeIndices(), other ? other->getScopeIndices() : nullptr) && isSubset(fromDeps.getAddressableIndices(), other ? other->getAddressableIndices() : nullptr); } bool LifetimeDependentInterface::hasTarget(unsigned targetIndex) const { return getLifetimeDependenceFor(lifetimes, targetIndex).has_value(); } // ============================================================================= // SIL parsing support // ============================================================================= // This implements the logic for SIL type descriptors similar to source-level // logic in LifetimeDependenceChecker::initializeAttributeDeps(). The SIL // context is substantially different from Sema. static std::optional checkSILTypeModifiers( LifetimeDependentTypeRepr *lifetimeDependentRepr, unsigned targetIndex, ArrayRef params, DeclContext *dc) { auto &ctx = dc->getASTContext(); auto &diags = ctx.Diags; auto capacity = params.size(); // SIL parameters include self SmallBitVector inheritLifetimeParamIndices(capacity); SmallBitVector scopeLifetimeParamIndices(capacity); SmallBitVector addressableLifetimeParamIndices(capacity); SmallBitVector conditionallyAddressableLifetimeParamIndices(capacity); auto updateLifetimeDependenceInfo = [&](LifetimeDescriptor descriptor, unsigned paramIndexToSet, ParameterConvention paramConvention) { auto loc = descriptor.getLoc(); auto kind = descriptor.getParsedLifetimeDependenceKind(); if (kind == ParsedLifetimeDependenceKind::Borrow && isConsumedParameterInCallee(paramConvention)) { diags.diagnose(loc, diag::lifetime_dependence_cannot_use_kind, "_scope", getStringForParameterConvention(paramConvention)); return true; } if (inheritLifetimeParamIndices.test(paramIndexToSet) || scopeLifetimeParamIndices.test(paramIndexToSet)) { diags.diagnose(loc, diag::lifetime_dependence_duplicate_param_id); return true; } if (kind == ParsedLifetimeDependenceKind::Inherit) { inheritLifetimeParamIndices.set(paramIndexToSet); } else { assert(kind == ParsedLifetimeDependenceKind::Borrow); scopeLifetimeParamIndices.set(paramIndexToSet); } return false; }; LifetimeFlags flags; flags.setAnnotated(true); for (auto source : lifetimeDependentRepr->getLifetimeEntry()->getSources()) { switch (source.getDescriptorKind()) { case LifetimeDescriptor::DescriptorKind::Ordered: { auto index = source.getIndex(); if (index > capacity) { diags.diagnose(source.getLoc(), diag::lifetime_dependence_invalid_param_index, index); return std::nullopt; } auto param = params[index]; auto paramConvention = param.getConvention(); if (updateLifetimeDependenceInfo(source, index, paramConvention)) { return std::nullopt; } switch (source.isAddressable()) { case LifetimeDescriptor::IsNotAddressable: break; case LifetimeDescriptor::IsConditionallyAddressable: conditionallyAddressableLifetimeParamIndices.set(index); break; case LifetimeDescriptor::IsAddressable: addressableLifetimeParamIndices.set(index); break; } break; } case LifetimeDescriptor::DescriptorKind::Named: { if (source.isImmortalSpecifier()) { flags.setImmortalSpecifier(true); } else if (source.isCapturesSpecifier()) { flags.setCaptures(true); } else { llvm_unreachable( "SIL can only have ordered, immortal or captures lifetime " "dependence specifier kind"); } break; } default: llvm_unreachable( "SIL can only have ordered, immortal or captures lifetime " "dependence specifier kind"); } } return LifetimeDependenceInfo( inheritLifetimeParamIndices.any() ? IndexSubset::get(ctx, inheritLifetimeParamIndices) : nullptr, scopeLifetimeParamIndices.any() ? IndexSubset::get(ctx, scopeLifetimeParamIndices) : nullptr, targetIndex, addressableLifetimeParamIndices.any() ? IndexSubset::get(ctx, addressableLifetimeParamIndices) : nullptr, conditionallyAddressableLifetimeParamIndices.any() ? IndexSubset::get(ctx, conditionallyAddressableLifetimeParamIndices) : nullptr, flags); } std::optional> LifetimeDependenceInfo::getFromSIL(FunctionTypeRepr *funcRepr, ArrayRef params, ArrayRef results, DeclContext *dc) { SmallVector lifetimeDependencies; auto getLifetimeDependenceFromTypeModifiers = [&](TypeRepr *typeRepr, unsigned targetIndex) -> std::optional { auto *lifetimeTypeRepr = dyn_cast_or_null(typeRepr); if (!lifetimeTypeRepr) { return std::nullopt; } return checkSILTypeModifiers(lifetimeTypeRepr, targetIndex, params, dc); }; auto argsTypeRepr = funcRepr->getArgsTypeRepr()->getElements(); for (unsigned targetIndex : indices(argsTypeRepr)) { if (auto result = getLifetimeDependenceFromTypeModifiers( argsTypeRepr[targetIndex].Type, targetIndex)) { lifetimeDependencies.push_back(*result); } } auto result = getLifetimeDependenceFromTypeModifiers( funcRepr->getResultTypeRepr(), params.size()); if (result) { lifetimeDependencies.push_back(*result); } return dc->getASTContext().AllocateCopy(lifetimeDependencies); }