//===--- AvailabilityScopeBuilder.cpp - Swift Availability Scope Builder --===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 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 implements the AvailabilityScope::buildForSourceFile() function.
//
//===----------------------------------------------------------------------===//

#include "swift/AST/AvailabilityScope.h"

#include "swift/AST/ASTContext.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/AvailabilityConstraint.h"
#include "swift/AST/AvailabilityInference.h"
#include "swift/AST/AvailabilitySpec.h"
#include "swift/AST/Decl.h"
#include "swift/AST/DeclExportabilityVisitor.h"
#include "swift/AST/DiagnosticsParse.h"
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/PrettyStackTrace.h"
#include "swift/AST/TypeCheckRequests.h"
#include "swift/Parse/Lexer.h"

using namespace swift;

static bool computeContainedByDeploymentTarget(AvailabilityScope *scope,
                                               ASTContext &ctx) {
  return scope->getPlatformAvailabilityRange().isContainedIn(
      AvailabilityRange::forDeploymentTarget(ctx));
}

namespace {
/// A class that walks the AST to build the availability scope tree.
class AvailabilityScopeBuilder : private ASTWalker {
  ASTContext &Context;

  /// Represents an entry in a stack of active availability scopes. The stack is
  /// used to facilitate building the availability scope tree structure. A new
  /// scope is pushed onto this stack before visiting children whenever the
  /// current AST node requires a new context and the scope is then popped
  /// post-visitation.
  struct ContextInfo {
    AvailabilityScope *Scope;

    /// The AST node. This node can be null (ParentTy()),
    /// indicating that custom logic elsewhere will handle removing
    /// the context when needed.
    ParentTy ScopeNode;

    bool ContainedByDeploymentTarget;
  };
  std::vector<ContextInfo> ContextStack;

  llvm::SmallVector<const Decl *, 4> ConcreteDeclStack;

  /// Represents an entry in a stack of pending decl body availability scopes.
  /// Scopes in this stack should be pushed onto \p ContextStack when
  /// \p BodyStmt is encountered.
  struct DeclBodyContextInfo {
    Decl *Decl;
    llvm::DenseMap<ASTNode, AvailabilityScope *> BodyScopes;
  };
  std::vector<DeclBodyContextInfo> DeclBodyContextStack;

  std::vector<const DeclContext *> DeclContextStack;

  AvailabilityScope *getCurrentScope() { return ContextStack.back().Scope; }

  const DeclContext *getCurrentDeclContext() const {
    assert(!DeclContextStack.empty());
    return DeclContextStack.back();
  }

  bool isCurrentScopeContainedByDeploymentTarget() {
    return ContextStack.back().ContainedByDeploymentTarget;
  }

  const AvailabilityContext constrainCurrentAvailabilityWithPlatformRange(
      const AvailabilityRange &platformRange) {
    auto availability = getCurrentScope()->getAvailabilityContext();
    availability.constrainWithPlatformRange(platformRange, Context);
    return availability;
  }

  const AvailabilityContext constrainCurrentAvailabilityWithContext(
      const AvailabilityContext &otherContext) {
    auto availability = getCurrentScope()->getAvailabilityContext();
    availability.constrainWithContext(otherContext, Context);
    return availability;
  }

  void pushContext(AvailabilityScope *scope, ParentTy popAfterNode) {
    ContextInfo info;
    info.Scope = scope;
    info.ScopeNode = popAfterNode;

    if (!ContextStack.empty() && isCurrentScopeContainedByDeploymentTarget()) {
      assert(computeContainedByDeploymentTarget(scope, Context) &&
             "incorrectly skipping computeContainedByDeploymentTarget()");
      info.ContainedByDeploymentTarget = true;
    } else {
      info.ContainedByDeploymentTarget =
          computeContainedByDeploymentTarget(scope, Context);
    }

    ContextStack.push_back(info);
  }

  void pushDeclBodyContext(
      Decl *decl, llvm::SmallVector<std::pair<ASTNode, AvailabilityScope *>, 4>
                      nodesAndScopes) {
    DeclBodyContextInfo info;
    info.Decl = decl;
    for (auto nodeAndScope : nodesAndScopes) {
      info.BodyScopes.insert(nodeAndScope);
    }

    DeclBodyContextStack.push_back(info);
  }

  const char *stackTraceAction() const {
    return "building availability scope for";
  }

  friend class swift::ExpandChildAvailabilityScopesRequest;

public:
  AvailabilityScopeBuilder(AvailabilityScope *scope, ASTContext &ctx)
      : Context(ctx) {
    assert(scope);
    pushContext(scope, ParentTy());
    DeclContextStack.push_back(scope->getIntroductionNode().getDeclContext());
  }

  void build(Decl *decl) {
    PrettyStackTraceDecl trace(stackTraceAction(), decl);
    unsigned stackHeight = ContextStack.size();
    decl->walk(*this);
    assert(ContextStack.size() == stackHeight);
    (void)stackHeight;
  }

  void build(Stmt *stmt) {
    PrettyStackTraceStmt trace(Context, stackTraceAction(), stmt);
    unsigned stackHeight = ContextStack.size();
    stmt->walk(*this);
    assert(ContextStack.size() == stackHeight);
    (void)stackHeight;
  }

  void build(Expr *expr) {
    PrettyStackTraceExpr trace(Context, stackTraceAction(), expr);
    unsigned stackHeight = ContextStack.size();
    expr->walk(*this);
    assert(ContextStack.size() == stackHeight);
    (void)stackHeight;
  }

private:
  MacroWalking getMacroWalkingBehavior() const override {
    // Expansion buffers will have their type availability scopes built lazily.
    return MacroWalking::Arguments;
  }

  /// Check whether this declaration is within a macro expansion buffer that
  /// will have its own availability scope that will be lazily expanded.
  bool isDeclInMacroExpansion(Decl *decl) const override {
    // If it's not in a macro expansion relative to its context, it's not
    // considered to be in a macro expansion.
    if (!decl->isInMacroExpansionInContext())
      return false;

    auto parentModule = decl->getDeclContext()->getParentModule();
    auto *declFile =
        parentModule->getSourceFileContainingLocation(decl->getLoc());
    if (!declFile)
      return false;

    // Look for a parent context that implies that we are producing an
    // availability scope for this expansion.
    for (auto iter = ContextStack.rbegin(), endIter = ContextStack.rend();
         iter != endIter; ++iter) {
      const auto &context = *iter;
      if (auto scope = context.Scope) {
        // If the context is the same source file, don't treat it as an
        // expansion.
        auto introNode = scope->getIntroductionNode();
        switch (scope->getReason()) {
        case AvailabilityScope::Reason::Root:
          if (auto contextFile = introNode.getAsSourceFile())
            if (declFile == contextFile)
              return false;

          break;

        case AvailabilityScope::Reason::Decl:
        case AvailabilityScope::Reason::DeclImplicit:
          // If the context is a declaration, check whether the declaration
          // is in the same source file as this declaration.
          if (auto contextDecl = introNode.getAsDecl()) {
            if (decl == contextDecl)
              return false;

            auto contextModule =
                contextDecl->getDeclContext()->getParentModule();
            SourceLoc contextDeclLoc = contextDecl->getLoc();
            auto contextDeclFile =
                contextModule->getSourceFileContainingLocation(contextDeclLoc);
            if (declFile == contextDeclFile)
              return false;
          }
          break;

        case AvailabilityScope::Reason::IfStmtThenBranch:
        case AvailabilityScope::Reason::IfStmtElseBranch:
        case AvailabilityScope::Reason::ConditionFollowingAvailabilityQuery:
        case AvailabilityScope::Reason::GuardStmtFallthrough:
        case AvailabilityScope::Reason::GuardStmtElseBranch:
        case AvailabilityScope::Reason::WhileStmtBody:
          // Nothing to check here.
          break;
        }
      }
    }

    return true;
  }

  bool shouldSkipDecl(Decl *decl) const {
    // Only visit a node that has a corresponding concrete syntax node if we are
    // already walking that concrete syntax node.
    auto *concreteDecl = decl->getConcreteSyntaxDeclForAttributes();
    if (concreteDecl != decl) {
      if (ConcreteDeclStack.empty() || ConcreteDeclStack.back() != concreteDecl)
        return true;
    }

    return false;
  }

  PreWalkAction walkToDeclPre(Decl *decl) override {
    PrettyStackTraceDecl trace(stackTraceAction(), decl);

    // Implicit decls don't have source locations so they cannot have a scope.
    // However, some implicit nodes contain non-implicit nodes (e.g. defer
    // blocks) so we must continue through them.
    if (!decl->isImplicit()) {
      if (shouldSkipDecl(decl))
        return Action::SkipNode();

      // The AST of this decl may not be ready to traverse yet if it hasn't been
      // full typechecked. If that's the case, we leave a placeholder node in
      // the tree to indicate that the subtree should be expanded lazily when it
      // needs to be traversed.
      if (buildLazyContextForDecl(decl))
        return Action::SkipNode();

      // Adds in a scope that covers the entire declaration.
      if (auto declScope = getNewContextForSignatureOfDecl(decl)) {
        pushContext(declScope, decl);
      }

      // Create scopes that cover only the body of the declaration.
      buildContextsForBodyOfDecl(decl);
    }

    if (auto *declContext = dyn_cast<DeclContext>(decl)) {
      DeclContextStack.push_back(declContext);
    }

    // If this decl is the concrete syntax decl for some abstract syntax decl,
    // push it onto the stack so that the abstract syntax decls may be visited.
    auto *abstractDecl = decl->getAbstractSyntaxDeclForAttributes();
    if (abstractDecl != decl) {
      ConcreteDeclStack.push_back(decl);
    }
    return Action::Continue();
  }

  PostWalkAction walkToDeclPost(Decl *decl) override {
    if (!ConcreteDeclStack.empty() && ConcreteDeclStack.back() == decl) {
      ConcreteDeclStack.pop_back();
    }

    if (auto *declContext = dyn_cast<DeclContext>(decl)) {
      assert(DeclContextStack.back() == declContext);
      DeclContextStack.pop_back();
    }

    while (ContextStack.back().ScopeNode.getAsDecl() == decl) {
      ContextStack.pop_back();
    }

    while (!DeclBodyContextStack.empty() &&
           DeclBodyContextStack.back().Decl == decl) {
      // All pending body scopes should have been consumed.
      assert(DeclBodyContextStack.back().BodyScopes.empty());
      DeclBodyContextStack.pop_back();
    }

    return Action::Continue();
  }

  bool shouldBuildLazyContextForDecl(Decl *decl) {
    // Skip functions that have unparsed bodies on an initial descent to avoid
    // eagerly parsing bodies unnecessarily.
    if (auto *afd = dyn_cast<AbstractFunctionDecl>(decl)) {
      if (afd->hasBody() && !afd->isBodySkipped() &&
          !afd->getBody(/*canSynthesize=*/false))
        return true;
    }

    // Pattern binding declarations may have attached property wrappers that
    // get expanded from macros attached to the parent declaration. We must
    // not eagerly expand the attached property wrappers to avoid request
    // cycles.
    if (isa<PatternBindingDecl>(decl))
      return true;

    if (isa<ExtensionDecl>(decl))
      return true;

    return false;
  }

  /// For declarations that were previously skipped prepare the AST before
  /// building out scopes.
  void prepareDeclForLazyExpansion(Decl *decl) {
    if (auto afd = dyn_cast<AbstractFunctionDecl>(decl))
      (void)afd->getBody(/*canSynthesize=*/true);
  }

  /// Constructs a placeholder scope that should be expanded later. This is
  /// useful for postponing unnecessary work (and request triggers) when
  /// initally building out the scope subtree under a declaration. Lazy nodes
  /// constructed here will be expanded by ExpandChildAvailabilityScopesRequest.
  /// Returns true if a node was created.
  bool buildLazyContextForDecl(Decl *decl) {
    // Check whether the current scope is already a lazy placeholder. If it is,
    // we should try to expand it rather than creating a new placeholder.
    auto currentScope = getCurrentScope();
    if (currentScope->getNeedsExpansion() &&
        currentScope->getDeclOrNull() == decl)
      return false;

    if (!shouldBuildLazyContextForDecl(decl))
      return false;

    // If we've made it this far then we've identified a declaration that
    // requires lazy expansion later.
    auto lazyScope = AvailabilityScope::createForDeclImplicit(
        Context, decl, currentScope, currentScope->getAvailabilityContext(),
        refinementSourceRangeForDecl(decl));
    lazyScope->setNeedsExpansion(true);
    return true;
  }

  /// Returns a new context to be introduced for the declaration, or nullptr
  /// if no new context should be introduced.
  AvailabilityScope *getNewContextForSignatureOfDecl(Decl *decl) {
    if (!isa<ValueDecl>(decl) && !isa<EnumCaseDecl>(decl) &&
        !isa<ExtensionDecl>(decl) && !isa<MacroExpansionDecl>(decl) &&
        !isa<PatternBindingDecl>(decl))
      return nullptr;

    // Only introduce for an AbstractStorageDecl if it is not local. We
    // introduce for the non-local case because these may have getters and
    // setters (and these may be synthesized, so they might not even exist yet).
    if (isa<AbstractStorageDecl>(decl) &&
        decl->getDeclContext()->isLocalContext())
      return nullptr;

    // Don't introduce for abstract syntax nodes that have separate concrete
    // syntax nodes. The scope will be introduced for the concrete node instead.
    if (decl->getConcreteSyntaxDeclForAttributes() != decl)
      return nullptr;

    // Declarations with explicit availability attributes always get a scope.
    if (decl->hasAnyActiveAvailableAttr()) {
      return AvailabilityScope::createForDecl(
          Context, decl, getCurrentScope(),
          getEffectiveAvailabilityForDeclSignature(decl),
          refinementSourceRangeForDecl(decl));
    }

    // Declarations without explicit availability attributes get a scope if they
    // are effectively less available than the surrounding context. For example,
    // an internal property in a public struct can be effectively less available
    // than the containing struct decl because the internal property will only
    // be accessed by code running at the deployment target or later.
    auto currentAvailability = getCurrentScope()->getAvailabilityContext();
    auto effectiveAvailability = getEffectiveAvailabilityForDeclSignature(decl);
    if (currentAvailability != effectiveAvailability)
      return AvailabilityScope::createForDeclImplicit(
          Context, decl, getCurrentScope(), effectiveAvailability,
          refinementSourceRangeForDecl(decl));

    return nullptr;
  }

  const AvailabilityContext
  getEffectiveAvailabilityForDeclSignature(const Decl *decl) {
    auto effectiveIntroduction = AvailabilityRange::alwaysAvailable();

    // Availability attributes are found on abstract syntax decls.
    decl = decl->getAbstractSyntaxDeclForAttributes();

    // As a special case, extension decls are treated as effectively as
    // available as the nominal type they extend, up to the deployment target.
    // This rule is a convenience for library authors who have written
    // extensions without specifying platform availability on the extension
    // itself.
    if (auto *extension = dyn_cast<ExtensionDecl>(decl)) {
      auto extendedType = extension->getExtendedType();
      if (extendedType && !decl->hasAnyMatchingActiveAvailableAttr(
                              [](SemanticAvailableAttr attr) -> bool {
                                return attr.getDomain().isPlatform();
                              })) {
        effectiveIntroduction.intersectWith(
            swift::AvailabilityInference::inferForType(extendedType));

        // We want to require availability to be specified on extensions of
        // types that would be potentially unavailable to the module containing
        // the extension, so limit the effective availability to the deployment
        // target.
        effectiveIntroduction.unionWith(
            AvailabilityRange::forDeploymentTarget(Context));
      }
    }

    if (shouldConstrainSignatureToDeploymentTarget(decl))
      effectiveIntroduction.intersectWith(
          AvailabilityRange::forDeploymentTarget(Context));

    auto availability = getCurrentScope()->getAvailabilityContext();
    availability.constrainWithDeclAndPlatformRange(decl, effectiveIntroduction);
    return availability;
  }

  /// Checks whether the entire declaration, including its signature, should be
  /// constrained to the deployment target. Generally public API declarations
  /// are not constrained since they appear in the interface of the module and
  /// may be consumed by clients with lower deployment targets, but there are
  /// some exceptions.
  bool shouldConstrainSignatureToDeploymentTarget(const Decl *decl) {
    if (isCurrentScopeContainedByDeploymentTarget())
      return false;

    // A declaration inside of a local context always inherits the availability
    // of the parent.
    if (decl->getDeclContext()->isLocalContext())
      return false;

    // As a convenience, explicitly unavailable decls are constrained to the
    // deployment target. There's not much benefit to checking these decls at a
    // lower availability version floor since they can't be invoked by clients.
    auto context = getCurrentScope()->getAvailabilityContext();
    if (context.isUnavailable())
      return true;

    // Check whether the decl is unavailable relative to the current context.
    if (auto constraint = getAvailabilityConstraintsForDecl(decl, context)
                              .getPrimaryConstraint()) {
      if (constraint->isUnavailable())
        return true;
    }

    // To remain compatible with a lot of existing SPIs that are declared
    // without availability attributes, constrain them to the deployment target
    // too.
    if (decl->isSPI())
      return true;

    return isExported(decl) == ExportedLevel::None;
  }

  /// Returns the source range which should be refined by declaration. This
  /// provides a convenient place to specify the refined range when it is
  /// different than the declaration's source range.
  SourceRange refinementSourceRangeForDecl(Decl *decl) {
    // We require a valid range in order to be able to query for the scope
    // corresponding to a given SourceLoc.
    // If this assert fires, it means we have probably synthesized an implicit
    // declaration without location information. The appropriate fix is
    // probably to gin up a source range for the declaration when synthesizing
    // it.
    assert(decl->getSourceRange().isValid());

    auto &ctx = decl->getASTContext();
    SourceRange range;
    if (auto *storageDecl = dyn_cast<AbstractStorageDecl>(decl)) {
      // Use the declaration's availability for the context when checking
      // the bodies of its accessors.
      range = storageDecl->getSourceRange();

      // HACK: For synthesized trivial accessors we may have not a valid
      // location for the end of the braces, so in that case we will fall back
      // to using the range for the storage declaration. The right fix here is
      // to update AbstractStorageDecl::addTrivialAccessors() to take brace
      // locations and have callers of that method provide appropriate source
      // locations.
      SourceRange bracesRange = storageDecl->getBracesRange();
      if (bracesRange.isValid()) {
        range.widen(bracesRange);
      }
    } else {
      range = decl->getSourceRangeIncludingAttrs();
    }

    range.End = Lexer::getLocForEndOfToken(ctx.SourceMgr, range.End);
    return range;
  }

  /// Enumerate the AST nodes and their corresponding source ranges for
  /// the body (or bodies) of the given declaration.
  void enumerateBodyRanges(
      Decl *decl,
      llvm::function_ref<void(Decl *decl, ASTNode body, SourceRange)>
          acceptBody) {
    // Top level code always uses the deployment target.
    if (auto tlcd = dyn_cast<TopLevelCodeDecl>(decl)) {
      if (auto bodyStmt = tlcd->getBody()) {
        acceptBody(tlcd, bodyStmt, refinementSourceRangeForDecl(tlcd));
      }
      return;
    }

    // For functions, provide the body source range.
    if (auto afd = dyn_cast<AbstractFunctionDecl>(decl)) {
      if (!afd->isImplicit()) {
        if (auto body = afd->getBody(/*canSynthesize=*/false)) {
          acceptBody(afd, body, afd->getBodySourceRange());
        }
      }
      return;
    }

    // Pattern binding declarations have initial values that are their
    // bodies.
    if (auto *pbd = dyn_cast<PatternBindingDecl>(decl)) {
      for (unsigned index : range(pbd->getNumPatternEntries())) {
        auto var = pbd->getAnchoringVarDecl(index);
        if (!var)
          continue;

        auto *initExpr = pbd->getInit(index);
        if (initExpr && !initExpr->isImplicit()) {
          assert(initExpr->getSourceRange().isValid());

          // Create a scope for the init written in the source.
          acceptBody(var, initExpr, initExpr->getSourceRange());
        }
      }
      return;
    }
  }

  /// Creates an implicit decl scope specifying the deployment target for
  /// `range` in `decl`.
  AvailabilityScope *
  createImplicitDeclContextForDeploymentTarget(Decl *decl, SourceRange range) {
    auto availability = constrainCurrentAvailabilityWithPlatformRange(
        AvailabilityRange::forDeploymentTarget(Context));
    return AvailabilityScope::createForDeclImplicit(
        Context, decl, getCurrentScope(), availability, range);
  }

  /// Determine whether the body of the given declaration has
  /// deployment-target availability.
  static bool bodyIsDeploymentTarget(Decl *decl) {
    if (auto afd = dyn_cast<AbstractFunctionDecl>(decl)) {
      return afd->getResilienceExpansion() != ResilienceExpansion::Minimal;
    }

    if (auto var = dyn_cast<VarDecl>(decl)) {
      // Var decls may have associated pattern binding decls or property
      // wrappers with init expressions. Those expressions need to be
      // constrained to the deployment target unless they are exposed to
      // clients.
      return var->hasInitialValue() && !var->isInitExposedToClients();
    }

    return true;
  }

  void buildContextsForBodyOfDecl(Decl *decl) {
    // If we are already constrained by the deployment target then adding
    // new contexts won't change availability.
    if (isCurrentScopeContainedByDeploymentTarget())
      return;

    // Enumerate all of the body scopes to apply availability.
    llvm::SmallVector<std::pair<ASTNode, AvailabilityScope *>, 4>
        nodesAndScopes;
    enumerateBodyRanges(decl, [&](Decl *decl, ASTNode body, SourceRange range) {
      auto availability = getCurrentScope()->getAvailabilityContext();

      // Apply deployment-target availability if appropriate for this body.
      if (!isCurrentScopeContainedByDeploymentTarget() &&
          bodyIsDeploymentTarget(decl)) {
        // Also constrain availability with the decl itself to handle the case
        // where the decl becomes obsolete at the deployment target.
        availability.constrainWithDeclAndPlatformRange(
            decl, AvailabilityRange::forDeploymentTarget(Context));
      }

      nodesAndScopes.push_back(
          {body, AvailabilityScope::createForDeclImplicit(
                     Context, decl, getCurrentScope(), availability, range)});
    });

    if (nodesAndScopes.size() > 0)
      pushDeclBodyContext(decl, nodesAndScopes);

    if (!isCurrentScopeContainedByDeploymentTarget()) {
      // Pattern binding declarations can have children corresponding to
      // property wrappers, which we handle separately.
      if (auto *pbd = dyn_cast<PatternBindingDecl>(decl)) {
        // Ideally any init expression would be returned by `getInit()` above.
        // However, for property wrappers it doesn't get populated until
        // typechecking completes (which is too late). Instead, we find the
        // the property wrapper attribute and use its source range to create a
        // scope for the initializer expression.
        //
        // FIXME: Since we don't have an expression here, we can't build out its
        // scope. If the Expr that will eventually be created contains a closure
        // expression, then it might have AST nodes that need to be refined. For
        // example, property wrapper initializers that takes block arguments
        // are not handled correctly because of this (rdar://77841331).
        if (auto firstVar = pbd->getAnchoringVarDecl(0)) {
          if (firstVar->hasInitialValue() &&
              !firstVar->isInitExposedToClients()) {
            for (auto *wrapper : firstVar->getAttachedPropertyWrappers()) {
              createImplicitDeclContextForDeploymentTarget(firstVar,
                                                           wrapper->getRange());
            }
          }
        }
      }
    }
  }

  PreWalkResult<Stmt *> walkToStmtPre(Stmt *stmt) override {
    PrettyStackTraceStmt trace(Context, stackTraceAction(), stmt);

    if (consumeDeclBodyContextIfNecessary(stmt)) {
      return Action::Continue(stmt);
    }

    if (auto *ifStmt = dyn_cast<IfStmt>(stmt)) {
      buildIfStmtRefinementContext(ifStmt);
      return Action::SkipNode(stmt);
    }

    if (auto *guardStmt = dyn_cast<GuardStmt>(stmt)) {
      buildGuardStmtRefinementContext(guardStmt);
      return Action::SkipNode(stmt);
    }

    if (auto *whileStmt = dyn_cast<WhileStmt>(stmt)) {
      buildWhileStmtRefinementContext(whileStmt);
      return Action::SkipNode(stmt);
    }

    return Action::Continue(stmt);
  }

  PostWalkResult<Stmt *> walkToStmtPost(Stmt *stmt) override {
    // If we have multiple guard statements in the same block
    // then we may have multiple availability scopes to pop
    // after walking that block.
    while (!ContextStack.empty() &&
           ContextStack.back().ScopeNode.getAsStmt() == stmt) {
      ContextStack.pop_back();
    }

    return Action::Continue(stmt);
  }

  /// Attempts to consume a scope from the `BodyScopes` of the top of
  /// `DeclBodyContextStack`. Returns \p true if a scope was pushed.
  template <typename T>
  bool consumeDeclBodyContextIfNecessary(T body) {
    if (DeclBodyContextStack.empty())
      return false;

    auto &info = DeclBodyContextStack.back();
    auto iter = info.BodyScopes.find(body);
    if (iter == info.BodyScopes.end())
      return false;

    pushContext(iter->getSecond(), body);
    info.BodyScopes.erase(iter);
    return true;
  }

  /// Builds the availability scope hierarchy for the IfStmt if the guard
  /// introduces a new scope for the Then branch.
  /// There is no need for the caller to explicitly traverse the children
  /// of this node.
  void buildIfStmtRefinementContext(IfStmt *ifStmt) {
    std::optional<AvailabilityContext> thenContext;
    std::optional<AvailabilityContext> elseContext;
    std::tie(thenContext, elseContext) =
        buildStmtConditionRefinementContext(ifStmt->getCond());

    if (thenContext.has_value()) {
      // Create a new context for the Then branch and traverse it in that new
      // context.
      auto availabilityContext =
          constrainCurrentAvailabilityWithContext(*thenContext);
      auto *thenScope = AvailabilityScope::createForIfStmtThen(
          Context, ifStmt, getCurrentDeclContext(), getCurrentScope(),
          availabilityContext);
      AvailabilityScopeBuilder(thenScope, Context).build(ifStmt->getThenStmt());
    } else {
      build(ifStmt->getThenStmt());
    }

    Stmt *elseStmt = ifStmt->getElseStmt();
    if (!elseStmt)
      return;

    // Refine the else branch if we're given a version range for that branch.
    // For now, if present, this will only be the empty range, indicating
    // that the branch is dead. We use it to suppress potential unavailability
    // and deprecation diagnostics on code that definitely will not run with
    // the current platform and minimum deployment target.
    // If we add a more precise version range lattice (i.e., one that can
    // support "<") we should create non-empty contexts for the Else branch.
    if (elseContext.has_value()) {
      // Create a new context for the Then branch and traverse it in that new
      // context.
      auto availabilityContext =
          constrainCurrentAvailabilityWithContext(*elseContext);
      auto *elseScope = AvailabilityScope::createForIfStmtElse(
          Context, ifStmt, getCurrentDeclContext(), getCurrentScope(),
          availabilityContext);
      AvailabilityScopeBuilder(elseScope, Context).build(elseStmt);
    } else {
      build(ifStmt->getElseStmt());
    }
  }

  /// Builds the availability scopes for the WhileStmt if the guard
  /// introduces a new availability scope for the body branch.
  /// There is no need for the caller to explicitly traverse the children
  /// of this node.
  void buildWhileStmtRefinementContext(WhileStmt *whileStmt) {
    std::optional<AvailabilityContext> bodyContext =
        buildStmtConditionRefinementContext(whileStmt->getCond()).first;

    if (bodyContext.has_value()) {
      // Create a new context for the body and traverse it in the new
      // context.
      auto availabilityContext =
          constrainCurrentAvailabilityWithContext(*bodyContext);
      auto *bodyScope = AvailabilityScope::createForWhileStmtBody(
          Context, whileStmt, getCurrentDeclContext(), getCurrentScope(),
          availabilityContext);
      AvailabilityScopeBuilder(bodyScope, Context).build(whileStmt->getBody());
    } else {
      build(whileStmt->getBody());
    }
  }

  /// Builds the availability scopes for the GuardStmt and pushes
  /// the fallthrough scope onto the scope stack so that subsequent
  /// AST elements in the same scope are analyzed in the context of the
  /// fallthrough scope.
  void buildGuardStmtRefinementContext(GuardStmt *guardStmt) {
    // 'guard' statements fall through if all of the guard conditions are true,
    // so we refine the range after the require until the end of the enclosing
    // block:
    //
    // if ... {
    //   guard available(...) else { return } <-- Refined range starts here
    //   ...
    // } <-- Refined range ends here
    //
    // This is slightly tricky because, unlike our other control constructs,
    // the refined region is not lexically contained inside the construct
    // introducing the availability scope.
    std::optional<AvailabilityContext> fallthroughContext;
    std::optional<AvailabilityContext> elseContext;
    std::tie(fallthroughContext, elseContext) =
        buildStmtConditionRefinementContext(guardStmt->getCond());

    if (Stmt *elseBody = guardStmt->getBody()) {
      if (elseContext.has_value()) {
        auto availabilityContext =
            constrainCurrentAvailabilityWithContext(*elseContext);
        auto *trueScope = AvailabilityScope::createForGuardStmtElse(
            Context, guardStmt, getCurrentDeclContext(), getCurrentScope(),
            availabilityContext);

        AvailabilityScopeBuilder(trueScope, Context).build(elseBody);
      } else {
        build(elseBody);
      }
    }

    auto *parentBrace = dyn_cast<BraceStmt>(Parent.getAsStmt());
    assert(parentBrace && "Expected parent of GuardStmt to be BraceStmt");
    if (!fallthroughContext.has_value())
      return;

    // Create a new context for the fallthrough.
    auto fallthroughAvailability =
        constrainCurrentAvailabilityWithContext(*fallthroughContext);
    auto *fallthroughScope = AvailabilityScope::createForGuardStmtFallthrough(
        Context, guardStmt, parentBrace, getCurrentDeclContext(),
        getCurrentScope(), fallthroughAvailability);

    pushContext(fallthroughScope, parentBrace);
  }

  AvailabilityQuery buildAvailabilityQuery(
      const SemanticAvailabilitySpec spec,
      const std::optional<SemanticAvailabilitySpec> &variantSpec) {
    auto domain = spec.getDomain();

    // Variant availability specfications are only supported for platform
    // domains when compiling with a -target-variant.
    if (!Context.LangOpts.TargetVariant)
      ASSERT(!variantSpec);

    auto runtimeRangeForSpec =
        [](const std::optional<SemanticAvailabilitySpec> &spec)
        -> std::optional<AvailabilityRange> {
      if (!spec || spec->isWildcard() || !spec->getDomain().isVersioned())
        return std::nullopt;

      return AvailabilityRange(spec->getRuntimeVersion());
    };

    auto primaryRange = runtimeRangeForSpec(spec);
    auto variantRange = runtimeRangeForSpec(variantSpec);

    switch (domain.getKind()) {
    case AvailabilityDomain::Kind::Embedded:
    case AvailabilityDomain::Kind::SwiftLanguageMode:
    case AvailabilityDomain::Kind::PackageDescription:
      // These domains don't support queries.
      llvm::report_fatal_error("unsupported domain");

    case AvailabilityDomain::Kind::Universal:
      DEBUG_ASSERT(spec.isWildcard());

      // If all of the specs that matched are '*', then the query trivially
      // evaluates to "true" at compile time.
      if (!variantRange)
        return AvailabilityQuery::constant(domain, true);

      // Otherwise, generate a dynamic query for the variant spec. For example,
      // when compiling zippered for macOS, this should generate a query that
      // just checks the iOS version at runtime:
      //
      //    if #available(iOS 18, *) { ... }
      //
      return AvailabilityQuery::dynamic(variantSpec->getDomain(), primaryRange,
                                        variantRange);

    case AvailabilityDomain::Kind::StandaloneSwiftRuntime:
      return AvailabilityQuery::dynamic(domain, primaryRange, std::nullopt);

    case AvailabilityDomain::Kind::Platform:
      // Platform and Swift runtime checks are always dynamic. The SIL optimizer
      // is responsible eliminating these checks when it can prove that they can
      // never fail (due to the deployment target). We can't perform that
      // analysis here because it may depend on inlining.
      return AvailabilityQuery::dynamic(domain, primaryRange, variantRange);
    case AvailabilityDomain::Kind::Custom:
      auto customDomain = domain.getCustomDomain();
      ASSERT(customDomain);

      switch (customDomain->getKind()) {
      case CustomAvailabilityDomain::Kind::Enabled:
      case CustomAvailabilityDomain::Kind::AlwaysEnabled:
        return AvailabilityQuery::constant(domain, true);
      case CustomAvailabilityDomain::Kind::Disabled:
        return AvailabilityQuery::constant(domain, false);
      case CustomAvailabilityDomain::Kind::Dynamic:
        return AvailabilityQuery::dynamic(domain, primaryRange, variantRange);
      }
    }
  }

  /// Build the availability scopes for a StmtCondition and return a pair of
  /// optional availability contexts, the first for the true branch and the
  /// second for the false branch. A value of `nullopt` for a given branch
  /// indicates that the branch does not introduce a new scope.
  std::pair<std::optional<AvailabilityContext>,
            std::optional<AvailabilityContext>>
  buildStmtConditionRefinementContext(StmtCondition cond) {
    if (Context.LangOpts.DisableAvailabilityChecking)
      return {};

    // Any availability scopes introduced in the statement condition will end
    // at the end of the last condition element.
    StmtConditionElement lastElement = cond.back();

    // Keep track of how many nested availability scopes we have pushed on
    // the scope stack so we can pop them when we're done building the scope
    // for the StmtCondition.
    unsigned nestedCount = 0;

    /// Tracks the state that is necessary to produce the `AvailabilityContext`
    /// for the false flow of the StmtCondition. The builder starts in a state
    /// where the false flow is assumed to be unreachable at runtime and then is
    /// refined by expanding the availability domain range it covers.
    class FalseFlowContextBuilder {
      enum class State {
        // The flow doesn't refine anything yet.
        Empty,
        // The flow has a valid refinement.
        Refined,
        // There is no possible refinement.
        Undefined,
      };

      State state;
      AvailabilityDomain refinedDomain;
      AvailabilityRange availableRange;
      const ASTContext &ctx;

    public:
      FalseFlowContextBuilder(const ASTContext &ctx)
          : state(State::Empty),
            refinedDomain(AvailabilityDomain::forUniversal()),
            availableRange(AvailabilityRange::neverAvailable()), ctx(ctx) {}

      /// Attempts to union the flow's existing domain and range with the given
      /// domain and range. If the given domain is compatible with the flow's
      /// existing domain then the refinement's range will be expanded as
      /// needed. Otherwise, the flow's availability context becomes "undefined"
      /// since it cannot be represented.
      void unionWithRange(const AvailabilityRange &range,
                          AvailabilityDomain domain) {
        switch (state) {
        case State::Empty:
          // There false flow doesn't refine any domain or range yet.
          refinedDomain = domain;
          availableRange = range;
          state = State::Refined;
          return;

        case State::Refined:
          // There's an existing domain and range. As long as the new domains is
          // compatible, then its available range can be expanded if needed.
          if (refinedDomain == domain || (refinedDomain.isActivePlatform(ctx) &&
                                          domain.isActivePlatform(ctx))) {
            availableRange.unionWith(range);
            return;
          }

          // The domains aren't compatible so the availability of the false flow
          // can't be represented.
          state = State::Undefined;
          return;

        case State::Undefined:
          // The availability of the false flow can't be represented.
          return;
        }
      }

      /// Force the availability context of the false flow to be undefined.
      void setUndefined() { state = State::Undefined; }

      /// Constrains the given context using the refined availability that has
      /// been built up.
      AvailabilityContext constrainContext(AvailabilityContext context) {
        auto contextCopy = context;
        switch (state) {
        case State::Empty:
          contextCopy.constrainWithPlatformRange(availableRange, ctx);
          break;
        case State::Refined:
          contextCopy.constrainWithAvailabilityRange(availableRange,
                                                     refinedDomain, ctx);
          break;
        case State::Undefined:
          break;
        }

        return contextCopy;
      }
    };

    AvailabilityScope *startingScope = getCurrentScope();
    FalseFlowContextBuilder falseFlowBuilder(Context);

    // Tracks if we're refining for availability or unavailability.
    std::optional<bool> isUnavailability = std::nullopt;

    for (StmtConditionElement element : cond) {
      auto *currentScope = getCurrentScope();
      auto currentContext = currentScope->getAvailabilityContext();

      // If the element is not a condition, walk it in the current scope.
      if (element.getKind() != StmtConditionElement::CK_Availability) {
        // Assume any condition element that is not a #available() can
        // potentially be false, so conservatively make the false flow's
        // refinement undefined since there is nothing we can prove about it.
        falseFlowBuilder.setUndefined();
        element.walk(*this);
        continue;
      }

      // #available query: introduce a new availability scope for the statement
      // condition elements following it.
      auto *query = element.getAvailability();

      if (isUnavailability == std::nullopt) {
        isUnavailability = query->isUnavailability();
      } else if (isUnavailability != query->isUnavailability()) {
        // Mixing availability with unavailability in the same statement will
        // cause the false flow's version range to be ambiguous. Report it.
        //
        // Technically we can support this by not refining ambiguous flows,
        // but there are currently no legitimate cases where one would have
        // to mix availability with unavailability.
        Context.Diags.diagnose(query->getLoc(),
                               diag::availability_cannot_be_mixed);
        break;
      }

      // If this query expression has no queries, we will not introduce a new
      // availability scope. We do not diagnose here: a diagnostic will already
      // have been emitted by the parser.
      // For #unavailable, empty queries are valid as wildcards are implied.
      if (!query->isUnavailability() && query->getQueries().empty())
        continue;

      auto spec = bestActiveSpecForQuery(query);
      if (!spec) {
        // We couldn't find an active spec so rather than refining, emit a
        // diagnostic and just use the current scope.
        Context.Diags.diagnose(
            query->getLoc(), diag::availability_query_required_for_platform,
            platformString(targetPlatform(Context.LangOpts)));
        falseFlowBuilder.setUndefined();
        continue;
      }

      // When compiling zippered for macCatalyst, we need to collect both
      // a macOS version (the target version) and an iOS/macCatalyst version
      // (the target-variant). These versions will both be passed to a runtime
      // entrypoint that will check either the macOS version or the iOS
      // version depending on the kind of process this code is loaded into.
      std::optional<SemanticAvailabilitySpec> variantSpec =
          (Context.LangOpts.TargetVariant)
              ? bestActiveSpecForQuery(query, /*ForTargetVariant*/ true)
              : std::nullopt;

      query->setAvailabilityQuery(
          buildAvailabilityQuery(*spec, variantSpec)
              .asUnavailable(query->isUnavailability()));

      // Wildcards are expected to be "useless". There may be other specs in
      // this query that are useful when compiling for other platforms.
      if (spec->isWildcard())
        continue;

      auto domain = spec->getDomain();
      auto newContext = currentContext;
      std::optional<AvailabilityRange> trueRange, falseRange;
      std::tie(trueRange, falseRange) =
          statementConditionRangesForSpec(*spec, currentContext);

      if (trueRange)
        newContext.constrainWithAvailabilityRange(*trueRange, domain, Context);

      // Check whether the new context refines availability. If it doesn't, the
      // query is useless and should potentially be diagnosed.
      if (currentContext.isContainedIn(newContext)) {
        // If the explicitly-specified (via #availability) version range for the
        // current scope is completely contained in the range for the spec, then
        // a version query can never be false, so the spec is useless.
        // If so, report this.
        auto explicitRange =
            currentScope->getExplicitAvailabilityRange(domain, Context);
        if (explicitRange && trueRange &&
            explicitRange->isContainedIn(*trueRange)) {
          // Platform unavailability queries never refine availability so don't
          // diangose them.
          if (isUnavailability.value())
            continue;

          DiagnosticEngine &diags = Context.Diags;
          if (currentScope->getReason() != AvailabilityScope::Reason::Root) {
            diags.diagnose(query->getLoc(),
                           diag::availability_query_useless_enclosing_scope,
                           domain.getNameForAttributePrinting());
            diags.diagnose(
                currentScope->getIntroductionLoc(),
                diag::availability_query_useless_enclosing_scope_here);
          }
        }

        continue;
      }

      // If the #available() is not useless then there is a potential false
      // flow and we need to potentially expand the range covered by the false
      // flow branch.
      if (falseRange)
        falseFlowBuilder.unionWithRange(*falseRange, domain);

      auto *scope = AvailabilityScope::createForConditionFollowingQuery(
          Context, query, lastElement, getCurrentDeclContext(), currentScope,
          newContext);

      pushContext(scope, ParentTy());
      ++nestedCount;
    }

    auto startingContext = startingScope->getAvailabilityContext();
    auto falseFlowContext = falseFlowBuilder.constrainContext(startingContext);

    // The version range for the false branch should never have any versions
    // that weren't possible when the condition started evaluating.
    DEBUG_ASSERT(falseFlowContext.isContainedIn(startingContext));

    // If the starting availability context is not completely contained in the
    // false flow context then it must be the case that false flow context
    // is strictly smaller than the starting context (because the false flow
    // context *is* contained in the starting context), so we should introduce a
    // new availability scope for the false flow.
    std::optional<AvailabilityContext> falseRefinement = std::nullopt;
    if (!startingScope->getAvailabilityContext().isContainedIn(
            falseFlowContext)) {
      falseRefinement = falseFlowContext;
    }

    auto makeResult =
        [isUnavailability](std::optional<AvailabilityContext> trueRefinement,
                           std::optional<AvailabilityContext> falseRefinement) {
          if (isUnavailability.has_value() && *isUnavailability) {
            // If this is an unavailability check, invert the result.
            return std::make_pair(falseRefinement, trueRefinement);
          }
          return std::make_pair(trueRefinement, falseRefinement);
        };

    if (nestedCount == 0)
      return makeResult(std::nullopt, falseRefinement);

    AvailabilityScope *nestedScope = getCurrentScope();
    while (nestedCount-- > 0)
      ContextStack.pop_back();

    assert(getCurrentScope() == startingScope);

    return makeResult(nestedScope->getAvailabilityContext(), falseRefinement);
  }

  /// Return the best active spec for the target platform or nullptr if no
  /// such spec exists.
  std::optional<SemanticAvailabilitySpec>
  bestActiveSpecForQuery(PoundAvailableInfo *available,
                         bool forTargetVariant = false) {
    std::optional<SemanticAvailabilitySpec> foundWildcardSpec;
    std::optional<SemanticAvailabilitySpec> bestSpec;

    for (auto spec :
         available->getSemanticAvailabilitySpecs(getCurrentDeclContext())) {
      if (spec.isWildcard()) {
        foundWildcardSpec = spec;
        continue;
      }

      auto domain = spec.getDomain();
      if (!domain.supportsQueries())
        continue;

      if (!domain.isPlatform()) {
        // We found an active, non-platform constraint. It should be the only
        // one, so just return it.
        return spec;
      }

      // FIXME: This is not quite right: we want to handle AppExtensions
      // properly. For example, on the OSXApplicationExtension platform
      // we want to chose the OS X spec unless there is an explicit
      // OSXApplicationExtension spec.
      auto platform = domain.getPlatformKind();
      if (isPlatformActive(platform, Context.LangOpts, forTargetVariant,
                           /* ForRuntimeQuery */ true)) {

        if (!bestSpec ||
            inheritsAvailabilityFromPlatform(
                platform, bestSpec->getDomain().getPlatformKind())) {
          bestSpec = spec;
        }
      }
    }

    if (bestSpec)
      return bestSpec;

    // If we have reached this point, we found no spec for our target, so
    // we return the other spec ('*'), if we found it, or nullptr, if not.
    if (foundWildcardSpec) {
      return foundWildcardSpec;
    } else if (available->isUnavailability()) {
      // For #unavailable, imply the presence of a wildcard.
      // FIXME: [availability] Creating a new wildcard spec here is wasteful.
      SourceLoc loc = available->getRParenLoc();
      return AvailabilitySpec::createWildcard(Context, loc);
    } else {
      return std::nullopt;
    }
  }

  /// For the given spec, returns a pair of availability ranges. The first range
  /// is for the if/then flow and the second is for the if/else flow.
  std::pair<std::optional<AvailabilityRange>, std::optional<AvailabilityRange>>
  statementConditionRangesForSpec(SemanticAvailabilitySpec spec,
                                  const AvailabilityContext &currentContext) {
    static auto always = AvailabilityRange::alwaysAvailable();
    static auto never = AvailabilityRange::neverAvailable();
    ASSERT(!spec.isWildcard());

    auto domain = spec.getDomain();
    if (!domain.isVersioned())
      return {always, never};

    return {AvailabilityRange(spec.getVersion()),
            currentContext.getAvailabilityRange(domain, Context)};
  }

  PreWalkResult<Expr *> walkToExprPre(Expr *expr) override {
    (void)consumeDeclBodyContextIfNecessary(expr);

    if (auto closureExpr = dyn_cast<ClosureExpr>(expr))
      DeclContextStack.push_back(closureExpr);

    return Action::Continue(expr);
  }

  PostWalkResult<Expr *> walkToExprPost(Expr *expr) override {
    if (ContextStack.back().ScopeNode.getAsExpr() == expr)
      ContextStack.pop_back();

    if (auto *closureExpr = dyn_cast<ClosureExpr>(expr)) {
      assert(DeclContextStack.back() == closureExpr);
      DeclContextStack.pop_back();
    }

    return Action::Continue(expr);
  }
};

} // end anonymous namespace

AvailabilityScope *AvailabilityScope::getOrBuildForSourceFile(SourceFile &SF) {
  switch (SF.Kind) {
  case SourceFileKind::SIL:
    // SIL doesn't support availability queries.
    return nullptr;
  case SourceFileKind::MacroExpansion:
  case SourceFileKind::DefaultArgument:
  case SourceFileKind::Library:
  case SourceFileKind::Main:
  case SourceFileKind::Interface:
    break;
  }
  ASTContext &ctx = SF.getASTContext();

  // If there's already a root node, then we're done.
  if (auto scope = SF.getAvailabilityScope())
    return scope;

  // The root availability scope reflects the fact that all parts of
  // the source file are guaranteed to be executing on at least the minimum
  // platform version for inlining.
  auto availabilityContext = AvailabilityContext::forInliningTarget(ctx);
  AvailabilityScope *rootScope =
      AvailabilityScope::createForSourceFile(&SF, availabilityContext);
  SF.setAvailabilityScope(rootScope);

  // Build availability scopes, if necessary, for all declarations starting
  // with StartElem.
  AvailabilityScopeBuilder builder(rootScope, ctx);
  for (auto item : SF.getTopLevelItems()) {
    if (auto decl = item.dyn_cast<Decl *>())
      builder.build(decl);
    else if (auto expr = item.dyn_cast<Expr *>())
      builder.build(expr);
    else if (auto stmt = item.dyn_cast<Stmt *>())
      builder.build(stmt);
  }

  return rootScope;
}

evaluator::SideEffect ExpandChildAvailabilityScopesRequest::evaluate(
    Evaluator &evaluator, AvailabilityScope *parentScope) const {
  assert(parentScope->getNeedsExpansion());
  if (auto decl = parentScope->getDeclOrNull()) {
    ASTContext &ctx = decl->getASTContext();
    AvailabilityScopeBuilder builder(parentScope, ctx);
    builder.prepareDeclForLazyExpansion(decl);
    builder.build(decl);
  }
  return evaluator::SideEffect();
}
