//===--- Availability.cpp - Swift Availability Structures -----------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2025 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
//
//===----------------------------------------------------------------------===//
//
// This file defines data structures for API availability.
//
//===----------------------------------------------------------------------===//

#include "swift/AST/ASTContext.h"
#include "swift/AST/Attr.h"
#include "swift/AST/AvailabilityContext.h"
#include "swift/AST/AvailabilityDomain.h"
#include "swift/AST/AvailabilityInference.h"
#include "swift/AST/AvailabilityRange.h"
#include "swift/AST/AvailabilityScope.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DeclExportabilityVisitor.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/Module.h"
#include "swift/AST/PlatformKindUtils.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/AST/TypeWalker.h"
#include "swift/AST/Types.h"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Platform.h"
#include "swift/ClangImporter/ClangModule.h"
#include <map>

using namespace swift;

void VersionRange::Profile(llvm::FoldingSetNodeID &id) const {
  id.AddBoolean(hasLowerEndpoint());
  if (!hasLowerEndpoint()) {
    id.AddBoolean(isAll());
    return;
  }

  auto profileVersionComponent = [&id](std::optional<unsigned> component) {
    id.AddBoolean(component.has_value());
    if (component)
      id.AddInteger(*component);
  };

  auto lowerEndpoint = getLowerEndpoint();
  id.AddInteger(lowerEndpoint.getMajor());
  profileVersionComponent(lowerEndpoint.getMinor());
  profileVersionComponent(lowerEndpoint.getSubminor());
  profileVersionComponent(lowerEndpoint.getBuild());
}

AvailabilityRange
AvailabilityRange::forDeploymentTarget(const ASTContext &Ctx) {
  return AvailabilityRange(Ctx.LangOpts.getMinPlatformVersion());
}

AvailabilityRange AvailabilityRange::forInliningTarget(const ASTContext &Ctx) {
  return AvailabilityRange(Ctx.LangOpts.MinimumInliningTargetVersion);
}

AvailabilityRange AvailabilityRange::forRuntimeTarget(const ASTContext &Ctx) {
  return AvailabilityRange(Ctx.LangOpts.RuntimeVersion);
}

namespace {

/// The inferred availability required to access a group of declarations
/// on a single platform.
struct InferredAvailability {
  AvailableAttr::Kind Kind = AvailableAttr::Kind::Default;

  std::optional<llvm::VersionTuple> Introduced;
  std::optional<llvm::VersionTuple> Deprecated;
  std::optional<llvm::VersionTuple> Obsoleted;

  StringRef Message;
  StringRef Rename;
  bool IsSPI = false;
};

/// The type of a function that merges two version tuples.
typedef const llvm::VersionTuple &(*MergeFunction)(
    const llvm::VersionTuple &, const llvm::VersionTuple &);

} // end anonymous namespace

/// Apply a merge function to two optional versions, returning the result
/// in Inferred.
static bool
mergeIntoInferredVersion(const std::optional<llvm::VersionTuple> &Version,
                         std::optional<llvm::VersionTuple> &Inferred,
                         MergeFunction Merge) {
  if (Version.has_value()) {
    if (Inferred.has_value()) {
      Inferred = Merge(Inferred.value(), Version.value());
      return *Inferred == *Version;
    } else {
      Inferred = Version;
      return true;
    }
  }
  return false;
}

/// Merge an attribute's availability with an existing inferred availability
/// so that the new inferred availability is at least as available as
/// the attribute requires.
static void mergeWithInferredAvailability(SemanticAvailableAttr Attr,
                                          InferredAvailability &Inferred) {
  auto *ParsedAttr = Attr.getParsedAttr();
  Inferred.Kind = static_cast<AvailableAttr::Kind>(
      std::max(static_cast<unsigned>(Inferred.Kind),
               static_cast<unsigned>(ParsedAttr->getKind())));

  // The merge of two introduction versions is the maximum of the two versions.
  if (mergeIntoInferredVersion(Attr.getIntroduced(), Inferred.Introduced,
                               std::max)) {
    Inferred.IsSPI = Attr.isSPI();
  }

  // The merge of deprecated and obsoleted versions takes the minimum.
  mergeIntoInferredVersion(Attr.getDeprecated(), Inferred.Deprecated, std::min);
  mergeIntoInferredVersion(Attr.getObsoleted(), Inferred.Obsoleted, std::min);

  if (Inferred.Message.empty() && !Attr.getMessage().empty())
    Inferred.Message = Attr.getMessage();

  if (Inferred.Rename.empty() && !Attr.getRename().empty())
    Inferred.Rename = Attr.getRename();
}

/// Create an implicit availability attribute for the given domain
/// and with the inferred availability.
static AvailableAttr *createAvailableAttr(AvailabilityDomain Domain,
                                          const InferredAvailability &Inferred,
                                          ASTContext &Context) {
  // If there is no information that would go into the availability attribute,
  // don't create one.
  if (Inferred.Kind == AvailableAttr::Kind::Default && !Inferred.Introduced &&
      !Inferred.Deprecated && !Inferred.Obsoleted && Inferred.Message.empty() &&
      Inferred.Rename.empty())
    return nullptr;

  llvm::VersionTuple Introduced =
      Inferred.Introduced.value_or(llvm::VersionTuple());
  llvm::VersionTuple Deprecated =
      Inferred.Deprecated.value_or(llvm::VersionTuple());
  llvm::VersionTuple Obsoleted =
      Inferred.Obsoleted.value_or(llvm::VersionTuple());

  return new (Context) AvailableAttr(
      SourceLoc(), SourceRange(), Domain, SourceLoc(), Inferred.Kind,
      Inferred.Message, Inferred.Rename, Introduced, SourceRange(), Deprecated,
      SourceRange(), Obsoleted, SourceRange(), /*Implicit=*/true,
      Inferred.IsSPI);
}

void AvailabilityInference::applyInferredAvailableAttrs(
    Decl *ToDecl, ArrayRef<const Decl *> InferredFromDecls) {
  auto &Context = ToDecl->getASTContext();

  // Iterate over the declarations and infer required availability on
  // a per-domain basis.
  std::map<AvailabilityDomain, InferredAvailability,
           StableAvailabilityDomainComparator>
      Inferred;
  for (const Decl *D : InferredFromDecls) {
    llvm::SmallVector<SemanticAvailableAttr, 8> MergedAttrs;

    do {
      llvm::SmallVector<SemanticAvailableAttr, 8> PendingAttrs;

      for (auto AvAttr : D->getSemanticAvailableAttrs()) {
        // Skip an attribute from an outer declaration if it is for a platform
        // that was already handled implicitly by an attribute from an inner
        // declaration.
        if (llvm::any_of(MergedAttrs,
                         [&AvAttr](SemanticAvailableAttr MergedAttr) {
                           return inheritsAvailabilityFromPlatform(
                               AvAttr.getPlatform(), MergedAttr.getPlatform());
                         }))
          continue;

        mergeWithInferredAvailability(AvAttr, Inferred[AvAttr.getDomain()]);
        PendingAttrs.push_back(AvAttr);
      }

      MergedAttrs.append(PendingAttrs);

      // Walk up the enclosing declaration hierarchy to make sure we aren't
      // missing any inherited attributes.
      D = D->parentDeclForAvailability();
    } while (D);
  }

  // Create an availability attribute for each observed platform and add
  // to ToDecl.
  for (auto &Pair : Inferred) {
    if (auto Attr = createAvailableAttr(Pair.first, Pair.second, Context))
      ToDecl->addAttribute(Attr);
  }
}

/// Returns the decl that should be considered the parent decl of the given decl
/// when looking for inherited availability annotations.
const Decl *Decl::parentDeclForAvailability() const {
  if (auto *AD = dyn_cast<AccessorDecl>(this))
    return AD->getStorage();

  if (auto *ED = dyn_cast<ExtensionDecl>(this)) {
    if (auto *NTD = ED->getExtendedNominal())
      return NTD;
  }

  if (auto *PBD = dyn_cast<PatternBindingDecl>(this)) {
    if (PBD->getNumPatternEntries() < 1)
      return nullptr;

    return PBD->getAnchoringVarDecl(0);
  }

  if (auto *OTD = dyn_cast<OpaqueTypeDecl>(this))
    return OTD->getNamingDecl();

  // Clang decls may be inaccurately parented rdar://53956555
  if (hasClangNode())
    return nullptr;

  // Availability is inherited from the enclosing context.
  return getDeclContext()->getInnermostDeclarationDeclContext();
}

/// Returns true if the introduced version in \p newAttr should be used instead
/// of the introduced version in \p prevAttr when both are attached to the same
/// declaration and refer to the active platform.
static bool isBetterThan(const SemanticAvailableAttr &newAttr,
                         const std::optional<SemanticAvailableAttr> &prevAttr) {
  // If there is no prevAttr, newAttr of course wins.
  if (!prevAttr)
    return true;

  // If they belong to the same platform, the one that introduces later wins.
  if (prevAttr->getPlatform() == newAttr.getPlatform())
    return prevAttr->getIntroduced().value() < newAttr.getIntroduced().value();

  // If the new attribute's platform inherits from the old one, it wins.
  return inheritsAvailabilityFromPlatform(newAttr.getPlatform(),
                                          prevAttr->getPlatform());
}

static std::optional<SemanticAvailableAttr>
getDeclAvailableAttrForPlatformIntroduction(const Decl *D) {
  std::optional<SemanticAvailableAttr> bestAvailAttr;

  D = D->getAbstractSyntaxDeclForAttributes();

  for (auto attr : D->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
    if (!attr.isPlatformSpecific() || !attr.getIntroduced())
      continue;

    if (isBetterThan(attr, bestAvailAttr))
      bestAvailAttr.emplace(attr);
  }

  return bestAvailAttr;
}

std::optional<AvailabilityRange>
AvailabilityInference::annotatedAvailableRange(const Decl *D) {
  auto bestAvailAttr = D->getAvailableAttrForPlatformIntroduction();
  if (!bestAvailAttr)
    return std::nullopt;

  return bestAvailAttr->getIntroducedRange(D->getASTContext());
}

bool Decl::isAvailableAsSPI() const {
  if (auto attr = getAvailableAttrForPlatformIntroduction())
    return attr->isSPI();

  return false;
}

bool SynthesizeLocalAvailableAttrsRequest::appliesTo(const Decl *decl) {
  // Only a declaration in a local context can be covered by an availability
  // scope that isn't already accounted for by the availability of a parent
  // declaration.
  if (!decl->getDeclContext()->isLocalContext())
    return false;

  // The storage for a local variable is emitted as part of its enclosing
  // function, and a use of the variable that precedes its declaration is
  // rejected, so every reference to it is already inside the region that the
  // attributes would describe.
  if (isa<AbstractStorageDecl>(decl) || isa<PatternBindingDecl>(decl))
    return false;

  // The accessors for local variables, on the other hand, need implicit
  // availability to control whether they get emitted as independent functions
  // by SILGen.
  if (isa<AccessorDecl>(decl))
    return true;

  // Implicit declarations should generally be synthesized with appropriate
  // availability.
  return !decl->isImplicit();
}

evaluator::SideEffect
SynthesizeLocalAvailableAttrsRequest::evaluate(Evaluator &evaluator,
                                               Decl *decl) const {
  DEBUG_ASSERT(appliesTo(decl));

  auto loc = decl->getSourceRangeIncludingAttrs().Start;
  if (loc.isInvalid())
    return {};

  auto &ctx = decl->getASTContext();
  auto *sf = decl->getDeclContext()
                 ->getParentModule()
                 ->getSourceFileContainingLocation(loc);
  if (!sf)
    return {};

  auto *rootScope = AvailabilityScope::getOrBuildForSourceFile(*sf);
  if (!rootScope)
    return {};

  // Find the scope that describes the availability of the position that the
  // declaration appears in. Scopes introduced by the declaration itself are
  // excluded since the restrictions they describe are the ones that are already
  // written on the declaration.
  ASTNode stopAtNode =
      const_cast<Decl *>(decl->getConcreteSyntaxDeclForAttributes());
  llvm::SmallVector<AvailabilityScope *, 8> scopeStack;
  auto *scope =
      rootScope->findMostRefinedSubContext(loc, ctx, scopeStack, stopAtNode);
  if (!scope)
    return {};

  // Find the innermost enclosing scope that a declaration introduced. The
  // restrictions that it and the scopes above it describe are already inherited
  // from that declaration.
  AvailabilityScope *declScope = scopeStack.front();
  for (auto [parent, candidate] :
       llvm::zip_first(llvm::drop_begin(llvm::reverse(scopeStack)),
                       llvm::reverse(scopeStack))) {
    // A scope that does not refine the availability of its parent, like the
    // placeholder that lazy expansion leaves behind for a declaration, does not
    // describe a restriction of its own.
    if (candidate->getAvailabilityContext() == parent->getAvailabilityContext())
      continue;

    if (!candidate->isIntroducedByStmt()) {
      declScope = candidate;
      break;
    }
  }

  if (declScope == scope)
    return {};

  llvm::SmallVector<AvailableAttr *, 4> attrs;
  scope->getAvailabilityContext().createImplicitAvailableAttrs(
      declScope->getAvailabilityContext(), ctx, attrs);

  for (auto *attr : attrs)
    decl->getAttrs().add(attr);

  return {};
}

SemanticAvailableAttributes
Decl::getSemanticAvailableAttrs(bool includeInactive) const {
  // A decl in an @abi gets its availability from the decl it's attached to.
  auto abiRole = ABIRoleInfo(this);
  if (!abiRole.providesAPI() && abiRole.getCounterpart())
    return abiRole.getCounterpart()->getSemanticAvailableAttrs(includeInactive);

  // Apply file defaults for availability attributes.
  const_cast<Decl *>(this)->applyFileDefaults();

  // Synthesize the attributes that are implied by the availability scopes
  // containing a declaration in a local context.
  if (SynthesizeLocalAvailableAttrsRequest::appliesTo(this)) {
    evaluateOrDefault(
        getASTContext().evaluator,
        SynthesizeLocalAvailableAttrsRequest{const_cast<Decl *>(this)}, {});
  }

  // We assume all requests that add attributes as a side effect have been made.
  // Requesting ExpandMemberAttributeMacros or similar would be a cycle though,
  // since it inspects availability as part of the request
  return SemanticAvailableAttributes(getAttrs(), this, includeInactive);
}

std::optional<SemanticAvailableAttr>
Decl::getSemanticAvailableAttr(const AvailableAttr *attr) const {
  return evaluateOrDefault(getASTContext().evaluator,
                           SemanticAvailableAttrRequest{attr, this},
                           std::nullopt);
}

std::optional<SemanticAvailableAttr>
Decl::getActiveAvailableAttrForCurrentPlatform() const {
  std::optional<SemanticAvailableAttr> bestAttr;

  for (auto attr : getSemanticAvailableAttrs(/*includingInactive=*/false)) {
    if (!attr.isPlatformSpecific())
      continue;

    // We have an attribute that is active for the platform, but is it more
    // specific than our current best?
    if (!bestAttr || inheritsAvailabilityFromPlatform(
                         attr.getPlatform(), bestAttr->getPlatform())) {
      bestAttr.emplace(attr);
    }
  }

  return bestAttr;
}

std::optional<SemanticAvailableAttr> Decl::getDeprecatedAttr() const {
  auto context = AvailabilityContext::forDeploymentTarget(getASTContext());
  if (auto restriction = context.restrictionForDecl(this)) {
    if (restriction->isDeprecated())
      return restriction->getAttr();
  }

  return std::nullopt;
}

std::optional<SemanticAvailableAttr> Decl::getSoftDeprecatedAttr() const {
  auto context = AvailabilityContext::forDeploymentTarget(getASTContext());
  if (auto restriction = context.restrictionForDecl(
          this, AvailabilityRestrictionFlag::IncludeSoftDeprecation)) {
    if (restriction->isDeprecated())
      return restriction->getAttr();
  }

  return std::nullopt;
}

std::optional<SemanticAvailableAttr> Decl::getNoAsyncAttr() const {
  std::optional<SemanticAvailableAttr> bestAttr;

  for (auto attr : getSemanticAvailableAttrs(/*includingInactive=*/false)) {
    if (!attr.isNoAsync())
      continue;

    if (!bestAttr) {
      // If there is no best attr selected and the attr either has an active
      // platform, or doesn't have one at all, select it.
      bestAttr.emplace(attr);
    } else if (bestAttr && attr.isPlatformSpecific() &&
               bestAttr->isPlatformSpecific() &&
               inheritsAvailabilityFromPlatform(attr.getPlatform(),
                                                bestAttr->getPlatform())) {
      // if they both have a viable platform, use the better one
      bestAttr.emplace(attr);
    } else if (attr.isPlatformSpecific() && !bestAttr->isPlatformSpecific()) {
      // Use the one more specific
      bestAttr.emplace(attr);
    }
  }
  return bestAttr;
}

bool Decl::isUnavailableInCurrentSwiftVersion() const {
  llvm::VersionTuple vers = getASTContext().LangOpts.EffectiveLanguageVersion;
  for (auto attr : getSemanticAvailableAttrs(/*includingInactive=*/false)) {
    if (attr.isSwiftLanguageModeSpecific()) {
      auto introduced = attr.getIntroduced();
      if (introduced && *introduced > vers)
        return true;

      auto obsoleted = attr.getObsoleted();
      if (obsoleted && *obsoleted <= vers)
        return true;
    }
  }

  return false;
}

std::optional<SemanticAvailableAttr> Decl::getUnavailableAttr() const {
  auto context = AvailabilityContext::forDeploymentTarget(getASTContext());
  if (auto restriction = context.restrictionForDecl(this)) {
    if (restriction->isUnavailable())
      return restriction->getAttr();
  }

  return std::nullopt;
}

/// Returns the mutually exclusive root platform domains that must all be
/// unavailable in order for a declaration to be unavailable at runtime.
static llvm::SmallSetVector<AvailabilityDomain, 2>
getRootTargetDomains(const ASTContext &ctx) {
  llvm::SmallSetVector<AvailabilityDomain, 2> domains;

  // Regardless of target platform, binaries built for Embedded do not require
  // compatibility.
  if (ctx.LangOpts.hasFeature(Feature::Embedded))
    return domains;

  auto targetPlatform = swift::targetPlatform(ctx.LangOpts);
  if (targetPlatform != PlatformKind::none)
    domains.insert(
        AvailabilityDomain::forPlatform(targetPlatform).getRootDomain());

  auto targetVariantPlatform = swift::targetVariantPlatform(ctx.LangOpts);
  if (targetVariantPlatform != PlatformKind::none)
    domains.insert(
        AvailabilityDomain::forPlatform(targetVariantPlatform).getRootDomain());

  return domains;
}

/// Returns true if a decl that is unavailable in the given domain must still be
/// emitted to preserve load time ABI compatibility.
static bool
domainRequiresABICompatibleUnavailableDecls(AvailabilityDomain domain,
                                            const ASTContext &ctx) {
  // FIXME: [availability] Restrict ABI compatible unavailable decls to modules
  // compiled with macOS, iOS, watchOS, tvOS, or visionOS target triples. For
  // other targets, unavailable code should always be stripped from binaries.
  return domain.isUniversal() || domain.isPlatform();
}

/// Computes the `DeclRuntimeAvailability` value for `decl` in isolation.
static DeclRuntimeAvailability
computeDeclRuntimeAvailability(const Decl *decl) {
  // Don't trust unavailability on declarations from Clang modules.
  if (isa<ClangModuleUnit>(decl->getDeclContext()->getModuleScopeContext()))
    return DeclRuntimeAvailability::PotentiallyAvailable;

  auto &ctx = decl->getASTContext();
  auto rootTargetDomains = getRootTargetDomains(ctx);
  auto remainingTargetDomains = rootTargetDomains;

  AvailabilityRestrictionFlags flags;

  // Semantic availability was already computed separately for any enclosing
  // extension.
  flags |= AvailabilityRestrictionFlag::SkipEnclosingExtension;

  // FIXME: [availability] Replace IncludeAllDomains with a RuntimeAvailability
  // flag that includes the target variant restrictions and keeps all
  // restrictions from active platforms.
  flags |= AvailabilityRestrictionFlag::IncludeAllDomains;

  auto restrictions =
      AvailabilityContext::forInliningTarget(ctx).allRestrictionsForDecl(decl,
                                                                         flags);

  // First, collect the unavailable domains from the restrictions.
  llvm::SmallVector<AvailabilityDomain, 8> unavailableDomains;
  getRuntimeUnavailableDomains(restrictions, unavailableDomains, ctx);

  // Check whether there are any available attributes that would make the
  // decl available in descendants of the unavailable domains.
  for (auto attr :
       decl->getSemanticAvailableAttrs(/*includingInactive=*/false)) {
    auto domain = attr.getDomain();
    if (llvm::is_contained(unavailableDomains, domain))
      continue;

    llvm::erase_if(unavailableDomains, [domain](auto unavailableDomain) {
      return unavailableDomain.contains(domain);
    });
  }

  // Check the remaining unavailable domains to see if the requirements for
  // runtime unreachability are met.
  auto result = DeclRuntimeAvailability::PotentiallyAvailable;
  for (auto domain : unavailableDomains) {
    // Check whether the constraint is from a relevant domain.
    bool isTargetDomain = rootTargetDomains.contains(domain);
    if (!domain.isActive(ctx) && !isTargetDomain)
      continue;

    // FIXME: [runtime availability] Update this?
    if (!domain.isRoot())
      continue;

    // We've found an unavailable target domain. If all the target domains are
    // unavailable then the decl is unreachable at runtime.
    if (isTargetDomain) {
      remainingTargetDomains.remove(domain);
      if (remainingTargetDomains.empty())
        result = DeclRuntimeAvailability::AlwaysUnavailableABICompatible;

      continue;
    }

    // We've found a single unavailable domain that alone proves the decl is
    // unreachable at runtime. It may still be required at load time, though.
    if (domainRequiresABICompatibleUnavailableDecls(domain, ctx)) {
      result = DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
      continue;
    }

    return DeclRuntimeAvailability::AlwaysUnavailable;
  }

  return result;
}

/// Determines the `DeclRuntimeAvailability` value for `decl` via
/// `DeclRuntimeAvailabilityRequest`.
static DeclRuntimeAvailability getDeclRuntimeAvailability(const Decl *decl) {
  return evaluateOrDefault(decl->getASTContext().evaluator,
                           DeclRuntimeAvailabilityRequest{decl},
                           DeclRuntimeAvailability::PotentiallyAvailable);
}

DeclRuntimeAvailability
DeclRuntimeAvailabilityRequest::evaluate(Evaluator &evaluator,
                                         const Decl *decl) const {
  auto inherited = DeclRuntimeAvailability::PotentiallyAvailable;
  if (auto *parent = decl->parentDeclForAvailability())
    inherited = getDeclRuntimeAvailability(parent);

  // If the inherited runtime availability is already maximally unavailable
  // then skip computing unavailability for this declaration.
  if (inherited == DeclRuntimeAvailability::AlwaysUnavailable)
    return DeclRuntimeAvailability::AlwaysUnavailable;

  auto availability = computeDeclRuntimeAvailability(decl);
  return std::max(inherited, availability);
}

bool Decl::isUnreachableAtRuntime() const {
  return getDeclRuntimeAvailability(this) >=
         DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
}

static UnavailableDeclOptimization
getEffectiveUnavailableDeclOptimization(ASTContext &ctx) {
  if (ctx.LangOpts.UnavailableDeclOptimizationMode.has_value())
    return *ctx.LangOpts.UnavailableDeclOptimizationMode;

  return UnavailableDeclOptimization::None;
}

bool Decl::isAvailableDuringLowering() const {
  auto availability = getDeclRuntimeAvailability(this);

  if (getEffectiveUnavailableDeclOptimization(getASTContext()) !=
      UnavailableDeclOptimization::Complete)
    return availability < DeclRuntimeAvailability::AlwaysUnavailable;

  // All unreachable declarations should be skipped during lowering
  // when -unavailable-decl-optimization=complete is specified.
  return availability < DeclRuntimeAvailability::AlwaysUnavailableABICompatible;
}

bool Decl::requiresUnavailableDeclABICompatibilityStubs() const {
  // Code associated with unavailable declarations should trap at runtime if
  // -unavailable-decl-optimization=stub is specified.
  if (getEffectiveUnavailableDeclOptimization(getASTContext()) !=
      UnavailableDeclOptimization::Stub)
    return false;

  return isUnreachableAtRuntime();
}

AvailabilityRange AvailabilityInference::annotatedAvailableRangeForAttr(
    const Decl *D, const AbstractSpecializeAttr *attr, ASTContext &ctx) {
  std::optional<SemanticAvailableAttr> bestAvailAttr;

  for (auto *availAttr : attr->getAvailableAttrs()) {
    auto semanticAttr = D->getSemanticAvailableAttr(availAttr);
    if (!semanticAttr)
      continue;

    if (!semanticAttr->getIntroduced() || !semanticAttr->isActive(ctx) ||
        !semanticAttr->isPlatformSpecific()) {
      continue;
    }

    if (isBetterThan(*semanticAttr, bestAvailAttr))
      bestAvailAttr.emplace(*semanticAttr);
  }

  if (bestAvailAttr)
    return bestAvailAttr->getIntroducedRange(ctx).value_or(
        AvailabilityRange::alwaysAvailable());

  return AvailabilityRange::alwaysAvailable();
}

std::optional<SemanticAvailableAttr>
Decl::getAvailableAttrForPlatformIntroduction(bool checkExtension) const {
  if (auto attr = getDeclAvailableAttrForPlatformIntroduction(this))
    return attr;

  // Unlike other declarations, extensions can be used without referring to them
  // by name (they don't have one) in the source. For this reason, when checking
  // the available range of a declaration we also need to check to see if it is
  // immediately contained in an extension and use the extension's availability
  // if the declaration does not have an explicit @available attribute
  // itself. This check relies on the fact that we cannot have nested
  // extensions.
  if (!checkExtension)
    return std::nullopt;

  if (auto parent = parentDeclForAvailability()) {
    if (auto *ED = dyn_cast<ExtensionDecl>(parent)) {
      if (auto attr = getDeclAvailableAttrForPlatformIntroduction(ED))
        return attr;
    }
  }

  return std::nullopt;
}

AvailabilityRange AvailabilityInference::availableRange(const Decl *D) {
  if (auto attr = D->getAvailableAttrForPlatformIntroduction())
    return attr->getIntroducedRange(D->getASTContext())
        .value_or(AvailabilityRange::alwaysAvailable());

  return AvailabilityRange::alwaysAvailable();
}

std::optional<SemanticAvailableAttr>
SemanticAvailableAttrRequest::evaluate(swift::Evaluator &evaluator,
                                       const AvailableAttr *attr,
                                       const Decl *decl) const {
  if (attr->getDomainOrIdentifier().isDomain())
    return SemanticAvailableAttr(attr);

  auto &ctx = decl->getASTContext();
  auto &diags = ctx.Diags;
  auto attrLoc = attr->getLocation();
  auto domainLoc = attr->getDomainLoc();
  auto declContext = decl->getInnermostDeclContext();
  auto mutableAttr = const_cast<AvailableAttr *>(attr);
  auto domain = mutableAttr->DomainOrIdentifier.resolveInDeclContext(
      domainLoc, declContext);

  if (!domain)
    return std::nullopt;

  auto checkVersion = [&](std::optional<llvm::VersionTuple> version,
                          SourceRange sourceRange) {
    if (!version)
      return false;

    auto diagLoc = sourceRange.isValid() ? sourceRange.Start : attrLoc;
    if (!VersionRange::isValidVersion(*version)) {
      diags
          .diagnose(diagLoc, diag::availability_unsupported_version_number,
                    *version)
          .highlight(sourceRange);
      return true;
    }

    // Warn if the version is not a valid one for the domain. For example, macOS
    // 17 will never exist.
    if (domain->isVersioned() && !domain->isVersionValid(*version)) {
      diags
          .diagnose(diagLoc,
                    diag::availability_invalid_version_number_for_domain,
                    *version, *domain)
          .highlight(sourceRange);
    }

    return false;
  };

  if (checkVersion(attr->getRawIntroduced(), attr->IntroducedRange))
    return std::nullopt;
  if (checkVersion(attr->getRawDeprecated(), attr->DeprecatedRange))
    return std::nullopt;
  if (checkVersion(attr->getRawObsoleted(), attr->ObsoletedRange))
    return std::nullopt;

  bool hasIntroduced = attr->getRawIntroduced().has_value();
  bool hasDeprecated = attr->getRawDeprecated().has_value();
  auto hasObsoleted = attr->getRawObsoleted().has_value();
  bool hasVersionSpec = (hasIntroduced || hasDeprecated || hasObsoleted);

  if (!domain->isVersioned() && hasVersionSpec) {
    SourceRange versionSourceRange;
    if (hasIntroduced)
      versionSourceRange = attr->IntroducedRange;
    else if (hasDeprecated)
      versionSourceRange = attr->DeprecatedRange;
    else if (hasObsoleted)
      versionSourceRange = attr->ObsoletedRange;

    diags.diagnose(attrLoc, diag::availability_unexpected_version, *domain)
        .limitBehaviorIf(domain->isUniversal(), DiagnosticBehavior::Warning)
        .highlight(versionSourceRange);
    return std::nullopt;
  }

  // Diagnose unsupported unconditional availability (deprecated, unavailable,
  // noasync).
  switch (domain->getKind()) {
  case AvailabilityDomain::Kind::Universal:
  case AvailabilityDomain::Kind::Embedded:
  case AvailabilityDomain::Kind::Custom:
    break;

  case AvailabilityDomain::Kind::Platform:
    // FIXME: [runtime availability] Diagnose Swift runtime platform, too.
    break;

  case AvailabilityDomain::Kind::SwiftLanguageMode:
  case AvailabilityDomain::Kind::StandaloneSwiftRuntime:
  case AvailabilityDomain::Kind::PackageDescription:
    switch (attr->getKind()) {
    case AvailableAttr::Kind::Deprecated:
      diags.diagnose(attrLoc,
                     diag::attr_availability_expected_deprecated_version, attr,
                     *domain);
      return std::nullopt;

    case AvailableAttr::Kind::Unavailable:
      diags.diagnose(attrLoc, diag::attr_availability_cannot_be_used_for_domain,
                     "unavailable", attr, *domain);
      return std::nullopt;

    case AvailableAttr::Kind::NoAsync:
      diags.diagnose(attrLoc, diag::attr_availability_cannot_be_used_for_domain,
                     "noasync", attr, *domain);
      return std::nullopt;

    case AvailableAttr::Kind::Default:
      break;
    }
    break;
  }

  if (!hasVersionSpec && domain->isVersioned()) {
    switch (attr->getKind()) {
    case AvailableAttr::Kind::Default:
      diags.diagnose(domainLoc, diag::attr_availability_expected_version_spec,
                     attr, *domain);
      return std::nullopt;
    case AvailableAttr::Kind::Deprecated:
    case AvailableAttr::Kind::Unavailable:
    case AvailableAttr::Kind::NoAsync:
      break;
    }
  }

  return SemanticAvailableAttr(attr);
}

std::optional<llvm::VersionTuple> SemanticAvailableAttr::getIntroduced() const {
  if (auto version = attr->getRawIntroduced())
    return canonicalizePlatformVersion(getPlatform(), *version);
  return std::nullopt;
}

std::optional<AvailabilityDomainAndRange>
SemanticAvailableAttr::getIntroducedDomainAndRange(
    const ASTContext &Ctx) const {
  auto domain = getDomain();
  if (domain.isUniversal())
    return std::nullopt;

  if (auto introduced = getIntroduced())
    return domain.getRemappedDomainAndRange(
        *introduced, AvailabilityVersionKind::Introduced, Ctx);

  // For versioned domains, an "introduced:" version is always required to
  // indicate introduction.
  if (domain.isVersioned())
    return std::nullopt;

  // For version-less domains, an attribute that does not indicate some other
  // kind of unconditional availability restriction implicitly specifies that
  // the decl is available in all versions of the domain.
  switch (attr->getKind()) {
  case AvailableAttr::Kind::Default:
    return AvailabilityDomainAndRange(domain.getRemappedDomain(Ctx),
                                      AvailabilityRange::alwaysAvailable());
  case AvailableAttr::Kind::Deprecated:
  case AvailableAttr::Kind::Unavailable:
  case AvailableAttr::Kind::NoAsync:
    return std::nullopt;
  }
}

std::optional<llvm::VersionTuple> SemanticAvailableAttr::getDeprecated() const {
  if (auto version = attr->getRawDeprecated())
    return canonicalizePlatformVersion(getPlatform(), *version);
  return std::nullopt;
}

std::optional<AvailabilityDomainAndRange>
SemanticAvailableAttr::getDeprecatedDomainAndRange(
    const ASTContext &Ctx) const {
  if (auto deprecated = getDeprecated())
    return getDomain().getRemappedDomainAndRange(
        *deprecated, AvailabilityVersionKind::Deprecated, Ctx);

  // Regardless of the whether the domain supports versions or not, an
  // unconditional deprecation attribute indicates the decl is always
  // deprecated.
  if (isUnconditionallyDeprecated())
    return AvailabilityDomainAndRange(getDomain().getRemappedDomain(Ctx),
                                      AvailabilityRange::alwaysAvailable());

  return std::nullopt;
}

std::optional<llvm::VersionTuple> SemanticAvailableAttr::getObsoleted() const {
  if (auto version = attr->getRawObsoleted())
    return canonicalizePlatformVersion(getPlatform(), *version);
  return std::nullopt;
}

std::optional<AvailabilityDomainAndRange>
SemanticAvailableAttr::getObsoletedDomainAndRange(const ASTContext &Ctx) const {
  if (auto obsoleted = getObsoleted())
    return getDomain().getRemappedDomainAndRange(
        *obsoleted, AvailabilityVersionKind::Obsoleted, Ctx);

  // An "unavailable" attribute effectively means obsolete in all versions.
  if (isUnconditionallyUnavailable())
    return AvailabilityDomainAndRange(getDomain().getRemappedDomain(Ctx),
                                      AvailabilityRange::alwaysAvailable());

  return std::nullopt;
}

namespace {
/// Infers the availability required to access a type.
class AvailabilityInferenceTypeWalker : public TypeWalker {
public:
  AvailabilityRange AvailabilityInfo = AvailabilityRange::alwaysAvailable();

  Action walkToTypePre(Type ty) override {
    if (auto *nominalDecl = ty->getAnyNominal()) {
      AvailabilityInfo.intersectWith(
          AvailabilityInference::availableRange(nominalDecl));
    }

    return Action::Continue;
  }
};
} // end anonymous namespace

AvailabilityRange AvailabilityInference::inferForType(Type t) {
  AvailabilityInferenceTypeWalker walker;
  t.walk(walker);
  return walker.AvailabilityInfo;
}

AvailabilityRange ASTContext::getSwiftFutureAvailability() const {
  auto target = LangOpts.Target;

  auto getFutureAvailabilityRange = []() -> AvailabilityRange {
    return AvailabilityRange(llvm::VersionTuple(99, 99, 0));
  };

  if (target.isMacOSX()) {
    return getFutureAvailabilityRange();
  } else if (target.isiOS()) {
    return getFutureAvailabilityRange();
  } else if (target.isWatchOS()) {
    return getFutureAvailabilityRange();
  } else if (target.isXROS()) {
    return getFutureAvailabilityRange();
  } else {
    return AvailabilityRange::alwaysAvailable();
  }
}

AvailabilityRange ASTContext::getSwiftAvailability(unsigned major,
                                                   unsigned minor) const {
  auto target = LangOpts.Target;

  // Deal with special cases for Swift 5.3 and lower
  if (major == 5 && minor <= 3) {
    if (target.getArchName() == "arm64e")
      return AvailabilityRange::alwaysAvailable();
    if (target.isMacOSX() && target.isAArch64())
      return AvailabilityRange::alwaysAvailable();
    if (target.isiOS() && target.isAArch64()
        && (target.isSimulatorEnvironment()
            || target.isMacCatalystEnvironment()))
      return AvailabilityRange::alwaysAvailable();
    if (target.isWatchOS() && target.isArch64Bit())
      return AvailabilityRange::alwaysAvailable();
  }

  switch (major) {
#define MAJOR_VERSION(V) case V: switch (minor) {
#define END_MAJOR_VERSION(V) } break;
#define PLATFORM(P, V)                                                         \
  if (IS_PLATFORM(P))                                                          \
    return AvailabilityRange(VersionRange::allGTE(llvm::VersionTuple V));
#define IS_PLATFORM(P) PLATFORM_TEST_##P
#define FUTURE                  return getSwiftFutureAvailability();
#define PLATFORM_TEST_macOS     target.isMacOSX()
#define PLATFORM_TEST_iOS       target.isiOS()
#define PLATFORM_TEST_watchOS   target.isWatchOS()
#define PLATFORM_TEST_visionOS  target.isXROS()

#define _SECOND(A, B) B
#define SECOND(T) _SECOND T

#define RUNTIME_VERSION(V, PLATFORMS)                                          \
  case SECOND(V):                                                              \
    PLATFORMS                                                                  \
    return AvailabilityRange::alwaysAvailable();

#include "swift/AST/RuntimeVersions.def"

#undef PLATFORM_TEST_macOS
#undef PLATFORM_TEST_iOS
#undef PLATFORM_TEST_watchOS
#undef PLATFORM_TEST_visionOS
#undef _SECOND
#undef SECOND

  case 99:
    if (minor == 99)
      return getSwiftFutureAvailability();
    break;
  }

  llvm::report_fatal_error(
    Twine("Missing runtime version data for Swift ") +
    Twine(major) + Twine('.') + Twine(minor));
}

bool ASTContext::supportsVersionedAvailability() const {
  return minimumAvailableOSVersionForTriple(LangOpts.Target).has_value();
}

ExportedLevel swift::isExported(const Decl *D) {
  if (auto *VD = dyn_cast<ValueDecl>(D)) {
    return isExported(VD);
  }
  if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
    for (unsigned i = 0, e = PBD->getNumPatternEntries(); i < e; ++i) {
      if (auto *VD = PBD->getAnchoringVarDecl(i))
        return isExported(VD);
    }

    return ExportedLevel::None;
  }
  if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
    return isExported(ED);
  }

  return ExportedLevel::Exported;
}

ExportedLevel swift::isExported(const ValueDecl *VD) {
  if (VD->getAttrs().hasAttribute<ImplementationOnlyAttr>()) {
    // Special case stored properties of classes to avoid the short-circuit.
    auto varDecl = dyn_cast<VarDecl>(VD);
    if (!varDecl || !varDecl->hasStorage() ||
        !isa<ClassDecl>(varDecl->getDeclContext()))
      return ExportedLevel::None;
  }
  if (VD->isObjCMemberImplementation())
    return ExportedLevel::None;

  // Is this part of the module's API or ABI?
  AccessScope accessScope =
      VD->getFormalAccessScope(nullptr,
                               /*treatUsableFromInlineAsPublic*/ true);
  if (accessScope.isPublic())
    return ExportedLevel::Exported;

  // Is this a stored property in a @frozen struct or class?
  if (auto *property = dyn_cast<VarDecl>(VD))
    return property->isLayoutExposedToClients();

  // Case of an enum not marked @_implementationOnly in a non-resilient module?
  if (auto *EED = dyn_cast<EnumElementDecl>(VD))
    return isExported(EED->getParentEnum());

  // Is this a type exposed by default in a non-resilient module?
  if (isa<NominalTypeDecl>(VD) &&
      VD->getDeclContext()->getParentModule()->getResilienceStrategy() !=
          ResilienceStrategy::Resilient &&
      !VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
    return ExportedLevel::ImplicitlyExported;

  return ExportedLevel::None;
}

bool swift::hasConformancesToPublicProtocols(const ExtensionDecl *ED) {
  auto nominal = ED->getExtendedNominal();
  if (!nominal)
    return false;

  // Extensions of protocols cannot introduce additional conformances.
  if (isa<ProtocolDecl>(nominal))
    return false;

  auto protocols = ED->getLocalProtocols(ConformanceLookupKind::OnlyExplicit);
  for (const ProtocolDecl *PD : protocols) {
    AccessScope scope =
        PD->getFormalAccessScope(/*useDC*/ nullptr,
                                 /*treatUsableFromInlineAsPublic*/ true);
    if (scope.isPublic())
      return true;
  }

  return false;
}

ExportedLevel swift::isExported(const ExtensionDecl *ED) {
  // An extension can only be exported if it extends an exported type.
  ExportedLevel exported = ExportedLevel::None;
  if (auto *NTD = ED->getExtendedNominal()) {
    exported = isExported(NTD);
    if (exported == ExportedLevel::None)
      return exported;
  }

  // If the extension declares a conformance to a public protocol then the
  // extension is exported.
  if (hasConformancesToPublicProtocols(ED))
    return exported;

  // If there are any exported members then the extension is exported.
  ExportedLevel membersExported = ExportedLevel::None;
  for (const Decl *D : ED->getMembers())
    membersExported = std::max(membersExported, isExported(D));

  return std::min(exported, membersExported);
}
