Repository: apple/swift
File: lib/AST/LifetimeDependence.cpp (C++)

Code before:
    // other annotations do not.
    if (!iter->second.isImmortal) {
      iter->second.addIfNew(paramIndex, LifetimeDependenceKind::Inherit);
    }
  }

  // Allocate LifetimeDependenceInfo in the ASTContext. Initialize it by
  // copying heap-allocated TargetDeps fields into ASTContext allocations
  // (e.g. convert SmallBitVector to IndexSubset).
  std::optional<llvm::ArrayRef<LifetimeDependenceInfo>>
  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<LifetimeDependenceInfo, 4> lifetimeDependencies;
    for (auto &idxAndDeps : depsArray) {
      unsigned targetIndex = idxAndDeps.first;
      auto &deps = idxAndDeps.second;
      if (deps.empty())
        continue;

      // Combining an immortal specifier with another dependency is the identity
      // transformation--the immortaility is lost.
      bool isFullyImmortal = deps.isImmortal;
      IndexSubset *inheritIndices = nullptr;
      if (deps.inheritIndices.any()) {
        inheritIndices = IndexSubset::get(ctx, deps.inheritIndices);
        ASSERT(!isFullyImmortal || deps.isInout() &&
               "cannot combine immortal lifetime with parameter dependency");
        isFullyImmortal = false;
      }
      IndexSubset *scopeIndices = nullptr;
      if (deps.scopeIndices.any()) {
        scopeIndices = IndexSubset::get(ctx, deps.scopeIndices);
        ASSERT(!isFullyImmortal || deps.isInout() &&
               "cannot combine immortal lifetime with parameter dependency");
        isFullyImmortal = false;
      }
      lifetimeDependencies.push_back(LifetimeDependenceInfo(
          /*inheritLifetimeParamIndices*/ inheritIndices,
          /*scopeLifetimeParamIndices*/ scopeIndices, targetIndex,
          /*isImmortal*/ isFullyImmortal,
          /*isFromAnnotation*/ deps.hasAnnotation()));
    }
    if (lifetimeDependencies.empty()) {
      return std::nullopt;
    }
    return ctx.AllocateCopy(lifetimeDependencies);
  }

Review comment (bug):
If we don't track whether an inout parameter had the `immortal` specifier in the `LifetimeDependenceInfo` representation, then we won't print it on function types in swiftinterface files. This will cause the default dependency to be incorrectly inferred when reading the swiftinterface.

Possible solutions:
- Add a flag to indicate that the lifetime dependence info is "exhaustive", meaning no additional dependencies should be inferred. Print `immortal` if it is set. This would be independent of the current syntax, which seems likely to change in the future, but would change the serialization format.
- Set the `immortal` flag in this case. This makes the representation consistent with the syntax, but would require us to change how the flag is handled in lifetime checking code. Probably also breaks the serialization format?
- Hack: Print `immortal` in `getLifetimeDependenceInfoSourceListString` if the target is an inout parameter that does not depend on itself. This would be the smallest change, but introduces assumptions about lifetime inference rules in a seemingly-unrelated part of the codebase.
- Suppress the default inout dependency inference when parsing a swiftinterface file. Undesirable, since lifetime annotations in swift and swiftinterface would have different meanings.

We need some test cases for this here: https://github.com/swiftlang/swift/blob/main/test/ModuleInterface/lifetime_underscored_dependence_test.swift

Code after:
    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.hasImmortalSpecifier) {
      iter->second.addIfNew(paramIndex, LifetimeDependenceKind::Inherit);
    }
  }

  // Allocate LifetimeDependenceInfo in the ASTContext. Initialize it by
  // copying heap-allocated TargetDeps fields into ASTContext allocations
  // (e.g. convert SmallBitVector to IndexSubset).
  std::optional<llvm::ArrayRef<LifetimeDependenceInfo>>
  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<LifetimeDependenceInfo, 4> 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.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.hasImmortalSpecifier || deps.isInout() &&
               "cannot combine immortal lifetime with parameter dependency");
      }
      lifetimeDependencies.push_back(LifetimeDependenceInfo(
          /*inheritLifetimeParamIndices*/ inheritIndices,
          /*scopeLifetimeParamIndices*/ scopeIndices, targetIndex,
          /*hasImmortalSpecifier*/ deps.hasImmortalSpecifier,
          /*isFromAnnotation*/ deps.hasAnnotation()));
    }
    if (lifetimeDependencies.empty()) {
      return std::nullopt;
    }
    return ctx.AllocateCopy(lifetimeDependencies);
  }
};