Revision ac4b68fbf45853ba4b9e327cb42f93f42a8fa252 authored by Ellie Shin on 17 March 2023, 04:14:20 UTC, committed by Ellie Shin on 17 March 2023, 04:14:20 UTC
1 parent f2c68fb
Raw File
TypeCheckAvailability.cpp
//===--- TypeCheckAvailability.cpp - Availability Diagnostics -------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 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 availability diagnostics.
//
//===----------------------------------------------------------------------===//

#include "TypeCheckAvailability.h"
#include "MiscDiagnostics.h"
#include "TypeCheckConcurrency.h"
#include "TypeCheckObjC.h"
#include "TypeChecker.h"
#include "swift/AST/ASTWalker.h"
#include "swift/AST/GenericEnvironment.h"
#include "swift/AST/Initializer.h"
#include "swift/AST/NameLookup.h"
#include "swift/AST/Pattern.h"
#include "swift/AST/PrettyStackTrace.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/TypeDeclFinder.h"
#include "swift/AST/TypeRefinementContext.h"
#include "swift/Basic/Defer.h"
#include "swift/Basic/SourceManager.h"
#include "swift/Basic/StringExtras.h"
#include "swift/Parse/Lexer.h"
#include "swift/Parse/Parser.h"
#include "swift/Sema/IDETypeChecking.h"
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/SaveAndRestore.h"
using namespace swift;

ExportContext::ExportContext(DeclContext *DC,
                             AvailabilityContext runningOSVersion,
                             FragileFunctionKind kind,
                             bool spi, bool exported, bool implicit, bool deprecated,
                             Optional<PlatformKind> unavailablePlatformKind)
    : DC(DC), RunningOSVersion(runningOSVersion), FragileKind(kind) {
  SPI = spi;
  Exported = exported;
  Implicit = implicit;
  Deprecated = deprecated;
  if (unavailablePlatformKind) {
    Unavailable = 1;
    Platform = unsigned(*unavailablePlatformKind);
  } else {
    Unavailable = 0;
    Platform = 0;
  }

  Reason = unsigned(ExportabilityReason::General);
}

bool swift::isExported(const ValueDecl *VD) {
  if (VD->getAttrs().hasAttribute<ImplementationOnlyAttr>())
    return false;

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

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

  return false;
}

static bool hasConformancesToPublicProtocols(const ExtensionDecl *ED) {
  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;
}

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

  // If there are any exported members then the extension is exported.
  for (const Decl *D : ED->getMembers()) {
    if (isExported(D))
      return true;
  }

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

  return false;
}

bool 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 false;
  }
  if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
    return isExported(ED);
  }

  return true;
}

template<typename Fn>
static void forEachOuterDecl(DeclContext *DC, Fn fn) {
  for (; !DC->isModuleScopeContext(); DC = DC->getParent()) {
    switch (DC->getContextKind()) {
    case DeclContextKind::AbstractClosureExpr:
    case DeclContextKind::TopLevelCodeDecl:
    case DeclContextKind::SerializedLocal:
    case DeclContextKind::Package:
    case DeclContextKind::Module:
    case DeclContextKind::FileUnit:
    case DeclContextKind::MacroDecl:
      break;

    case DeclContextKind::Initializer:
      if (auto *PBI = dyn_cast<PatternBindingInitializer>(DC))
        fn(PBI->getBinding());
      else if (auto *I = dyn_cast<PropertyWrapperInitializer>(DC))
        fn(I->getWrappedVar());
      break;

    case DeclContextKind::SubscriptDecl:
      fn(cast<SubscriptDecl>(DC));
      break;

    case DeclContextKind::EnumElementDecl:
      fn(cast<EnumElementDecl>(DC));
      break;

    case DeclContextKind::AbstractFunctionDecl:
      fn(cast<AbstractFunctionDecl>(DC));

      if (auto *AD = dyn_cast<AccessorDecl>(DC))
        fn(AD->getStorage());
      break;

    case DeclContextKind::GenericTypeDecl:
      fn(cast<GenericTypeDecl>(DC));
      break;

    case DeclContextKind::ExtensionDecl:
      fn(cast<ExtensionDecl>(DC));
      break;
    }
  }
}

static void computeExportContextBits(ASTContext &Ctx, Decl *D,
                                     bool *spi, bool *implicit, bool *deprecated,
                                     Optional<PlatformKind> *unavailablePlatformKind) {
  if (D->isSPI() ||
      D->isAvailableAsSPI())
    *spi = true;

  // Defer bodies are desugared to an implicit closure expression. We need to
  // dilute the meaning of "implicit" to make sure we're still checking
  // availability inside of defer statements.
  const auto isDeferBody = isa<FuncDecl>(D) && cast<FuncDecl>(D)->isDeferBody();
  if (D->isImplicit() && !isDeferBody)
    *implicit = true;

  if (D->getAttrs().getDeprecated(Ctx))
    *deprecated = true;

  if (auto *A = D->getAttrs().getUnavailable(Ctx)) {
    *unavailablePlatformKind = A->Platform;
  }

  if (auto *PBD = dyn_cast<PatternBindingDecl>(D)) {
    for (unsigned i = 0, e = PBD->getNumPatternEntries(); i < e; ++i) {
      if (auto *VD = PBD->getAnchoringVarDecl(i))
        computeExportContextBits(Ctx, VD, spi, implicit, deprecated,
                                 unavailablePlatformKind);
    }
  }
}

ExportContext ExportContext::forDeclSignature(Decl *D) {
  auto &Ctx = D->getASTContext();

  auto *DC = D->getInnermostDeclContext();
  auto fragileKind = DC->getFragileFunctionKind();
  auto runningOSVersion =
      (Ctx.LangOpts.DisableAvailabilityChecking
       ? AvailabilityContext::alwaysAvailable()
       : TypeChecker::overApproximateAvailabilityAtLocation(D->getLoc(), DC));
  bool spi = Ctx.LangOpts.LibraryLevel == LibraryLevel::SPI;
  bool implicit = false;
  bool deprecated = false;
  Optional<PlatformKind> unavailablePlatformKind;
  computeExportContextBits(Ctx, D, &spi, &implicit, &deprecated,
                           &unavailablePlatformKind);
  forEachOuterDecl(D->getDeclContext(),
                   [&](Decl *D) {
                     computeExportContextBits(Ctx, D,
                                              &spi, &implicit, &deprecated,
                                              &unavailablePlatformKind);
                   });

  bool exported = ::isExported(D);

  return ExportContext(DC, runningOSVersion, fragileKind,
                       spi, exported, implicit, deprecated,
                       unavailablePlatformKind);
}

ExportContext ExportContext::forFunctionBody(DeclContext *DC, SourceLoc loc) {
  auto &Ctx = DC->getASTContext();

  auto fragileKind = DC->getFragileFunctionKind();
  auto runningOSVersion =
      (Ctx.LangOpts.DisableAvailabilityChecking
       ? AvailabilityContext::alwaysAvailable()
       : TypeChecker::overApproximateAvailabilityAtLocation(loc, DC));

  bool spi = Ctx.LangOpts.LibraryLevel == LibraryLevel::SPI;
  bool implicit = false;
  bool deprecated = false;
  Optional<PlatformKind> unavailablePlatformKind;
  forEachOuterDecl(DC,
                   [&](Decl *D) {
                     computeExportContextBits(Ctx, D,
                                              &spi, &implicit, &deprecated,
                                              &unavailablePlatformKind);
                   });

  bool exported = false;

  return ExportContext(DC, runningOSVersion, fragileKind,
                       spi, exported, implicit, deprecated,
                       unavailablePlatformKind);
}

ExportContext ExportContext::forConformance(DeclContext *DC,
                                            ProtocolDecl *proto) {
  assert(isa<ExtensionDecl>(DC) || isa<NominalTypeDecl>(DC));
  auto where = forDeclSignature(DC->getInnermostDeclarationDeclContext());

  where.Exported &= proto->getFormalAccessScope(
      DC, /*usableFromInlineAsPublic*/true).isPublic();

  return where;
}

ExportContext ExportContext::withReason(ExportabilityReason reason) const {
  auto copy = *this;
  copy.Reason = unsigned(reason);
  return copy;
}

ExportContext ExportContext::withExported(bool exported) const {
  auto copy = *this;
  copy.Exported = isExported() && exported;
  return copy;
}

Optional<PlatformKind> ExportContext::getUnavailablePlatformKind() const {
  if (Unavailable)
    return PlatformKind(Platform);
  return None;
}

bool ExportContext::mustOnlyReferenceExportedDecls() const {
  return Exported || FragileKind.kind != FragileFunctionKind::None;
}

Optional<ExportabilityReason> ExportContext::getExportabilityReason() const {
  if (Exported)
    return ExportabilityReason(Reason);
  return None;
}

/// Returns the first availability attribute on the declaration that is active
/// on the target platform.
static const AvailableAttr *getActiveAvailableAttribute(const Decl *D,
                                                        ASTContext &AC) {
  for (auto Attr : D->getAttrs())
    if (auto AvAttr = dyn_cast<AvailableAttr>(Attr)) {
      if (!AvAttr->isInvalid() && AvAttr->isActivePlatform(AC)) {
        return AvAttr;
      }
    }
  return nullptr;
}

/// Returns true if there is any availability attribute on the declaration
/// that is active on the target platform.
static bool hasActiveAvailableAttribute(Decl *D,
                                           ASTContext &AC) {
  return getActiveAvailableAttribute(D, AC);
}

static bool computeContainedByDeploymentTarget(TypeRefinementContext *TRC,
                                               ASTContext &ctx) {
  return TRC->getAvailabilityInfo()
                  .isContainedIn(AvailabilityContext::forDeploymentTarget(ctx));
}

/// Returns true if the reference or any of its parents is an
/// unconditional unavailable declaration for the same platform.
static bool isInsideCompatibleUnavailableDeclaration(
    const Decl *D, const ExportContext &where, const AvailableAttr *attr) {
  auto referencedPlatform = where.getUnavailablePlatformKind();
  if (!referencedPlatform)
    return false;

  if (!attr->isUnconditionallyUnavailable()) {
    return false;
  }

  // Refuse calling unavailable functions from unavailable code,
  // but allow the use of types.
  PlatformKind platform = attr->Platform;
  if (platform == PlatformKind::none && !isa<TypeDecl>(D) &&
      !isa<ExtensionDecl>(D)) {
    return false;
  }

  return (*referencedPlatform == platform ||
          inheritsAvailabilityFromPlatform(platform, *referencedPlatform));
}

namespace {

/// A class to walk the AST to build the type refinement context hierarchy.
class TypeRefinementContextBuilder : private ASTWalker {

  ASTContext &Context;

  /// Represents an entry in a stack of active type refinement contexts. The
  /// stack is used to facilitate building the TRC's tree structure. A new TRC
  /// is pushed onto this stack before visiting children whenever the current
  /// AST node requires a new context and the TRC is then popped
  /// post-visitation.
  struct ContextInfo {
    TypeRefinementContext *TRC;

    /// 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;

  /// Represents an entry in a stack of pending decl body type refinement
  /// contexts. TRCs in this stack should be pushed onto \p ContextStack when
  /// \p BodyStmt is encountered.
  struct DeclBodyContextInfo {
    TypeRefinementContext *TRC;
    Decl *Decl;
    Stmt *BodyStmt;
  };
  std::vector<DeclBodyContextInfo> DeclBodyContextStack;

  /// A mapping from abstract storage declarations with accessors to
  /// to the type refinement contexts for those declarations. We refer to
  /// this map to determine the appropriate parent TRC to use when
  /// walking the accessor function.
  llvm::DenseMap<AbstractStorageDecl *, TypeRefinementContext *>
      StorageContexts;

  /// A mapping from pattern binding storage declarations to the type refinement
  /// contexts for those declarations. We refer to this map to determine the
  /// appropriate parent TRC to use when walking a var decl that belongs to a
  /// pattern containing multiple vars.
  llvm::DenseMap<PatternBindingDecl *, TypeRefinementContext *>
      PatternBindingContexts;

  TypeRefinementContext *getCurrentTRC() {
    return ContextStack.back().TRC;
  }

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

  void pushContext(TypeRefinementContext *TRC, ParentTy PopAfterNode) {
    ContextInfo Info;
    Info.TRC = TRC;
    Info.ScopeNode = PopAfterNode;

    if (!ContextStack.empty() && isCurrentTRCContainedByDeploymentTarget()) {
      assert(computeContainedByDeploymentTarget(TRC, Context) &&
             "incorrectly skipping computeContainedByDeploymentTarget()");
      Info.ContainedByDeploymentTarget = true;
    } else {
      Info.ContainedByDeploymentTarget =
          computeContainedByDeploymentTarget(TRC, Context);
    }

    ContextStack.push_back(Info);
  }

  void pushDeclBodyContext(TypeRefinementContext *TRC, Decl *D, Stmt *S) {
    DeclBodyContextInfo Info;
    Info.TRC = TRC;
    Info.Decl = D;
    Info.BodyStmt = S;

    DeclBodyContextStack.push_back(Info);
  }

  const char *stackTraceAction() const {
    return "building type refinement context for";
  }

public:
  TypeRefinementContextBuilder(TypeRefinementContext *TRC, ASTContext &Context)
      : Context(Context) {
    assert(TRC);
    pushContext(TRC, ParentTy());
  }

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

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

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

private:
  MacroWalking getMacroWalkingBehavior() const override {
    return MacroWalking::ArgumentsAndExpansion;
  }

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

    // Adds in a parent TRC for decls which are syntactically nested but are not
    // represented that way in the AST. (Particularly, AbstractStorageDecl
    // parents for AccessorDecl children.)
    if (auto ParentTRC = getEffectiveParentContextForDecl(D)) {
      pushContext(ParentTRC, D);
    }

    // Adds in a TRC that covers the entire declaration.
    if (auto DeclTRC = getNewContextForSignatureOfDecl(D)) {
      pushContext(DeclTRC, D);

      // Possibly use this as an effective parent context later.
      recordEffectiveParentContext(D, DeclTRC);
    }

    // Create TRCs that cover only the body of the declaration.
    buildContextsForBodyOfDecl(D);
    return Action::Continue();
  }

  PostWalkAction walkToDeclPost(Decl *D) override {
    while (ContextStack.back().ScopeNode.getAsDecl() == D) {
      ContextStack.pop_back();
    }

    while (!DeclBodyContextStack.empty() &&
           DeclBodyContextStack.back().Decl == D) {
      DeclBodyContextStack.pop_back();
    }

    return Action::Continue();
  }

  TypeRefinementContext *getEffectiveParentContextForDecl(Decl *D) {
    // FIXME: Can we assert that we won't walk parent decls later that should
    //        have been returned here?
    if (auto *accessor = dyn_cast<AccessorDecl>(D)) {
      // Use TRC of the storage rather the current TRC when walking this
      // function.
      auto it = StorageContexts.find(accessor->getStorage());
      if (it != StorageContexts.end()) {
        return it->second;
      }
    } else if (auto *VD = dyn_cast<VarDecl>(D)) {
      // Use the TRC of the pattern binding decl as the parent for var decls.
      if (auto *PBD = VD->getParentPatternBinding()) {
        auto it = PatternBindingContexts.find(PBD);
        if (it != PatternBindingContexts.end()) {
          return it->second;
        }
      }
    }

    return nullptr;
  }

  /// If necessary, records a TRC so it can be returned by subsequent calls to
  /// `getEffectiveParentContextForDecl()`.
  void recordEffectiveParentContext(Decl *D, TypeRefinementContext *NewTRC) {
    if (auto *StorageDecl = dyn_cast<AbstractStorageDecl>(D)) {
      // Stash the TRC for the storage declaration to use as the parent of
      // accessor decls later.
      if (StorageDecl->hasParsedAccessors())
        StorageContexts[StorageDecl] = NewTRC;
    }

    if (auto *VD = dyn_cast<VarDecl>(D)) {
      // Stash the TRC for the var decl if its parent pattern binding decl has
      // more than one entry so that the sibling var decls can reuse it.
      if (auto *PBD = VD->getParentPatternBinding()) {
        if (PBD->getNumPatternEntries() > 1)
          PatternBindingContexts[PBD] = NewTRC;
      }
    }
  }

  /// Returns a new context to be introduced for the declaration, or nullptr
  /// if no new context should be introduced.
  TypeRefinementContext *getNewContextForSignatureOfDecl(Decl *D) {
    if (!isa<ValueDecl>(D) && !isa<ExtensionDecl>(D))
      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>(D) && D->getDeclContext()->isLocalContext())
      return nullptr;

    // Ignore implicit declarations (mainly skips over `DeferStmt` functions).
    if (D->isImplicit())
      return nullptr;

    // Skip introducing additional contexts for var decls past the first in a
    // pattern. The context necessary for the pattern as a whole was already
    // introduced if necessary by the first var decl.
    if (auto *VD = dyn_cast<VarDecl>(D)) {
      if (auto *PBD = VD->getParentPatternBinding())
        if (VD != PBD->getAnchoringVarDecl(0))
          return nullptr;
    }

    // Declarations with an explicit availability attribute always get a TRC.
    if (hasActiveAvailableAttribute(D, Context)) {
      AvailabilityContext DeclaredAvailability =
          swift::AvailabilityInference::availableRange(D, Context);

      return TypeRefinementContext::createForDecl(
          Context, D, getCurrentTRC(),
          getEffectiveAvailabilityForDeclSignature(D, DeclaredAvailability),
          DeclaredAvailability, refinementSourceRangeForDecl(D));
    }

    // Declarations without explicit availability get a TRC 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.
    AvailabilityContext CurrentAvailability =
        getCurrentTRC()->getAvailabilityInfo();
    AvailabilityContext EffectiveAvailability =
        getEffectiveAvailabilityForDeclSignature(D, CurrentAvailability);
    if (CurrentAvailability.isSupersetOf(EffectiveAvailability))
      return TypeRefinementContext::createForDeclImplicit(
          Context, D, getCurrentTRC(), EffectiveAvailability,
          refinementSourceRangeForDecl(D));

    return nullptr;
  }

  AvailabilityContext getEffectiveAvailabilityForDeclSignature(
      Decl *D, const AvailabilityContext BaseAvailability) {
    AvailabilityContext EffectiveAvailability = BaseAvailability;

    // 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 availabilty on the extension itself.
    if (auto *ED = dyn_cast<ExtensionDecl>(D)) {
      auto ET = ED->getExtendedType();
      if (ET && !hasActiveAvailableAttribute(D, Context)) {
        EffectiveAvailability.intersectWith(
            swift::AvailabilityInference::inferForType(ET));

        // 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.
        EffectiveAvailability.unionWith(
            AvailabilityContext::forDeploymentTarget(Context));
      }
    }

    EffectiveAvailability.intersectWith(getCurrentTRC()->getAvailabilityInfo());
    if (shouldConstrainSignatureToDeploymentTarget(D))
      EffectiveAvailability.intersectWith(
          AvailabilityContext::forDeploymentTarget(Context));

    return EffectiveAvailability;
  }

  /// 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(Decl *D) {
    if (isCurrentTRCContainedByDeploymentTarget())
      return false;

    // As a convenience, SPI decls and explicitly unavailable decls are
    // constrained to the deployment target. There's not much benefit to
    // checking these declarations at a lower availability version floor since
    // neither can be used by API clients.
    if (D->isSPI() || AvailableAttr::isUnavailable(D))
      return true;

    return !::isExported(D);
  }

  /// 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 *D) {
    // We require a valid range in order to be able to query for the TRC
    // 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(D->getSourceRange().isValid());

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

      // For a variable declaration (without accessors) we use the range of the
      // containing pattern binding declaration to make sure that we include
      // any type annotation in the type refinement context range. We also
      // need to include any attached property wrappers.
      if (auto *varDecl = dyn_cast<VarDecl>(storageDecl)) {
        if (auto *PBD = varDecl->getParentPatternBinding())
          Range = PBD->getSourceRange();

        for (auto *propertyWrapper : varDecl->getAttachedPropertyWrappers()) {
          Range.widen(propertyWrapper->getRange());
        }
      }

      // 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 (storageDecl->hasParsedAccessors() && BracesRange.isValid()) {
        Range.widen(BracesRange);
      }

      return Range;
    }
    
    return D->getSourceRange();
  }

  void buildContextsForBodyOfDecl(Decl *D) {
    // Are we already constrained by the deployment target? If not, adding
    // new contexts won't change availability.
    if (isCurrentTRCContainedByDeploymentTarget())
      return;

    // A lambda that creates an implicit decl TRC specifying the deployment
    // target for `range` in decl `D`.
    auto createContext = [this](Decl *D, SourceRange range) {
      AvailabilityContext Availability =
          AvailabilityContext::forDeploymentTarget(Context);
      Availability.intersectWith(getCurrentTRC()->getAvailabilityInfo());

      return TypeRefinementContext::createForDeclImplicit(
          Context, D, getCurrentTRC(), Availability, range);
    };

    // Top level code always uses the deployment target.
    if (auto tlcd = dyn_cast<TopLevelCodeDecl>(D)) {
      if (auto bodyStmt = tlcd->getBody()) {
        pushDeclBodyContext(createContext(tlcd, tlcd->getSourceRange()), tlcd,
                            bodyStmt);
      }
      return;
    }

    // Function bodies use the deployment target if they are within the module's
    // resilience domain.
    if (auto afd = dyn_cast<AbstractFunctionDecl>(D)) {
      if (!afd->isImplicit() &&
          afd->getResilienceExpansion() != ResilienceExpansion::Minimal) {
        if (auto body = afd->getBody(/*canSynthesize*/ false)) {
          pushDeclBodyContext(createContext(afd, afd->getBodySourceRange()),
                              afd, body);
        }
      }
      return;
    }

    // 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.
    if (auto vd = dyn_cast<VarDecl>(D)) {
      if (!vd->hasInitialValue() || vd->isInitExposedToClients())
        return;

      if (auto *pbd = vd->getParentPatternBinding()) {
        int idx = pbd->getPatternEntryIndexForVarDecl(vd);
        auto *initExpr = pbd->getInit(idx);
        if (initExpr && !initExpr->isImplicit()) {
          assert(initExpr->getSourceRange().isValid());

          // Create a TRC for the init written in the source. The ASTWalker
          // won't visit these expressions so instead of pushing these onto the
          // stack we build them directly.
          auto *initTRC = createContext(vd, initExpr->getSourceRange());
          TypeRefinementContextBuilder(initTRC, Context).build(initExpr);
        }

        // 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
        // TRC for the initializer expression.
        //
        // FIXME: Since we don't have an expression here, we can't build out its
        // TRC. 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).
        for (auto *wrapper : vd->getAttachedPropertyWrappers()) {
          createContext(vd, wrapper->getRange());
        }
      }

      return;
    }
  }

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

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

    if (auto *IS = dyn_cast<IfStmt>(S)) {
      buildIfStmtRefinementContext(IS);
      return Action::SkipChildren(S);
    }

    if (auto *RS = dyn_cast<GuardStmt>(S)) {
      buildGuardStmtRefinementContext(RS);
      return Action::SkipChildren(S);
    }

    if (auto *WS = dyn_cast<WhileStmt>(S)) {
      buildWhileStmtRefinementContext(WS);
      return Action::SkipChildren(S);
    }

    return Action::Continue(S);
  }

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

    return Action::Continue(S);
  }

  /// Consumes the top TRC from \p DeclBodyContextStack and pushes it onto the
  /// \p Context stack if the given \p Stmt is the matching body statement.
  /// Returns \p true if a context was pushed.
  bool consumeDeclBodyContextIfNecessary(Stmt *S) {
    if (DeclBodyContextStack.empty())
      return false;

    auto Info = DeclBodyContextStack.back();
    if (S != Info.BodyStmt)
      return false;

    pushContext(Info.TRC, Info.BodyStmt);
    DeclBodyContextStack.pop_back();
    return true;
  }

  /// Builds the type refinement hierarchy for the IfStmt if the guard
  /// introduces a new refinement context for the Then branch.
  /// There is no need for the caller to explicitly traverse the children
  /// of this node.
  void buildIfStmtRefinementContext(IfStmt *IS) {
    Optional<AvailabilityContext> ThenRange;
    Optional<AvailabilityContext> ElseRange;
    std::tie(ThenRange, ElseRange) =
        buildStmtConditionRefinementContext(IS->getCond());

    if (ThenRange.has_value()) {
      // Create a new context for the Then branch and traverse it in that new
      // context.
      auto *ThenTRC =
          TypeRefinementContext::createForIfStmtThen(Context, IS,
                                                     getCurrentTRC(),
                                                     ThenRange.value());
      TypeRefinementContextBuilder(ThenTRC, Context).build(IS->getThenStmt());
    } else {
      build(IS->getThenStmt());
    }

    Stmt *ElseStmt = IS->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 (ElseRange.has_value()) {
      // Create a new context for the Then branch and traverse it in that new
      // context.
      auto *ElseTRC =
          TypeRefinementContext::createForIfStmtElse(Context, IS,
                                                     getCurrentTRC(),
                                                     ElseRange.value());
      TypeRefinementContextBuilder(ElseTRC, Context).build(ElseStmt);
    } else {
      build(IS->getElseStmt());
    }
  }

  /// Builds the type refinement hierarchy for the WhileStmt if the guard
  /// introduces a new refinement context for the body branch.
  /// There is no need for the caller to explicitly traverse the children
  /// of this node.
  void buildWhileStmtRefinementContext(WhileStmt *WS) {
    Optional<AvailabilityContext> BodyRange =
        buildStmtConditionRefinementContext(WS->getCond()).first;

    if (BodyRange.has_value()) {
      // Create a new context for the body and traverse it in the new
      // context.
      auto *BodyTRC = TypeRefinementContext::createForWhileStmtBody(
          Context, WS, getCurrentTRC(), BodyRange.value());
      TypeRefinementContextBuilder(BodyTRC, Context).build(WS->getBody());
    } else {
      build(WS->getBody());
    }
  }

  /// Builds the type refinement hierarchy for the GuardStmt and pushes
  /// the fallthrough context onto the context stack so that subsequent
  /// AST elements in the same scope are analyzed in the context of the
  /// fallthrough TRC.
  void buildGuardStmtRefinementContext(GuardStmt *GS) {
    // '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 refinement context.
    Optional<AvailabilityContext> FallthroughRange;
    Optional<AvailabilityContext> ElseRange;
    std::tie(FallthroughRange, ElseRange) =
        buildStmtConditionRefinementContext(GS->getCond());

    if (Stmt *ElseBody = GS->getBody()) {
      if (ElseRange.has_value()) {
        auto *TrueTRC = TypeRefinementContext::createForGuardStmtElse(
            Context, GS, getCurrentTRC(), ElseRange.value());

        TypeRefinementContextBuilder(TrueTRC, Context).build(ElseBody);
      } else {
        build(ElseBody);
      }
    }

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

    // Create a new context for the fallthrough.

    auto *FallthroughTRC =
          TypeRefinementContext::createForGuardStmtFallthrough(Context, GS,
              ParentBrace, getCurrentTRC(), FallthroughRange.value());

    pushContext(FallthroughTRC, ParentBrace);
  }

  /// Build the type refinement context for a StmtCondition and return a pair
  /// of optional version ranges, the first for the true branch and the second
  /// for the false branch. A value of None for a given branch indicates that
  /// the branch does not introduce a new refinement.
  std::pair<Optional<AvailabilityContext>, Optional<AvailabilityContext>>
  buildStmtConditionRefinementContext(StmtCondition Cond) {

    // Any refinement contexts 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 refinement contexts we have pushed on
    // the context stack so we can pop them when we're done building the
    // context for the StmtCondition.
    unsigned NestedCount = 0;

    // Tracks the potential version range when the condition is false.
    auto FalseFlow = AvailabilityContext::neverAvailable();

    TypeRefinementContext *StartingTRC = getCurrentTRC();

    // Tracks if we're refining for availability or unavailability.
    Optional<bool> isUnavailability = None;

    for (StmtConditionElement Element : Cond) {
      TypeRefinementContext *CurrentTRC = getCurrentTRC();
      AvailabilityContext CurrentInfo = CurrentTRC->getAvailabilityInfo();
      AvailabilityContext CurrentExplicitInfo =
        CurrentTRC->getExplicitAvailabilityInfo();

      // If the element is not a condition, walk it in the current TRC.
      if (Element.getKind() != StmtConditionElement::CK_Availability) {

        // Assume any condition element that is not a #available() can
        // potentially be false, so conservatively combine the version
        // range of the current context with the accumulated false flow
        // of all other conjuncts.
        FalseFlow.unionWith(CurrentInfo);

        Element.walk(*this);
        continue;
      }

      // #available query: introduce a new refinement context for the statement
      // condition elements following it.
      auto *Query = Element.getAvailability();

      if (isUnavailability == None) {
        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
      // refinement context. 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;

      AvailabilitySpec *Spec = bestActiveSpecForQuery(Query);
      if (!Spec) {
        // We couldn't find an appropriate spec for the current platform,
        // so rather than refining, emit a diagnostic and just use the current
        // TRC.
        Context.Diags.diagnose(
            Query->getLoc(), diag::availability_query_required_for_platform,
            platformString(targetPlatform(Context.LangOpts)));

        continue;
      }

      AvailabilityContext NewConstraint = contextForSpec(Spec, false);
      Query->setAvailableRange(contextForSpec(Spec, true).getOSVersion());

      // 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.
      if (Context.LangOpts.TargetVariant) {
        AvailabilitySpec *VariantSpec =
            bestActiveSpecForQuery(Query, /*ForTargetVariant*/ true);
        VersionRange VariantRange =
            contextForSpec(VariantSpec, true).getOSVersion();
        Query->setVariantAvailableRange(VariantRange);
      }

      if (Spec->getKind() == AvailabilitySpecKind::OtherPlatform) {
        // The wildcard spec '*' represents the minimum deployment target, so
        // there is no need to create a refinement context for this query.
        // Further, we won't diagnose for useless #available() conditions
        // where * matched on this platform -- presumably those conditions are
        // needed for some other platform.
        continue;
      }

      // If the explicitly-specified (via #availability) version range for the
      // current TRC 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.
      if (CurrentExplicitInfo.isContainedIn(NewConstraint)) {
        // Unavailability refinements are always "useless" from a symbol
        // availability point of view, so only useless availability specs are
        // reported.
        if (isUnavailability.value()) {
          continue;
        }
        DiagnosticEngine &Diags = Context.Diags;
        if (CurrentTRC->getReason() != TypeRefinementContext::Reason::Root) {
          PlatformKind BestPlatform = targetPlatform(Context.LangOpts);
          auto *PlatformSpec =
              dyn_cast<PlatformVersionConstraintAvailabilitySpec>(Spec);

          // If possible, try to report the diagnostic in terms for the
          // platform the user uttered in the '#available()'. For a platform
          // that inherits availability from another platform it may be
          // different from the platform specified in the target triple.
          if (PlatformSpec)
            BestPlatform = PlatformSpec->getPlatform();
          Diags.diagnose(Query->getLoc(),
                         diag::availability_query_useless_enclosing_scope,
                         platformString(BestPlatform));
          Diags.diagnose(CurrentTRC->getIntroductionLoc(),
                         diag::availability_query_useless_enclosing_scope_here);
        }
      }

      if (CurrentInfo.isContainedIn(NewConstraint)) {
        // No need to actually create the refinement context if we know it is
        // useless.
        continue;
      }

      // If the #available() is not useless then there is potential false flow,
      // so join the false flow with the potential versions of the current
      // context.
      // We could be more precise here if we enriched the lattice to include
      // ranges of the form [x, y).
      FalseFlow.unionWith(CurrentInfo);

      auto *TRC = TypeRefinementContext::createForConditionFollowingQuery(
          Context, Query, LastElement, CurrentTRC, NewConstraint);

      pushContext(TRC, ParentTy());
      ++NestedCount;
    }


    Optional<AvailabilityContext> FalseRefinement = None;
    // The version range for the false branch should never have any versions
    // that weren't possible when the condition started evaluating.
    assert(FalseFlow.isContainedIn(StartingTRC->getAvailabilityInfo()));

    // If the starting version range is not completely contained in the
    // false flow version range then it must be the case that false flow range
    // is strictly smaller than the starting range (because the false flow
    // range *is* contained in the starting range), so we should introduce a
    // new refinement for the false flow.
    if (!StartingTRC->getAvailabilityInfo().isContainedIn(FalseFlow)) {
      FalseRefinement = FalseFlow;
    }

    auto makeResult =
        [isUnavailability](Optional<AvailabilityContext> TrueRefinement,
                           Optional<AvailabilityContext> FalseRefinement) {
          if (isUnavailability.has_value() && isUnavailability.value()) {
            // 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(None, FalseRefinement);

    TypeRefinementContext *NestedTRC = getCurrentTRC();
    while (NestedCount-- > 0)
      ContextStack.pop_back();

    assert(getCurrentTRC() == StartingTRC);

    return makeResult(NestedTRC->getAvailabilityInfo(), FalseRefinement);
  }

  /// Return the best active spec for the target platform or nullptr if no
  /// such spec exists.
  AvailabilitySpec *bestActiveSpecForQuery(PoundAvailableInfo *available,
                                           bool forTargetVariant = false) {
    OtherPlatformAvailabilitySpec *FoundOtherSpec = nullptr;
    PlatformVersionConstraintAvailabilitySpec *BestSpec = nullptr;

    for (auto *Spec : available->getQueries()) {
      if (auto *OtherSpec = dyn_cast<OtherPlatformAvailabilitySpec>(Spec)) {
        FoundOtherSpec = OtherSpec;
        continue;
      }

      auto *VersionSpec =
          dyn_cast<PlatformVersionConstraintAvailabilitySpec>(Spec);
      if (!VersionSpec)
        continue;

      // 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.
      if (isPlatformActive(VersionSpec->getPlatform(), Context.LangOpts,
                           forTargetVariant)) {
        if (!BestSpec ||
            inheritsAvailabilityFromPlatform(VersionSpec->getPlatform(),
                                             BestSpec->getPlatform())) {
          BestSpec = VersionSpec;
        }
      }
    }

    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 (FoundOtherSpec) {
      return FoundOtherSpec;
    } else if (available->isUnavailability()) {
      // For #unavailable, imply the presence of a wildcard.
      SourceLoc Loc = available->getRParenLoc();
      return new (Context) OtherPlatformAvailabilitySpec(Loc);
    } else {
      return nullptr;
    }
  }

  /// Return the availability context for the given spec.
  AvailabilityContext contextForSpec(AvailabilitySpec *Spec,
                                    bool GetRuntimeContext) {
    if (isa<OtherPlatformAvailabilitySpec>(Spec)) {
      return AvailabilityContext::alwaysAvailable();
    }

    auto *VersionSpec = cast<PlatformVersionConstraintAvailabilitySpec>(Spec);

    llvm::VersionTuple Version = (GetRuntimeContext ?
                                    VersionSpec->getRuntimeVersion() :
                                    VersionSpec->getVersion());

    return AvailabilityContext(VersionRange::allGTE(Version));
  }

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

    return Action::Continue(E);
  }
};
  
} // end anonymous namespace

void TypeChecker::buildTypeRefinementContextHierarchy(SourceFile &SF) {
  TypeRefinementContext *RootTRC = SF.getTypeRefinementContext();
  ASTContext &Context = SF.getASTContext();

  if (!RootTRC) {
    // The root type refinement context 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 MinPlatformReq = AvailabilityContext::forInliningTarget(Context);
    RootTRC = TypeRefinementContext::createRoot(&SF, MinPlatformReq);
    SF.setTypeRefinementContext(RootTRC);
  }

  // Build refinement contexts, if necessary, for all declarations starting
  // with StartElem.
  TypeRefinementContextBuilder Builder(RootTRC, Context);
  for (auto D : SF.getTopLevelDecls()) {
    Builder.build(D);
  }
}

void TypeChecker::buildTypeRefinementContextHierarchyDelayed(SourceFile &SF, AbstractFunctionDecl *AFD) {
  // If there's no TRC for the file, we likely don't want this one either.
  // RootTRC is not set when availability checking is disabled.
  TypeRefinementContext *RootTRC = SF.getTypeRefinementContext();
  if(!RootTRC)
    return;

  if (AFD->getBodyKind() != AbstractFunctionDecl::BodyKind::Unparsed)
    return;

  // Parse the function body.
  AFD->getBody(/*canSynthesize=*/true);

  // Build the refinement context for the function body.
  ASTContext &Context = SF.getASTContext();
  auto LocalTRC = RootTRC->findMostRefinedSubContext(AFD->getLoc(), Context.SourceMgr);
  TypeRefinementContextBuilder Builder(LocalTRC, Context);
  Builder.build(AFD);
}

TypeRefinementContext *
TypeChecker::getOrBuildTypeRefinementContext(SourceFile *SF) {
  TypeRefinementContext *TRC = SF->getTypeRefinementContext();
  if (!TRC) {
    buildTypeRefinementContextHierarchy(*SF);
    TRC = SF->getTypeRefinementContext();
  }

  return TRC;
}

AvailabilityContext
TypeChecker::overApproximateAvailabilityAtLocation(SourceLoc loc,
                                                   const DeclContext *DC,
                                                   const TypeRefinementContext **MostRefined) {
  SourceFile *SF = DC->getParentSourceFile();
  auto &Context = DC->getASTContext();

  // If our source location is invalid (this may be synthesized code), climb
  // the decl context hierarchy until we find a location that is valid,
  // collecting availability ranges on the way up.
  // We will combine the version ranges from these annotations
  // with the TRC for the valid location to overapproximate the running
  // OS versions at the original source location.
  // Because we are climbing DeclContexts we will miss refinement contexts in
  // synthesized code that are introduced by AST elements that are themselves
  // not DeclContexts, such as  #available(..) and property declarations.
  // That is, a reference with an invalid location that is contained
  // inside a #available() and with no intermediate DeclContext will not be
  // refined. For now, this is fine -- but if we ever synthesize #available(),
  // this will be a real problem.

  // We can assume we are running on at least the minimum inlining target.
  auto OverApproximateContext = AvailabilityContext::forInliningTarget(Context);
  auto isInvalidLoc = [SF](SourceLoc loc) {
    return SF ? loc.isInvalid() : true;
  };
  while (DC && isInvalidLoc(loc)) {
    const Decl *D = DC->getInnermostDeclarationDeclContext();
    if (!D)
      break;

    loc = D->getLoc();

    Optional<AvailabilityContext> Info =
        AvailabilityInference::annotatedAvailableRange(D, Context);

    if (Info.has_value()) {
      OverApproximateContext.constrainWith(Info.value());
    }

    DC = D->getDeclContext();
  }

  if (SF && loc.isValid()) {
    TypeRefinementContext *rootTRC = getOrBuildTypeRefinementContext(SF);
    TypeRefinementContext *TRC =
        rootTRC->findMostRefinedSubContext(loc, Context.SourceMgr);
    OverApproximateContext.constrainWith(TRC->getAvailabilityInfo());
    if (MostRefined) {
      *MostRefined = TRC;
    }
  }

  return OverApproximateContext;
}

bool TypeChecker::isDeclarationUnavailable(
    const Decl *D, const DeclContext *referenceDC,
    llvm::function_ref<AvailabilityContext()> getAvailabilityContext) {
  ASTContext &Context = referenceDC->getASTContext();
  if (Context.LangOpts.DisableAvailabilityChecking) {
    return false;
  }

  if (!referenceDC->getParentSourceFile()) {
    // We only check availability if this reference is in a source file; we do
    // not check in other kinds of FileUnits.
    return false;
  }

  AvailabilityContext safeRangeUnderApprox{
      AvailabilityInference::availableRange(D, Context)};

  if (safeRangeUnderApprox.isAlwaysAvailable())
    return false;

  AvailabilityContext runningOSOverApprox = getAvailabilityContext();

  // The reference is safe if an over-approximation of the running OS
  // versions is fully contained within an under-approximation
  // of the versions on which the declaration is available. If this
  // containment cannot be guaranteed, we say the reference is
  // not available.
  return !runningOSOverApprox.isContainedIn(safeRangeUnderApprox);
}

Optional<UnavailabilityReason>
TypeChecker::checkDeclarationAvailability(const Decl *D,
                                          const ExportContext &Where) {
  // Skip computing potential unavailability if the declaration is explicitly
  // unavailable and the context is also unavailable.
  if (const AvailableAttr *Attr = AvailableAttr::isUnavailable(D))
    if (isInsideCompatibleUnavailableDeclaration(D, Where, Attr))
      return None;

  if (isDeclarationUnavailable(D, Where.getDeclContext(), [&Where] {
        return Where.getAvailabilityContext();
      })) {
    auto &Context = Where.getDeclContext()->getASTContext();
    AvailabilityContext safeRangeUnderApprox{
        AvailabilityInference::availableRange(D, Context)};

    VersionRange version = safeRangeUnderApprox.getOSVersion();
    return UnavailabilityReason::requiresVersionRange(version);
  }

  return None;
}

Optional<UnavailabilityReason>
TypeChecker::checkConformanceAvailability(const RootProtocolConformance *conf,
                                          const ExtensionDecl *ext,
                                          const ExportContext &where) {
  return checkDeclarationAvailability(ext, where);
}

/// A class that walks the AST to find the innermost (i.e., deepest) node that
/// contains a target SourceRange and matches a particular criterion.
/// This class finds the innermost nodes of interest by walking
/// down the root until it has found the target range (in a Pre-visitor)
/// and then recording the innermost node on the way back up in the
/// Post-visitors. It does its best to not search unnecessary subtrees,
/// although this is complicated by the fact that not all nodes have
/// source range information.
class InnermostAncestorFinder : private ASTWalker {
public:

  /// The type of a match predicate, which takes as input a node and its
  /// parent and returns a bool indicating whether the node matches.
  using MatchPredicate = std::function<bool(ASTNode, ASTWalker::ParentTy)>;

private:
  const SourceRange TargetRange;
  const SourceManager &SM;
  const MatchPredicate Predicate;

  bool FoundTarget = false;
  Optional<ASTNode> InnermostMatchingNode;

public:
  InnermostAncestorFinder(SourceRange TargetRange, const SourceManager &SM,
                          ASTNode SearchNode, const MatchPredicate &Predicate)
      : TargetRange(TargetRange), SM(SM), Predicate(Predicate) {
    assert(TargetRange.isValid());

    SearchNode.walk(*this);
  }

  /// Returns the innermost node containing the target range that matches
  /// the predicate.
  Optional<ASTNode> getInnermostMatchingNode() { return InnermostMatchingNode; }

  MacroWalking getMacroWalkingBehavior() const override {
    return MacroWalking::ArgumentsAndExpansion;
  }

  PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
    return getPreWalkActionFor(E);
  }

  PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
    return getPreWalkActionFor(S);
  }

  PreWalkAction walkToDeclPre(Decl *D) override {
    return getPreWalkActionFor(D).Action;
  }

  PreWalkResult<Pattern *> walkToPatternPre(Pattern *P) override {
    return getPreWalkActionFor(P);
  }

  PreWalkAction walkToTypeReprPre(TypeRepr *T) override {
    return getPreWalkActionFor(T).Action;
  }

  /// Retrieve the pre-walk action for a given node, which determines whether
  /// or not it should be walked into.
  template <typename T>
  PreWalkResult<T> getPreWalkActionFor(T Node) {
    // When walking down the tree, we traverse until we have found a node
    // inside the target range. Once we have found such a node, there is no
    // need to traverse any deeper.
    if (FoundTarget)
      return Action::SkipChildren(Node);

    // If we haven't found our target yet and the node we are pre-visiting
    // doesn't have a valid range, we still have to traverse it because its
    // subtrees may have valid ranges.
    auto Range = Node->getSourceRange();
    if (Range.isInvalid())
      return Action::Continue(Node);

    // We have found our target if the range of the node we are visiting
    // is contained in the range we are looking for.
    FoundTarget = SM.rangeContains(TargetRange, Range);

    if (FoundTarget)
      return Action::SkipChildren(Node);

    // Search the subtree if the target range is inside its range.
    if (!SM.rangeContains(Range, TargetRange))
      return Action::SkipChildren(Node);

    return Action::Continue(Node);
  }

  PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
    return walkToNodePost(E);
  }

  PostWalkResult<Stmt *> walkToStmtPost(Stmt *S) override {
    return walkToNodePost(S);
  }

  PostWalkAction walkToDeclPost(Decl *D) override {
    return walkToNodePost(D).Action;
  }

  /// Once we have found the target node, look for the innermost ancestor
  /// matching our criteria on the way back up the spine of the tree.
  template <typename T>
  PostWalkResult<T> walkToNodePost(T Node) {
    if (!InnermostMatchingNode.has_value() && Predicate(Node, Parent)) {
      assert(Node->getSourceRange().isInvalid() ||
             SM.rangeContains(Node->getSourceRange(), TargetRange));

      InnermostMatchingNode = Node;
      return Action::Stop();
    }

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

/// Starting from SearchRoot, finds the innermost node containing ChildRange
/// for which Predicate returns true. Returns None if no such root is found.
static Optional<ASTNode> findInnermostAncestor(
    SourceRange ChildRange, const SourceManager &SM, ASTNode SearchRoot,
    const InnermostAncestorFinder::MatchPredicate &Predicate) {
  InnermostAncestorFinder Finder(ChildRange, SM, SearchRoot, Predicate);
  return Finder.getInnermostMatchingNode();
}

/// Given a reference range and a declaration context containing the range,
/// attempt to find a declaration containing the reference. This may not
/// be the innermost declaration containing the range.
/// Returns null if no such declaration can be found.
static const Decl *findContainingDeclaration(SourceRange ReferenceRange,
                                             const DeclContext *ReferenceDC,
                                             const SourceManager &SM) {
  auto ContainsReferenceRange = [&](const Decl *D) -> bool {
    if (ReferenceRange.isInvalid())
      return false;

    // Members of an active #if are represented both inside the
    // IfConfigDecl and in the enclosing context. Skip over the IfConfigDecl
    // so that the member declaration is found rather the #if itself.
    if (isa<IfConfigDecl>(D))
      return false;

    return SM.rangeContains(D->getSourceRange(), ReferenceRange);
  };

  if (const Decl *D = ReferenceDC->getInnermostDeclarationDeclContext()) {
    // If we have an inner declaration context, see if we can narrow the search
    // down to one of its members. This is important for properties, which don't
    // count as DeclContexts of their own but which can still introduce
    // availability.
    if (auto *IDC = dyn_cast<IterableDeclContext>(D)) {
      auto BestMember = llvm::find_if(IDC->getMembers(),
                                      ContainsReferenceRange);
      if (BestMember != IDC->getMembers().end())
        return *BestMember;
    }
    return D;
  }

  // We couldn't find a suitable node by climbing the DeclContext hierarchy, so
  // fall back to looking for a top-level declaration that contains the
  // reference range. We will hit this case for top-level elements that do not
  // themselves introduce DeclContexts, such as global variables. If we don't
  // have a reference range, there is nothing we can do, so return null.
  if (ReferenceRange.isInvalid())
    return nullptr;

  SourceFile *SF = ReferenceDC->getParentSourceFile();
  if (!SF)
    return nullptr;

  auto BestTopLevelDecl = llvm::find_if(SF->getTopLevelDecls(),
                                        ContainsReferenceRange);
  if (BestTopLevelDecl != SF->getTopLevelDecls().end())
    return *BestTopLevelDecl;

  return nullptr;
}

/// Given a declaration that allows availability attributes in the abstract
/// syntax tree, return the declaration upon which the declaration would
/// appear in concrete syntax. This function is necessary because for semantic
/// analysis, the parser attaches attributes to declarations other
/// than those on which they, concretely, appear. For these declarations (enum
/// cases and variable declarations) a Fix-It for an added availability
/// attribute should be suggested for the appropriate concrete location.
static const Decl *
concreteSyntaxDeclForAvailableAttribute(const Decl *AbstractSyntaxDecl) {
  // This function needs to be kept in sync with its counterpart,
  // abstractSyntaxDeclForAvailableAttribute().

  // The source range for VarDecls does not include 'var ' (and, in any
  // event, multiple variables can be introduced with a single 'var'),
  // so suggest adding an attribute to the PatterningBindingDecl instead.
  if (auto *VD = dyn_cast<VarDecl>(AbstractSyntaxDecl)) {
    return VD->getParentPatternBinding();
  }

  // Similarly suggest applying the Fix-It to the parent enum case rather than
  // the enum element.
  if (auto *EE = dyn_cast<EnumElementDecl>(AbstractSyntaxDecl)) {
    return EE->getParentCase();
  }

  return AbstractSyntaxDecl;
}

/// Given a declaration upon which an availability attribute would appear in
/// concrete syntax, return a declaration to which the parser
/// actually attaches the attribute in the abstract syntax tree. We use this
/// function to determine whether the concrete syntax already has an
/// availability attribute.
static const Decl *
abstractSyntaxDeclForAvailableAttribute(const Decl *ConcreteSyntaxDecl) {
  // This function needs to be kept in sync with its counterpart,
  // concreteSyntaxDeclForAvailableAttribute().

  if (auto *PBD = dyn_cast<PatternBindingDecl>(ConcreteSyntaxDecl)) {
    // Existing @available attributes in the AST are attached to VarDecls
    // rather than PatternBindingDecls, so we return the first VarDecl for
    // the pattern binding declaration.
    // This is safe, even though there may be multiple VarDecls, because
    // all parsed attribute that appear in the concrete syntax upon on the
    // PatternBindingDecl are added to all of the VarDecls for the pattern
    // binding.
    if (PBD->getNumPatternEntries() != 0) {
      return PBD->getAnchoringVarDecl(0);
    }
  } else if (auto *ECD = dyn_cast<EnumCaseDecl>(ConcreteSyntaxDecl)) {
    // Similar to the PatternBindingDecl case above, we return the
    // first EnumElementDecl.
    if (auto *Elem = ECD->getFirstElement()) {
      return Elem;
    }
  }

  return ConcreteSyntaxDecl;
}

/// Given a declaration, return a better related declaration for which
/// to suggest an @available fixit, or the original declaration
/// if no such related declaration exists.
static const Decl *relatedDeclForAvailabilityFixit(const Decl *D) {
  if (auto *accessor = dyn_cast<AccessorDecl>(D)) {
    // Suggest @available Fix-Its on property rather than individual
    // accessors.
    D = accessor->getStorage();
  }

  return abstractSyntaxDeclForAvailableAttribute(D);
}

/// Walk the DeclContext hierarchy starting from D to find a declaration
/// at the member level (i.e., declared in a type context) on which to provide
/// an @available() Fix-It.
static const Decl *ancestorMemberLevelDeclForAvailabilityFixit(const Decl *D) {
  while (D) {
    D = relatedDeclForAvailabilityFixit(D);

    if (!D->isImplicit() &&
        D->getDeclContext()->isTypeContext() &&
        DeclAttribute::canAttributeAppearOnDecl(DeclAttrKind::DAK_Available,
                                                D)) {
      break;
    }

    D = cast_or_null<AbstractFunctionDecl>(
        D->getDeclContext()->getInnermostMethodContext());
  }

  return D;
}

/// Returns true if the declaration is at the type level (either a nominal
/// type, an extension, or a global function) and can support an @available
/// attribute.
static bool isTypeLevelDeclForAvailabilityFixit(const Decl *D) {
  if (!DeclAttribute::canAttributeAppearOnDecl(DeclAttrKind::DAK_Available,
                                               D)) {
    return false;
  }

  if (isa<ExtensionDecl>(D) || isa<NominalTypeDecl>(D)) {
    return true;
  }

  bool IsModuleScopeContext = D->getDeclContext()->isModuleScopeContext();

  // We consider global functions to be "type level"
  if (isa<FuncDecl>(D)) {
    return IsModuleScopeContext;
  }

  if (auto *VD = dyn_cast<VarDecl>(D)) {
    if (!IsModuleScopeContext)
      return false;

    if (PatternBindingDecl *PBD = VD->getParentPatternBinding()) {
      return PBD->getDeclContext()->isModuleScopeContext();
    }
  }

  return false;
}

/// Walk the DeclContext hierarchy starting from D to find a declaration
/// at a member level (i.e., declared in a type context) on which to provide an
/// @available() Fix-It.
static const Decl *ancestorTypeLevelDeclForAvailabilityFixit(const Decl *D) {
  assert(D);

  D = relatedDeclForAvailabilityFixit(D);

  while (D && !isTypeLevelDeclForAvailabilityFixit(D)) {
    D = D->getDeclContext()->getInnermostDeclarationDeclContext();
  }

  return D;
}

/// Given the range of a reference to an unavailable symbol and the
/// declaration context containing the reference, make a best effort find up to
/// three locations for potential fixits.
///
/// \param FoundVersionCheckNode Returns a node that can be wrapped in a
/// if #available(...) { ... } version check to fix the unavailable reference,
/// or None if such a node cannot be found.
///
/// \param FoundMemberLevelDecl Returns member-level declaration (i.e., the
///  child of a type DeclContext) for which an @available attribute would
/// fix the unavailable reference.
///
/// \param FoundTypeLevelDecl returns a type-level declaration (a
/// a nominal type, an extension, or a global function) for which an
/// @available attribute would fix the unavailable reference.
static void findAvailabilityFixItNodes(SourceRange ReferenceRange,
                                       const DeclContext *ReferenceDC,
                                       const SourceManager &SM,
                                       Optional<ASTNode> &FoundVersionCheckNode,
                                       const Decl *&FoundMemberLevelDecl,
                                       const Decl *&FoundTypeLevelDecl) {
  FoundVersionCheckNode = None;
  FoundMemberLevelDecl = nullptr;
  FoundTypeLevelDecl = nullptr;

  // Limit tree to search based on the DeclContext of the reference.
  const Decl *DeclarationToSearch =
      findContainingDeclaration(ReferenceRange, ReferenceDC, SM);
  if (!DeclarationToSearch)
    return;

  // Const-cast to inject into ASTNode. This search will not modify
  // the declaration.
  ASTNode SearchRoot = const_cast<Decl *>(DeclarationToSearch);

  // The node to wrap in if #available(...) { ... } is the innermost node in
  // SearchRoot that (1) can be guarded with an if statement and (2)
  // contains the ReferenceRange.
  // We make no guarantee that the Fix-It, when applied, will result in
  // semantically valid code -- but, at a minimum, it should parse. So,
  // for example, we may suggest wrapping a variable declaration in a guard,
  // which would not be valid if the variable is later used. The goal
  // is discoverability of #os() (via the diagnostic and Fix-It) rather than
  // magically fixing the code in all cases.

  InnermostAncestorFinder::MatchPredicate IsGuardable =
      [](ASTNode Node, ASTWalker::ParentTy Parent) {
        if (Expr *ParentExpr = Parent.getAsExpr()) {
          auto *ParentClosure = dyn_cast<ClosureExpr>(ParentExpr);
          if (!ParentClosure ||
              ParentClosure->isSeparatelyTypeChecked()) {
            return false;
          }
        } else if (auto *ParentStmt = Parent.getAsStmt()) {
          if (!isa<BraceStmt>(ParentStmt)) {
            return false;
          }
        } else {
          return false;
        }

        return true;
      };

  FoundVersionCheckNode =
      findInnermostAncestor(ReferenceRange, SM, SearchRoot, IsGuardable);

  // Try to find declarations on which @available attributes can be added.
  // The heuristics for finding these declarations are biased towards deeper
  // nodes in the AST to limit the scope of suggested availability regions
  // and provide a better IDE experience (it can get jumpy if Fix-It locations
  // are far away from the error needing the Fix-It).
  if (DeclarationToSearch) {
    FoundMemberLevelDecl =
        ancestorMemberLevelDeclForAvailabilityFixit(DeclarationToSearch);

    FoundTypeLevelDecl =
        ancestorTypeLevelDeclForAvailabilityFixit(DeclarationToSearch);
  }
}

/// Emit a diagnostic note and Fix-It to add an @available attribute
/// on the given declaration for the given version range.
static void fixAvailabilityForDecl(SourceRange ReferenceRange, const Decl *D,
                                   const VersionRange &RequiredRange,
                                   ASTContext &Context) {
  assert(D);

  // Don't suggest adding an @available() to a declaration where we would
  // emit a diagnostic saying it is not allowed.
  if (TypeChecker::diagnosticIfDeclCannotBePotentiallyUnavailable(D).has_value())
    return;

  if (getActiveAvailableAttribute(D, Context)) {
    // For QoI, in future should emit a fixit to update the existing attribute.
    return;
  }

  // For some declarations (variables, enum elements), the location in concrete
  // syntax to suggest the Fix-It may differ from the declaration to which
  // we attach availability attributes in the abstract syntax tree during
  // parsing.
  const Decl *ConcDecl = concreteSyntaxDeclForAvailableAttribute(D);

  DescriptiveDeclKind KindForDiagnostic = ConcDecl->getDescriptiveKind();
  SourceLoc InsertLoc;

  // To avoid exposing the pattern binding declaration to the user, get the
  // descriptive kind from one of the VarDecls. We get the Fix-It location
  // from the PatternBindingDecl unless the VarDecl has attributes,
  // in which case we get the start location of the VarDecl attributes.
  DeclAttributes AttrsForLoc;
  if (KindForDiagnostic == DescriptiveDeclKind::PatternBinding) {
    KindForDiagnostic = D->getDescriptiveKind();
    AttrsForLoc = D->getAttrs();
  } else {
    InsertLoc = ConcDecl->getAttrs().getStartLoc(/*forModifiers=*/false);
  }

  InsertLoc = D->getAttrs().getStartLoc(/*forModifiers=*/false);
  if (InsertLoc.isInvalid()) {
    InsertLoc = ConcDecl->getStartLoc();
  }

  if (InsertLoc.isInvalid())
    return;

  StringRef OriginalIndent =
      Lexer::getIndentationForLine(Context.SourceMgr, InsertLoc);
  PlatformKind Target = targetPlatform(Context.LangOpts);

  D->diagnose(diag::availability_add_attribute, KindForDiagnostic)
      .fixItInsert(InsertLoc, diag::insert_available_attr,
                   platformString(Target),
                   RequiredRange.getLowerEndpoint().getAsString(),
                   OriginalIndent);
}

/// In the special case of being in an existing, nontrivial type refinement
/// context that's close but not quite narrow enough to satisfy requirements
/// (i.e.  requirements are contained-in the existing TRC but off by a subminor
/// version), emit a diagnostic and fixit that narrows the existing TRC
/// condition to the required range.
static bool fixAvailabilityByNarrowingNearbyVersionCheck(
    SourceRange ReferenceRange,
    const DeclContext *ReferenceDC,
    const VersionRange &RequiredRange,
    ASTContext &Context,
    InFlightDiagnostic &Err) {
  const TypeRefinementContext *TRC = nullptr;
  (void)TypeChecker::overApproximateAvailabilityAtLocation(ReferenceRange.Start,
                                                           ReferenceDC, &TRC);
  if (!TRC)
    return false;
  VersionRange RunningRange = TRC->getExplicitAvailabilityInfo().getOSVersion();
  if (RunningRange.hasLowerEndpoint() &&
      RequiredRange.hasLowerEndpoint() &&
      TRC->getReason() != TypeRefinementContext::Reason::Root &&
      AvailabilityContext(RequiredRange).isContainedIn(
                                 AvailabilityContext(RunningRange))) {

    // Only fix situations that are "nearby" versions, meaning
    // disagreement on a minor-or-less version for non-macOS,
    // or disagreement on a subminor-or-less version for macOS.
    auto RunningVers = RunningRange.getLowerEndpoint();
    auto RequiredVers = RequiredRange.getLowerEndpoint();
    auto Platform = targetPlatform(Context.LangOpts);
    if (RunningVers.getMajor() != RequiredVers.getMajor())
      return false;
    if ((Platform == PlatformKind::macOS ||
         Platform == PlatformKind::macOSApplicationExtension) &&
        !(RunningVers.getMinor().has_value() &&
          RequiredVers.getMinor().has_value() &&
          RunningVers.getMinor().value() ==
          RequiredVers.getMinor().value()))
      return false;

    auto FixRange = TRC->getAvailabilityConditionVersionSourceRange(
      Platform, RunningVers);
    if (!FixRange.isValid())
      return false;
    // Have found a nontrivial type refinement context-introducer to narrow.
    Err.fixItReplace(FixRange, RequiredVers.getAsString());
    return true;
  }
  return false;
}

/// Emit a diagnostic note and Fix-It to add an if #available(...) { } guard
/// that checks for the given version range around the given node.
static void fixAvailabilityByAddingVersionCheck(
    ASTNode NodeToWrap, const VersionRange &RequiredRange,
    SourceRange ReferenceRange, ASTContext &Context) {
  // If this is an implicit variable that wraps an expression,
  // let's point to it's initializer. For example, result builder
  // transform captures expressions into implicit variables.
  if (auto *PB =
          dyn_cast_or_null<PatternBindingDecl>(NodeToWrap.dyn_cast<Decl *>())) {
    if (PB->isImplicit() && PB->getSingleVar()) {
      if (auto *init = PB->getInit(0))
        NodeToWrap = init;
    }
  }

  SourceRange RangeToWrap = NodeToWrap.getSourceRange();
  if (RangeToWrap.isInvalid())
    return;

  SourceLoc ReplaceLocStart = RangeToWrap.Start;
  StringRef ExtraIndent;
  StringRef OriginalIndent = Lexer::getIndentationForLine(
      Context.SourceMgr, ReplaceLocStart, &ExtraIndent);

  std::string IfText;
  {
    llvm::raw_string_ostream Out(IfText);

    SourceLoc ReplaceLocEnd =
        Lexer::getLocForEndOfToken(Context.SourceMgr, RangeToWrap.End);

    std::string GuardedText =
        Context.SourceMgr.extractText(CharSourceRange(Context.SourceMgr,
                                                      ReplaceLocStart,
                                                      ReplaceLocEnd)).str();

    std::string NewLine = "\n";
    std::string NewLineReplacement = (NewLine + ExtraIndent).str();

    // Indent the body of the Fix-It if. Because the body may be a compound
    // statement, we may have to indent multiple lines.
    size_t StartAt = 0;
    while ((StartAt = GuardedText.find(NewLine, StartAt)) !=
           std::string::npos) {
      GuardedText.replace(StartAt, NewLine.length(), NewLineReplacement);
      StartAt += NewLine.length();
    }

    PlatformKind Target = targetPlatform(Context.LangOpts);

    Out << "if #available(" << platformString(Target)
        << " " << RequiredRange.getLowerEndpoint().getAsString()
        << ", *) {\n";

    Out << OriginalIndent << ExtraIndent << GuardedText << "\n";

    // We emit an empty fallback case with a comment to encourage the developer
    // to think explicitly about whether fallback on earlier versions is needed.
    Out << OriginalIndent << "} else {\n";
    Out << OriginalIndent << ExtraIndent << "// Fallback on earlier versions\n";
    Out << OriginalIndent << "}";
  }

  Context.Diags.diagnose(
      ReferenceRange.Start, diag::availability_guard_with_version_check)
      .fixItReplace(RangeToWrap, IfText);
}

/// Emit suggested Fix-Its for a reference with to an unavailable symbol
/// requiting the given OS version range.
static void fixAvailability(SourceRange ReferenceRange,
                            const DeclContext *ReferenceDC,
                            const VersionRange &RequiredRange,
                            ASTContext &Context) {
  if (ReferenceRange.isInvalid())
    return;

  Optional<ASTNode> NodeToWrapInVersionCheck;
  const Decl *FoundMemberDecl = nullptr;
  const Decl *FoundTypeLevelDecl = nullptr;

  findAvailabilityFixItNodes(ReferenceRange, ReferenceDC, Context.SourceMgr,
                             NodeToWrapInVersionCheck, FoundMemberDecl,
                             FoundTypeLevelDecl);

  // Suggest wrapping in if #available(...) { ... } if possible.
  if (NodeToWrapInVersionCheck.has_value()) {
    fixAvailabilityByAddingVersionCheck(NodeToWrapInVersionCheck.value(),
                                        RequiredRange, ReferenceRange, Context);
  }

  // Suggest adding availability attributes.
  if (FoundMemberDecl) {
    fixAvailabilityForDecl(ReferenceRange, FoundMemberDecl, RequiredRange,
                           Context);
  }

  if (FoundTypeLevelDecl) {
    fixAvailabilityForDecl(ReferenceRange, FoundTypeLevelDecl, RequiredRange,
                           Context);
  }
}

void TypeChecker::diagnosePotentialOpaqueTypeUnavailability(
    SourceRange ReferenceRange, const DeclContext *ReferenceDC,
    const UnavailabilityReason &Reason) {
  ASTContext &Context = ReferenceDC->getASTContext();

  auto RequiredRange = Reason.getRequiredOSVersionRange();
  {
    auto Err =
      Context.Diags.diagnose(
               ReferenceRange.Start, diag::availability_opaque_types_only_version_newer,
               prettyPlatformString(targetPlatform(Context.LangOpts)),
               Reason.getRequiredOSVersionRange().getLowerEndpoint());

    // Direct a fixit to the error if an existing guard is nearly-correct
    if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
                                                     ReferenceDC,
                                                     RequiredRange, Context, Err))
      return;
  }
  fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
}

static void diagnosePotentialConcurrencyUnavailability(
    SourceRange ReferenceRange, const DeclContext *ReferenceDC,
    const UnavailabilityReason &Reason) {
  ASTContext &Context = ReferenceDC->getASTContext();

  auto RequiredRange = Reason.getRequiredOSVersionRange();
  {
    auto Err =
      Context.Diags.diagnose(
          ReferenceRange.Start,
          diag::availability_concurrency_only_version_newer,
          prettyPlatformString(targetPlatform(Context.LangOpts)),
          Reason.getRequiredOSVersionRange().getLowerEndpoint());

    // Direct a fixit to the error if an existing guard is nearly-correct
    if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
                                                     ReferenceDC,
                                                     RequiredRange, Context, Err))
      return;
  }
  fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
}

void TypeChecker::checkConcurrencyAvailability(SourceRange ReferenceRange,
                                               const DeclContext *ReferenceDC) {
  // Check the availability of concurrency runtime support.
  ASTContext &ctx = ReferenceDC->getASTContext();
  if (ctx.LangOpts.DisableAvailabilityChecking)
    return;

  if (!shouldCheckAvailability(ReferenceDC->getAsDecl()))
    return;
  
  auto runningOS =
    TypeChecker::overApproximateAvailabilityAtLocation(
      ReferenceRange.Start, ReferenceDC);
  auto availability = ctx.getBackDeployedConcurrencyAvailability();
  if (!runningOS.isContainedIn(availability)) {
    diagnosePotentialConcurrencyUnavailability(
      ReferenceRange, ReferenceDC,
      UnavailabilityReason::requiresVersionRange(availability.getOSVersion()));
  }
}

/// Returns the diagnostic to emit for the potentially unavailable decl and sets
/// \p IsError accordingly.
static Diagnostic getPotentialUnavailabilityDiagnostic(
    const ValueDecl *D, const DeclContext *ReferenceDC,
    const UnavailabilityReason &Reason, bool WarnBeforeDeploymentTarget,
    bool &IsError) {
  ASTContext &Context = ReferenceDC->getASTContext();
  auto Platform = prettyPlatformString(targetPlatform(Context.LangOpts));
  auto Version = Reason.getRequiredOSVersionRange().getLowerEndpoint();

  if (Reason.requiresDeploymentTargetOrEarlier(Context)) {
    // The required OS version is at or before the deployment target so this
    // diagnostic should indicate that the decl could be unavailable to clients
    // of the module containing the reference.
    IsError = !WarnBeforeDeploymentTarget;

    return Diagnostic(
        IsError ? diag::availability_decl_only_version_newer_for_clients
                : diag::availability_decl_only_version_newer_for_clients_warn,
        D->getName(), Platform, Version, ReferenceDC->getParentModule());
  }

  IsError = true;
  return Diagnostic(diag::availability_decl_only_version_newer, D->getName(),
                    Platform, Version);
}

bool TypeChecker::diagnosePotentialUnavailability(
    const ValueDecl *D, SourceRange ReferenceRange,
    const DeclContext *ReferenceDC,
    const UnavailabilityReason &Reason,
    bool WarnBeforeDeploymentTarget = false) {
  ASTContext &Context = ReferenceDC->getASTContext();

  auto RequiredRange = Reason.getRequiredOSVersionRange();
  bool IsError;
  {
    auto Diag = Context.Diags.diagnose(
        ReferenceRange.Start,
        getPotentialUnavailabilityDiagnostic(
            D, ReferenceDC, Reason, WarnBeforeDeploymentTarget, IsError));

    // Direct a fixit to the error if an existing guard is nearly-correct
    if (fixAvailabilityByNarrowingNearbyVersionCheck(
            ReferenceRange, ReferenceDC, RequiredRange, Context, Diag))
      return IsError;
  }

  fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
  return IsError;
}

void TypeChecker::diagnosePotentialAccessorUnavailability(
    const AccessorDecl *Accessor, SourceRange ReferenceRange,
    const DeclContext *ReferenceDC, const UnavailabilityReason &Reason,
    bool ForInout) {
  ASTContext &Context = ReferenceDC->getASTContext();

  assert(Accessor->isGetterOrSetter());

  const AbstractStorageDecl *ASD = Accessor->getStorage();
  DeclName Name = ASD->getName();

  auto &diag = ForInout ? diag::availability_inout_accessor_only_version_newer
                        : diag::availability_accessor_only_version_newer;

  auto RequiredRange = Reason.getRequiredOSVersionRange();
  {
    auto Err =
      Context.Diags.diagnose(
               ReferenceRange.Start, diag,
               static_cast<unsigned>(Accessor->getAccessorKind()), Name,
               prettyPlatformString(targetPlatform(Context.LangOpts)),
               Reason.getRequiredOSVersionRange().getLowerEndpoint());


    // Direct a fixit to the error if an existing guard is nearly-correct
    if (fixAvailabilityByNarrowingNearbyVersionCheck(ReferenceRange,
                                                     ReferenceDC,
                                                     RequiredRange, Context, Err))
      return;
  }

  fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
}

static DiagnosticBehavior
behaviorLimitForExplicitUnavailability(
    const RootProtocolConformance *rootConf,
    const DeclContext *fromDC) {
  auto protoDecl = rootConf->getProtocol();

  // Soften errors about unavailable `Sendable` conformances depending on the
  // concurrency checking mode.
  if (protoDecl->isSpecificProtocol(KnownProtocolKind::Sendable)) {
    SendableCheckContext checkContext(fromDC);
    if (auto nominal = rootConf->getType()->getAnyNominal())
      return checkContext.diagnosticBehavior(nominal);

    return checkContext.defaultDiagnosticBehavior();
  }

  return DiagnosticBehavior::Unspecified;
}

void TypeChecker::diagnosePotentialUnavailability(
    const RootProtocolConformance *rootConf,
    const ExtensionDecl *ext,
    SourceLoc loc,
    const DeclContext *dc,
    const UnavailabilityReason &reason) {
  ASTContext &ctx = dc->getASTContext();

  auto requiredRange = reason.getRequiredOSVersionRange();
  {
    auto type = rootConf->getType();
    auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();

    auto diagID = (ctx.LangOpts.EnableConformanceAvailabilityErrors
                   ? diag::conformance_availability_only_version_newer
                   : diag::conformance_availability_only_version_newer_warn);
    auto behavior = behaviorLimitForExplicitUnavailability(rootConf, dc);
    auto err =
      ctx.Diags.diagnose(
               loc, diagID,
               type, proto, prettyPlatformString(targetPlatform(ctx.LangOpts)),
               reason.getRequiredOSVersionRange().getLowerEndpoint());
    err.limitBehavior(behavior);

    // Direct a fixit to the error if an existing guard is nearly-correct
    if (fixAvailabilityByNarrowingNearbyVersionCheck(loc, dc,
                                                     requiredRange, ctx, err))
      return;
  }

  fixAvailability(loc, dc, requiredRange, ctx);
}

const AvailableAttr *TypeChecker::getDeprecated(const Decl *D) {
  if (auto *Attr = D->getAttrs().getDeprecated(D->getASTContext()))
    return Attr;

  // Treat extensions methods as deprecated if their extension
  // is deprecated.
  DeclContext *DC = D->getDeclContext();
  if (auto *ED = dyn_cast<ExtensionDecl>(DC)) {
    return getDeprecated(ED);
  }

  return nullptr;
}

static void fixItAvailableAttrRename(InFlightDiagnostic &diag,
                                     SourceRange referenceRange,
                                     const ValueDecl *renamedDecl,
                                     const AvailableAttr *attr,
                                     const Expr *call) {
  if (isa<AccessorDecl>(renamedDecl))
    return;

  ParsedDeclName parsed = swift::parseDeclName(attr->Rename);
  if (!parsed)
    return;

  bool originallyWasKnownOperatorExpr = false;
  if (call) {
    originallyWasKnownOperatorExpr =
        isa<BinaryExpr>(call) ||
        isa<PrefixUnaryExpr>(call) ||
        isa<PostfixUnaryExpr>(call);
  }
  if (parsed.isOperator() != originallyWasKnownOperatorExpr)
    return;

  auto &ctx = renamedDecl->getASTContext();
  SourceManager &sourceMgr = ctx.SourceMgr;
  if (parsed.isInstanceMember()) {
    auto *CE = dyn_cast_or_null<CallExpr>(call);
    if (!CE)
      return;

    // Replace the base of the call with the "self argument".
    // We can only do a good job with the fix-it if we have the whole call
    // expression.
    // FIXME: Should we be validating the ContextName in some way?
    unsigned selfIndex = parsed.SelfIndex.value();
    const Expr *selfExpr = nullptr;
    SourceLoc removeRangeStart;
    SourceLoc removeRangeEnd;

    auto *originalArgs = CE->getArgs()->getOriginalArgs();
    size_t numElementsWithinParens = originalArgs->size();
    numElementsWithinParens -= originalArgs->getNumTrailingClosures();
    if (selfIndex >= numElementsWithinParens)
      return;

    if (parsed.IsGetter) {
      if (numElementsWithinParens != 1)
        return;
    } else if (parsed.IsSetter) {
      if (numElementsWithinParens != 2)
        return;
    } else {
      if (parsed.ArgumentLabels.size() != originalArgs->size() - 1)
        return;
    }

    selfExpr = originalArgs->getExpr(selfIndex);

    if (selfIndex + 1 == numElementsWithinParens) {
      if (selfIndex > 0) {
        // Remove from the previous comma to the close-paren (half-open).
        removeRangeStart = originalArgs->getExpr(selfIndex - 1)->getEndLoc();
        removeRangeStart = Lexer::getLocForEndOfToken(sourceMgr,
                                                      removeRangeStart);
      } else {
        // Remove from after the open paren to the close paren (half-open).
        removeRangeStart =
            Lexer::getLocForEndOfToken(sourceMgr, originalArgs->getStartLoc());
      }

      // Prefer the r-paren location, so that we get the right behavior when
      // there's a trailing closure, but handle some implicit cases too.
      removeRangeEnd = originalArgs->getRParenLoc();
      if (removeRangeEnd.isInvalid())
        removeRangeEnd = originalArgs->getEndLoc();

    } else {
      // Remove from the label to the start of the next argument (half-open).
      SourceLoc labelLoc = originalArgs->getLabelLoc(selfIndex);
      if (labelLoc.isValid())
        removeRangeStart = labelLoc;
      else
        removeRangeStart = selfExpr->getStartLoc();

      SourceLoc nextLabelLoc = originalArgs->getLabelLoc(selfIndex + 1);
      if (nextLabelLoc.isValid())
        removeRangeEnd = nextLabelLoc;
      else
        removeRangeEnd = originalArgs->getExpr(selfIndex + 1)->getStartLoc();
    }

    // Avoid later argument label fix-its for this argument.
    if (!parsed.isPropertyAccessor()) {
      Identifier oldLabel = originalArgs->getLabel(selfIndex);
      StringRef oldLabelStr;
      if (!oldLabel.empty())
        oldLabelStr = oldLabel.str();
      parsed.ArgumentLabels.insert(parsed.ArgumentLabels.begin() + selfIndex,
                                    oldLabelStr);
    }

    if (auto *inoutSelf = dyn_cast<InOutExpr>(selfExpr))
      selfExpr = inoutSelf->getSubExpr();

    CharSourceRange selfExprRange =
        Lexer::getCharSourceRangeFromSourceRange(sourceMgr,
                                                 selfExpr->getSourceRange());
    bool needsParens = !selfExpr->canAppendPostfixExpression();

    SmallString<64> selfReplace;
    if (needsParens)
      selfReplace.push_back('(');

    // If the base is contextual member lookup and we know the type,
    // let's just prepend it, otherwise we'll end up with an incorrect fix-it.
    auto base = sourceMgr.extractText(selfExprRange);
    if (!base.empty() && base.front() == '.') {
      auto newName = attr->Rename;
      // If this is not a rename, let's not
      // even try to emit a fix-it because
      // it's going to be invalid.
      if (newName.empty())
        return;

      auto parts = newName.split('.');
      auto nominalName = parts.first;
      assert(!nominalName.empty());

      selfReplace += nominalName;
    }

    selfReplace += base;
    if (needsParens)
      selfReplace.push_back(')');

    selfReplace.push_back('.');
    selfReplace += parsed.BaseName;

    diag.fixItReplace(CE->getFn()->getSourceRange(), selfReplace);

    if (!parsed.isPropertyAccessor())
      diag.fixItRemoveChars(removeRangeStart, removeRangeEnd);

    // Continue on to diagnose any argument label renames.

  } else if (parsed.BaseName == "init" && isa_and_nonnull<CallExpr>(call)) {
    auto *CE = cast<CallExpr>(call);

    // If it is a call to an initializer (rather than a first-class reference):

    if (parsed.isMember()) {
      // replace with a "call" to the type (instead of writing `.init`)
      diag.fixItReplace(CE->getFn()->getSourceRange(), parsed.ContextName);
    } else if (auto *dotCall = dyn_cast<DotSyntaxCallExpr>(CE->getFn())) {
      // if it's a dot call, and the left side is a type (and not `self` or 
      // `super`, for example), just remove the dot and the right side, again 
      // in order to make it a "call" to the type
      if (isa<TypeExpr>(dotCall->getBase())) {
        SourceLoc removeLoc = dotCall->getDotLoc();
        if (removeLoc.isInvalid())
          return;

        diag.fixItRemove(SourceRange(removeLoc, dotCall->getFn()->getEndLoc()));
      }
    } else if (!isa<ConstructorRefCallExpr>(CE->getFn())) {
      return;
    }

    // Continue on to diagnose any constructor argument label renames.

  } else if (parsed.IsSubscript) {
    if (auto *CE = dyn_cast_or_null<CallExpr>(call)) {
      // Renaming from CallExpr to SubscriptExpr. Remove function name and
      // replace parens with square brackets.

      if (auto *DSCE = dyn_cast<DotSyntaxCallExpr>(CE->getFn())) {
        if (DSCE->getBase()->isImplicit()) {
          // If self is implicit, self must be inserted before subscript syntax.
          diag.fixItInsert(CE->getStartLoc(), "self");
        }
      }

      diag.fixItReplace(CE->getFn()->getEndLoc(), "[");
      diag.fixItReplace(CE->getEndLoc(), "]");
    }
  } else {
    // Just replace the base name.
    SmallString<64> baseReplace;

    if (!parsed.ContextName.empty()) {
      baseReplace += parsed.ContextName;
      baseReplace += '.';
    }
    baseReplace += parsed.BaseName;

    if (parsed.IsFunctionName && isa_and_nonnull<SubscriptExpr>(call)) {
      auto *SE = cast<SubscriptExpr>(call);

      // Renaming from SubscriptExpr to CallExpr. Insert function name and
      // replace square brackets with parens.
      diag.fixItReplace(SE->getArgs()->getStartLoc(),
                        ("." + baseReplace.str() + "(").str());
      diag.fixItReplace(SE->getEndLoc(), ")");
    } else {
      bool shouldEmitRenameFixit = true;
      if (auto *CE = dyn_cast_or_null<CallExpr>(call)) {
        SmallString<64> callContextName;
        llvm::raw_svector_ostream name(callContextName);
        if (auto *DCE = dyn_cast<DotSyntaxCallExpr>(CE->getDirectCallee())) {
          if (auto *TE = dyn_cast<TypeExpr>(DCE->getBase())) {
            TE->getTypeRepr()->print(name);
            if (!parsed.ContextName.empty()) {
              // If there is a context in rename function e.g.
              // `Context.function()` and call context is a `DotSyntaxCallExpr`
              // adjust the range so it replaces the base as well.
              referenceRange =
                  SourceRange(TE->getStartLoc(), referenceRange.End);
            }
          }
        }
        // Function names are the same(including context if applicable), so
        // renaming fix-it doesn't need do be produced.
        if ((parsed.ContextName.empty() ||
             parsed.ContextName == callContextName) &&
            CE->getCalledValue()->getBaseName() == parsed.BaseName) {
          shouldEmitRenameFixit = false;
        }
      }
      if (shouldEmitRenameFixit) {
        if (parsed.IsFunctionName && parsed.ArgumentLabels.empty() &&
            isa<VarDecl>(renamedDecl)) {
          // If we're going from a var to a function with no arguments, emit an
          // empty parameter list.
          baseReplace += "()";
        }

        diag.fixItReplace(referenceRange, baseReplace);
      }
    }
  }

  if (!call || !call->getArgs())
    return;

  auto *originalArgs = call->getArgs()->getOriginalArgs();
  if (parsed.IsGetter) {
    diag.fixItRemove(originalArgs->getSourceRange());
    return;
  }

  if (parsed.IsSetter) {
    const Expr *newValueExpr = nullptr;

    if (originalArgs->size() >= 1) {
      size_t newValueIndex = 0;
      if (parsed.isInstanceMember()) {
        assert(parsed.SelfIndex.value() == 0 ||
               parsed.SelfIndex.value() == 1);
        newValueIndex = !parsed.SelfIndex.value();
      }
      newValueExpr = originalArgs->getExpr(newValueIndex);
    } else {
      newValueExpr = originalArgs->getExpr(0);
    }

    diag.fixItReplaceChars(originalArgs->getStartLoc(),
                           newValueExpr->getStartLoc(), " = ");
    diag.fixItRemoveChars(
        Lexer::getLocForEndOfToken(sourceMgr, newValueExpr->getEndLoc()),
        Lexer::getLocForEndOfToken(sourceMgr, originalArgs->getEndLoc()));
    return;
  }

  if (!parsed.IsFunctionName)
    return;

  SmallVector<Identifier, 4> argumentLabelIDs;
  llvm::transform(parsed.ArgumentLabels, std::back_inserter(argumentLabelIDs),
                  [&ctx](StringRef labelStr) -> Identifier {
                    return labelStr.empty() ? Identifier()
                                            : ctx.getIdentifier(labelStr);
                  });

  // Coerce the `argumentLabelIDs` to the user supplied arguments.
  // e.g:
  //   @available(.., renamed: "new(w:x:y:z:)")
  //   func old(a: Int, b: Int..., c: String="", d: Int=0){}
  //   old(a: 1, b: 2, 3, 4, d: 5)
  // coerce
  //   argumentLabelIDs = {"w", "x", "y", "z"}
  // to
  //   argumentLabelIDs = {"w", "x", "", "", "z"}
  auto I = argumentLabelIDs.begin();

  auto updateLabelsForArg = [&](Expr *expr) -> bool {
    if (isa<DefaultArgumentExpr>(expr)) {
      // Defaulted: remove param label of it.
      if (I == argumentLabelIDs.end())
        return true;

      I = argumentLabelIDs.erase(I);
      return false;
    }

    if (auto *varargExpr = dyn_cast<VarargExpansionExpr>(expr)) {
      if (auto *arrayExpr = dyn_cast<ArrayExpr>(varargExpr->getSubExpr())) {
        auto variadicArgsNum = arrayExpr->getNumElements();
        if (variadicArgsNum == 0) {
          // No arguments: Remove param label of it.
          I = argumentLabelIDs.erase(I);
        } else if (variadicArgsNum == 1) {
          // One argument: Just advance.
          ++I;
        } else {
          ++I;

          // Two or more arguments: Insert empty labels after the first one.
          --variadicArgsNum;
          I = argumentLabelIDs.insert(I, variadicArgsNum, Identifier());
          I += variadicArgsNum;
        }
        return false;
      }
    }

    // Normal: Just advance.
    if (I == argumentLabelIDs.end())
      return true;

    ++I;
    return false;
  };

  for (auto arg : *call->getArgs()) {
    if (updateLabelsForArg(arg.getExpr()))
      return;
  }

  if (argumentLabelIDs.size() != originalArgs->size()) {
    // Mismatched lengths; give up.
    return;
  }

  // If any of the argument labels are mismatched, perform label correction.
  for (auto i : indices(*originalArgs)) {
    // The argument label of an unlabeled trailing closure is ignored.
    if (originalArgs->isUnlabeledTrailingClosureIndex(i))
      continue;
    if (argumentLabelIDs[i] != originalArgs->getLabel(i)) {
      auto paramContext = parsed.IsSubscript ? ParameterContext::Subscript
                                             : ParameterContext::Call;
      diagnoseArgumentLabelError(ctx, originalArgs, argumentLabelIDs,
                                 paramContext, &diag);
      return;
    }
  }
}

// Must be kept in sync with diag::availability_decl_unavailable_rename and
// others.
namespace {
  enum class ReplacementDeclKind : unsigned {
    None,
    InstanceMethod,
    Property,
  };
} // end anonymous namespace

static Optional<ReplacementDeclKind>
describeRename(ASTContext &ctx, const AvailableAttr *attr, const ValueDecl *D,
               SmallVectorImpl<char> &nameBuf) {
  ParsedDeclName parsed = swift::parseDeclName(attr->Rename);
  if (!parsed)
    return None;

  // Only produce special descriptions for renames to
  // - instance members
  // - properties (or global bindings)
  // - class/static methods
  // - initializers, unless the original was known to be an initializer
  // Leave non-member renames alone, as well as renames from top-level types
  // and bindings to member types and class/static properties.
  if (!(parsed.isInstanceMember() || parsed.isPropertyAccessor() ||
        (parsed.isMember() && parsed.IsFunctionName) ||
        (parsed.BaseName == "init" &&
         !dyn_cast_or_null<ConstructorDecl>(D)))) {
    return None;
  }

  llvm::raw_svector_ostream name(nameBuf);

  if (!parsed.ContextName.empty())
    name << parsed.ContextName << '.';

  if (parsed.IsFunctionName) {
    name << parsed.formDeclName(ctx, (D && isa<SubscriptDecl>(D)));
  } else {
    name << parsed.BaseName;
  }

  if (parsed.isMember() && parsed.isPropertyAccessor())
    return ReplacementDeclKind::Property;
  if (parsed.isInstanceMember() && parsed.IsFunctionName)
    return ReplacementDeclKind::InstanceMethod;

  // We don't have enough information.
  return ReplacementDeclKind::None;
}

void TypeChecker::diagnoseIfDeprecated(SourceRange ReferenceRange,
                                       const ExportContext &Where,
                                       const ValueDecl *DeprecatedDecl,
                                       const Expr *Call) {
  const AvailableAttr *Attr = TypeChecker::getDeprecated(DeprecatedDecl);
  if (!Attr)
    return;

  // We match the behavior of clang to not report deprecation warnings
  // inside declarations that are themselves deprecated on all deployment
  // targets.
  if (Where.isDeprecated()) {
    return;
  }

  auto *ReferenceDC = Where.getDeclContext();
  auto &Context = ReferenceDC->getASTContext();
  if (!Context.LangOpts.DisableAvailabilityChecking) {
    AvailabilityContext RunningOSVersions = Where.getAvailabilityContext();
    if (RunningOSVersions.isKnownUnreachable()) {
      // Suppress a deprecation warning if the availability checking machinery
      // thinks the reference program location will not execute on any
      // deployment target for the current platform.
      return;
    }
  }

  DeclName Name;
  unsigned RawAccessorKind;
  std::tie(RawAccessorKind, Name) =
      getAccessorKindAndNameForDiagnostics(DeprecatedDecl);

  StringRef Platform = Attr->prettyPlatformString();
  llvm::VersionTuple DeprecatedVersion;
  if (Attr->Deprecated)
    DeprecatedVersion = Attr->Deprecated.value();

  if (Attr->Message.empty() && Attr->Rename.empty()) {
    Context.Diags.diagnose(
             ReferenceRange.Start, diag::availability_deprecated,
             RawAccessorKind, Name, Attr->hasPlatform(), Platform,
             Attr->Deprecated.has_value(), DeprecatedVersion,
             /*message*/ StringRef())
        .highlight(Attr->getRange());
    return;
  }

  SmallString<32> newNameBuf;
  Optional<ReplacementDeclKind> replacementDeclKind =
    describeRename(Context, Attr, /*decl*/nullptr, newNameBuf);
  StringRef newName = replacementDeclKind ? newNameBuf.str() : Attr->Rename;

  if (!Attr->Message.empty()) {
    EncodedDiagnosticMessage EncodedMessage(Attr->Message);
    Context.Diags.diagnose(
             ReferenceRange.Start, diag::availability_deprecated,
             RawAccessorKind, Name, Attr->hasPlatform(), Platform,
             Attr->Deprecated.has_value(), DeprecatedVersion,
             EncodedMessage.Message)
        .highlight(Attr->getRange());
  } else {
    unsigned rawReplaceKind = static_cast<unsigned>(
        replacementDeclKind.value_or(ReplacementDeclKind::None));
    Context.Diags.diagnose(
             ReferenceRange.Start, diag::availability_deprecated_rename,
             RawAccessorKind, Name, Attr->hasPlatform(), Platform,
             Attr->Deprecated.has_value(), DeprecatedVersion,
             replacementDeclKind.has_value(), rawReplaceKind, newName)
      .highlight(Attr->getRange());
  }

  if (!Attr->Rename.empty() && !isa<AccessorDecl>(DeprecatedDecl)) {
    auto renameDiag = Context.Diags.diagnose(
                               ReferenceRange.Start,
                               diag::note_deprecated_rename,
                               newName);
    fixItAvailableAttrRename(renameDiag, ReferenceRange, DeprecatedDecl,
                             Attr, Call);
  }
}

bool TypeChecker::diagnoseIfDeprecated(SourceLoc loc,
                                       const RootProtocolConformance *rootConf,
                                       const ExtensionDecl *ext,
                                       const ExportContext &where) {
  const AvailableAttr *attr = TypeChecker::getDeprecated(ext);
  if (!attr)
    return false;

  // We match the behavior of clang to not report deprecation warnings
  // inside declarations that are themselves deprecated on all deployment
  // targets.
  if (where.isDeprecated()) {
    return false;
  }

  auto *dc = where.getDeclContext();
  auto &ctx = dc->getASTContext();
  if (!ctx.LangOpts.DisableAvailabilityChecking) {
    AvailabilityContext runningOSVersion = where.getAvailabilityContext();
    if (runningOSVersion.isKnownUnreachable()) {
      // Suppress a deprecation warning if the availability checking machinery
      // thinks the reference program location will not execute on any
      // deployment target for the current platform.
      return false;
    }
  }

  auto type = rootConf->getType();
  auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();

  StringRef platform = attr->prettyPlatformString();
  llvm::VersionTuple deprecatedVersion;
  if (attr->Deprecated)
    deprecatedVersion = attr->Deprecated.value();

  if (attr->Message.empty()) {
    ctx.Diags.diagnose(
             loc, diag::conformance_availability_deprecated,
             type, proto, attr->hasPlatform(), platform,
             attr->Deprecated.has_value(), deprecatedVersion,
             /*message*/ StringRef())
        .highlight(attr->getRange());
    return true;
  }

  EncodedDiagnosticMessage encodedMessage(attr->Message);
  ctx.Diags.diagnose(
      loc, diag::conformance_availability_deprecated,
      type, proto, attr->hasPlatform(), platform,
      attr->Deprecated.has_value(), deprecatedVersion,
      encodedMessage.Message)
    .highlight(attr->getRange());
  return true;
}

void swift::diagnoseUnavailableOverride(ValueDecl *override,
                                        const ValueDecl *base,
                                        const AvailableAttr *attr) {
  ASTContext &ctx = override->getASTContext();
  auto &diags = ctx.Diags;
  if (attr->Rename.empty()) {
    EncodedDiagnosticMessage EncodedMessage(attr->Message);
    diags.diagnose(override, diag::override_unavailable,
                   override->getBaseName(), EncodedMessage.Message);

    DeclName name;
    unsigned rawAccessorKind;
    std::tie(rawAccessorKind, name) =
        getAccessorKindAndNameForDiagnostics(base);
    diags.diagnose(base, diag::availability_marked_unavailable,
                   rawAccessorKind, name);
    return;
  }

  ExportContext where = ExportContext::forDeclSignature(override);
  diagnoseExplicitUnavailability(base, override->getLoc(), where,
                                 /*Flags*/None,
                                 [&](InFlightDiagnostic &diag) {
    ParsedDeclName parsedName = parseDeclName(attr->Rename);
    if (!parsedName || parsedName.isPropertyAccessor() ||
        parsedName.isMember() || parsedName.isOperator()) {
      return;
    }

    // Only initializers should be named 'init'.
    if (isa<ConstructorDecl>(override) ^
        (parsedName.BaseName == "init")) {
      return;
    }

    if (!parsedName.IsFunctionName) {
      diag.fixItReplace(override->getNameLoc(), parsedName.BaseName);
      return;
    }

    DeclName newName = parsedName.formDeclName(ctx);
    size_t numArgs = override->getName().getArgumentNames().size();
    if (!newName || newName.getArgumentNames().size() != numArgs)
      return;

    fixDeclarationName(diag, override, newName);
  });
}

/// Emit a diagnostic for references to declarations that have been
/// marked as unavailable, either through "unavailable" or "obsoleted:".
bool swift::diagnoseExplicitUnavailability(const ValueDecl *D, SourceRange R,
                                           const ExportContext &Where,
                                           const Expr *call,
                                           DeclAvailabilityFlags Flags) {
  return diagnoseExplicitUnavailability(D, R, Where, Flags,
                                        [=](InFlightDiagnostic &diag) {
    fixItAvailableAttrRename(diag, R, D, AvailableAttr::isUnavailable(D),
                             call);
  });
}

/// Emit a diagnostic for references to declarations that have been
/// marked as unavailable, either through "unavailable" or "obsoleted:".
bool swift::diagnoseExplicitUnavailability(SourceLoc loc,
                                           const RootProtocolConformance *rootConf,
                                           const ExtensionDecl *ext,
                                           const ExportContext &where,
                                           bool useConformanceAvailabilityErrorsOption) {
  auto *attr = AvailableAttr::isUnavailable(ext);
  if (!attr)
    return false;

  // Calling unavailable code from within code with the same
  // unavailability is OK -- the eventual caller can't call the
  // enclosing code in the same situations it wouldn't be able to
  // call this code.
  if (isInsideCompatibleUnavailableDeclaration(ext, where, attr))
    return false;

  ASTContext &ctx = ext->getASTContext();
  auto &diags = ctx.Diags;

  auto type = rootConf->getType();
  auto proto = rootConf->getProtocol()->getDeclaredInterfaceType();

  StringRef platform;
  auto behavior = DiagnosticBehavior::Unspecified;
  switch (attr->getPlatformAgnosticAvailability()) {
  case PlatformAgnosticAvailabilityKind::Deprecated:
    llvm_unreachable("shouldn't see deprecations in explicit unavailability");

  case PlatformAgnosticAvailabilityKind::NoAsync:
    llvm_unreachable("shouldn't see noasync in explicit unavailability");

  case PlatformAgnosticAvailabilityKind::None:
  case PlatformAgnosticAvailabilityKind::Unavailable:
    if (attr->Platform != PlatformKind::none) {
      // This was platform-specific; indicate the platform.
      platform = attr->prettyPlatformString();
      break;
    }

    // Downgrade unavailable Sendable conformance diagnostics where
    // appropriate.
    behavior = behaviorLimitForExplicitUnavailability(
        rootConf, where.getDeclContext());
    LLVM_FALLTHROUGH;

  case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
  case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific:
    // We don't want to give further detail about these.
    platform = "";
    break;

  case PlatformAgnosticAvailabilityKind::UnavailableInSwift:
    // This API is explicitly unavailable in Swift.
    platform = "Swift";
    break;
  }

  EncodedDiagnosticMessage EncodedMessage(attr->Message);
  diags.diagnose(loc, diag::conformance_availability_unavailable,
                 type, proto,
                 platform.empty(), platform, EncodedMessage.Message)
      .limitBehavior(behavior)
      .warnUntilSwiftVersionIf(useConformanceAvailabilityErrorsOption &&
                               !ctx.LangOpts.EnableConformanceAvailabilityErrors,
                               6);

  switch (attr->getVersionAvailability(ctx)) {
  case AvailableVersionComparison::Available:
  case AvailableVersionComparison::PotentiallyUnavailable:
    llvm_unreachable("These aren't considered unavailable");

  case AvailableVersionComparison::Unavailable:
    if ((attr->isLanguageVersionSpecific() ||
         attr->isPackageDescriptionVersionSpecific())
        && attr->Introduced.has_value())
      diags.diagnose(ext, diag::conformance_availability_introduced_in_version,
                     type, proto,
                     (attr->isLanguageVersionSpecific() ?
                      "Swift" : "PackageDescription"),
                     *attr->Introduced)
        .highlight(attr->getRange());
    else
      diags.diagnose(ext, diag::conformance_availability_marked_unavailable,
                     type, proto)
        .highlight(attr->getRange());
    break;

  case AvailableVersionComparison::Obsoleted:
    // FIXME: Use of the platformString here is non-awesome for application
    // extensions.

    StringRef platformDisplayString;
    if (attr->isLanguageVersionSpecific()) {
      platformDisplayString = "Swift";
    } else if (attr->isPackageDescriptionVersionSpecific()) {
      platformDisplayString = "PackageDescription";
    } else {
      platformDisplayString = platform;
    }

    diags.diagnose(ext, diag::conformance_availability_obsoleted,
                   type, proto, platformDisplayString, *attr->Obsoleted)
      .highlight(attr->getRange());
    break;
  }
  return true;
}

/// Check if this is a subscript declaration inside String or
/// Substring that returns String, and if so return true.
bool isSubscriptReturningString(const ValueDecl *D, ASTContext &Context) {
  // Is this a subscript?
  if (!isa<SubscriptDecl>(D))
    return false;

  // Is the subscript declared in String or Substring?
  auto *declContext = D->getDeclContext();
  assert(declContext && "Expected decl context!");

  auto *stringDecl = Context.getStringDecl();
  auto *substringDecl = Context.getSubstringDecl();

  auto *typeDecl = declContext->getSelfNominalTypeDecl();
  if (!typeDecl)
    return false;

  if (typeDecl != stringDecl && typeDecl != substringDecl)
    return false;

  // Is the subscript index one we want to emit a special diagnostic
  // for, and the return type String?
  auto fnTy = D->getInterfaceType()->getAs<AnyFunctionType>();
  assert(fnTy && "Expected function type for subscript decl!");

  // We're only going to warn for BoundGenericStructType with a single
  // type argument that is not Int!
  auto params = fnTy->getParams();
  if (params.size() != 1)
    return false;

  const auto &param = params.front();
  if (param.hasLabel() || param.isVariadic() || param.isInOut())
    return false;

  auto inputTy = param.getPlainType()->getAs<BoundGenericStructType>();
  if (!inputTy)
    return false;

  auto genericArgs = inputTy->getGenericArgs();
  if (genericArgs.size() != 1)
    return false;

  // The subscripts taking T<Int> do not return Substring, and our
  // special fixit does not help here.
  auto nominalTypeParam = genericArgs[0]->getAs<NominalType>();
  if (!nominalTypeParam)
    return false;

  if (nominalTypeParam->isInt())
    return false;

  auto resultTy = fnTy->getResult()->getAs<NominalType>();
  if (!resultTy)
    return false;

  return resultTy->isString();
}

static bool diagnosePotentialParameterizedProtocolUnavailability(
    SourceRange ReferenceRange, const DeclContext *ReferenceDC,
    const UnavailabilityReason &Reason) {
  ASTContext &Context = ReferenceDC->getASTContext();

  auto RequiredRange = Reason.getRequiredOSVersionRange();
  {
    auto Err = Context.Diags.diagnose(
        ReferenceRange.Start,
        diag::availability_parameterized_protocol_only_version_newer,
        prettyPlatformString(targetPlatform(Context.LangOpts)),
        Reason.getRequiredOSVersionRange().getLowerEndpoint());

    // Direct a fixit to the error if an existing guard is nearly-correct
    if (fixAvailabilityByNarrowingNearbyVersionCheck(
            ReferenceRange, ReferenceDC, RequiredRange, Context, Err))
      return true;
  }
  fixAvailability(ReferenceRange, ReferenceDC, RequiredRange, Context);
  return true;
}

bool swift::diagnoseParameterizedProtocolAvailability(
    SourceRange ReferenceRange, const DeclContext *ReferenceDC) {
  // Check the availability of parameterized existential runtime support.
  ASTContext &ctx = ReferenceDC->getASTContext();
  if (ctx.LangOpts.DisableAvailabilityChecking)
    return false;

  if (!shouldCheckAvailability(ReferenceDC->getAsDecl()))
    return false;

  auto runningOS = TypeChecker::overApproximateAvailabilityAtLocation(
      ReferenceRange.Start, ReferenceDC);
  auto availability = ctx.getParameterizedExistentialRuntimeAvailability();
  if (!runningOS.isContainedIn(availability)) {
    return diagnosePotentialParameterizedProtocolUnavailability(
        ReferenceRange, ReferenceDC,
        UnavailabilityReason::requiresVersionRange(
            availability.getOSVersion()));
  }
  return false;
}

static void
maybeDiagParameterizedExistentialErasure(ErasureExpr *EE,
                                         const ExportContext &Where) {
  if (auto *OE = dyn_cast<OpaqueValueExpr>(EE->getSubExpr())) {
    auto *OAT = OE->getType()->getAs<OpenedArchetypeType>();
    if (!OAT)
      return;

    auto opened = OAT->getGenericEnvironment()->getOpenedExistentialType();
    if (!opened || !opened->hasParameterizedExistential())
      return;

    (void)diagnoseParameterizedProtocolAvailability(EE->getLoc(),
                                                    Where.getDeclContext());
  }

  if (EE->getType() &&
      EE->getType()->isAny() &&
      EE->getSubExpr()->getType()->hasParameterizedExistential()) {
    (void)diagnoseParameterizedProtocolAvailability(EE->getLoc(),
                                                    Where.getDeclContext());
  }
}

bool swift::diagnoseExplicitUnavailability(
    const ValueDecl *D,
    SourceRange R,
    const ExportContext &Where,
    DeclAvailabilityFlags Flags,
    llvm::function_ref<void(InFlightDiagnostic &)> attachRenameFixIts) {
  auto *Attr = AvailableAttr::isUnavailable(D);
  if (!Attr)
    return false;

  // Calling unavailable code from within code with the same
  // unavailability is OK -- the eventual caller can't call the
  // enclosing code in the same situations it wouldn't be able to
  // call this code.
  if (isInsideCompatibleUnavailableDeclaration(D, Where, Attr))
    return false;

  SourceLoc Loc = R.Start;
  DeclName Name;
  unsigned RawAccessorKind;
  std::tie(RawAccessorKind, Name) = getAccessorKindAndNameForDiagnostics(D);

  ASTContext &ctx = D->getASTContext();
  auto &diags = ctx.Diags;

  StringRef platform;
  switch (Attr->getPlatformAgnosticAvailability()) {
  case PlatformAgnosticAvailabilityKind::Deprecated:
    llvm_unreachable("shouldn't see deprecations in explicit unavailability");

  case PlatformAgnosticAvailabilityKind::NoAsync:
    llvm_unreachable("shouldn't see noasync with explicit unavailability");

  case PlatformAgnosticAvailabilityKind::None:
  case PlatformAgnosticAvailabilityKind::Unavailable:
    if (Attr->Platform != PlatformKind::none) {
      // This was platform-specific; indicate the platform.
      platform = Attr->prettyPlatformString();
      break;
    }
    LLVM_FALLTHROUGH;

  case PlatformAgnosticAvailabilityKind::SwiftVersionSpecific:
  case PlatformAgnosticAvailabilityKind::PackageDescriptionVersionSpecific:
    // We don't want to give further detail about these.
    platform = "";
    break;

  case PlatformAgnosticAvailabilityKind::UnavailableInSwift:
    // This API is explicitly unavailable in Swift.
    platform = "Swift";
    break;
  }

  // TODO: Consider removing this.
  // ObjC keypaths components weren't checked previously, so errors are demoted
  // to warnings to avoid source breakage. In some cases unavailable or
  // obsolete decls still map to valid ObjC runtime names, so behave correctly
  // at runtime, even though their use would produce an error outside of a
  // #keyPath expression.
  bool warnInObjCKeyPath = Flags.contains(DeclAvailabilityFlag::ForObjCKeyPath);

  if (!Attr->Rename.empty()) {
    SmallString<32> newNameBuf;
    Optional<ReplacementDeclKind> replaceKind =
        describeRename(ctx, Attr, D, newNameBuf);
    unsigned rawReplaceKind = static_cast<unsigned>(
        replaceKind.value_or(ReplacementDeclKind::None));
    StringRef newName = replaceKind ? newNameBuf.str() : Attr->Rename;
      EncodedDiagnosticMessage EncodedMessage(Attr->Message);
      auto diag =
          diags.diagnose(Loc, warnInObjCKeyPath
                         ? diag::availability_decl_unavailable_rename_warn
                         : diag::availability_decl_unavailable_rename,
                         RawAccessorKind, Name, replaceKind.has_value(),
                         rawReplaceKind, newName, EncodedMessage.Message);
      attachRenameFixIts(diag);
  } else if (isSubscriptReturningString(D, ctx)) {
    diags.diagnose(Loc, diag::availability_string_subscript_migration)
      .highlight(R)
      .fixItInsert(R.Start, "String(")
      .fixItInsertAfter(R.End, ")");

    // Skip the note emitted below.
    return true;
  } else {
    EncodedDiagnosticMessage EncodedMessage(Attr->Message);
    diags
        .diagnose(Loc, warnInObjCKeyPath
                  ? diag::availability_decl_unavailable_warn
                  : diag::availability_decl_unavailable, RawAccessorKind,
                  Name, platform.empty(), platform, EncodedMessage.Message)
        .highlight(R);
  }

  switch (Attr->getVersionAvailability(ctx)) {
  case AvailableVersionComparison::Available:
  case AvailableVersionComparison::PotentiallyUnavailable:
    llvm_unreachable("These aren't considered unavailable");

  case AvailableVersionComparison::Unavailable:
    if ((Attr->isLanguageVersionSpecific() ||
         Attr->isPackageDescriptionVersionSpecific())
        && Attr->Introduced.has_value())
      diags.diagnose(D, diag::availability_introduced_in_version,
                     RawAccessorKind, Name,
                     (Attr->isLanguageVersionSpecific() ? 
                      "Swift" : "PackageDescription"),
                     *Attr->Introduced)
        .highlight(Attr->getRange());
    else
      diags.diagnose(D, diag::availability_marked_unavailable, RawAccessorKind,
                     Name)
        .highlight(Attr->getRange());
    break;

  case AvailableVersionComparison::Obsoleted:
    // FIXME: Use of the platformString here is non-awesome for application
    // extensions.

    StringRef platformDisplayString;
    if (Attr->isLanguageVersionSpecific()) {
      platformDisplayString = "Swift";
    } else if (Attr->isPackageDescriptionVersionSpecific()) {
      platformDisplayString = "PackageDescription";
    } else {
      platformDisplayString = platform;
    }

    diags.diagnose(D, diag::availability_obsoleted,
                   RawAccessorKind, Name,
                   platformDisplayString,
                   *Attr->Obsoleted)
      .highlight(Attr->getRange());
    break;
  }
  return true;
}

namespace {
class ExprAvailabilityWalker : public ASTWalker {
  /// Describes how the next member reference will be treated as we traverse
  /// the AST.
  enum class MemberAccessContext : unsigned {
    /// The member reference is in a context where an access will call
    /// the getter.
    Getter,

    /// The member reference is in a context where an access will call
    /// the setter.
    Setter,

    /// The member reference is in a context where it will be turned into
    /// an inout argument. (Once this happens, we have to conservatively assume
    /// that both the getter and setter could be called.)
    InOut
  };

  ASTContext &Context;
  MemberAccessContext AccessContext = MemberAccessContext::Getter;
  SmallVector<const Expr *, 16> ExprStack;
  const ExportContext &Where;

public:
  explicit ExprAvailabilityWalker(const ExportContext &Where)
    : Context(Where.getDeclContext()->getASTContext()), Where(Where) {}

  bool shouldWalkIntoSeparatelyCheckedClosure(ClosureExpr *expr) override {
    return false;
  }

  bool shouldWalkIntoTapExpression() override { return false; }

  MacroWalking getMacroWalkingBehavior() const override {
    return MacroWalking::ArgumentsAndExpansion;
  }

  PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
    auto *DC = Where.getDeclContext();

    ExprStack.push_back(E);

    auto skipChildren = [&]() {
      ExprStack.pop_back();
      return Action::SkipChildren(E);
    };

    if (auto DR = dyn_cast<DeclRefExpr>(E)) {
      diagnoseDeclRefAvailability(DR->getDeclRef(), DR->getSourceRange(),
                                  getEnclosingApplyExpr(), None);
      maybeDiagStorageAccess(DR->getDecl(), DR->getSourceRange(), DC);
    }
    if (auto MR = dyn_cast<MemberRefExpr>(E)) {
      walkMemberRef(MR);
      return skipChildren();
    }
    if (auto OCDR = dyn_cast<OtherConstructorDeclRefExpr>(E))
      diagnoseDeclRefAvailability(OCDR->getDeclRef(),
                                  OCDR->getConstructorLoc().getSourceRange(),
                                  getEnclosingApplyExpr());
    if (auto DMR = dyn_cast<DynamicMemberRefExpr>(E))
      diagnoseDeclRefAvailability(DMR->getMember(),
                                  DMR->getNameLoc().getSourceRange(),
                                  getEnclosingApplyExpr());
    if (auto DS = dyn_cast<DynamicSubscriptExpr>(E))
      diagnoseDeclRefAvailability(DS->getMember(), DS->getSourceRange());
    if (auto S = dyn_cast<SubscriptExpr>(E)) {
      if (S->hasDecl()) {
        diagnoseDeclRefAvailability(S->getDecl(), S->getSourceRange(), S);
        maybeDiagStorageAccess(S->getDecl().getDecl(), S->getSourceRange(), DC);
      }
    }

    if (auto *LE = dyn_cast<LiteralExpr>(E)) {
      if (auto literalType = LE->getType()) {
        // Check availability of the type produced by implicit literal
        // initializer.
        if (auto *nominalDecl = literalType->getAnyNominal()) {
          diagnoseDeclAvailability(nominalDecl, LE->getSourceRange(),
                                   /*call=*/nullptr, Where);
        }
      }
      diagnoseDeclRefAvailability(LE->getInitializer(), LE->getSourceRange());
    }

    if (auto *CE = dyn_cast<CollectionExpr>(E)) {
      // Diagnose availability of implicit collection literal initializers.
      diagnoseDeclRefAvailability(CE->getInitializer(), CE->getSourceRange());
    }

    if (auto *EE = dyn_cast<ErasureExpr>(E)) {
      maybeDiagParameterizedExistentialErasure(EE, Where);
    }
    if (auto *CC = dyn_cast<ExplicitCastExpr>(E)) {
      if (!isa<CoerceExpr>(CC) && CC->getCastType() &&
          CC->getCastType()->hasParameterizedExistential()) {
        SourceLoc loc = CC->getCastTypeRepr() ? CC->getCastTypeRepr()->getLoc()
                                              : E->getLoc();
        diagnoseParameterizedProtocolAvailability(loc, Where.getDeclContext());
      }
    }
    if (auto KP = dyn_cast<KeyPathExpr>(E)) {
      maybeDiagKeyPath(KP);
    }
    if (auto A = dyn_cast<AssignExpr>(E)) {
      walkAssignExpr(A);
      return skipChildren();
    }
    if (auto IO = dyn_cast<InOutExpr>(E)) {
      walkInOutExpr(IO);
      return skipChildren();
    }
    if (auto T = dyn_cast<TypeExpr>(E)) {
      if (!T->isImplicit()) {
        diagnoseTypeAvailability(T->getTypeRepr(), T->getType(), E->getLoc(),
                                 Where);
      }
    }
    if (auto CE = dyn_cast<ClosureExpr>(E)) {
      for (auto *param : *CE->getParameters()) {
        diagnoseTypeAvailability(param->getTypeRepr(), param->getInterfaceType(),
                                 E->getLoc(), Where);
      }
      diagnoseTypeAvailability(CE->hasExplicitResultType()
                               ? CE->getExplicitResultTypeRepr()
                               : nullptr,
                               CE->getResultType(), E->getLoc(), Where);
    }
    if (auto CE = dyn_cast<ExplicitCastExpr>(E)) {
      diagnoseTypeAvailability(CE->getCastTypeRepr(), CE->getCastType(),
                               E->getLoc(), Where);
    }

    if (AbstractClosureExpr *closure = dyn_cast<AbstractClosureExpr>(E)) {
      if (shouldWalkIntoClosure(closure)) {
        walkAbstractClosure(closure);
        return skipChildren();
      }
    }
    
    if (auto EE = dyn_cast<ErasureExpr>(E)) {
      for (ProtocolConformanceRef C : EE->getConformances()) {
        diagnoseConformanceAvailability(E->getLoc(), C, Where, Type(), Type(),
                                        /*useConformanceAvailabilityErrorsOpt=*/true);
      }
    }

    return Action::Continue(E);
  }

  PostWalkResult<Expr *> walkToExprPost(Expr *E) override {
    assert(ExprStack.back() == E);
    ExprStack.pop_back();

    return Action::Continue(E);
  }

  PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {

    // We end up here when checking the output of the result builder transform,
    // which includes closures that are not "separately typechecked" and yet
    // contain statements and declarations. We need to walk them recursively,
    // since these availability for these statements is not diagnosed from
    // typeCheckStmt() as usual.
    diagnoseStmtAvailability(S, Where.getDeclContext(), /*walkRecursively=*/true);
    return Action::SkipChildren(S);
  }

  bool diagnoseDeclRefAvailability(ConcreteDeclRef declRef, SourceRange R,
                                   const Expr *call = nullptr,
                                   DeclAvailabilityFlags flags = None) const;

private:
  bool diagnoseIncDecRemoval(const ValueDecl *D, SourceRange R,
                             const AvailableAttr *Attr) const;
  bool diagnoseMemoryLayoutMigration(const ValueDecl *D, SourceRange R,
                                     const AvailableAttr *Attr,
                                     const ApplyExpr *call) const;

  /// Walks up from a potential callee to the enclosing ApplyExpr.
  const ApplyExpr *getEnclosingApplyExpr() const {
    ArrayRef<const Expr *> parents = ExprStack;
    assert(!parents.empty() && "must be called while visiting an expression");
    size_t idx = parents.size() - 1;

    do {
      if (idx == 0)
        return nullptr;
      --idx;
    } while (isa<DotSyntaxBaseIgnoredExpr>(parents[idx]) || // Mod.f(a)
             isa<SelfApplyExpr>(parents[idx]) || // obj.f(a)
             isa<IdentityExpr>(parents[idx]) || // (f)(a)
             isa<ForceValueExpr>(parents[idx]) || // f!(a)
             isa<BindOptionalExpr>(parents[idx])); // f?(a)

    auto *call = dyn_cast<ApplyExpr>(parents[idx]);
    if (!call || call->getFn() != parents[idx+1])
      return nullptr;
    return call;
  }

  /// Walk an assignment expression, checking for availability.
  void walkAssignExpr(AssignExpr *E) {
    // We take over recursive walking of assignment expressions in order to
    // walk the destination and source expressions in different member
    // access contexts.
    Expr *Dest = E->getDest();
    if (!Dest) {
      return;
    }

    // Check the Dest expression in a setter context.
    // We have an implicit assumption here that the first MemberRefExpr
    // encountered walking (pre-order) is the Dest is the destination of the
    // write. For the moment this is fine -- but future syntax might violate
    // this assumption.
    walkInContext(E, Dest, MemberAccessContext::Setter);

    // Check RHS in getter context
    Expr *Source = E->getSrc();
    if (!Source) {
      return;
    }
    walkInContext(E, Source, MemberAccessContext::Getter);
  }
  
  /// Walk a member reference expression, checking for availability.
  void walkMemberRef(MemberRefExpr *E) {
    // Walk the base in a getter context.
    // FIXME: We may need to look at the setter too, if we're going to do
    // writeback. The AST should have this information.
    walkInContext(E, E->getBase(), MemberAccessContext::Getter);

    ConcreteDeclRef DR = E->getMember();
    // Diagnose for the member declaration itself.
    if (diagnoseDeclRefAvailability(DR, E->getNameLoc().getSourceRange(),
                                    getEnclosingApplyExpr(), None))
      return;

    // Diagnose for appropriate accessors, given the access context.
    auto *DC = Where.getDeclContext();
    maybeDiagStorageAccess(DR.getDecl(), E->getSourceRange(), DC);
  }

  /// Walk a keypath expression, checking all of its components for
  /// availability.
  void maybeDiagKeyPath(KeyPathExpr *KP) {
    auto flags = DeclAvailabilityFlags();
    if (KP->isObjC())
      flags = DeclAvailabilityFlag::ForObjCKeyPath;

    for (auto &component : KP->getComponents()) {
      switch (component.getKind()) {
      case KeyPathExpr::Component::Kind::Property:
      case KeyPathExpr::Component::Kind::Subscript: {
        auto decl = component.getDeclRef();
        auto loc = component.getLoc();
        diagnoseDeclRefAvailability(decl, loc, nullptr, flags);
        break;
      }

      case KeyPathExpr::Component::Kind::TupleElement:
        break;

      case KeyPathExpr::Component::Kind::Invalid:
      case KeyPathExpr::Component::Kind::UnresolvedProperty:
      case KeyPathExpr::Component::Kind::UnresolvedSubscript:
      case KeyPathExpr::Component::Kind::OptionalChain:
      case KeyPathExpr::Component::Kind::OptionalWrap:
      case KeyPathExpr::Component::Kind::OptionalForce:
      case KeyPathExpr::Component::Kind::Identity:
      case KeyPathExpr::Component::Kind::DictionaryKey:
      case KeyPathExpr::Component::Kind::CodeCompletion:
        break;
      }
    }
  }

  /// Walk an inout expression, checking for availability.
  void walkInOutExpr(InOutExpr *E) {
    walkInContext(E, E->getSubExpr(), MemberAccessContext::InOut);
  }

  bool shouldWalkIntoClosure(AbstractClosureExpr *closure) const {
    return true;
  }

  /// Walk an abstract closure expression, checking for availability
  void walkAbstractClosure(AbstractClosureExpr *closure) {
    // Do the walk with the closure set as the decl context of the 'where'
    auto where = ExportContext::forFunctionBody(closure, closure->getStartLoc());
    if (where.isImplicit())
      return;
    ExprAvailabilityWalker walker(where);

    // Manually dive into the body
    closure->getBody()->walk(walker);

    return;
  }


  /// Walk the given expression in the member access context.
  void walkInContext(Expr *baseExpr, Expr *E,
                     MemberAccessContext AccessContext) {
    llvm::SaveAndRestore<MemberAccessContext>
      C(this->AccessContext, AccessContext);
    E->walk(*this);
  }

  /// Emit diagnostics, if necessary, for accesses to storage where
  /// the accessor for the AccessContext is not available.
  void maybeDiagStorageAccess(const ValueDecl *VD,
                              SourceRange ReferenceRange,
                              const DeclContext *ReferenceDC) const {
    if (Context.LangOpts.DisableAvailabilityChecking)
      return;

    auto *D = dyn_cast<AbstractStorageDecl>(VD);
    if (!D)
      return;

    if (!D->requiresOpaqueAccessors()) {
      return;
    }

    // Check availability of accessor functions.
    // TODO: if we're talking about an inlineable storage declaration,
    // this probably needs to be refined to not assume that the accesses are
    // specifically using the getter/setter.
    switch (AccessContext) {
    case MemberAccessContext::Getter:
      diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Get),
                               ReferenceRange, ReferenceDC, None);
      break;

    case MemberAccessContext::Setter:
      diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
                               ReferenceRange, ReferenceDC, None);
      break;

    case MemberAccessContext::InOut:
      diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Get),
                               ReferenceRange, ReferenceDC,
                               DeclAvailabilityFlag::ForInout);

      diagAccessorAvailability(D->getOpaqueAccessor(AccessorKind::Set),
                               ReferenceRange, ReferenceDC,
                               DeclAvailabilityFlag::ForInout);
      break;
    }
  }

  /// Emit a diagnostic, if necessary for a potentially unavailable accessor.
  void diagAccessorAvailability(AccessorDecl *D, SourceRange ReferenceRange,
                                const DeclContext *ReferenceDC,
                                DeclAvailabilityFlags Flags) const {
    if (!D)
      return;

    Flags &= DeclAvailabilityFlag::ForInout;
    Flags |= DeclAvailabilityFlag::ContinueOnPotentialUnavailability;
    if (diagnoseDeclAvailability(D, ReferenceRange, /*call*/ nullptr, Where,
                                 Flags))
      return;
  }
};
} // end anonymous namespace

/// Diagnose uses of unavailable declarations. Returns true if a diagnostic
/// was emitted.
bool ExprAvailabilityWalker::diagnoseDeclRefAvailability(
    ConcreteDeclRef declRef, SourceRange R, const Expr *call,
    DeclAvailabilityFlags Flags) const {
  if (!declRef)
    return false;
  const ValueDecl *D = declRef.getDecl();

  if (auto *attr = AvailableAttr::isUnavailable(D)) {
    if (diagnoseIncDecRemoval(D, R, attr))
      return true;
    if (isa_and_nonnull<ApplyExpr>(call) &&
        diagnoseMemoryLayoutMigration(D, R, attr, cast<ApplyExpr>(call)))
      return true;
  }

  if (diagnoseDeclAvailability(D, R, call, Where, Flags))
      return true;

  if (R.isValid()) {
    if (diagnoseSubstitutionMapAvailability(R.Start, declRef.getSubstitutions(),
                                            Where)) {
      return true;
    }
  }

  return false;
}

/// Diagnose misuses of API in asynchronous contexts.
/// Returns true if a fatal diagnostic was emitted, false otherwise.
static bool
diagnoseDeclAsyncAvailability(const ValueDecl *D, SourceRange R,
                              const Expr *call, const ExportContext &Where) {
  // If we are in a synchronous context, don't check it
  if (!Where.getDeclContext()->isAsyncContext())
    return false;

  ASTContext &ctx = Where.getDeclContext()->getASTContext();

  if (const AbstractFunctionDecl *afd = dyn_cast<AbstractFunctionDecl>(D)) {
    if (const AbstractFunctionDecl *asyncAlt = afd->getAsyncAlternative()) {
      SourceLoc diagLoc = call ? call->getLoc() : R.Start;
      ctx.Diags.diagnose(diagLoc, diag::warn_use_async_alternative);

      if (auto *accessor = dyn_cast<AccessorDecl>(asyncAlt)) {
        SmallString<32> name;
        llvm::raw_svector_ostream os(name);
        accessor->printUserFacingName(os);
        ctx.Diags.diagnose(asyncAlt->getLoc(),
                           diag::descriptive_decl_declared_here, name);
      } else {
        ctx.Diags.diagnose(asyncAlt->getLoc(), diag::decl_declared_here,
                           asyncAlt->getName());
      }
    }
  }

  // @available(noasync) spelling
  if (const AvailableAttr *attr = D->getAttrs().getNoAsync(ctx)) {
    SourceLoc diagLoc = call ? call->getLoc() : R.Start;
    auto diag = ctx.Diags.diagnose(diagLoc, diag::async_unavailable_decl,
                                   D->getDescriptiveKind(), D->getBaseName(),
                                   attr->Message);
    diag.warnUntilSwiftVersion(6);

    if (!attr->Rename.empty()) {
      fixItAvailableAttrRename(diag, R, D, attr, call);
    }
    return true;
  }

  const bool hasUnavailableAttr =
      D->getAttrs().hasAttribute<UnavailableFromAsyncAttr>();

  if (!hasUnavailableAttr)
    return false;
  // @_unavailableFromAsync spelling
  const UnavailableFromAsyncAttr *attr =
      D->getAttrs().getAttribute<UnavailableFromAsyncAttr>();
  SourceLoc diagLoc = call ? call->getLoc() : R.Start;
  ctx.Diags
      .diagnose(diagLoc, diag::async_unavailable_decl, D->getDescriptiveKind(),
                D->getBaseName(), attr->Message)
      .warnUntilSwiftVersion(6);
  D->diagnose(diag::decl_declared_here, D->getName());
  return true;
}

/// Diagnose uses of unavailable declarations. Returns true if a diagnostic
/// was emitted.
bool swift::diagnoseDeclAvailability(const ValueDecl *D, SourceRange R,
                                     const Expr *call,
                                     const ExportContext &Where,
                                     DeclAvailabilityFlags Flags) {
  assert(!Where.isImplicit());

  // Generic parameters are always available.
  if (isa<GenericTypeParamDecl>(D))
    return false;

  // Keep track if this is an accessor.
  auto accessor = dyn_cast<AccessorDecl>(D);

  if (accessor) {
    // If the property/subscript is unconditionally unavailable, don't bother
    // with any of the rest of this.
    if (AvailableAttr::isUnavailable(accessor->getStorage()))
      return false;
  }

  if (R.isValid()) {
    if (TypeChecker::diagnoseInlinableDeclRefAccess(R.Start, D, Where))
      return true;

    if (TypeChecker::diagnoseDeclRefExportability(R.Start, D, Where))
      return true;
  }

  if (diagnoseExplicitUnavailability(D, R, Where, call, Flags))
    return true;

  if (diagnoseDeclAsyncAvailability(D, R, call, Where))
    return true;

  // Make sure not to diagnose an accessor's deprecation if we already
  // complained about the property/subscript.
  bool isAccessorWithDeprecatedStorage =
    accessor && TypeChecker::getDeprecated(accessor->getStorage());

  // Diagnose for deprecation
  if (!isAccessorWithDeprecatedStorage)
    TypeChecker::diagnoseIfDeprecated(R, Where, D, call);

  if (Flags.contains(DeclAvailabilityFlag::AllowPotentiallyUnavailableProtocol)
        && isa<ProtocolDecl>(D))
    return false;

  // Diagnose (and possibly signal) for potential unavailability
  auto maybeUnavail = TypeChecker::checkDeclarationAvailability(D, Where);
  if (!maybeUnavail.has_value())
    return false;

  auto unavailReason = maybeUnavail.value();
  auto *DC = Where.getDeclContext();
  if (Flags.contains(
          DeclAvailabilityFlag::
              AllowPotentiallyUnavailableAtOrBelowDeploymentTarget) &&
      unavailReason.requiresDeploymentTargetOrEarlier(DC->getASTContext()))
    return false;

  if (accessor) {
    bool forInout = Flags.contains(DeclAvailabilityFlag::ForInout);
    TypeChecker::diagnosePotentialAccessorUnavailability(
        accessor, R, DC, unavailReason, forInout);
  } else {
    if (!TypeChecker::diagnosePotentialUnavailability(D, R, DC, unavailReason))
      return false;
  }

  return !Flags.contains(
      DeclAvailabilityFlag::ContinueOnPotentialUnavailability);
}

/// Return true if the specified type looks like an integer of floating point
/// type.
static bool isIntegerOrFloatingPointType(Type ty, ModuleDecl *M) {
  return (TypeChecker::conformsToKnownProtocol(
            ty, KnownProtocolKind::ExpressibleByIntegerLiteral, M) ||
          TypeChecker::conformsToKnownProtocol(
            ty, KnownProtocolKind::ExpressibleByFloatLiteral, M));
}


/// If this is a call to an unavailable ++ / -- operator, try to diagnose it
/// with a fixit hint and return true.  If not, or if we fail, return false.
bool
ExprAvailabilityWalker::diagnoseIncDecRemoval(const ValueDecl *D, SourceRange R,
                                              const AvailableAttr *Attr) const {
  // We can only produce a fixit if we're talking about ++ or --.
  bool isInc = D->getBaseName() == "++";
  if (!isInc && D->getBaseName() != "--")
    return false;

  // We can only handle the simple cases of lvalue++ and ++lvalue.  This is
  // always modeled as:
  //   (postfix_unary_expr (declrefexpr ++), (inoutexpr (lvalue)))
  // if not, bail out.
  if (ExprStack.size() != 2 ||
      !isa<DeclRefExpr>(ExprStack[1]) ||
      !(isa<PostfixUnaryExpr>(ExprStack[0]) ||
        isa<PrefixUnaryExpr>(ExprStack[0])))
    return false;

  auto call = cast<ApplyExpr>(ExprStack[0]);

  // If the expression type is integer or floating point, then we can rewrite it
  // to "lvalue += 1".
  auto *DC = Where.getDeclContext();
  std::string replacement;
  if (isIntegerOrFloatingPointType(call->getType(), DC->getParentModule()))
    replacement = isInc ? " += 1" : " -= 1";
  else {
    // Otherwise, it must be an index type.  Rewrite to:
    // "lvalue = lvalue.successor()".
    auto &SM = Context.SourceMgr;
    auto CSR = Lexer::getCharSourceRangeFromSourceRange(
        SM, call->getArgs()->getSourceRange());
    replacement = " = " + SM.extractText(CSR).str();
    replacement += isInc ? ".successor()" : ".predecessor()";
  }
  
  if (!replacement.empty()) {
    DeclName Name;
    unsigned RawAccessorKind;
    std::tie(RawAccessorKind, Name) = getAccessorKindAndNameForDiagnostics(D);

    // If we emit a deprecation diagnostic, produce a fixit hint as well.
    auto diag = Context.Diags.diagnose(
        R.Start, diag::availability_decl_unavailable,
        RawAccessorKind, Name, true, "",
        "it has been removed in Swift 3");
    if (isa<PrefixUnaryExpr>(call)) {
      // Prefix: remove the ++ or --.
      diag.fixItRemove(call->getFn()->getSourceRange());
      diag.fixItInsertAfter(call->getArgs()->getEndLoc(), replacement);
    } else {
      // Postfix: replace the ++ or --.
      diag.fixItReplace(call->getFn()->getSourceRange(), replacement);
    }

    return true;
  }


  return false;
}

/// If this is a call to an unavailable sizeof family function, diagnose it
/// with a fixit hint and return true. If not, or if we fail, return false.
bool
ExprAvailabilityWalker::diagnoseMemoryLayoutMigration(const ValueDecl *D,
                                                      SourceRange R,
                                                      const AvailableAttr *Attr,
                                                      const ApplyExpr *call) const {

  if (!D->getModuleContext()->isStdlibModule())
    return false;

  StringRef Property;
  if (D->getBaseName() == "sizeof") {
    Property = "size";
  } else if (D->getBaseName() == "alignof") {
    Property = "alignment";
  } else if (D->getBaseName() == "strideof") {
    Property = "stride";
  }

  if (Property.empty())
    return false;

  auto *args = call->getArgs();
  auto *subject = args->getUnlabeledUnaryExpr();
  if (!subject)
    return false;

  DeclName Name;
  unsigned RawAccessorKind;
  std::tie(RawAccessorKind, Name) = getAccessorKindAndNameForDiagnostics(D);

  EncodedDiagnosticMessage EncodedMessage(Attr->Message);
  auto diag =
      Context.Diags.diagnose(
          R.Start, diag::availability_decl_unavailable, RawAccessorKind,
          Name, true, "", EncodedMessage.Message);
  diag.highlight(R);

  StringRef Prefix = "MemoryLayout<";
  StringRef Suffix = ">.";

  if (auto DTE = dyn_cast<DynamicTypeExpr>(subject)) {
    // Replace `sizeof(type(of: x))` with `MemoryLayout<X>.size`, where `X` is
    // the static type of `x`. The previous spelling misleadingly hinted that
    // `sizeof(_:)` might return the size of the *dynamic* type of `x`, when
    // it is not the case.
    auto valueType = DTE->getBase()->getType()->getRValueType();
    if (!valueType || valueType->hasError()) {
      // If we don't have a suitable argument, we can't emit a fixit.
      return true;
    }
    // Note that in rare circumstances we may be destructively replacing the
    // source text. For example, we'd replace `sizeof(type(of: doSomething()))`
    // with `MemoryLayout<T>.size`, if T is the return type of `doSomething()`.
    diag.fixItReplace(call->getSourceRange(),
                   (Prefix + valueType->getString() + Suffix + Property).str());
  } else {
    SourceRange PrefixRange(call->getStartLoc(), args->getLParenLoc());
    SourceRange SuffixRange(args->getRParenLoc());

    // We must remove `.self`.
    if (auto *DSE = dyn_cast<DotSelfExpr>(subject))
      SuffixRange.Start = DSE->getDotLoc();

    diag
      .fixItReplace(PrefixRange, Prefix)
      .fixItReplace(SuffixRange, (Suffix + Property).str());
  }

  return true;
}

/// Diagnose uses of unavailable declarations.
void swift::diagnoseExprAvailability(const Expr *E, DeclContext *DC) {
  auto where = ExportContext::forFunctionBody(DC, E->getStartLoc());
  if (where.isImplicit())
    return;
  ExprAvailabilityWalker walker(where);
  const_cast<Expr*>(E)->walk(walker);
}

namespace {

class StmtAvailabilityWalker : public BaseDiagnosticWalker {
  DeclContext *DC;
  bool WalkRecursively;

public:
  explicit StmtAvailabilityWalker(DeclContext *dc, bool walkRecursively)
    : DC(dc), WalkRecursively(walkRecursively) {}

  PreWalkResult<Stmt *> walkToStmtPre(Stmt *S) override {
    if (!WalkRecursively && isa<BraceStmt>(S))
      return Action::SkipChildren(S);

    return Action::Continue(S);
  }

  PreWalkResult<Expr *> walkToExprPre(Expr *E) override {
    if (WalkRecursively)
      diagnoseExprAvailability(E, DC);
    return Action::SkipChildren(E);
  }

  PreWalkAction walkToTypeReprPre(TypeRepr *T) override {
    auto where = ExportContext::forFunctionBody(DC, T->getStartLoc());
    diagnoseTypeReprAvailability(T, where);
    return Action::SkipChildren();
  }

  PreWalkResult<Pattern *> walkToPatternPre(Pattern *P) override {
    if (auto *IP = dyn_cast<IsPattern>(P)) {
      auto where = ExportContext::forFunctionBody(DC, P->getLoc());
      diagnoseTypeAvailability(IP->getCastType(), P->getLoc(), where);
    }

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

void swift::diagnoseStmtAvailability(const Stmt *S, DeclContext *DC,
                                     bool walkRecursively) {
  // We'll visit the individual statements when we check them.
  if (!walkRecursively && isa<BraceStmt>(S))
    return;

  StmtAvailabilityWalker walker(DC, walkRecursively);
  const_cast<Stmt*>(S)->walk(walker);
}

namespace {

class TypeReprAvailabilityWalker : public ASTWalker {
  const ExportContext &where;
  DeclAvailabilityFlags flags;

  bool checkIdentTypeRepr(IdentTypeRepr *ITR) {
    if (auto *typeDecl = ITR->getBoundDecl()) {
      auto range = ITR->getNameLoc().getSourceRange();
      if (diagnoseDeclAvailability(typeDecl, range, nullptr, where, flags))
        return true;
    }

    bool foundAnyIssues = false;

    if (auto *GTR = dyn_cast<GenericIdentTypeRepr>(ITR)) {
      auto genericFlags = flags;
      genericFlags -= DeclAvailabilityFlag::AllowPotentiallyUnavailableProtocol;

      for (auto *genericArg : GTR->getGenericArgs()) {
        if (diagnoseTypeReprAvailability(genericArg, where, genericFlags))
          foundAnyIssues = true;
      }
    }

    return foundAnyIssues;
  }

public:
  bool foundAnyIssues = false;

  TypeReprAvailabilityWalker(const ExportContext &where,
                             DeclAvailabilityFlags flags)
      : where(where), flags(flags) {}

  MacroWalking getMacroWalkingBehavior() const override {
    return MacroWalking::ArgumentsAndExpansion;
  }

  PreWalkAction walkToTypeReprPre(TypeRepr *T) override {
    auto *declRefTR = dyn_cast<DeclRefTypeRepr>(T);
    if (!declRefTR)
      return Action::Continue();

    auto *baseComp = declRefTR->getBaseComponent();
    if (auto *identBase = dyn_cast<IdentTypeRepr>(baseComp)) {
      if (checkIdentTypeRepr(identBase)) {
        foundAnyIssues = true;
        return Action::SkipChildren();
      }
    } else if (diagnoseTypeReprAvailability(baseComp, where, flags)) {
      foundAnyIssues = true;
      return Action::SkipChildren();
    }

    if (auto *memberTR = dyn_cast<MemberTypeRepr>(T)) {
      for (auto *comp : memberTR->getMemberComponents()) {
        // If a parent type is unavailable, don't go on to diagnose
        // the member since that will just produce a redundant
        // diagnostic.
        if (checkIdentTypeRepr(comp)) {
          foundAnyIssues = true;
          break;
        }
      }
    }

    // We've already visited all the children above, so we don't
    // need to recurse.
    return Action::SkipChildren();
  }
};

}

bool swift::diagnoseTypeReprAvailability(const TypeRepr *T,
                                         const ExportContext &where,
                                         DeclAvailabilityFlags flags) {
  if (!T)
    return false;
  TypeReprAvailabilityWalker walker(where, flags);
  const_cast<TypeRepr*>(T)->walk(walker);
  return walker.foundAnyIssues;
}

namespace {

class ProblematicTypeFinder : public TypeDeclFinder {
  SourceLoc Loc;
  const ExportContext &Where;
  DeclAvailabilityFlags Flags;

public:
  ProblematicTypeFinder(SourceLoc Loc, const ExportContext &Where,
                        DeclAvailabilityFlags Flags)
      : Loc(Loc), Where(Where), Flags(Flags) {}

  void visitTypeDecl(TypeDecl *decl) {
    // We only need to diagnose exportability here. Availability was
    // already checked on the TypeRepr.
    if (Where.mustOnlyReferenceExportedDecls())
      TypeChecker::diagnoseDeclRefExportability(Loc, decl, Where);
  }

  Action visitNominalType(NominalType *ty) override {
    visitTypeDecl(ty->getDecl());

    // If some generic parameters are missing, don't check conformances.
    if (ty->hasUnboundGenericType())
      return Action::Continue;

    // When the DeclContext parameter to getContextSubstitutionMap()
    // is a protocol declaration, the receiver must be a concrete
    // type, so it doesn't make sense to perform this check on
    // protocol types.
    if (isa<ProtocolType>(ty))
      return Action::Continue;

    ModuleDecl *useModule = Where.getDeclContext()->getParentModule();
    auto subs = ty->getContextSubstitutionMap(useModule, ty->getDecl());
    (void) diagnoseSubstitutionMapAvailability(Loc, subs, Where);
    return Action::Continue;
  }

  Action visitBoundGenericType(BoundGenericType *ty) override {
    visitTypeDecl(ty->getDecl());

    ModuleDecl *useModule = Where.getDeclContext()->getParentModule();
    auto subs = ty->getContextSubstitutionMap(useModule, ty->getDecl());
    (void)diagnoseSubstitutionMapAvailability(
        Loc, subs, Where,
        /*depTy=*/Type(),
        /*replacementTy=*/Type(),
        /*useConformanceAvailabilityErrorsOption=*/false,
        /*suppressParameterizationCheckForOptional=*/ty->isOptional());
    return Action::Continue;
  }

  Action visitTypeAliasType(TypeAliasType *ty) override {
    visitTypeDecl(ty->getDecl());

    auto subs = ty->getSubstitutionMap();
    (void) diagnoseSubstitutionMapAvailability(Loc, subs, Where);
    return Action::Continue;
  }

  // We diagnose unserializable Clang function types in the
  // post-visitor so that we diagnose any unexportable component
  // types first.
  Action walkToTypePost(Type T) override {
    if (Where.mustOnlyReferenceExportedDecls()) {
      if (auto fnType = T->getAs<AnyFunctionType>()) {
        if (auto clangType = fnType->getClangTypeInfo().getType()) {
          auto *DC = Where.getDeclContext();
          auto &ctx = DC->getASTContext();
          auto loader = ctx.getClangModuleLoader();
          // Serialization will serialize the sugared type if it can,
          // but we need the canonical type to be serializable or else
          // canonicalization (e.g. in SIL) might break things.
          if (!loader->isSerializable(clangType, /*check canonical*/ true)) {
            ctx.Diags.diagnose(Loc, diag::unexportable_clang_function_type, T);
          }
        }
      }
    }

    if (auto *TT = T->getAs<TupleType>()) {
      for (auto component : TT->getElementTypes()) {
        // Let the walker find inner tuple types, we only want to diagnose
        // non-compound components.
        if (component->is<TupleType>())
          continue;

        if (component->hasParameterizedExistential())
          (void)diagnoseParameterizedProtocolAvailability(
              Loc, Where.getDeclContext());
      }
    }

    return TypeDeclFinder::walkToTypePost(T);
  }
};

}

void swift::diagnoseTypeAvailability(Type T, SourceLoc loc,
                                     const ExportContext &where,
                                     DeclAvailabilityFlags flags) {
  if (!T)
    return;
  T.walk(ProblematicTypeFinder(loc, where, flags));
}

void swift::diagnoseTypeAvailability(const TypeRepr *TR, Type T, SourceLoc loc,
                                     const ExportContext &where,
                                     DeclAvailabilityFlags flags) {
  if (diagnoseTypeReprAvailability(TR, where, flags))
    return;
  diagnoseTypeAvailability(T, loc, where, flags);
}

static void diagnoseMissingConformance(
    SourceLoc loc, Type type, ProtocolDecl *proto, const DeclContext *fromDC) {
  assert(proto->isSpecificProtocol(KnownProtocolKind::Sendable));
  diagnoseMissingSendableConformance(loc, type, fromDC);
}

bool
swift::diagnoseConformanceAvailability(SourceLoc loc,
                                       ProtocolConformanceRef conformance,
                                       const ExportContext &where,
                                       Type depTy, Type replacementTy,
                                       bool useConformanceAvailabilityErrorsOption) {
  assert(!where.isImplicit());

  if (!conformance.isConcrete())
    return false;

  const ProtocolConformance *concreteConf = conformance.getConcrete();
  const RootProtocolConformance *rootConf = concreteConf->getRootConformance();

  // Diagnose "missing" conformances where we needed a conformance but
  // didn't have one.
  auto *DC = where.getDeclContext();
  if (auto builtinConformance = dyn_cast<BuiltinProtocolConformance>(rootConf)){
    if (builtinConformance->isMissing()) {
      diagnoseMissingConformance(loc, builtinConformance->getType(),
                                 builtinConformance->getProtocol(), DC);
    }
  }

  auto maybeEmitAssociatedTypeNote = [&]() {
    if (!depTy && !replacementTy)
      return;

    Type selfTy = rootConf->getProtocol()->getProtocolSelfType();
    if (!depTy->isEqual(selfTy)) {
      auto &ctx = DC->getASTContext();
      ctx.Diags.diagnose(
          loc,
          diag::assoc_conformance_from_implementation_only_module,
          depTy, replacementTy->getCanonicalType());
    }
  };

  if (auto *ext = dyn_cast<ExtensionDecl>(rootConf->getDeclContext())) {
    if (TypeChecker::diagnoseConformanceExportability(loc, rootConf, ext, where,
                                                      useConformanceAvailabilityErrorsOption)) {
      maybeEmitAssociatedTypeNote();
      return true;
    }

    if (diagnoseExplicitUnavailability(loc, rootConf, ext, where,
                                       useConformanceAvailabilityErrorsOption)) {
      maybeEmitAssociatedTypeNote();
      return true;
    }

    // Diagnose (and possibly signal) for potential unavailability
    auto maybeUnavail = TypeChecker::checkConformanceAvailability(
        rootConf, ext, where);
    if (maybeUnavail.has_value()) {
      TypeChecker::diagnosePotentialUnavailability(rootConf, ext, loc, DC,
                                                   maybeUnavail.value());
      maybeEmitAssociatedTypeNote();
      return true;
    }

    // Diagnose for deprecation
    if (TypeChecker::diagnoseIfDeprecated(loc, rootConf, ext, where)) {
      maybeEmitAssociatedTypeNote();

      // Deprecation is just a warning, so keep going with checking the
      // substitution map below.
    }
  }

  // Now, check associated conformances.
  SubstitutionMap subConformanceSubs =
      concreteConf->getSubstitutions(DC->getParentModule());
  if (diagnoseSubstitutionMapAvailability(loc, subConformanceSubs, where,
                                          depTy, replacementTy,
                                          useConformanceAvailabilityErrorsOption))
    return true;

  return false;
}

bool
swift::diagnoseSubstitutionMapAvailability(SourceLoc loc,
                                           SubstitutionMap subs,
                                           const ExportContext &where,
                                           Type depTy, Type replacementTy,
                                           bool useConformanceAvailabilityErrorsOption,
                                           bool suppressParameterizationCheckForOptional) {
  bool hadAnyIssues = false;
  for (ProtocolConformanceRef conformance : subs.getConformances()) {
    if (diagnoseConformanceAvailability(loc, conformance, where,
                                        depTy, replacementTy,
                                        useConformanceAvailabilityErrorsOption))
      hadAnyIssues = true;
  }

  // If we're looking at \c (any P)? (or any other depth of optional) then
  // there's no availability problem.
  if (suppressParameterizationCheckForOptional)
    return hadAnyIssues;

  for (auto replacement : subs.getReplacementTypes()) {
    if (replacement->hasParameterizedExistential())
      if (diagnoseParameterizedProtocolAvailability(loc,
                                                    where.getDeclContext()))
        hadAnyIssues = true;
  }
  return hadAnyIssues;
}

/// Should we warn that \p decl needs an explicit availability annotation
/// in -require-explicit-availability mode?
static bool declNeedsExplicitAvailability(const Decl *decl) {
  // Skip non-public decls.
  if (auto valueDecl = dyn_cast<const ValueDecl>(decl)) {
    AccessScope scope =
      valueDecl->getFormalAccessScope(/*useDC*/nullptr,
                                      /*treatUsableFromInlineAsPublic*/true);
    if (!scope.isPublic())
      return false;
  }

  // Skip functions emitted into clients, SPI or implicit.
  if (decl->getAttrs().hasAttribute<AlwaysEmitIntoClientAttr>() ||
      decl->isSPI() ||
      decl->isImplicit())
    return false;

  // Skip unavailable decls.
  if (AvailableAttr::isUnavailable(decl))
    return false;

  // Warn on decls without an introduction version.
  auto &ctx = decl->getASTContext();
  auto safeRangeUnderApprox = AvailabilityInference::availableRange(decl, ctx);
  return !safeRangeUnderApprox.getOSVersion().hasLowerEndpoint();
}

void swift::checkExplicitAvailability(Decl *decl) {
  // Skip if the command line option was not set and
  // accessors as we check the pattern binding decl instead.
  auto DiagLevel = decl->getASTContext().LangOpts.RequireExplicitAvailability;
  if (!DiagLevel ||
      isa<AccessorDecl>(decl))
    return;

  // Only look at decls at module level or in extensions.
  // This could be changed to force having attributes on all decls.
  if (!decl->getDeclContext()->isModuleScopeContext() &&
      !isa<ExtensionDecl>(decl->getDeclContext())) return;

  if (auto extension = dyn_cast<ExtensionDecl>(decl)) {
    // decl should be either a ValueDecl or an ExtensionDecl.
    auto extended = extension->getExtendedNominal();
    if (!extended || !extended->getFormalAccessScope().isPublic())
      return;

    // Skip extensions without public members or conformances.
    auto members = extension->getMembers();
    auto hasMembers = std::any_of(members.begin(), members.end(),
                                  [](const Decl *D) -> bool {
      if (auto VD = dyn_cast<ValueDecl>(D))
        if (declNeedsExplicitAvailability(VD))
          return true;
      return false;
    });

    auto hasProtocols = hasConformancesToPublicProtocols(extension);

    if (!hasMembers && !hasProtocols) return;

  } else if (auto pbd = dyn_cast<PatternBindingDecl>(decl)) {
    // Check the first var instead.
    if (pbd->getNumPatternEntries() == 0)
      return;

    llvm::SmallVector<VarDecl *, 2> vars;
    pbd->getPattern(0)->collectVariables(vars);
    if (vars.empty())
      return;

    decl = vars.front();
  }

  if (declNeedsExplicitAvailability(decl)) {
    auto diag = decl->diagnose(diag::public_decl_needs_availability);
    diag.limitBehavior(*DiagLevel);

    auto suggestPlatform =
      decl->getASTContext().LangOpts.RequireExplicitAvailabilityTarget;
    if (!suggestPlatform.empty()) {
      auto InsertLoc = decl->getAttrs().getStartLoc(/*forModifiers=*/false);
      if (InsertLoc.isInvalid())
        InsertLoc = decl->getStartLoc();

      if (InsertLoc.isInvalid())
        return;

      std::string AttrText;
      {
         llvm::raw_string_ostream Out(AttrText);

         auto &ctx = decl->getASTContext();
         StringRef OriginalIndent = Lexer::getIndentationForLine(
           ctx.SourceMgr, InsertLoc);
         Out << "@available(" << suggestPlatform << ", *)\n"
             << OriginalIndent;
      }

      diag.fixItInsert(InsertLoc, AttrText);
    }
  }
}
back to top