[clang] [clang-tools-extra] Implement Sema iosmac support (PR #218843)

Ian Anderson via cfe-commits cfe-commits at lists.llvm.org
Wed Aug 26 01:38:08 PDT 2026


https://github.com/ian-twilightcoder updated https://github.com/llvm/llvm-project/pull/218843

>From 167e143a368e47bc8b57b632ed6e6eaef2b47f0c Mon Sep 17 00:00:00 2001
From: Alex Lorenz <aleksei_lorenz at apple.com>
Date: Fri, 30 Mar 2018 15:44:31 -0700
Subject: [PATCH] Implement Sema iosmac support

rdar://38885683

apple-llvm-split-commit: 40aab84d831ddfc623de95fbbca06de6f0baf0a6
apple-llvm-split-dir: clang/
---
 clang-tools-extra/clangd/FindSymbols.cpp      |    4 +-
 clang-tools-extra/clangd/XRefs.cpp            |    4 +-
 clang/include/clang/AST/DeclBase.h            |   18 +-
 clang/include/clang/AST/ExprObjC.h            |   15 +-
 .../clang/Basic/DiagnosticSemaKinds.td        |    3 +
 clang/include/clang/Basic/TargetInfo.h        |   24 +
 clang/include/clang/Sema/DelayedDiagnostic.h  |   20 +-
 clang/include/clang/Sema/Sema.h               |    9 +-
 clang/lib/AST/DeclBase.cpp                    |   53 +-
 clang/lib/Basic/Targets/ARM.cpp               |    4 +-
 clang/lib/Basic/Targets/OSTargets.cpp         |   14 +-
 clang/lib/Basic/Targets/OSTargets.h           |   21 +-
 clang/lib/CodeGen/CGExprScalar.cpp            |   11 +-
 clang/lib/CodeGen/CGObjC.cpp                  |   55 +-
 clang/lib/CodeGen/CodeGenFunction.h           |    3 +-
 clang/lib/CodeGen/CodeGenModule.h             |    2 +
 clang/lib/Edit/RewriteObjCFoundationAPI.cpp   |    2 +-
 clang/lib/Sema/CodeCompleteConsumer.cpp       |   12 +-
 clang/lib/Sema/DelayedDiagnostic.cpp          |   16 +-
 clang/lib/Sema/SemaAvailability.cpp           | 1198 +++++++++++------
 clang/lib/Sema/SemaDecl.cpp                   |    5 +-
 clang/lib/Sema/SemaDeclAttr.cpp               |   25 +-
 clang/lib/Sema/SemaDeclObjC.cpp               |   45 +-
 clang/lib/Sema/SemaExpr.cpp                   |    4 +-
 clang/lib/Sema/SemaExprObjC.cpp               |   14 +-
 clang/lib/Sema/SemaLookup.cpp                 |    2 +-
 clang/lib/Sema/SemaObjCProperty.cpp           |    9 +-
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |    7 +-
 clang/lib/Serialization/ASTReaderStmt.cpp     |    1 +
 clang/lib/Serialization/ASTWriterStmt.cpp     |    1 +
 .../CodeGenObjC/availability-check-zippered.m |   41 +
 .../test/FixIt/fixit-availability-zippered.m  |   23 +
 clang/test/Sema/attr-availability-zippered.c  |  133 ++
 .../unguarded-availability-zippered.m         |   42 +
 clang/tools/libclang/CIndex.cpp               |    5 +-
 35 files changed, 1371 insertions(+), 474 deletions(-)
 create mode 100644 clang/test/CodeGenObjC/availability-check-zippered.m
 create mode 100644 clang/test/FixIt/fixit-availability-zippered.m
 create mode 100644 clang/test/Sema/attr-availability-zippered.c
 create mode 100644 clang/test/SemaObjC/unguarded-availability-zippered.m

diff --git a/clang-tools-extra/clangd/FindSymbols.cpp b/clang-tools-extra/clangd/FindSymbols.cpp
index 2a674d375b5d9..f3a10c281f424 100644
--- a/clang-tools-extra/clangd/FindSymbols.cpp
+++ b/clang-tools-extra/clangd/FindSymbols.cpp
@@ -250,7 +250,7 @@ SymbolTags computeSymbolTags(const NamedDecl &ND) {
   SymbolTags Result = 0;
   const auto IsDef = isUniqueDefinition(&ND);
 
-  if (ND.isDeprecated())
+  if (ND.isDeprecatedInAnyTargetPlatform())
     Result |= toSymbolTagBitmask(SymbolTag::Deprecated);
 
   if (isConst(&ND))
@@ -563,7 +563,7 @@ std::optional<DocumentSymbol> declToSym(ASTContext &Ctx, const NamedDecl &ND) {
   DocumentSymbol SI;
   SI.name = getSymbolName(Ctx, ND);
   SI.kind = SK;
-  SI.deprecated = ND.isDeprecated();
+  SI.deprecated = ND.isDeprecatedInAnyTargetPlatform();
   SI.range = Range{sourceLocToPosition(SM, SymbolRange->getBegin()),
                    sourceLocToPosition(SM, SymbolRange->getEnd())};
   SI.detail = getSymbolDetail(Ctx, ND);
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 86528d806eab3..bfc440302696c 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1865,7 +1865,7 @@ static std::optional<TypeHierarchyItem>
 declToTypeHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
   auto Result = declToHierarchyItem<TypeHierarchyItem>(ND, TUPath);
   if (Result) {
-    Result->deprecated = ND.isDeprecated();
+    Result->deprecated = ND.isDeprecatedInAnyTargetPlatform();
     // Compute the SymbolID and store it in the 'data' field.
     // This allows typeHierarchy/resolve to be used to
     // resolve children of items returned in a previous request
@@ -1880,7 +1880,7 @@ declToCallHierarchyItem(const NamedDecl &ND, llvm::StringRef TUPath) {
   auto Result = declToHierarchyItem<CallHierarchyItem>(ND, TUPath);
   if (!Result)
     return Result;
-  if (ND.isDeprecated())
+  if (ND.isDeprecatedInAnyTargetPlatform())
     Result->tags.push_back(SymbolTag::Deprecated);
   if (auto ID = getSymbolID(&ND))
     Result->data = ID.str();
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index 9d233be282dbb..2cf8a792c9df8 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -758,6 +758,13 @@ class alignas(8) Decl {
                   VersionTuple EnclosingVersion = VersionTuple(),
                   StringRef *RealizedPlatform = nullptr) const;
 
+  /// Determine the availability of the given declaration for the given
+  /// target platform and its minimum version.
+  AvailabilityResult
+  getAvailability(StringRef Platform, const VersionTuple &PlatformMinVersion,
+                  std::string *Message = nullptr,
+                  StringRef *RealizedPlatform = nullptr) const;
+
   /// Retrieve the version of the target platform in which this
   /// declaration was introduced.
   ///
@@ -766,14 +773,13 @@ class alignas(8) Decl {
   /// attribute otherwise.
   VersionTuple getVersionIntroduced() const;
 
-  /// Determine whether this declaration is marked 'deprecated'.
+  /// \brief Determine whether this declaration is marked 'deprecated' in any
+  /// target platform that we're compiling for.
   ///
   /// \param Message If non-NULL and the declaration is deprecated,
   /// this will be set to the message describing why the declaration
   /// was deprecated (which may be empty).
-  bool isDeprecated(std::string *Message = nullptr) const {
-    return getAvailability(Message) == AR_Deprecated;
-  }
+  bool isDeprecatedInAnyTargetPlatform(std::string *Message = nullptr) const;
 
   /// Determine whether this declaration is marked 'unavailable'.
   ///
@@ -784,6 +790,10 @@ class alignas(8) Decl {
     return getAvailability(Message) == AR_Unavailable;
   }
 
+  /// \brief Determine whether this declaration is marked 'deprecated' for
+  /// all target platforms that we're compiling for.
+  bool isUnavailabledForAllTargetPlatforms() const;
+
   /// Determine whether this is a weak-imported symbol.
   ///
   /// Weak-imported symbols are typically marked with the
diff --git a/clang/include/clang/AST/ExprObjC.h b/clang/include/clang/AST/ExprObjC.h
index b424530c8447c..39f22c961ce96 100644
--- a/clang/include/clang/AST/ExprObjC.h
+++ b/clang/include/clang/AST/ExprObjC.h
@@ -1736,13 +1736,18 @@ class ObjCAvailabilityCheckExpr : public Expr {
   friend class ASTStmtReader;
 
   VersionTuple VersionToCheck;
+  VersionTuple VariantVersionToCheck;
   SourceLocation AtLoc, RParen;
 
 public:
-  ObjCAvailabilityCheckExpr(VersionTuple VersionToCheck, SourceLocation AtLoc,
-                            SourceLocation RParen, QualType Ty)
+  ObjCAvailabilityCheckExpr(VersionTuple VersionToCheck,
+                            VersionTuple VariantVersionToCheck,
+                            SourceLocation AtLoc, SourceLocation RParen,
+                            QualType Ty)
       : Expr(ObjCAvailabilityCheckExprClass, Ty, VK_PRValue, OK_Ordinary),
-        VersionToCheck(VersionToCheck), AtLoc(AtLoc), RParen(RParen) {
+        VersionToCheck(VersionToCheck),
+        VariantVersionToCheck(VariantVersionToCheck), AtLoc(AtLoc),
+        RParen(RParen) {
     setDependence(ExprDependence::None);
   }
 
@@ -1757,6 +1762,10 @@ class ObjCAvailabilityCheckExpr : public Expr {
   bool hasVersion() const { return !VersionToCheck.empty(); }
   VersionTuple getVersion() const { return VersionToCheck; }
 
+  bool hasVariantVersion() const { return !VariantVersionToCheck.empty(); }
+
+  VersionTuple getVariantVersion() const { return VariantVersionToCheck; }
+
   child_range children() {
     return child_range(child_iterator(), child_iterator());
   }
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index cfb2ee3368201..c6b237c883a2b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4300,6 +4300,9 @@ def warn_unguarded_availability_new :
 def warn_unguarded_availability_unavailable_new :
   Warning<warn_unguarded_availability_unavailable.Summary>,
   InGroup<UnguardedAvailabilityNew>;
+def warn_zippered_unguarded_availability :
+  Warning<"%0 is only available on %1 %2 and %3 %4 or newer">,
+  InGroup<UnguardedAvailabilityNew>;
 def note_decl_unguarded_availability_silence : Note<
   "annotate %select{%1|anonymous %1}0 with an availability attribute to silence this warning">;
 def note_unguarded_available_silence : Note<
diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index 5a541aaf797fe..66a7ba2bd096f 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -260,6 +260,8 @@ class TargetInfo : public TransferrableTargetInfo,
 
   mutable StringRef PlatformName;
   mutable VersionTuple PlatformMinVersion;
+  mutable StringRef TargetVariantPlatform;
+  mutable VersionTuple TargetVariantPlatformMinVersion;
 
   LLVM_PREFERRED_TYPE(bool)
   unsigned HasAlignMac68kSupport : 1;
@@ -1737,6 +1739,28 @@ class TargetInfo : public TransferrableTargetInfo,
   /// which the program should be compiled.
   VersionTuple getPlatformMinVersion() const { return PlatformMinVersion; }
 
+  /// Returns true when are building for an auxillary target variant platform.
+  bool hasTargetVariantPlatform() const {
+    return !TargetVariantPlatform.empty();
+  }
+
+  /// Retrieve the name of the target variant platform as it is used in the
+  /// availability attribute.
+  StringRef getTargetVariantPlatform() const {
+    assert(hasTargetVariantPlatform() &&
+           "no target variant platform specified");
+    return TargetVariantPlatform;
+  }
+
+  /// Retrieve the minimum desired deployment version of the target variant
+  /// platform,
+  /// for which the program should be compiled.
+  VersionTuple getTargetVariantPlatformMinVersion() const {
+    assert(hasTargetVariantPlatform() &&
+           "no target variant platform specified");
+    return TargetVariantPlatformMinVersion;
+  }
+
   bool isBigEndian() const { return BigEndian; }
   bool isLittleEndian() const { return !BigEndian; }
 
diff --git a/clang/include/clang/Sema/DelayedDiagnostic.h b/clang/include/clang/Sema/DelayedDiagnostic.h
index 0105089a393f1..5e45c3459f151 100644
--- a/clang/include/clang/Sema/DelayedDiagnostic.h
+++ b/clang/include/clang/Sema/DelayedDiagnostic.h
@@ -134,14 +134,13 @@ class DelayedDiagnostic {
 
   void Destroy();
 
-  static DelayedDiagnostic makeAvailability(AvailabilityResult AR,
-                                            ArrayRef<SourceLocation> Locs,
-                                            const NamedDecl *ReferringDecl,
-                                            const NamedDecl *OffendingDecl,
-                                            const ObjCInterfaceDecl *UnknownObjCClass,
-                                            const ObjCPropertyDecl  *ObjCProperty,
-                                            StringRef Msg,
-                                            bool ObjCPropertyAccess);
+  static DelayedDiagnostic
+  makeAvailability(AvailabilityResult AR, ArrayRef<SourceLocation> Locs,
+                   const NamedDecl *ReferringDecl,
+                   const NamedDecl *OffendingDecl,
+                   const ObjCInterfaceDecl *UnknownObjCClass,
+                   const ObjCPropertyDecl *ObjCProperty, StringRef Msg,
+                   bool ObjCPropertyAccess, bool IsTargetVariantPlatform);
 
   static DelayedDiagnostic makeAccess(SourceLocation Loc,
                                       const AccessedEntity &Entity) {
@@ -232,6 +231,10 @@ class DelayedDiagnostic {
     return AvailabilityData.ObjCPropertyAccess;
   }
 
+  bool isTargetVariantPlatform() const {
+    return AvailabilityData.IsTargetVariantPlatform;
+  }
+
 private:
   struct AD {
     const NamedDecl *ReferringDecl;
@@ -244,6 +247,7 @@ class DelayedDiagnostic {
     size_t NumSelectorLocs;
     AvailabilityResult AR;
     bool ObjCPropertyAccess;
+    bool IsTargetVariantPlatform;
   };
 
   struct FTD {
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index dcf112fd8eaa4..42858681fba88 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -2466,6 +2466,11 @@ class Sema final : public SemaBase {
 
   void handleDelayedAvailabilityCheck(sema::DelayedDiagnostic &DD, Decl *Ctx);
 
+  void
+  handleZipperedDelayedAvailabilityCheck(sema::DelayedDiagnostic &DD,
+                                         sema::DelayedDiagnostic &VariantDD,
+                                         Decl *Ctx);
+
   /// Retrieve the current function, if any, that should be analyzed for
   /// potential availability violations.
   sema::FunctionScopeInfo *getCurFunctionAvailabilityContext();
@@ -2479,7 +2484,9 @@ class Sema final : public SemaBase {
   void DiagnoseAvailabilityOfDecl(NamedDecl *D, ArrayRef<SourceLocation> Locs);
 
   std::pair<AvailabilityResult, const NamedDecl *>
-  ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message,
+  ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, StringRef Platform,
+                                   const VersionTuple &PlatformVersion,
+                                   std::string *Message,
                                    ObjCInterfaceDecl *ClassReceiver);
   ///@}
 
diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp
index 70f61fa57a682..aff04541c6781 100644
--- a/clang/lib/AST/DeclBase.cpp
+++ b/clang/lib/AST/DeclBase.cpp
@@ -674,15 +674,12 @@ static StringRef getRealizedPlatform(const AvailabilityAttr *A,
 static AvailabilityResult CheckAvailability(ASTContext &Context,
                                             const AvailabilityAttr *A,
                                             std::string *Message,
+                                            StringRef TargetPlatform,
                                             VersionTuple EnclosingVersion) {
-  if (EnclosingVersion.empty())
-    EnclosingVersion = Context.getTargetInfo().getPlatformMinVersion();
-
   if (EnclosingVersion.empty())
     return AR_Available;
 
   StringRef ActualPlatform = A->getPlatform()->getName();
-  StringRef TargetPlatform = Context.getTargetInfo().getPlatformName();
 
   // Match the platform name.
   if (getRealizedPlatform(A, Context) != TargetPlatform)
@@ -779,9 +776,20 @@ static AvailabilityResult CheckAvailability(ASTContext &Context,
 AvailabilityResult Decl::getAvailability(std::string *Message,
                                          VersionTuple EnclosingVersion,
                                          StringRef *RealizedPlatform) const {
+  const TargetInfo &TI = getASTContext().getTargetInfo();
+  return getAvailability(TI.getPlatformName(),
+                         EnclosingVersion.empty() ? TI.getPlatformMinVersion()
+                                                  : EnclosingVersion,
+                         Message, RealizedPlatform);
+}
+
+AvailabilityResult Decl::getAvailability(StringRef Platform,
+                                         const VersionTuple &PlatformMinVersion,
+                                         std::string *Message,
+                                         StringRef *RealizedPlatform) const {
   if (auto *FTD = dyn_cast<FunctionTemplateDecl>(this))
-    return FTD->getTemplatedDecl()->getAvailability(Message, EnclosingVersion,
-                                                    RealizedPlatform);
+    return FTD->getTemplatedDecl()->getAvailability(
+        Platform, PlatformMinVersion, Message, RealizedPlatform);
 
   AvailabilityResult Result = AR_Available;
   std::string ResultMessage;
@@ -806,8 +814,8 @@ AvailabilityResult Decl::getAvailability(std::string *Message,
 
     if (const auto *Availability = dyn_cast<AvailabilityAttr>(A)) {
       Availability = Availability->getEffectiveAttr();
-      AvailabilityResult AR = CheckAvailability(getASTContext(), Availability,
-                                                Message, EnclosingVersion);
+      AvailabilityResult AR = CheckAvailability(
+          getASTContext(), Availability, Message, Platform, PlatformMinVersion);
 
       if (AR == AR_Unavailable) {
         if (RealizedPlatform)
@@ -844,6 +852,26 @@ VersionTuple Decl::getVersionIntroduced() const {
   return {};
 }
 
+bool Decl::isDeprecatedInAnyTargetPlatform(std::string *Message) const {
+  const TargetInfo &TI = getASTContext().getTargetInfo();
+  return getAvailability(TI.getPlatformName(), TI.getPlatformMinVersion()) ==
+             AR_Deprecated ||
+         (TI.hasTargetVariantPlatform() &&
+          getAvailability(TI.getTargetVariantPlatform(),
+                          TI.getTargetVariantPlatformMinVersion()) ==
+              AR_Deprecated);
+}
+
+bool Decl::isUnavailabledForAllTargetPlatforms() const {
+  const TargetInfo &TI = getASTContext().getTargetInfo();
+  return getAvailability(TI.getPlatformName(), TI.getPlatformMinVersion()) ==
+             AR_Unavailable &&
+         (!getASTContext().getTargetInfo().hasTargetVariantPlatform() ||
+          getAvailability(TI.getTargetVariantPlatform(),
+                          TI.getTargetVariantPlatformMinVersion()) ==
+              AR_Unavailable);
+}
+
 bool Decl::canBeWeakImported(bool &IsDefinition) const {
   IsDefinition = false;
 
@@ -878,6 +906,7 @@ bool Decl::isWeakImported() const {
   if (!canBeWeakImported(IsDefinition))
     return false;
 
+  const TargetInfo &TI = getASTContext().getTargetInfo();
   for (const auto *A : getMostRecentDecl()->attrs()) {
     if (isa<WeakImportAttr>(A))
       return true;
@@ -885,7 +914,13 @@ bool Decl::isWeakImported() const {
     if (const auto *Availability = dyn_cast<AvailabilityAttr>(A)) {
       Availability = Availability->getEffectiveAttr();
       if (CheckAvailability(getASTContext(), Availability, nullptr,
-                            VersionTuple()) == AR_NotYetIntroduced)
+                            TI.getPlatformName(), TI.getPlatformMinVersion()) ==
+              AR_NotYetIntroduced ||
+          (TI.hasTargetVariantPlatform() &&
+           CheckAvailability(getASTContext(), Availability, nullptr,
+                             TI.getTargetVariantPlatform(),
+                             TI.getTargetVariantPlatformMinVersion()) ==
+               AR_NotYetIntroduced))
         return true;
     }
   }
diff --git a/clang/lib/Basic/Targets/ARM.cpp b/clang/lib/Basic/Targets/ARM.cpp
index 8d6e8d6990e80..0cd4b705bbcb9 100644
--- a/clang/lib/Basic/Targets/ARM.cpp
+++ b/clang/lib/Basic/Targets/ARM.cpp
@@ -1558,5 +1558,7 @@ DarwinARMTargetInfo::DarwinARMTargetInfo(const llvm::Triple &Triple,
 void DarwinARMTargetInfo::getOSDefines(const LangOptions &Opts,
                                        const llvm::Triple &Triple,
                                        MacroBuilder &Builder) const {
-  getDarwinDefines(Builder, Opts, Triple, PlatformName, PlatformMinVersion);
+  getDarwinDefines(Builder, Opts, Triple, /*TargetVariantTriple=*/"",
+                   PlatformName, PlatformMinVersion, TargetVariantPlatform,
+                   TargetVariantPlatformMinVersion);
 }
diff --git a/clang/lib/Basic/Targets/OSTargets.cpp b/clang/lib/Basic/Targets/OSTargets.cpp
index 04d9e13d3c9a6..91f98595dbbf5 100644
--- a/clang/lib/Basic/Targets/OSTargets.cpp
+++ b/clang/lib/Basic/Targets/OSTargets.cpp
@@ -55,8 +55,10 @@ void getAppleMachODefines(MacroBuilder &Builder, const LangOptions &Opts,
 }
 
 void getDarwinDefines(MacroBuilder &Builder, const LangOptions &Opts,
-                      const llvm::Triple &Triple, StringRef &PlatformName,
-                      VersionTuple &PlatformMinVersion) {
+                      const llvm::Triple &Triple, StringRef TargetVariantTriple,
+                      StringRef &PlatformName, VersionTuple &PlatformMinVersion,
+                      StringRef &TargetVariantPlatformName,
+                      VersionTuple &TargetVariantPlatformMinVersion) {
   getAppleMachODefines(Builder, Opts, Triple);
 
   // Darwin's libc doesn't have threads.h
@@ -67,6 +69,14 @@ void getDarwinDefines(MacroBuilder &Builder, const LangOptions &Opts,
   if (Triple.isMacOSX()) {
     Triple.getMacOSXVersion(OsVersion);
     PlatformName = "macos";
+    if (!TargetVariantTriple.empty()) {
+      llvm::Triple TVT(TargetVariantTriple);
+      if (TVT.getOS() == llvm::Triple::IOS &&
+          TVT.getEnvironment() == llvm::Triple::MacABI) {
+        TargetVariantPlatformName = "maccatalyst";
+        TargetVariantPlatformMinVersion = TVT.getiOSVersion();
+      }
+    }
   } else {
     OsVersion = Triple.getOSVersion();
     PlatformName = llvm::Triple::getOSTypeName(Triple.getOS());
diff --git a/clang/lib/Basic/Targets/OSTargets.h b/clang/lib/Basic/Targets/OSTargets.h
index fb5c17cefc004..724406f82b725 100644
--- a/clang/lib/Basic/Targets/OSTargets.h
+++ b/clang/lib/Basic/Targets/OSTargets.h
@@ -38,8 +38,10 @@ void getAppleMachODefines(MacroBuilder &Builder, const LangOptions &Opts,
                           const llvm::Triple &Triple);
 
 void getDarwinDefines(MacroBuilder &Builder, const LangOptions &Opts,
-                      const llvm::Triple &Triple, StringRef &PlatformName,
-                      VersionTuple &PlatformMinVersion);
+                      const llvm::Triple &Triple, StringRef TargetVariantTriple,
+                      StringRef &PlatformName, VersionTuple &PlatformMinVersion,
+                      StringRef &TargetVariantPlatformName,
+                      VersionTuple &TargetVariantPlatform);
 
 template <typename Target>
 class LLVM_LIBRARY_VISIBILITY AppleMachOTargetInfo
@@ -77,8 +79,10 @@ class LLVM_LIBRARY_VISIBILITY DarwinTargetInfo
 protected:
   void getOSDefines(const LangOptions &Opts, const llvm::Triple &Triple,
                     MacroBuilder &Builder) const override {
-    getDarwinDefines(Builder, Opts, Triple, this->PlatformName,
-                     this->PlatformMinVersion);
+    getDarwinDefines(Builder, Opts, Triple, TargetVariantTriple,
+                     this->PlatformName, this->PlatformMinVersion,
+                     this->TargetVariantPlatform,
+                     this->TargetVariantPlatformMinVersion);
   }
 
 public:
@@ -87,6 +91,7 @@ class LLVM_LIBRARY_VISIBILITY DarwinTargetInfo
     // By default, no TLS, and we list permitted architecture/OS
     // combinations.
     this->TLSSupported = false;
+    TargetVariantTriple = Opts.DarwinTargetVariantTriple;
 
     if (Triple.isMacOSX())
       this->TLSSupported = !Triple.isMacOSXVersionLT(10, 7);
@@ -185,6 +190,14 @@ class LLVM_LIBRARY_VISIBILITY DarwinTargetInfo
   bool areDefaultedSMFStillPOD(const LangOptions &) const override {
     return false;
   }
+
+  /// Darwin does not support protected visibility.  Darwin's "default"
+  /// is very similar to ELF's "protected";  Darwin requires a "weak"
+  /// attribute on declarations that can be dynamically replaced.
+  bool hasProtectedVisibility() const override { return false; }
+
+private:
+  std::string TargetVariantTriple;
 };
 
 // DragonFlyBSD Target
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 173931213ce68..5be65e13a34f9 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -635,13 +635,20 @@ class ScalarExprEmitter
 
   Value *VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) {
     VersionTuple Version = E->getVersion();
+    VersionTuple VariantVersion = E->getVariantVersion();
 
     // If we're checking for a platform older than our minimum deployment
     // target, we can fold the check away.
-    if (Version <= CGF.CGM.getTarget().getPlatformMinVersion())
+    if ((Version.empty() && VariantVersion.empty()) ||
+        (!Version.empty() &&
+         Version <= CGF.CGM.getTarget().getPlatformMinVersion() &&
+         (!CGF.CGM.getTarget().hasTargetVariantPlatform() ||
+          (!VariantVersion.empty() &&
+           VariantVersion <=
+               CGF.CGM.getTarget().getTargetVariantPlatformMinVersion()))))
       return llvm::ConstantInt::get(Builder.getInt1Ty(), 1);
 
-    return CGF.EmitBuiltinAvailable(Version);
+    return CGF.EmitBuiltinAvailable(Version, VariantVersion);
   }
 
   Value *VisitArraySubscriptExpr(ArraySubscriptExpr *E);
diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index b9cbf593fb1c4..73b1cdba09514 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -4049,9 +4049,11 @@ static llvm::Value *emitIsPlatformVersionAtLeast(CodeGenFunction &CGF,
 }
 
 llvm::Value *
-CodeGenFunction::EmitBuiltinAvailable(const VersionTuple &Version) {
+CodeGenFunction::EmitBuiltinAvailable(const VersionTuple &Version,
+                                      const VersionTuple &VariantVersion) {
   // Darwin uses the new __isPlatformVersionAtLeast family of routines.
-  if (CGM.getTarget().getTriple().isOSDarwin())
+  if (CGM.getTarget().getTriple().isOSDarwin() &&
+      !CGM.getTarget().hasTargetVariantPlatform())
     return emitIsPlatformVersionAtLeast(*this, Version);
 
   if (!CGM.IsOSVersionAtLeastFn) {
@@ -4059,19 +4061,50 @@ CodeGenFunction::EmitBuiltinAvailable(const VersionTuple &Version) {
         llvm::FunctionType::get(Int32Ty, {Int32Ty, Int32Ty, Int32Ty}, false);
     CGM.IsOSVersionAtLeastFn =
         CGM.CreateRuntimeFunction(FTy, "__isOSVersionAtLeast");
+    llvm::FunctionType *FTy2 = llvm::FunctionType::get(Int32Ty, {}, false);
+    CGM.IsTargetPlatformNativeFn =
+        CGM.CreateRuntimeFunction(FTy2, "__isTargetPlatformNative");
+    CGM.IsTargetVariantOSVersionAtLeastFn =
+        CGM.CreateRuntimeFunction(FTy, "__isTargetVariantOSVersionAtLeast");
   }
 
-  std::optional<unsigned> Min = Version.getMinor(),
-                          SMin = Version.getSubminor();
-  llvm::Value *Args[] = {
-      llvm::ConstantInt::get(CGM.Int32Ty, Version.getMajor()),
-      llvm::ConstantInt::get(CGM.Int32Ty, Min.value_or(0)),
-      llvm::ConstantInt::get(CGM.Int32Ty, SMin.value_or(0))};
+  auto EmitVersionArgs = [&](const VersionTuple &V,
+                             llvm::SmallVectorImpl<llvm::Value *> &Args) {
+    if (V.empty())
+      return;
+    std::optional<unsigned> Min = V.getMinor(), SMin = V.getSubminor();
+    Args.push_back(llvm::ConstantInt::get(CGM.Int32Ty, V.getMajor()));
+    Args.push_back(llvm::ConstantInt::get(CGM.Int32Ty, Min.value_or(0)));
+    Args.push_back(llvm::ConstantInt::get(CGM.Int32Ty, SMin.value_or(0)));
+  };
 
-  llvm::Value *CallRes =
-      EmitNounwindRuntimeCall(CGM.IsOSVersionAtLeastFn, Args);
+  llvm::SmallVector<llvm::Value *, 3> Args;
+  llvm::SmallVector<llvm::Value *, 3> VariantArgs;
+  EmitVersionArgs(Version, Args);
+  EmitVersionArgs(VariantVersion, VariantArgs);
+
+  llvm::Value *Check = nullptr;
+  if (!Args.empty()) {
+    Check = EmitNounwindRuntimeCall(CGM.IsOSVersionAtLeastFn, Args);
+  }
+  llvm::Value *VariantCheck = nullptr;
+  if (!VariantArgs.empty()) {
+    VariantCheck = EmitNounwindRuntimeCall(
+        CGM.IsTargetVariantOSVersionAtLeastFn, VariantArgs);
+  }
+  llvm::Value *IsNativeCheck = nullptr;
+  if (CGM.getTarget().hasTargetVariantPlatform()) {
+    IsNativeCheck = EmitNounwindRuntimeCall(CGM.IsTargetPlatformNativeFn);
+    IsNativeCheck = Builder.CreateICmpNE(IsNativeCheck,
+                                         llvm::Constant::getNullValue(Int32Ty));
+    if (!Check)
+      Check = llvm::Constant::getNullValue(Int32Ty);
+    if (!VariantCheck)
+      VariantCheck = llvm::Constant::getNullValue(Int32Ty);
+    Check = Builder.CreateSelect(IsNativeCheck, Check, VariantCheck);
+  }
 
-  return Builder.CreateICmpNE(CallRes, llvm::Constant::getNullValue(Int32Ty));
+  return Builder.CreateICmpNE(Check, llvm::Constant::getNullValue(Int32Ty));
 }
 
 static bool isFoundationNeededForDarwinAvailabilityCheck(
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 7bdc79d86ea0a..3bf62755ac270 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -5028,7 +5028,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   enum class MSVCIntrin;
   llvm::Value *EmitMSVCBuiltinExpr(MSVCIntrin BuiltinID, const CallExpr *E);
 
-  llvm::Value *EmitBuiltinAvailable(const VersionTuple &Version);
+  llvm::Value *EmitBuiltinAvailable(const VersionTuple &Version,
+                                    const VersionTuple &VariantVersion);
 
   llvm::Value *EmitObjCProtocolExpr(const ObjCProtocolExpr *E);
   llvm::Value *EmitObjCStringLiteral(const ObjCStringLiteral *E);
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index 1f5ecf734c528..ffca028f65709 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -832,6 +832,8 @@ class CodeGenModule : public CodeGenTypeCache {
   llvm::FunctionCallee IsOSVersionAtLeastFn = nullptr;
   // i32 @__isPlatformVersionAtLeast(i32, i32, i32, i32)
   llvm::FunctionCallee IsPlatformVersionAtLeastFn = nullptr;
+  llvm::FunctionCallee IsTargetPlatformNativeFn = nullptr;
+  llvm::FunctionCallee IsTargetVariantOSVersionAtLeastFn = nullptr;
 
   InstrProfStats &getPGOStats() { return PGOStats; }
   llvm::IndexedInstrProfReader *getPGOReader() const { return PGOReader.get(); }
diff --git a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
index e8d4660fd36b2..fb3d1fef764b0 100644
--- a/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
+++ b/clang/lib/Edit/RewriteObjCFoundationAPI.cpp
@@ -163,7 +163,7 @@ static bool canRewriteToSubscriptSyntax(const ObjCInterfaceDecl *&IFace,
   IFace = maybeAdjustInterfaceForSubscriptingCheck(IFace, Rec, Ctx);
 
   if (const ObjCMethodDecl *MD = IFace->lookupInstanceMethod(subscriptSel)) {
-    if (!MD->isUnavailable())
+    if (!MD->isUnavailabledForAllTargetPlatforms())
       return true;
   }
   return false;
diff --git a/clang/lib/Sema/CodeCompleteConsumer.cpp b/clang/lib/Sema/CodeCompleteConsumer.cpp
index 50a552272f421..750a0f00ecff9 100644
--- a/clang/lib/Sema/CodeCompleteConsumer.cpp
+++ b/clang/lib/Sema/CodeCompleteConsumer.cpp
@@ -19,6 +19,7 @@
 #include "clang/AST/DeclarationName.h"
 #include "clang/AST/Type.h"
 #include "clang/Basic/IdentifierTable.h"
+#include "clang/Basic/TargetInfo.h"
 #include "clang/Lex/Preprocessor.h"
 #include "clang/Sema/Sema.h"
 #include "llvm/ADT/StringExtras.h"
@@ -765,9 +766,16 @@ void PrintingCodeCompleteConsumer::ProcessOverloadCandidates(
 
 /// Retrieve the effective availability of the given declaration.
 static AvailabilityResult getDeclAvailability(const Decl *D) {
-  AvailabilityResult AR = D->getAvailability();
+  // FIXME: Take min of target and target-variant AR for better Xcode
+  // experience?
+  const TargetInfo &TI =
+      D->getTranslationUnitDecl()->getASTContext().getTargetInfo();
+  AvailabilityResult AR =
+      D->getAvailability(TI.getPlatformName(), TI.getPlatformMinVersion());
   if (isa<EnumConstantDecl>(D))
-    AR = std::max(AR, cast<Decl>(D->getDeclContext())->getAvailability());
+    AR = std::max(AR, cast<Decl>(D->getDeclContext())
+                          ->getAvailability(TI.getPlatformName(),
+                                            TI.getPlatformMinVersion()));
   return AR;
 }
 
diff --git a/clang/lib/Sema/DelayedDiagnostic.cpp b/clang/lib/Sema/DelayedDiagnostic.cpp
index cb2721b92090e..e825abaa5bc71 100644
--- a/clang/lib/Sema/DelayedDiagnostic.cpp
+++ b/clang/lib/Sema/DelayedDiagnostic.cpp
@@ -20,15 +20,12 @@
 using namespace clang;
 using namespace sema;
 
-DelayedDiagnostic
-DelayedDiagnostic::makeAvailability(AvailabilityResult AR,
-                                    ArrayRef<SourceLocation> Locs,
-                                    const NamedDecl *ReferringDecl,
-                                    const NamedDecl *OffendingDecl,
-                                    const ObjCInterfaceDecl *UnknownObjCClass,
-                                    const ObjCPropertyDecl  *ObjCProperty,
-                                    StringRef Msg,
-                                    bool ObjCPropertyAccess) {
+DelayedDiagnostic DelayedDiagnostic::makeAvailability(
+    AvailabilityResult AR, ArrayRef<SourceLocation> Locs,
+    const NamedDecl *ReferringDecl, const NamedDecl *OffendingDecl,
+    const ObjCInterfaceDecl *UnknownObjCClass,
+    const ObjCPropertyDecl *ObjCProperty, StringRef Msg,
+    bool ObjCPropertyAccess, bool IsTargetVariantPlatform) {
   assert(!Locs.empty());
   DelayedDiagnostic DD;
   DD.Kind = Availability;
@@ -53,6 +50,7 @@ DelayedDiagnostic::makeAvailability(AvailabilityResult AR,
 
   DD.AvailabilityData.AR = AR;
   DD.AvailabilityData.ObjCPropertyAccess = ObjCPropertyAccess;
+  DD.AvailabilityData.IsTargetVariantPlatform = IsTargetVariantPlatform;
   return DD;
 }
 
diff --git a/clang/lib/Sema/SemaAvailability.cpp b/clang/lib/Sema/SemaAvailability.cpp
index 28a4b760dbd4d..2c736015c20ca 100644
--- a/clang/lib/Sema/SemaAvailability.cpp
+++ b/clang/lib/Sema/SemaAvailability.cpp
@@ -44,6 +44,7 @@ static bool hasMatchingEnvironmentOrNone(const ASTContext &Context,
 }
 
 static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context,
+                                                  StringRef TargetPlatform,
                                                   const Decl *D) {
   AvailabilityAttr const *PartialMatch = nullptr;
   // Check each AvailabilityAttr to find the one for this platform.
@@ -73,8 +74,6 @@ static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context,
           RealizedPlatform = RealizedPlatform.slice(0, suffix);
       }
 
-      StringRef TargetPlatform = Context.getTargetInfo().getPlatformName();
-
       // Match the platform name.
       if (RealizedPlatform == TargetPlatform) {
         // Find the best matching attribute for this environment
@@ -96,9 +95,12 @@ static const AvailabilityAttr *getAttrForPlatform(ASTContext &Context,
 /// \param ClassReceiver If we're checking the method of a class message
 /// send, the class. Otherwise nullptr.
 std::pair<AvailabilityResult, const NamedDecl *>
-Sema::ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message,
+Sema::ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, StringRef Platform,
+                                       const VersionTuple &PlatformVersion,
+                                       std::string *Message,
                                        ObjCInterfaceDecl *ClassReceiver) {
-  AvailabilityResult Result = D->getAvailability(Message);
+  AvailabilityResult Result =
+      D->getAvailability(Platform, PlatformVersion, Message);
 
   // For typedefs, if the typedef declaration appears available look
   // to the underlying type to see if it is more restrictive.
@@ -121,7 +123,9 @@ Sema::ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message,
         T = NextT;
         continue;
       }
-      Result = D->getAvailability(Message);
+      /* TO_UPSTREAM(iosmac) ON*/
+      Result = D->getAvailability(Platform, PlatformVersion, Message);
+      /* TO_UPSTREAM(iosmac) OFF*/
       break;
     }
   }
@@ -130,7 +134,7 @@ Sema::ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message,
   if (const auto *IDecl = dyn_cast<ObjCInterfaceDecl>(D)) {
     if (IDecl->getDefinition()) {
       D = IDecl->getDefinition();
-      Result = D->getAvailability(Message);
+      Result = D->getAvailability(Platform, PlatformVersion, Message);
     }
   }
 
@@ -138,7 +142,8 @@ Sema::ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message,
     if (Result == AR_Available) {
       const DeclContext *DC = ECD->getDeclContext();
       if (const auto *TheEnumDecl = dyn_cast<EnumDecl>(DC)) {
-        Result = TheEnumDecl->getAvailability(Message);
+        Result =
+            TheEnumDecl->getAvailability(Platform, PlatformVersion, Message);
         D = TheEnumDecl;
       }
     }
@@ -151,7 +156,7 @@ Sema::ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message,
       if (Init && Result == AR_Available && MD->isClassMethod() &&
           MD->getSelector() == ObjC().NSAPIObj->getNewSelector() &&
           MD->definedInNSObject(getASTContext())) {
-        Result = Init->getAvailability(Message);
+        Result = Init->getAvailability(Platform, PlatformVersion, Message);
         D = Init;
       }
     }
@@ -165,7 +170,8 @@ Sema::ShouldDiagnoseAvailabilityOfDecl(const NamedDecl *D, std::string *Message,
 /// in a deprecated context, but not the other way around.
 static bool ShouldDiagnoseAvailabilityInContext(
     Sema &S, AvailabilityResult K, VersionTuple DeclVersion,
-    const IdentifierInfo *DeclEnv, Decl *Ctx, const NamedDecl *OffendingDecl) {
+    const IdentifierInfo *DeclEnv, Decl *Ctx, const NamedDecl *OffendingDecl,
+    StringRef TargetPlatform, const VersionTuple &TargetPlatformMinVersion) {
   assert(K != AR_Available && "Expected an unavailable declaration here!");
 
   // If this was defined using CF_OPTIONS, etc. then ignore the diagnostic.
@@ -198,19 +204,21 @@ static bool ShouldDiagnoseAvailabilityInContext(
 
   if (K == AR_Deprecated) {
     if (const auto *VD = dyn_cast<VarDecl>(OffendingDecl))
-      if (VD->isLocalVarDeclOrParm() && VD->isDeprecated())
+      if (VD->isLocalVarDeclOrParm() && VD->isDeprecatedInAnyTargetPlatform())
         return true;
   }
 
   // Checks if we should emit the availability diagnostic in the context of C.
   auto CheckContext = [&](const Decl *C) {
     if (K == AR_NotYetIntroduced) {
-      if (const AvailabilityAttr *AA = getAttrForPlatform(S.Context, C))
+      if (const AvailabilityAttr *AA =
+              getAttrForPlatform(S.Context, TargetPlatform, C))
         if (AA->getEffectiveIntroduced() >= DeclVersion &&
             AA->getEffectiveEnvironment() == DeclEnv)
           return true;
     } else if (K == AR_Deprecated) {
-      if (C->isDeprecated())
+      if (C->getAvailability(TargetPlatform, TargetPlatformMinVersion) ==
+          AR_Deprecated)
         return true;
     } else if (K == AR_Unavailable) {
       // It is perfectly fine to refer to an 'unavailable' Objective-C method
@@ -224,7 +232,8 @@ static bool ShouldDiagnoseAvailabilityInContext(
       }
     }
 
-    if (C->isUnavailable())
+    if (C->getAvailability(TargetPlatform, TargetPlatformMinVersion) ==
+        AR_Unavailable)
       return true;
     return false;
   };
@@ -257,8 +266,12 @@ static bool ShouldDiagnoseAvailabilityInContext(
 
 static unsigned getAvailabilityDiagnosticKind(
     const ASTContext &Context, const VersionTuple &DeploymentVersion,
-    const VersionTuple &DeclVersion, bool HasMatchingEnv) {
-  const auto &Triple = Context.getTargetInfo().getTriple();
+    const VersionTuple &DeclVersion, bool HasMatchingEnv,
+    bool IsTargetVariantCheck = false) {
+  const auto &Triple =
+      IsTargetVariantCheck
+          ? *Context.getTargetInfo().getDarwinTargetVariantTriple()
+          : Context.getTargetInfo().getTriple();
   VersionTuple ForceAvailabilityFromVersion;
   switch (Triple.getOS()) {
   // For iOS, emit the diagnostic even if -Wunguarded-availability is
@@ -395,129 +408,198 @@ createAttributeInsertion(const NamedDecl *D, const SourceManager &SM,
   return AttributeInsertion::createInsertionBefore(D);
 }
 
-/// Actually emit an availability diagnostic for a reference to an unavailable
-/// decl.
-///
-/// \param Ctx The context that the reference occurred in
-/// \param ReferringDecl The exact declaration that was referenced.
-/// \param OffendingDecl A related decl to \c ReferringDecl that has an
-/// availability attribute corresponding to \c K attached to it. Note that this
-/// may not be the same as ReferringDecl, i.e. if an EnumDecl is annotated and
-/// we refer to a member EnumConstantDecl, ReferringDecl is the EnumConstantDecl
-/// and OffendingDecl is the EnumDecl.
-static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
-                                      Decl *Ctx, const NamedDecl *ReferringDecl,
-                                      const NamedDecl *OffendingDecl,
-                                      StringRef Message,
-                                      ArrayRef<SourceLocation> Locs,
-                                      const ObjCInterfaceDecl *UnknownObjCClass,
-                                      const ObjCPropertyDecl *ObjCProperty,
-                                      bool ObjCPropertyAccess) {
-  // Diagnostics for deprecated or unavailable.
-  unsigned diag, diag_message, diag_fwdclass_message;
-  unsigned diag_available_here = diag::note_availability_specified_here;
-  SourceLocation NoteLocation = OffendingDecl->getLocation();
+/// Target-independent information about an availability diagnostic.
+struct PlatformAgnosticAvailabilityDiagInfo {
+  const NamedDecl *ReferringDecl;
+  ArrayRef<SourceLocation> Locs;
+  const ObjCInterfaceDecl *UnknownObjCClass;
+  bool ObjCPropertyAccess;
+
+  PlatformAgnosticAvailabilityDiagInfo(
+      const NamedDecl *ReferringDecl, ArrayRef<SourceLocation> Locs,
+      const ObjCInterfaceDecl *UnknownObjCClass, bool ObjCPropertyAccess)
+      : ReferringDecl(ReferringDecl), Locs(Locs),
+        UnknownObjCClass(UnknownObjCClass),
+        ObjCPropertyAccess(ObjCPropertyAccess) {}
+};
 
-  // Matches 'diag::note_property_attribute' options.
-  unsigned property_note_select;
+// Target-specific information about an availability diagnostic.
+struct PlatformSpecificAvailabilityDiag {
+  AvailabilityResult AR;
+  const NamedDecl *OffendingDecl;
+  std::string Message;
+  const ObjCPropertyDecl *ObjCProperty;
+  bool IsTargetVariantPlatform;
+
+  PlatformSpecificAvailabilityDiag(AvailabilityResult AR,
+                                   const NamedDecl *OffendingDecl,
+                                   std::string Message,
+                                   const ObjCPropertyDecl *ObjCProperty,
+                                   bool IsTargetVariantPlatform)
+      : AR(AR), OffendingDecl(OffendingDecl), Message(std::move(Message)),
+        ObjCProperty(ObjCProperty),
+        IsTargetVariantPlatform(IsTargetVariantPlatform) {}
+
+  PlatformSpecificAvailabilityDiag(AvailabilityResult AR,
+                                   const NamedDecl *OffendingDecl,
+                                   bool IsTargetVariantPlatform)
+      : AR(AR), OffendingDecl(OffendingDecl), ObjCProperty(nullptr),
+        IsTargetVariantPlatform(IsTargetVariantPlatform) {}
+
+  /// Returns the location of the note which points to the availability
+  /// attribute.
+  SourceLocation getNoteLocation(Sema &S) const {
+    const TargetInfo &TI = S.getASTContext().getTargetInfo();
+    StringRef Platform = IsTargetVariantPlatform ? TI.getTargetVariantPlatform()
+                                                 : TI.getPlatformName();
+    // The declaration can have multiple availability attributes, we are looking
+    // at one of them.
+    const AvailabilityAttr *A =
+        getAttrForPlatform(S.Context, Platform, OffendingDecl);
+    if (A && A->isInherited()) {
+      for (const Decl *Redecl = OffendingDecl->getMostRecentDecl(); Redecl;
+           Redecl = Redecl->getPreviousDecl()) {
+        const AvailabilityAttr *AForRedecl =
+            getAttrForPlatform(S.Context, Platform, Redecl);
+        if (AForRedecl && !AForRedecl->isInherited()) {
+          // If D is a declaration with inherited attributes, the note should
+          // point to the declaration with actual attributes.
+          return Redecl->getLocation();
+        }
+      }
+    }
+    return OffendingDecl->getLocation();
+  }
+};
 
-  // Matches diag::note_availability_specified_here.
-  unsigned available_here_select_kind;
+// Check if we have to emit an availability diagnostic for a particular target.
+static bool ShouldEmitAvailabilityWarning(
+    Sema &S, Decl *Ctx, const PlatformAgnosticAvailabilityDiagInfo &Info,
+    const PlatformSpecificAvailabilityDiag &PlatformInfo) {
+  const TargetInfo &TI = S.getASTContext().getTargetInfo();
+  StringRef Platform = PlatformInfo.IsTargetVariantPlatform
+                           ? TI.getTargetVariantPlatform()
+                           : TI.getPlatformName();
+  VersionTuple PlatformMinVersion =
+      PlatformInfo.IsTargetVariantPlatform
+          ? TI.getTargetVariantPlatformMinVersion()
+          : TI.getPlatformMinVersion();
 
   VersionTuple DeclVersion;
-  const AvailabilityAttr *AA = getAttrForPlatform(S.Context, OffendingDecl);
   const IdentifierInfo *IIEnv = nullptr;
-  if (AA) {
+  if (const AvailabilityAttr *AA =
+          getAttrForPlatform(S.Context, Platform, PlatformInfo.OffendingDecl)) {
     DeclVersion = AA->getEffectiveIntroduced();
     IIEnv = AA->getEffectiveEnvironment();
   }
 
-  if (!ShouldDiagnoseAvailabilityInContext(S, K, DeclVersion, IIEnv, Ctx,
-                                           OffendingDecl))
-    return;
-
-  SourceLocation Loc = Locs.front();
-
-  // The declaration can have multiple availability attributes, we are looking
-  // at one of them.
-  if (AA && AA->isInherited()) {
-    for (const Decl *Redecl = OffendingDecl->getMostRecentDecl(); Redecl;
-         Redecl = Redecl->getPreviousDecl()) {
-      const AvailabilityAttr *AForRedecl =
-          getAttrForPlatform(S.Context, Redecl);
-      if (AForRedecl && !AForRedecl->isInherited()) {
-        // If D is a declaration with inherited attributes, the note should
-        // point to the declaration with actual attributes.
-        NoteLocation = Redecl->getLocation();
-        break;
-      }
-    }
-  }
+  return ShouldDiagnoseAvailabilityInContext(
+      S, PlatformInfo.AR, DeclVersion, IIEnv, Ctx, PlatformInfo.OffendingDecl,
+      Platform, PlatformMinVersion);
+}
 
-  switch (K) {
-  case AR_NotYetIntroduced: {
+/// Emits an availability diagnostic for a not yet introduced declaration.
+static void EmitNotIntroducedAvailabilityWarning(
+    Sema &S, Decl *Ctx, const PlatformAgnosticAvailabilityDiagInfo &Info,
+    const PlatformSpecificAvailabilityDiag &PlatformInfo,
+    const PlatformSpecificAvailabilityDiag *VariantPlatformInfo = nullptr) {
+  assert(PlatformInfo.AR == AR_NotYetIntroduced && "unexpected AR");
+  const TargetInfo &TI = S.Context.getTargetInfo();
+  StringRef TargetPlatform = PlatformInfo.IsTargetVariantPlatform
+                                 ? TI.getTargetVariantPlatform()
+                                 : TI.getPlatformName();
+  const AvailabilityAttr *AA = getAttrForPlatform(
+      S.getASTContext(), TargetPlatform, PlatformInfo.OffendingDecl);
+  assert(AA != nullptr && "expecting valid availability attribute");
+  VersionTuple Introduced = AA->getEffectiveIntroduced();
+  bool EnvironmentMatchesOrNone =
+      hasMatchingEnvironmentOrNone(S.getASTContext(), AA->getEffectiveAttr());
+
+  std::string PlatformName(
+      AvailabilityAttr::getPrettyPlatformName(TargetPlatform));
+  llvm::StringRef TargetEnvironment(
+      llvm::Triple::getEnvironmentTypeName(TI.getTriple().getEnvironment()));
+  llvm::StringRef AttrEnvironment =
+      AA->getEnvironment() ? AA->getEnvironment()->getName() : "";
+  bool UseEnvironment =
+      (!AttrEnvironment.empty() && !TargetEnvironment.empty());
+  unsigned DiagKind;
+  if (VariantPlatformInfo) {
+    DiagKind = diag::warn_zippered_unguarded_availability;
+  } else {
     // We would like to emit the diagnostic even if -Wunguarded-availability is
     // not specified for deployment targets >= to iOS 11 or equivalent or
     // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or
     // later.
-    assert(AA != nullptr && "expecting valid availability attribute");
-    VersionTuple Introduced = AA->getEffectiveIntroduced();
-    bool EnvironmentMatchesOrNone =
-        hasMatchingEnvironmentOrNone(S.getASTContext(), AA->getEffectiveAttr());
-
-    const TargetInfo &TI = S.getASTContext().getTargetInfo();
-    std::string PlatformName(
-        AvailabilityAttr::getPrettyPlatformName(TI.getPlatformName()));
-    llvm::StringRef TargetEnvironment(
-        llvm::Triple::getEnvironmentTypeName(TI.getTriple().getEnvironment()));
-    llvm::StringRef AttrEnvironment =
-        AA->getEnvironment() ? AA->getEnvironment()->getName() : "";
-    bool UseEnvironment =
-        (!AttrEnvironment.empty() && !TargetEnvironment.empty());
-
-    unsigned DiagKind = getAvailabilityDiagnosticKind(
+    // FIXME: Should we look at the target-variant platform here too
+    // potentially?
+    DiagKind = getAvailabilityDiagnosticKind(
         S.Context, S.Context.getTargetInfo().getPlatformMinVersion(),
         Introduced, EnvironmentMatchesOrNone);
+  }
 
-    S.Diag(Loc, DiagKind) << OffendingDecl << PlatformName
-                          << Introduced.getAsString() << UseEnvironment
-                          << TargetEnvironment;
+  VersionTuple VariantIntroduced;
+  if (VariantPlatformInfo)
+    VariantIntroduced =
+        getAttrForPlatform(S.getASTContext(), TI.getTargetVariantPlatform(),
+                           VariantPlatformInfo->OffendingDecl)
+            ->getIntroduced();
+  {
+    auto Diag = S.Diag(Info.Locs[0], DiagKind);
+    Diag << Info.ReferringDecl << PlatformName << Introduced.getAsString();
+    if (!VariantPlatformInfo)
+      Diag << UseEnvironment << TargetEnvironment;
+    else
+      Diag << AvailabilityAttr::getPrettyPlatformName(
+                  TI.getTargetVariantPlatform())
+           << VariantIntroduced.getAsString();
+  }
 
-    S.Diag(OffendingDecl->getLocation(),
+  S.Diag(PlatformInfo.OffendingDecl->getLocation(),
+         diag::note_partial_availability_specified_here)
+      << PlatformInfo.OffendingDecl << PlatformName << Introduced.getAsString()
+      << (PlatformInfo.IsTargetVariantPlatform
+              ? TI.getTargetVariantPlatformMinVersion()
+              : TI.getPlatformMinVersion())
+             .getAsString()
+      << UseEnvironment << AttrEnvironment << TargetEnvironment;
+  if (VariantPlatformInfo &&
+      VariantPlatformInfo->OffendingDecl != PlatformInfo.OffendingDecl)
+    S.Diag(VariantPlatformInfo->OffendingDecl->getLocation(),
            diag::note_partial_availability_specified_here)
-        << OffendingDecl << PlatformName << Introduced.getAsString()
-        << S.Context.getTargetInfo().getPlatformMinVersion().getAsString()
-        << UseEnvironment << AttrEnvironment << TargetEnvironment;
-
-    // Do not offer to silence the warning or fixits for HLSL
-    if (S.getLangOpts().HLSL)
-      return;
+        << VariantPlatformInfo->OffendingDecl
+        << AvailabilityAttr::getPrettyPlatformName(
+               TI.getTargetVariantPlatform())
+        << VariantIntroduced.getAsString()
+        << TI.getTargetVariantPlatformMinVersion().getAsString();
+
+  // Do not offer to silence the warning or fixits for HLSL
+  if (S.getLangOpts().HLSL)
+    return;
 
-    if (const auto *Enclosing = findEnclosingDeclToAnnotate(Ctx)) {
-      if (const auto *TD = dyn_cast<TagDecl>(Enclosing))
-        if (TD->getDeclName().isEmpty()) {
-          S.Diag(TD->getLocation(),
-                 diag::note_decl_unguarded_availability_silence)
-              << /*Anonymous*/ 1 << TD->getKindName();
-          return;
-        }
-      auto FixitNoteDiag =
-          S.Diag(Enclosing->getLocation(),
-                 diag::note_decl_unguarded_availability_silence)
-          << /*Named*/ 0 << Enclosing;
-      // Don't offer a fixit for declarations with availability attributes.
-      if (Enclosing->hasAttr<AvailabilityAttr>())
-        return;
-      Preprocessor &PP = S.getPreprocessor();
-      if (!PP.isMacroDefined("API_AVAILABLE"))
+  if (const auto *Enclosing = findEnclosingDeclToAnnotate(Ctx)) {
+    if (const auto *TD = dyn_cast<TagDecl>(Enclosing))
+      if (TD->getDeclName().isEmpty()) {
+        S.Diag(TD->getLocation(),
+               diag::note_decl_unguarded_availability_silence)
+            << /*Anonymous*/ 1 << TD->getKindName();
         return;
-      std::optional<AttributeInsertion> Insertion = createAttributeInsertion(
-          Enclosing, S.getSourceManager(), S.getLangOpts());
-      if (!Insertion)
-        return;
-      StringRef PlatformName =
-          S.getASTContext().getTargetInfo().getPlatformName();
+      }
+    auto FixitNoteDiag = S.Diag(Enclosing->getLocation(),
+                                diag::note_decl_unguarded_availability_silence)
+                         << /*Named*/ 0 << Enclosing;
+    // Don't offer a fixit for declarations with availability attributes.
+    if (Enclosing->hasAttr<AvailabilityAttr>())
+      return;
+    Preprocessor &PP = S.getPreprocessor();
+    if (!PP.isMacroDefined("API_AVAILABLE"))
+      return;
+    std::optional<AttributeInsertion> Insertion = createAttributeInsertion(
+        Enclosing, S.getSourceManager(), S.getLangOpts());
+    if (!Insertion)
+      return;
 
+    auto GetAvailablePlatform = [&](StringRef PlatformName) -> StringRef {
       // Apple's API_AVAILABLE macro expands roughly like this.
       // API_AVAILABLE(ios(17.0))
       // __attribute__((availability(__API_AVAILABLE_PLATFORM_ios(17.0)))
@@ -536,60 +618,120 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
             return PP.isMacroDefined((MacroPrefix + EquivalentPlatform).str());
           });
       if (AvailablePlatform == EquivalentPlatforms.end())
-        return;
-      std::string Introduced =
-          OffendingDecl->getVersionIntroduced().getAsString();
-      FixitNoteDiag << FixItHint::CreateInsertion(
-          Insertion->Loc,
-          (llvm::Twine(Insertion->Prefix) + "API_AVAILABLE(" +
-           *AvailablePlatform + "(" + Introduced + "))" + Insertion->Suffix)
-              .str());
+        return {};
+      return *AvailablePlatform;
+    };
+
+    StringRef PlatformName = GetAvailablePlatform(TargetPlatform);
+    if (PlatformName.empty())
+      return;
+
+    std::string FixItText;
+    llvm::raw_string_ostream OS(FixItText);
+    OS << Insertion->Prefix << "API_AVAILABLE(" << PlatformName << '('
+       << Introduced.getAsString() << ')';
+    if (VariantPlatformInfo) {
+      StringRef VariantPlatformName =
+          GetAvailablePlatform(TI.getTargetVariantPlatform());
+      if (!VariantPlatformName.empty())
+        OS << ", " << VariantPlatformName << '('
+           << VariantIntroduced.getAsString() << ')';
     }
-    return;
+    OS << ')' << Insertion->Suffix;
+    FixitNoteDiag << FixItHint::CreateInsertion(Insertion->Loc, OS.str());
   }
-  case AR_Deprecated:
-    // Suppress -Wdeprecated-declarations in implicit
-    // functions.
-    if (const auto *FD = dyn_cast_or_null<FunctionDecl>(S.getCurFunctionDecl());
-        FD && FD->isImplicit())
-      return;
+}
 
-    if (ObjCPropertyAccess)
-      diag = diag::warn_property_method_deprecated;
-    else if (S.currentEvaluationContext().IsCaseExpr)
-      diag = diag::warn_deprecated_switch_case;
-    else
-      diag = diag::warn_deprecated;
-
-    diag_message = diag::warn_deprecated_message;
-    diag_fwdclass_message = diag::warn_deprecated_fwdclass_message;
-    property_note_select = /* deprecated */ 0;
-    available_here_select_kind = /* deprecated */ 2;
-    if (const auto *AL = OffendingDecl->getAttr<DeprecatedAttr>())
-      NoteLocation = AL->getLocation();
-    break;
+static void EmitZipperedNotIntroducedAvailabilityWarning(
+    Sema &S, Decl *Ctx, const PlatformAgnosticAvailabilityDiagInfo &Info,
+    const PlatformSpecificAvailabilityDiag &PlatformInfo,
+    const PlatformSpecificAvailabilityDiag &VariantPlatformInfo) {
+  EmitNotIntroducedAvailabilityWarning(S, Ctx, Info, PlatformInfo,
+                                       &VariantPlatformInfo);
+}
+
+// Information about the diagnostic for a 'deprecated' or 'unavailable'.
+struct UnavailableDeprecatedAvailabilityDiag {
+  // Diagnostics for deprecated or unavailable.
+  unsigned diag, diag_message, diag_fwdclass_message;
+  unsigned diag_available_here = diag::note_availability_specified_here;
+
+  // Matches 'diag::note_property_attribute' options.
+  unsigned property_note_select;
+
+  // Matches diag::note_availability_specified_here.
+  unsigned available_here_select_kind;
+
+  // An optional note location for the note.
+  SourceLocation NoteLocation;
 
-  case AR_Unavailable:
-    diag = !ObjCPropertyAccess ? diag::err_unavailable
-                               : diag::err_property_method_unavailable;
+  CharSourceRange UseRange;
+  StringRef Replacement;
+
+  UnavailableDeprecatedAvailabilityDiag(
+      Sema &S, const PlatformAgnosticAvailabilityDiagInfo &Info,
+      const PlatformSpecificAvailabilityDiag &PlatformInfo) {
+    const TargetInfo &TI = S.getASTContext().getTargetInfo();
+    StringRef TargetPlatform = PlatformInfo.IsTargetVariantPlatform
+                                   ? TI.getTargetVariantPlatform()
+                                   : TI.getPlatformName();
+    Replacement = "";
+    UseRange = CharSourceRange();
+    if (PlatformInfo.AR == AR_Deprecated) {
+      if (auto AL = PlatformInfo.OffendingDecl->getAttr<DeprecatedAttr>())
+        Replacement = AL->getReplacement();
+      if (auto AL = getAttrForPlatform(S.Context, TargetPlatform,
+                                       PlatformInfo.OffendingDecl))
+        Replacement = AL->getReplacement();
+
+      if (!Replacement.empty())
+        UseRange = CharSourceRange::getCharRange(
+            Info.Locs[0], S.getLocForEndOfToken(Info.Locs[0]));
+    }
+
+    if (PlatformInfo.AR == AR_Deprecated) {
+      if (Info.ObjCPropertyAccess)
+        diag = diag::warn_property_method_deprecated;
+      else if (S.currentEvaluationContext().IsCaseExpr)
+        diag = diag::warn_deprecated_switch_case;
+      else
+        diag = diag::warn_deprecated;
+
+      diag_message = diag::warn_deprecated_message;
+      diag_fwdclass_message = diag::warn_deprecated_fwdclass_message;
+      property_note_select = /* deprecated */ 0;
+      available_here_select_kind = /* deprecated */ 2;
+      if (const auto *AL =
+              PlatformInfo.OffendingDecl->getAttr<DeprecatedAttr>())
+        NoteLocation = AL->getLocation();
+      else
+        NoteLocation = PlatformInfo.getNoteLocation(S);
+      return;
+    }
+    assert(PlatformInfo.AR == AR_Unavailable && "unexpected AR");
+
+    diag = !Info.ObjCPropertyAccess ? diag::err_unavailable
+                                    : diag::err_property_method_unavailable;
     diag_message = diag::err_unavailable_message;
     diag_fwdclass_message = diag::warn_unavailable_fwdclass_message;
     property_note_select = /* unavailable */ 1;
     available_here_select_kind = /* unavailable */ 0;
+    NoteLocation = PlatformInfo.getNoteLocation(S);
 
-    if (auto AL = OffendingDecl->getAttr<UnavailableAttr>()) {
+    if (auto AL = PlatformInfo.OffendingDecl->getAttr<UnavailableAttr>()) {
       if (AL->isImplicit() && AL->getImplicitReason()) {
         // Most of these failures are due to extra restrictions in ARC;
         // reflect that in the primary diagnostic when applicable.
         auto flagARCError = [&] {
           if (S.getLangOpts().ObjCAutoRefCount &&
               S.getSourceManager().isInSystemHeader(
-                  OffendingDecl->getLocation()))
+                  PlatformInfo.OffendingDecl->getLocation()))
             diag = diag::err_unavailable_in_arc;
         };
 
         switch (AL->getImplicitReason()) {
-        case UnavailableAttr::IR_None: break;
+        case UnavailableAttr::IR_None:
+          break;
 
         case UnavailableAttr::IR_ARCForbiddenType:
           flagARCError();
@@ -620,55 +762,60 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
         }
       }
     }
-    break;
+  }
 
-  case AR_Available:
-    llvm_unreachable("Warning for availability of available declaration?");
+  bool operator==(const UnavailableDeprecatedAvailabilityDiag &Other) {
+    return diag == Other.diag && diag_message == Other.diag_message &&
+           diag_fwdclass_message == Other.diag_fwdclass_message &&
+           diag_available_here == Other.diag_available_here;
   }
+};
+
+static void EmitUnavailableDeprecatedAvailabilityWarning(
+    Sema &S, StringRef Message,
+    const UnavailableDeprecatedAvailabilityDiag &Diag,
+    const PlatformAgnosticAvailabilityDiagInfo &Info,
+    const PlatformSpecificAvailabilityDiag &PlatformInfo,
+    const UnavailableDeprecatedAvailabilityDiag *VariantDiag = nullptr,
+    const PlatformSpecificAvailabilityDiag *VariantPlatformInfo = nullptr) {
+  // Create the fix-it only when the replacement on both platforms matches.
 
   SmallVector<FixItHint, 12> FixIts;
-  if (K == AR_Deprecated) {
-    StringRef Replacement;
-    if (auto AL = OffendingDecl->getAttr<DeprecatedAttr>())
-      Replacement = AL->getReplacement();
-    if (auto AL = getAttrForPlatform(S.Context, OffendingDecl))
-      Replacement = AL->getReplacement();
-
-    CharSourceRange UseRange;
-    if (!Replacement.empty())
-      UseRange =
-          CharSourceRange::getCharRange(Loc, S.getLocForEndOfToken(Loc));
-    if (UseRange.isValid()) {
-      if (const auto *MethodDecl = dyn_cast<ObjCMethodDecl>(ReferringDecl)) {
-        Selector Sel = MethodDecl->getSelector();
-        SmallVector<StringRef, 12> SelectorSlotNames;
-        std::optional<unsigned> NumParams = tryParseObjCMethodName(
-            Replacement, SelectorSlotNames, S.getLangOpts());
-        if (NumParams && *NumParams == Sel.getNumArgs()) {
-          assert(SelectorSlotNames.size() == Locs.size());
-          for (unsigned I = 0; I < Locs.size(); ++I) {
-            if (!Sel.getNameForSlot(I).empty()) {
-              CharSourceRange NameRange = CharSourceRange::getCharRange(
-                  Locs[I], S.getLocForEndOfToken(Locs[I]));
-              FixIts.push_back(FixItHint::CreateReplacement(
-                  NameRange, SelectorSlotNames[I]));
-            } else
-              FixIts.push_back(
-                  FixItHint::CreateInsertion(Locs[I], SelectorSlotNames[I]));
-          }
-        } else
-          FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement));
+  if (Diag.UseRange.isValid() &&
+      (!VariantDiag || Diag.Replacement == VariantDiag->Replacement)) {
+    if (const auto *MethodDecl = dyn_cast<ObjCMethodDecl>(Info.ReferringDecl)) {
+      Selector Sel = MethodDecl->getSelector();
+      SmallVector<StringRef, 12> SelectorSlotNames;
+      std::optional<unsigned> NumParams = tryParseObjCMethodName(
+          Diag.Replacement, SelectorSlotNames, S.getLangOpts());
+      if (NumParams && *NumParams == Sel.getNumArgs()) {
+        assert(SelectorSlotNames.size() == Info.Locs.size());
+        for (unsigned I = 0; I < Info.Locs.size(); ++I) {
+          if (!Sel.getNameForSlot(I).empty()) {
+            CharSourceRange NameRange = CharSourceRange::getCharRange(
+                Info.Locs[I], S.getLocForEndOfToken(Info.Locs[I]));
+            FixIts.push_back(
+                FixItHint::CreateReplacement(NameRange, SelectorSlotNames[I]));
+          } else
+            FixIts.push_back(
+                FixItHint::CreateInsertion(Info.Locs[I], SelectorSlotNames[I]));
+        }
       } else
-        FixIts.push_back(FixItHint::CreateReplacement(UseRange, Replacement));
-    }
+        FixIts.push_back(
+            FixItHint::CreateReplacement(Diag.UseRange, Diag.Replacement));
+    } else
+      FixIts.push_back(
+          FixItHint::CreateReplacement(Diag.UseRange, Diag.Replacement));
   }
 
+  SourceLocation Loc = Info.Locs[0];
+
   // We emit deprecation warning for deprecated specializations
   // when their instantiation stacks originate outside
   // of a system header, even if the diagnostics is suppresed at the
   // point of definition.
   SourceLocation InstantiationLoc =
-      S.getTopMostPointOfInstantiation(ReferringDecl);
+      S.getTopMostPointOfInstantiation(Info.ReferringDecl);
   bool ShouldAllowWarningInSystemHeader =
       InstantiationLoc != Loc &&
       !S.getSourceManager().isInSystemHeader(InstantiationLoc);
@@ -687,23 +834,112 @@ static void DoEmitAvailabilityWarning(Sema &S, AvailabilityResult K,
   } SystemWarningOverrideRAII(S.getDiagnostics(),
                               ShouldAllowWarningInSystemHeader);
 
+  auto EmitObjCPropNote = [&]() {
+    if (PlatformInfo.ObjCProperty)
+      S.Diag(PlatformInfo.ObjCProperty->getLocation(),
+             diag::note_property_attribute)
+          << PlatformInfo.ObjCProperty->getDeclName()
+          << Diag.property_note_select;
+    if (VariantPlatformInfo && VariantPlatformInfo->ObjCProperty)
+      S.Diag(VariantPlatformInfo->ObjCProperty->getLocation(),
+             diag::note_property_attribute)
+          << VariantPlatformInfo->ObjCProperty->getDeclName()
+          << VariantDiag->property_note_select;
+  };
   if (!Message.empty()) {
-    S.Diag(Loc, diag_message) << ReferringDecl << Message << FixIts;
-    if (ObjCProperty)
-      S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute)
-          << ObjCProperty->getDeclName() << property_note_select;
-  } else if (!UnknownObjCClass) {
-    S.Diag(Loc, diag) << ReferringDecl << FixIts;
-    if (ObjCProperty)
-      S.Diag(ObjCProperty->getLocation(), diag::note_property_attribute)
-          << ObjCProperty->getDeclName() << property_note_select;
+    S.Diag(Loc, Diag.diag_message) << Info.ReferringDecl << Message << FixIts;
+    EmitObjCPropNote();
+  } else if (!Info.UnknownObjCClass) {
+    S.Diag(Loc, Diag.diag) << Info.ReferringDecl << FixIts;
+    EmitObjCPropNote();
   } else {
-    S.Diag(Loc, diag_fwdclass_message) << ReferringDecl << FixIts;
-    S.Diag(UnknownObjCClass->getLocation(), diag::note_forward_class);
+    S.Diag(Loc, Diag.diag_fwdclass_message) << Info.ReferringDecl << FixIts;
+    S.Diag(Info.UnknownObjCClass->getLocation(), diag::note_forward_class);
   }
 
-  S.Diag(NoteLocation, diag_available_here)
-    << OffendingDecl << available_here_select_kind;
+  S.Diag(Diag.NoteLocation, Diag.diag_available_here)
+      << PlatformInfo.OffendingDecl << Diag.available_here_select_kind;
+  if (VariantDiag && VariantDiag->NoteLocation != Diag.NoteLocation)
+    S.Diag(VariantDiag->NoteLocation, VariantDiag->diag_available_here)
+        << VariantPlatformInfo->OffendingDecl
+        << VariantDiag->available_here_select_kind;
+}
+
+/// Actually emit an availability diagnostic for a reference to an unavailable
+/// decl.
+///
+/// \param Ctx The context that the reference occurred in
+/// \param ReferringDecl The exact declaration that was referenced.
+/// \param OffendingDecl A related decl to \c ReferringDecl that has an
+/// availability attribute corresponding to \c K attached to it. Note that this
+/// may not be the same as ReferringDecl, i.e. if an EnumDecl is annotated and
+/// we refer to a member EnumConstantDecl, ReferringDecl is the EnumConstantDecl
+/// and OffendingDecl is the EnumDecl.
+static void DoEmitAvailabilityWarning(
+    Sema &S, Decl *Ctx, const PlatformAgnosticAvailabilityDiagInfo &Info,
+    const PlatformSpecificAvailabilityDiag &PlatformInfo) {
+  if (!ShouldEmitAvailabilityWarning(S, Ctx, Info, PlatformInfo))
+    return;
+
+  if (PlatformInfo.AR == AR_NotYetIntroduced)
+    return EmitNotIntroducedAvailabilityWarning(S, Ctx, Info, PlatformInfo);
+  assert(PlatformInfo.AR != AR_Available &&
+         "expected an unavailable/deprecated AR");
+
+  if (PlatformInfo.AR == AR_Deprecated)
+    // Suppress -Wdeprecated-declarations in implicit
+    // functions.
+    if (const auto *FD = dyn_cast_or_null<FunctionDecl>(S.getCurFunctionDecl());
+        FD && FD->isImplicit())
+      return;
+
+  UnavailableDeprecatedAvailabilityDiag Diag(S, Info, PlatformInfo);
+  EmitUnavailableDeprecatedAvailabilityWarning(S, PlatformInfo.Message, Diag,
+                                               Info, PlatformInfo);
+}
+
+static void DoEmitZipperedAvailabilityWarning(
+    Sema &S, Decl *Ctx, const PlatformAgnosticAvailabilityDiagInfo &Info,
+    const PlatformSpecificAvailabilityDiag &PlatformInfo,
+    const PlatformSpecificAvailabilityDiag &VariantPlatformInfo) {
+  assert(PlatformInfo.IsTargetVariantPlatform == false &&
+         VariantPlatformInfo.IsTargetVariantPlatform == true &&
+         "invalid zippered diag");
+  bool EmitP1 = ShouldEmitAvailabilityWarning(S, Ctx, Info, PlatformInfo);
+  bool EmitP2 =
+      ShouldEmitAvailabilityWarning(S, Ctx, Info, VariantPlatformInfo);
+  if (!EmitP1 && !EmitP2)
+    return;
+  if (EmitP1 && EmitP2) {
+    // Check if we can merge the diagnostic into one.
+    if (PlatformInfo.AR == VariantPlatformInfo.AR) {
+      if (PlatformInfo.AR == AR_NotYetIntroduced) {
+        EmitZipperedNotIntroducedAvailabilityWarning(S, Ctx, Info, PlatformInfo,
+                                                     VariantPlatformInfo);
+        return;
+      }
+      UnavailableDeprecatedAvailabilityDiag D1(S, Info, PlatformInfo);
+      UnavailableDeprecatedAvailabilityDiag D2(S, Info, VariantPlatformInfo);
+      if (D1 == D2) {
+        std::string Message;
+        llvm::raw_string_ostream OS(Message);
+        if (!PlatformInfo.Message.empty() &&
+            !VariantPlatformInfo.Message.empty())
+          OS << PlatformInfo.Message << " and " << VariantPlatformInfo.Message;
+        else if (!PlatformInfo.Message.empty())
+          OS << PlatformInfo.Message;
+        else if (!VariantPlatformInfo.Message.empty())
+          OS << VariantPlatformInfo.Message;
+        EmitUnavailableDeprecatedAvailabilityWarning(
+            S, OS.str(), D1, Info, PlatformInfo, &D2, &VariantPlatformInfo);
+        return;
+      }
+    }
+  }
+  if (EmitP1)
+    DoEmitAvailabilityWarning(S, Ctx, Info, PlatformInfo);
+  if (EmitP2)
+    DoEmitAvailabilityWarning(S, Ctx, Info, VariantPlatformInfo);
 }
 
 void Sema::handleDelayedAvailabilityCheck(DelayedDiagnostic &DD, Decl *Ctx) {
@@ -711,34 +947,39 @@ void Sema::handleDelayedAvailabilityCheck(DelayedDiagnostic &DD, Decl *Ctx) {
          "Expected an availability diagnostic here");
 
   DD.Triggered = true;
-  DoEmitAvailabilityWarning(
-      *this, DD.getAvailabilityResult(), Ctx, DD.getAvailabilityReferringDecl(),
-      DD.getAvailabilityOffendingDecl(), DD.getAvailabilityMessage(),
-      DD.getAvailabilitySelectorLocs(), DD.getUnknownObjCClass(),
-      DD.getObjCProperty(), false);
+  PlatformAgnosticAvailabilityDiagInfo Info(
+      DD.getAvailabilityReferringDecl(), DD.getAvailabilitySelectorLocs(),
+      DD.getUnknownObjCClass(), DD.getObjCPropertyAccess());
+  PlatformSpecificAvailabilityDiag PlatformInfo(
+      DD.getAvailabilityResult(), DD.getAvailabilityOffendingDecl(),
+      DD.getAvailabilityMessage().str(), DD.getObjCProperty(),
+      DD.isTargetVariantPlatform());
+  DoEmitAvailabilityWarning(*this, Ctx, Info, PlatformInfo);
 }
 
-static void EmitAvailabilityWarning(Sema &S, AvailabilityResult AR,
-                                    const NamedDecl *ReferringDecl,
-                                    const NamedDecl *OffendingDecl,
-                                    StringRef Message,
-                                    ArrayRef<SourceLocation> Locs,
-                                    const ObjCInterfaceDecl *UnknownObjCClass,
-                                    const ObjCPropertyDecl *ObjCProperty,
-                                    bool ObjCPropertyAccess) {
-  // Delay if we're currently parsing a declaration.
-  if (S.DelayedDiagnostics.shouldDelayDiagnostics()) {
-    S.DelayedDiagnostics.add(
-        DelayedDiagnostic::makeAvailability(
-            AR, Locs, ReferringDecl, OffendingDecl, UnknownObjCClass,
-            ObjCProperty, Message, ObjCPropertyAccess));
-    return;
-  }
+void Sema::handleZipperedDelayedAvailabilityCheck(DelayedDiagnostic &DD,
+                                                  DelayedDiagnostic &VariantDD,
+                                                  Decl *Ctx) {
+  assert(DD.Kind == DelayedDiagnostic::Availability &&
+         VariantDD.Kind == DelayedDiagnostic::Availability &&
+         "Expected an availability diagnostic here");
 
-  Decl *Ctx = cast<Decl>(S.getCurLexicalContext());
-  DoEmitAvailabilityWarning(S, AR, Ctx, ReferringDecl, OffendingDecl,
-                            Message, Locs, UnknownObjCClass, ObjCProperty,
-                            ObjCPropertyAccess);
+  DD.Triggered = true;
+  VariantDD.Triggered = true;
+  PlatformAgnosticAvailabilityDiagInfo Info(
+      DD.getAvailabilityReferringDecl(), DD.getAvailabilitySelectorLocs(),
+      DD.getUnknownObjCClass(), DD.getObjCPropertyAccess());
+  PlatformSpecificAvailabilityDiag PlatformInfo(
+      DD.getAvailabilityResult(), DD.getAvailabilityOffendingDecl(),
+      DD.getAvailabilityMessage().str(), DD.getObjCProperty(),
+      DD.isTargetVariantPlatform());
+  PlatformSpecificAvailabilityDiag VariantPlatformInfo(
+      VariantDD.getAvailabilityResult(),
+      VariantDD.getAvailabilityOffendingDecl(),
+      VariantDD.getAvailabilityMessage().str(), VariantDD.getObjCProperty(),
+      VariantDD.isTargetVariantPlatform());
+  DoEmitZipperedAvailabilityWarning(*this, Ctx, Info, PlatformInfo,
+                                    VariantPlatformInfo);
 }
 
 namespace {
@@ -816,17 +1057,58 @@ class DiagnoseUnguardedAvailability : public DynamicRecursiveASTVisitor {
   Decl *Ctx;
 
   /// Stack of potentially nested 'if (@available(...))'s.
-  SmallVector<VersionTuple, 8> AvailabilityStack;
+  struct ZipperedVersionTuple {
+    std::optional<VersionTuple> Version;
+    std::optional<VersionTuple> VariantVersion;
+
+    static ZipperedVersionTuple make(VersionTuple V) {
+      return {V, std::nullopt};
+    }
+    static ZipperedVersionTuple makeVariant(VersionTuple V) {
+      return {std::nullopt, V};
+    }
+    static ZipperedVersionTuple makeZippered(VersionTuple V,
+                                             VersionTuple VariantV) {
+      return {V, VariantV};
+    }
+  };
+  SmallVector<ZipperedVersionTuple, 8> AvailabilityStack;
   SmallVector<const Stmt *, 16> StmtStack;
 
+  void EmitNotYetIntroducedDiagnostic(
+      NamedDecl *D, SourceRange Range,
+      const PlatformSpecificAvailabilityDiag &PlatformInfo,
+      const PlatformSpecificAvailabilityDiag *VariantPlatformInfo = nullptr);
+
   void DiagnoseDeclAvailability(NamedDecl *D, SourceRange Range,
                                 ObjCInterfaceDecl *ClassReceiver = nullptr);
 
 public:
   DiagnoseUnguardedAvailability(Sema &SemaRef, Decl *Ctx)
       : SemaRef(SemaRef), Ctx(Ctx) {
+    const TargetInfo &TI = SemaRef.Context.getTargetInfo();
     AvailabilityStack.push_back(
-        SemaRef.Context.getTargetInfo().getPlatformMinVersion());
+        TI.hasTargetVariantPlatform()
+            ? ZipperedVersionTuple::makeZippered(
+                  TI.getPlatformMinVersion(),
+                  TI.getTargetVariantPlatformMinVersion())
+            : ZipperedVersionTuple::make(TI.getPlatformMinVersion()));
+  }
+
+  // Returns the OS version for the native/variant platform that's guarded by
+  // the @available
+  // checks.
+  const VersionTuple &getGuardedVersion(bool IsTargetVariantPlatform) {
+    if (!SemaRef.Context.getTargetInfo().hasTargetVariantPlatform())
+      return *AvailabilityStack.back().Version;
+    for (const auto &S : llvm::reverse(AvailabilityStack)) {
+      if (IsTargetVariantPlatform) {
+        if (S.VariantVersion)
+          return *S.VariantVersion;
+      } else if (S.Version)
+        return *S.Version;
+    }
+    llvm_unreachable("missing availability version guard");
   }
 
   bool TraverseStmt(Stmt *S) override {
@@ -882,143 +1164,226 @@ class DiagnoseUnguardedAvailability : public DynamicRecursiveASTVisitor {
   bool VisitTypeLoc(TypeLoc Ty) override;
 };
 
+void DiagnoseUnguardedAvailability::EmitNotYetIntroducedDiagnostic(
+    NamedDecl *D, SourceRange Range,
+    const PlatformSpecificAvailabilityDiag &PlatformInfo,
+    const PlatformSpecificAvailabilityDiag *VariantPlatformInfo) {
+  // We would like to emit the diagnostic even if -Wunguarded-availability is
+  // not specified for deployment targets >= to iOS 11 or equivalent or
+  // for declarations that were introduced in iOS 11 (macOS 10.13, ...) or
+  // later.
+  const TargetInfo &TI = SemaRef.getASTContext().getTargetInfo();
+  StringRef PlatformNameStrRef = PlatformInfo.IsTargetVariantPlatform
+                                     ? TI.getTargetVariantPlatform()
+                                     : TI.getPlatformName();
+  auto *AA = getAttrForPlatform(SemaRef.getASTContext(), PlatformNameStrRef,
+                                PlatformInfo.OffendingDecl);
+  VersionTuple Introduced = AA->getEffectiveIntroduced();
+
+  std::string PlatformName(
+      AvailabilityAttr::getPrettyPlatformName(PlatformNameStrRef));
+  llvm::StringRef TargetEnvironment(TI.getTriple().getEnvironmentName());
+  bool EnvironmentMatchesOrNone = hasMatchingEnvironmentOrNone(
+      SemaRef.getASTContext(), AA->getEffectiveAttr());
+  llvm::StringRef AttrEnvironment =
+      AA->getEnvironment() ? AA->getEnvironment()->getName() : "";
+  bool UseEnvironment =
+      (!AttrEnvironment.empty() && !TargetEnvironment.empty());
+
+  unsigned DiagKind =
+      VariantPlatformInfo
+          ? diag::warn_zippered_unguarded_availability
+          : getAvailabilityDiagnosticKind(
+                SemaRef.Context,
+                PlatformInfo.IsTargetVariantPlatform
+                    ? TI.getTargetVariantPlatformMinVersion()
+                    : TI.getPlatformMinVersion(),
+                Introduced, EnvironmentMatchesOrNone,
+                /*IsTargetVariantCheck=*/PlatformInfo.IsTargetVariantPlatform);
+
+  VersionTuple VariantIntroduced;
+  if (VariantPlatformInfo)
+    VariantIntroduced = getAttrForPlatform(SemaRef.getASTContext(),
+                                           TI.getTargetVariantPlatform(),
+                                           VariantPlatformInfo->OffendingDecl)
+                            ->getIntroduced();
+  {
+    auto Diag = SemaRef.Diag(Range.getBegin(), DiagKind)
+                << Range << D << PlatformName << Introduced.getAsString();
+    if (!VariantPlatformInfo)
+      Diag << UseEnvironment << TargetEnvironment;
+    else
+      Diag << AvailabilityAttr::getPrettyPlatformName(
+                  TI.getTargetVariantPlatform())
+           << VariantIntroduced.getAsString();
+  }
+
+  SemaRef.Diag(PlatformInfo.OffendingDecl->getLocation(),
+               diag::note_partial_availability_specified_here)
+      << PlatformInfo.OffendingDecl << PlatformName << Introduced.getAsString()
+      << (PlatformInfo.IsTargetVariantPlatform
+              ? TI.getTargetVariantPlatformMinVersion()
+              : TI.getPlatformMinVersion())
+             .getAsString()
+      << UseEnvironment << AttrEnvironment << TargetEnvironment;
+  if (VariantPlatformInfo &&
+      PlatformInfo.OffendingDecl != VariantPlatformInfo->OffendingDecl)
+    SemaRef.Diag(VariantPlatformInfo->OffendingDecl->getLocation(),
+                 diag::note_partial_availability_specified_here)
+        << VariantPlatformInfo->OffendingDecl
+        << AvailabilityAttr::getPrettyPlatformName(
+               TI.getTargetVariantPlatform())
+        << VariantIntroduced.getAsString()
+        << TI.getTargetVariantPlatformMinVersion().getAsString();
+
+  // Do not offer to silence the warning or fixits for HLSL
+  if (SemaRef.getLangOpts().HLSL)
+    return;
+
+  auto FixitDiag =
+      SemaRef.Diag(Range.getBegin(), diag::note_unguarded_available_silence)
+      << Range << D
+      << (SemaRef.getLangOpts().ObjC ? /*@available*/ 0
+                                     : /*__builtin_available*/ 1);
+
+  // Find the statement which should be enclosed in the if @available check.
+  if (StmtStack.empty())
+    return;
+  const Stmt *StmtOfUse = StmtStack.back();
+  const CompoundStmt *Scope = nullptr;
+  for (const Stmt *S : llvm::reverse(StmtStack)) {
+    if (const auto *CS = dyn_cast<CompoundStmt>(S)) {
+      Scope = CS;
+      break;
+    }
+    if (isBodyLikeChildStmt(StmtOfUse, S)) {
+      // The declaration won't be seen outside of the statement, so we don't
+      // have to wrap the uses of any declared variables in if (@available).
+      // Therefore we can avoid setting Scope here.
+      break;
+    }
+    StmtOfUse = S;
+  }
+  const Stmt *LastStmtOfUse = nullptr;
+  if (isa<DeclStmt>(StmtOfUse) && Scope) {
+    for (const Decl *D : cast<DeclStmt>(StmtOfUse)->decls()) {
+      if (StmtUSEFinder::isContained(StmtStack.back(), D)) {
+        LastStmtOfUse = LastDeclUSEFinder::findLastStmtThatUsesDecl(D, Scope);
+        break;
+      }
+    }
+  }
+
+  const SourceManager &SM = SemaRef.getSourceManager();
+  SourceLocation IfInsertionLoc = SM.getExpansionLoc(StmtOfUse->getBeginLoc());
+  SourceLocation StmtEndLoc =
+      SM.getExpansionRange(
+            (LastStmtOfUse ? LastStmtOfUse : StmtOfUse)->getEndLoc())
+          .getEnd();
+  if (SM.getFileID(IfInsertionLoc) != SM.getFileID(StmtEndLoc))
+    return;
+
+  StringRef Indentation = Lexer::getIndentationForLine(IfInsertionLoc, SM);
+  const char *ExtraIndentation = "    ";
+  std::string FixItString;
+  llvm::raw_string_ostream FixItOS(FixItString);
+  StringRef FixItPlatformName;
+  VersionTuple FixItVersion;
+
+  if (AA->getInferredAttr()) {
+    FixItPlatformName = "anyAppleOS";
+    FixItVersion = AA->getIntroduced();
+  } else {
+    FixItPlatformName =
+        AvailabilityAttr::getPlatformNameSourceSpelling(PlatformName);
+    FixItVersion = Introduced;
+  }
+  FixItOS << "if ("
+          << (SemaRef.getLangOpts().ObjC ? "@available" : "__builtin_available")
+          << "(" << FixItPlatformName << " " << FixItVersion.getAsString();
+  if (VariantPlatformInfo)
+    FixItOS << ", "
+            << AvailabilityAttr::getPlatformNameSourceSpelling(
+                   TI.getTargetVariantPlatform())
+            << " " << VariantIntroduced.getAsString();
+  FixItOS << ", *)) {\n" << Indentation << ExtraIndentation;
+  FixitDiag << FixItHint::CreateInsertion(IfInsertionLoc, FixItOS.str());
+  SourceLocation ElseInsertionLoc = Lexer::findLocationAfterToken(
+      StmtEndLoc, tok::semi, SM, SemaRef.getLangOpts(),
+      /*SkipTrailingWhitespaceAndNewLine=*/false);
+  if (ElseInsertionLoc.isInvalid())
+    ElseInsertionLoc =
+        Lexer::getLocForEndOfToken(StmtEndLoc, 0, SM, SemaRef.getLangOpts());
+  FixItOS.str().clear();
+  FixItOS << "\n"
+          << Indentation << "} else {\n"
+          << Indentation << ExtraIndentation
+          << "// Fallback on earlier versions\n"
+          << Indentation << "}";
+  FixitDiag << FixItHint::CreateInsertion(ElseInsertionLoc, FixItOS.str());
+}
+
 void DiagnoseUnguardedAvailability::DiagnoseDeclAvailability(
     NamedDecl *D, SourceRange Range, ObjCInterfaceDecl *ReceiverClass) {
-  AvailabilityResult Result;
-  const NamedDecl *OffendingDecl;
-  std::tie(Result, OffendingDecl) =
-      SemaRef.ShouldDiagnoseAvailabilityOfDecl(D, nullptr, ReceiverClass);
-  if (Result != AR_Available) {
+  const TargetInfo &TI = SemaRef.getASTContext().getTargetInfo();
+
+  auto CreatePlatformDiagInfo = [&](bool IsTargetVariantPlatform)
+      -> std::optional<PlatformSpecificAvailabilityDiag> {
+    AvailabilityResult Result;
+    const NamedDecl *OffendingDecl;
+    StringRef PlatformName = IsTargetVariantPlatform
+                                 ? TI.getTargetVariantPlatform()
+                                 : TI.getPlatformName();
+    const VersionTuple &PlatformMinVersion =
+        IsTargetVariantPlatform ? TI.getTargetVariantPlatformMinVersion()
+                                : TI.getPlatformMinVersion();
+    std::tie(Result, OffendingDecl) = SemaRef.ShouldDiagnoseAvailabilityOfDecl(
+        D, PlatformName, PlatformMinVersion, nullptr, ReceiverClass);
     // All other diagnostic kinds have already been handled in
     // DiagnoseAvailabilityOfDecl.
     if (Result != AR_NotYetIntroduced)
-      return;
+      return std::nullopt;
 
-    const AvailabilityAttr *AA =
-      getAttrForPlatform(SemaRef.getASTContext(), OffendingDecl);
+    const AvailabilityAttr *AA = getAttrForPlatform(
+        SemaRef.getASTContext(), PlatformName, OffendingDecl);
     assert(AA != nullptr && "expecting valid availability attribute");
     bool EnvironmentMatchesOrNone = hasMatchingEnvironmentOrNone(
         SemaRef.getASTContext(), AA->getEffectiveAttr());
     VersionTuple Introduced = AA->getEffectiveIntroduced();
 
-    if (EnvironmentMatchesOrNone && AvailabilityStack.back() >= Introduced)
-      return;
+    if (EnvironmentMatchesOrNone &&
+        getGuardedVersion(IsTargetVariantPlatform) >= Introduced)
+      return std::nullopt;
 
     // If the context of this function is less available than D, we should not
     // emit a diagnostic.
-    if (!ShouldDiagnoseAvailabilityInContext(SemaRef, Result, Introduced,
-                                             AA->getEffectiveEnvironment(), Ctx,
-                                             OffendingDecl))
-      return;
-
-    const TargetInfo &TI = SemaRef.getASTContext().getTargetInfo();
-    std::string PlatformName(
-        AvailabilityAttr::getPrettyPlatformName(TI.getPlatformName()));
-    llvm::StringRef TargetEnvironment(TI.getTriple().getEnvironmentName());
-    llvm::StringRef AttrEnvironment =
-        AA->getEnvironment() ? AA->getEnvironment()->getName() : "";
-    bool UseEnvironment =
-        (!AttrEnvironment.empty() && !TargetEnvironment.empty());
-
-    unsigned DiagKind = getAvailabilityDiagnosticKind(
-        SemaRef.Context,
-        SemaRef.Context.getTargetInfo().getPlatformMinVersion(), Introduced,
-        EnvironmentMatchesOrNone);
-
-    SemaRef.Diag(Range.getBegin(), DiagKind)
-        << Range << D << PlatformName << Introduced.getAsString()
-        << UseEnvironment << TargetEnvironment;
-
-    SemaRef.Diag(OffendingDecl->getLocation(),
-                 diag::note_partial_availability_specified_here)
-        << OffendingDecl << PlatformName << Introduced.getAsString()
-        << SemaRef.Context.getTargetInfo().getPlatformMinVersion().getAsString()
-        << UseEnvironment << AttrEnvironment << TargetEnvironment;
-
-    // Do not offer to silence the warning or fixits for HLSL
-    if (SemaRef.getLangOpts().HLSL)
-      return;
-
-    auto FixitDiag =
-        SemaRef.Diag(Range.getBegin(), diag::note_unguarded_available_silence)
-        << Range << D
-        << (SemaRef.getLangOpts().ObjC ? /*@available*/ 0
-                                       : /*__builtin_available*/ 1);
-
-    // Find the statement which should be enclosed in the if @available check.
-    if (StmtStack.empty())
-      return;
-    const Stmt *StmtOfUse = StmtStack.back();
-    const CompoundStmt *Scope = nullptr;
-    for (const Stmt *S : llvm::reverse(StmtStack)) {
-      if (const auto *CS = dyn_cast<CompoundStmt>(S)) {
-        Scope = CS;
-        break;
-      }
-      if (isBodyLikeChildStmt(StmtOfUse, S)) {
-        // The declaration won't be seen outside of the statement, so we don't
-        // have to wrap the uses of any declared variables in if (@available).
-        // Therefore we can avoid setting Scope here.
-        break;
-      }
-      StmtOfUse = S;
-    }
-    const Stmt *LastStmtOfUse = nullptr;
-    if (isa<DeclStmt>(StmtOfUse) && Scope) {
-      for (const Decl *D : cast<DeclStmt>(StmtOfUse)->decls()) {
-        if (StmtUSEFinder::isContained(StmtStack.back(), D)) {
-          LastStmtOfUse = LastDeclUSEFinder::findLastStmtThatUsesDecl(D, Scope);
-          break;
-        }
-      }
-    }
+    if (!ShouldDiagnoseAvailabilityInContext(
+            SemaRef, Result, Introduced, AA->getEffectiveEnvironment(), Ctx,
+            OffendingDecl, PlatformName, PlatformMinVersion))
+      return std::nullopt;
+    return PlatformSpecificAvailabilityDiag(Result, OffendingDecl,
+                                            IsTargetVariantPlatform);
+  };
 
-    const SourceManager &SM = SemaRef.getSourceManager();
-    SourceLocation IfInsertionLoc =
-        SM.getExpansionLoc(StmtOfUse->getBeginLoc());
-    SourceLocation StmtEndLoc =
-        SM.getExpansionRange(
-              (LastStmtOfUse ? LastStmtOfUse : StmtOfUse)->getEndLoc())
-            .getEnd();
-    if (SM.getFileID(IfInsertionLoc) != SM.getFileID(StmtEndLoc))
-      return;
+  std::optional<PlatformSpecificAvailabilityDiag> PlatformInfo =
+      CreatePlatformDiagInfo(/*IsTargetVariantPlatform=*/false);
+  std::optional<PlatformSpecificAvailabilityDiag> VariantPlatformInfo;
+  if (TI.hasTargetVariantPlatform())
+    VariantPlatformInfo =
+        CreatePlatformDiagInfo(/*IsTargetVariantPlatform=*/true);
+  if (!PlatformInfo && !VariantPlatformInfo)
+    return;
 
-    StringRef Indentation = Lexer::getIndentationForLine(IfInsertionLoc, SM);
-    const char *ExtraIndentation = "    ";
-    std::string FixItString;
-    llvm::raw_string_ostream FixItOS(FixItString);
-    StringRef FixItPlatformName;
-    VersionTuple FixItVersion;
-
-    if (AA->getInferredAttr()) {
-      FixItPlatformName = "anyAppleOS";
-      FixItVersion = AA->getIntroduced();
-    } else {
-      FixItPlatformName = AvailabilityAttr::getPlatformNameSourceSpelling(
-          SemaRef.getASTContext().getTargetInfo().getPlatformName());
-      FixItVersion = AA->getEffectiveIntroduced();
-    }
-    FixItOS << "if ("
-            << (SemaRef.getLangOpts().ObjC ? "@available"
-                                           : "__builtin_available")
-            << "(" << FixItPlatformName << " " << FixItVersion.getAsString()
-            << ", *)) {\n"
-            << Indentation << ExtraIndentation;
-    FixitDiag << FixItHint::CreateInsertion(IfInsertionLoc, FixItOS.str());
-    SourceLocation ElseInsertionLoc = Lexer::findLocationAfterToken(
-        StmtEndLoc, tok::semi, SM, SemaRef.getLangOpts(),
-        /*SkipTrailingWhitespaceAndNewLine=*/false);
-    if (ElseInsertionLoc.isInvalid())
-      ElseInsertionLoc =
-          Lexer::getLocForEndOfToken(StmtEndLoc, 0, SM, SemaRef.getLangOpts());
-    FixItOS.str().clear();
-    FixItOS << "\n"
-            << Indentation << "} else {\n"
-            << Indentation << ExtraIndentation
-            << "// Fallback on earlier versions\n"
-            << Indentation << "}";
-    FixitDiag << FixItHint::CreateInsertion(ElseInsertionLoc, FixItOS.str());
+  if (PlatformInfo) {
+    if (VariantPlatformInfo)
+      EmitNotYetIntroducedDiagnostic(D, Range, *PlatformInfo,
+                                     &*VariantPlatformInfo);
+    else
+      EmitNotYetIntroducedDiagnostic(D, Range, *PlatformInfo);
+    return;
   }
+  if (VariantPlatformInfo)
+    EmitNotYetIntroducedDiagnostic(D, Range, *VariantPlatformInfo);
 }
 
 bool DiagnoseUnguardedAvailability::VisitTypeLoc(TypeLoc Ty) {
@@ -1068,16 +1433,34 @@ ExtractedAvailabilityExpr extractAvailabilityExpr(const Expr *IfCond) {
 }
 
 bool DiagnoseUnguardedAvailability::TraverseIfStmt(IfStmt *If) {
+  VersionTuple CondVersion;
+  VersionTuple VariantCondVersion;
+  if (auto *E = dyn_cast<ObjCAvailabilityCheckExpr>(If->getCond())) {
+    CondVersion = E->getVersion();
+    VariantCondVersion = E->getVariantVersion();
+
+    bool IsStar = CondVersion.empty() && VariantCondVersion.empty();
+    bool IsCondRedundant =
+        CondVersion <= getGuardedVersion(/*IsTargetVariantPlatform=*/false);
+    bool IsVariantCondRedundant =
+        VariantCondVersion.empty() ||
+        VariantCondVersion <=
+            getGuardedVersion(/*IsTargetVariantPlatform=*/true);
+    // If we're using the '*' case here or if this check is redundant, then we
+    // use the enclosing version to check both branches.
+    if (IsStar || (IsCondRedundant && IsVariantCondRedundant))
+      return TraverseStmt(If->getThen()) && TraverseStmt(If->getElse());
+  }
+
   ExtractedAvailabilityExpr IfCond = extractAvailabilityExpr(If->getCond());
   if (!IfCond.E) {
     // This isn't an availability checking 'if', we can just continue.
     return DynamicRecursiveASTVisitor::TraverseIfStmt(If);
   }
-
-  VersionTuple CondVersion = IfCond.E->getVersion();
+  CondVersion = IfCond.E->getVersion();
   // If we're using the '*' case here or if this check is redundant, then we
   // use the enclosing version to check both branches.
-  if (CondVersion.empty() || CondVersion <= AvailabilityStack.back()) {
+  if (CondVersion.empty() || CondVersion <= AvailabilityStack.back().Version) {
     return TraverseStmt(If->getThen()) && TraverseStmt(If->getElse());
   }
 
@@ -1086,14 +1469,18 @@ bool DiagnoseUnguardedAvailability::TraverseIfStmt(IfStmt *If) {
   if (IfCond.isNegated) {
     std::swap(Guarded, Unguarded);
   }
-
-  AvailabilityStack.push_back(CondVersion);
+  AvailabilityStack.push_back(ZipperedVersionTuple{
+      CondVersion.empty() ? std::nullopt
+                          : std::optional<VersionTuple>(CondVersion),
+      VariantCondVersion.empty()
+          ? std::nullopt
+          : std::optional<VersionTuple>(VariantCondVersion)});
   bool ShouldContinue = TraverseStmt(Guarded);
+
   AvailabilityStack.pop_back();
 
   return ShouldContinue && TraverseStmt(Unguarded);
 }
-
 } // end anonymous namespace
 
 void Sema::DiagnoseUnguardedAvailabilityViolations(Decl *D) {
@@ -1133,40 +1520,91 @@ void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D,
                                       bool ObjCPropertyAccess,
                                       bool AvoidPartialAvailabilityChecks,
                                       ObjCInterfaceDecl *ClassReceiver) {
+  auto CreateAvailabilityDiagnostic = [&](bool IsTargetVariantPlatform)
+      -> std::optional<PlatformSpecificAvailabilityDiag> {
+    std::string Message;
+    AvailabilityResult Result;
+    const NamedDecl *OffendingDecl;
+    // See if this declaration is unavailable, deprecated, or partial.
+    const TargetInfo &TI = getASTContext().getTargetInfo();
+    StringRef Platform = IsTargetVariantPlatform ? TI.getTargetVariantPlatform()
+                                                 : TI.getPlatformName();
+    VersionTuple VT = IsTargetVariantPlatform
+                          ? TI.getTargetVariantPlatformMinVersion()
+                          : TI.getPlatformMinVersion();
+    std::tie(Result, OffendingDecl) = ShouldDiagnoseAvailabilityOfDecl(
+        D, Platform, VT, &Message, ClassReceiver);
+    if (Result == AR_Available)
+      return std::nullopt;
 
-  std::string Message;
-  AvailabilityResult Result;
-  const NamedDecl* OffendingDecl;
-  // See if this declaration is unavailable, deprecated, or partial.
-  std::tie(Result, OffendingDecl) =
-      ShouldDiagnoseAvailabilityOfDecl(D, &Message, ClassReceiver);
-  if (Result == AR_Available)
-    return;
+    if (Result == AR_NotYetIntroduced) {
+      if (AvoidPartialAvailabilityChecks)
+        return std::nullopt;
 
-  if (Result == AR_NotYetIntroduced) {
-    if (AvoidPartialAvailabilityChecks)
-      return;
+      // We need to know the @available context in the current function to
+      // diagnose this use, let DiagnoseUnguardedAvailabilityViolations do that
+      // when we're done parsing the current function.
+      if (FunctionScopeInfo *Context = getCurFunctionAvailabilityContext()) {
+        Context->HasPotentialAvailabilityViolations = true;
+        return std::nullopt;
+      }
+    }
 
-    // We need to know the @available context in the current function to
-    // diagnose this use, let DiagnoseUnguardedAvailabilityViolations do that
-    // when we're done parsing the current function.
-    if (FunctionScopeInfo *Context = getCurFunctionAvailabilityContext()) {
-      Context->HasPotentialAvailabilityViolations = true;
-      return;
+    const ObjCPropertyDecl *ObjCPDecl = nullptr;
+    if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
+      if (const ObjCPropertyDecl *PD = MD->findPropertyDecl()) {
+        AvailabilityResult PDeclResult =
+            PD->getAvailability(Platform, VT, nullptr);
+        if (PDeclResult == Result)
+          ObjCPDecl = PD;
+      }
     }
+
+    return PlatformSpecificAvailabilityDiag(Result, OffendingDecl,
+                                            std::move(Message), ObjCPDecl,
+                                            IsTargetVariantPlatform);
+  };
+  std::optional<PlatformSpecificAvailabilityDiag> TargetAvailabilityDiag =
+      CreateAvailabilityDiagnostic(/*IsTargetVariantPlatform=*/false);
+  std::optional<PlatformSpecificAvailabilityDiag> TargetVariantAvailabilityDiag;
+  if (getASTContext().getTargetInfo().hasTargetVariantPlatform())
+    TargetVariantAvailabilityDiag =
+        CreateAvailabilityDiagnostic(/*IsTargetVariantPlatform=*/true);
+  if (!TargetAvailabilityDiag && !TargetVariantAvailabilityDiag)
+    return;
+
+  PlatformAgnosticAvailabilityDiagInfo Info(D, Locs, UnknownObjCClass,
+                                            ObjCPropertyAccess);
+
+  // Delay if we're currently parsing a declaration.
+  if (DelayedDiagnostics.shouldDelayDiagnostics()) {
+    auto MakeDiag = [&](const PlatformSpecificAvailabilityDiag &PlatformInfo)
+        -> DelayedDiagnostic {
+      return DelayedDiagnostic::makeAvailability(
+          PlatformInfo.AR, Info.Locs, Info.ReferringDecl,
+          PlatformInfo.OffendingDecl, Info.UnknownObjCClass,
+          PlatformInfo.ObjCProperty, PlatformInfo.Message,
+          Info.ObjCPropertyAccess, PlatformInfo.IsTargetVariantPlatform);
+    };
+    if (TargetAvailabilityDiag)
+      DelayedDiagnostics.add(MakeDiag(*TargetAvailabilityDiag));
+    if (TargetVariantAvailabilityDiag)
+      DelayedDiagnostics.add(MakeDiag(*TargetVariantAvailabilityDiag));
+    return;
   }
 
-  const ObjCPropertyDecl *ObjCPDecl = nullptr;
-  if (const auto *MD = dyn_cast<ObjCMethodDecl>(D)) {
-    if (const ObjCPropertyDecl *PD = MD->findPropertyDecl()) {
-      AvailabilityResult PDeclResult = PD->getAvailability(nullptr);
-      if (PDeclResult == Result)
-        ObjCPDecl = PD;
+  Decl *Ctx = cast<Decl>(getCurLexicalContext());
+  if (TargetAvailabilityDiag) {
+    if (TargetVariantAvailabilityDiag) {
+      DoEmitZipperedAvailabilityWarning(*this, Ctx, Info,
+                                        *TargetAvailabilityDiag,
+                                        *TargetVariantAvailabilityDiag);
+      return;
     }
+    DoEmitAvailabilityWarning(*this, Ctx, Info, *TargetAvailabilityDiag);
   }
-
-  EmitAvailabilityWarning(*this, Result, D, OffendingDecl, Message, Locs,
-                          UnknownObjCClass, ObjCPDecl, ObjCPropertyAccess);
+  if (TargetVariantAvailabilityDiag)
+    DoEmitAvailabilityWarning(*this, Ctx, Info, *TargetVariantAvailabilityDiag);
 }
 
 void Sema::DiagnoseAvailabilityOfDecl(NamedDecl *D,
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index a99fcb56d1138..2181617de7a6f 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -17205,7 +17205,8 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, bool IsInstantiation,
         };
         // Don't issue this warning for unavailable inits or direct subclasses
         // of NSObject.
-        if (!MD->isUnavailable() && !superIsNSObject(MD)) {
+        if (!MD->isUnavailabledForAllTargetPlatforms() &&
+            !superIsNSObject(MD)) {
           Diag(MD->getLocation(),
                diag::warn_objc_designated_init_missing_super_call);
           Diag(InitMethod->getLocation(),
@@ -17215,7 +17216,7 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, bool IsInstantiation,
       }
       if (FSI->ObjCWarnForNoInitDelegation) {
         // Don't issue this warning for unavailable inits.
-        if (!MD->isUnavailable())
+        if (!MD->isUnavailabledForAllTargetPlatforms())
           Diag(MD->getLocation(),
                diag::warn_objc_secondary_init_missing_init_call);
         FSI->ObjCWarnForNoInitDelegation = false;
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index a61fc54ade757..d56f136889d2e 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -3012,9 +3012,14 @@ static void handleAvailabilityAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
       if (NewAttr)
         D->addAttr(NewAttr);
     }
-  } else if (S.Context.getTargetInfo().getTriple().getOS() ==
-                 llvm::Triple::IOS &&
-             S.Context.getTargetInfo().getTriple().isMacCatalystEnvironment()) {
+  } else if ((S.Context.getTargetInfo().getTriple().getOS() ==
+                  llvm::Triple::IOS &&
+              S.Context.getTargetInfo()
+                  .getTriple()
+                  .isMacCatalystEnvironment()) ||
+             (S.Context.getTargetInfo().hasTargetVariantPlatform() &&
+              S.Context.getTargetInfo().getTargetVariantPlatform() ==
+                  "maccatalyst")) {
     auto GetSDKInfo = [&]() {
       return S.getDarwinSDKInfoForAvailabilityChecking(AL.getRange().getBegin(),
                                                        "macOS");
@@ -9015,8 +9020,20 @@ void Sema::PopParsingDeclaration(ParsingDeclState state, Decl *decl) {
       case DelayedDiagnostic::Availability:
         // Don't bother giving deprecation/unavailable diagnostics if
         // the decl is invalid.
-        if (!decl->isInvalidDecl())
+        if (!decl->isInvalidDecl()) {
+          if (!diag.isTargetVariantPlatform() && (i + 1) != pool->pool_end()) {
+            DelayedDiagnostic &next = const_cast<DelayedDiagnostic &>(*(i + 1));
+            if (!next.Triggered && next.isTargetVariantPlatform() &&
+                diag.Loc == next.Loc &&
+                diag.getAvailabilityReferringDecl() ==
+                    next.getAvailabilityReferringDecl()) {
+              handleZipperedDelayedAvailabilityCheck(diag, next, decl);
+              ++i;
+              break;
+            }
+          }
           handleDelayedAvailabilityCheck(diag, decl);
+        }
         break;
 
       case DelayedDiagnostic::Access:
diff --git a/clang/lib/Sema/SemaDeclObjC.cpp b/clang/lib/Sema/SemaDeclObjC.cpp
index 04ba528098af4..51da6c624972a 100644
--- a/clang/lib/Sema/SemaDeclObjC.cpp
+++ b/clang/lib/Sema/SemaDeclObjC.cpp
@@ -290,10 +290,13 @@ static void DiagnoseObjCImplementedDeprecations(Sema &S, const NamedDecl *ND,
   if (!ND)
     return;
   bool IsCategory = false;
+  // FIXME: Implement support for TVT.
+  StringRef Platform = S.getASTContext().getTargetInfo().getPlatformName();
+  VersionTuple PlatformVersionMin =
+      S.getASTContext().getTargetInfo().getPlatformMinVersion();
   StringRef RealizedPlatform;
   AvailabilityResult Availability = ND->getAvailability(
-      /*Message=*/nullptr, /*EnclosingVersion=*/VersionTuple(),
-      &RealizedPlatform);
+      Platform, PlatformVersionMin, /*Message=*/nullptr, &RealizedPlatform);
   if (Availability != AR_Deprecated) {
     if (isa<ObjCMethodDecl>(ND)) {
       if (Availability != AR_Unavailable)
@@ -310,7 +313,8 @@ static void DiagnoseObjCImplementedDeprecations(Sema &S, const NamedDecl *ND,
       return;
     }
     if (const auto *CD = dyn_cast<ObjCCategoryDecl>(ND)) {
-      if (!CD->getClassInterface()->isDeprecated())
+      if (CD->getClassInterface()->getAvailability(
+              Platform, PlatformVersionMin) != AR_Deprecated)
         return;
       ND = CD->getClassInterface();
       IsCategory = true;
@@ -2243,16 +2247,20 @@ void SemaObjC::CheckImplementationIvars(ObjCImplementationDecl *ImpDecl,
     Diag(IVI->getLocation(), diag::err_inconsistent_ivar_count);
 }
 
-static bool shouldWarnUndefinedMethod(const ObjCMethodDecl *M) {
+static bool shouldWarnUndefinedMethod(Sema &S, const ObjCMethodDecl *M) {
   // No point warning no definition of method which is 'unavailable'.
-  return M->getAvailability() != AR_Unavailable;
+  // FIXME: We can warn on a definition of partially unavailable method.
+  return M->getAvailability(
+             S.getASTContext().getTargetInfo().getPlatformName(),
+             S.getASTContext().getTargetInfo().getPlatformMinVersion()) !=
+         AR_Unavailable;
 }
 
 static void WarnUndefinedMethod(Sema &S, ObjCImplDecl *Impl,
                                 ObjCMethodDecl *method, bool &IncompleteImpl,
                                 unsigned DiagID,
                                 NamedDecl *NeededFor = nullptr) {
-  if (!shouldWarnUndefinedMethod(method))
+  if (!shouldWarnUndefinedMethod(S, method))
     return;
 
   // FIXME: For now ignore 'IncompleteImpl'.
@@ -3372,13 +3380,18 @@ void SemaObjC::addMethodToGlobalList(ObjCMethodList *List,
 
       // For methods with the same declaration, the one that is deprecated
       // should be put in the front for better diagnostics.
-      if (Method->isDeprecated() && SameDeclaration &&
-          !ListWithSameDeclaration && !List->getMethod()->isDeprecated())
+      if (Method->isDeprecatedInAnyTargetPlatform() && SameDeclaration &&
+          !ListWithSameDeclaration &&
+          !List->getMethod()->isDeprecatedInAnyTargetPlatform())
         ListWithSameDeclaration = List;
 
-      if (Method->isUnavailable() && SameDeclaration &&
+      // FIXME: What about TVT?
+      if (Method->isUnavailabledForAllTargetPlatforms() && SameDeclaration &&
           !ListWithSameDeclaration &&
-          List->getMethod()->getAvailability() < AR_Deprecated)
+          List->getMethod()->getAvailability(
+              getASTContext().getTargetInfo().getPlatformName(),
+              getASTContext().getTargetInfo().getPlatformMinVersion()) <
+              AR_Deprecated)
         ListWithSameDeclaration = List;
       continue;
     }
@@ -3398,14 +3411,18 @@ void SemaObjC::addMethodToGlobalList(ObjCMethodList *List,
 
     // If a method is deprecated, push it in the global pool.
     // This is used for better diagnostics.
-    if (Method->isDeprecated()) {
-      if (!PrevObjCMethod->isDeprecated())
+    if (Method->isDeprecatedInAnyTargetPlatform()) {
+      if (!PrevObjCMethod->isDeprecatedInAnyTargetPlatform())
         List->setMethod(Method);
     }
+    // FIXME: What about TVT?
     // If the new method is unavailable, push it into global pool
     // unless previous one is deprecated.
-    if (Method->isUnavailable()) {
-      if (PrevObjCMethod->getAvailability() < AR_Deprecated)
+    if (Method->isUnavailabledForAllTargetPlatforms()) {
+      if (PrevObjCMethod->getAvailability(
+              getASTContext().getTargetInfo().getPlatformName(),
+              getASTContext().getTargetInfo().getPlatformMinVersion()) <
+          AR_Deprecated)
         List->setMethod(Method);
     }
 
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 2b524a956ecc4..3ed20095993e5 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -100,8 +100,8 @@ bool Sema::CanUseDecl(NamedDecl *D, bool TreatUnavailableAsInvalid) {
   }
 
   // See if this function is unavailable.
-  if (TreatUnavailableAsInvalid && D->getAvailability() == AR_Unavailable &&
-      cast<Decl>(CurContext)->getAvailability() != AR_Unavailable)
+  if (TreatUnavailableAsInvalid && D->isUnavailabledForAllTargetPlatforms() &&
+      !cast<Decl>(CurContext)->isUnavailabledForAllTargetPlatforms())
     return false;
 
   if (isa<UnresolvedUsingIfExistsDecl>(D))
diff --git a/clang/lib/Sema/SemaExprObjC.cpp b/clang/lib/Sema/SemaExprObjC.cpp
index 25cc068ec30bd..2de5ee4d2598b 100644
--- a/clang/lib/Sema/SemaExprObjC.cpp
+++ b/clang/lib/Sema/SemaExprObjC.cpp
@@ -5328,13 +5328,23 @@ ExprResult SemaObjC::ActOnObjCAvailabilityCheckExpr(
                           Context.getTargetInfo().getTriple().getOS()))
     Version = *MaybeVersion;
 
+  VersionTuple VariantVersion;
+  if (Context.getTargetInfo().hasTargetVariantPlatform()) {
+    const llvm::Triple *VariantTriple =
+        Context.getTargetInfo().getDarwinTargetVariantTriple();
+    if (auto MaybeVariantVersion = FindSpecVersion(
+            Context.getTargetInfo().getTargetVariantPlatform(),
+            VariantTriple ? VariantTriple->getOS() : llvm::Triple::UnknownOS))
+      VariantVersion = *MaybeVariantVersion;
+  }
+
   // The use of `@available` in the enclosing context should be analyzed to
   // warn when it's used inappropriately (i.e. not if(@available)).
   if (FunctionScopeInfo *Context = SemaRef.getCurFunctionAvailabilityContext())
     Context->HasPotentialAvailabilityViolations = true;
 
-  return new (Context)
-      ObjCAvailabilityCheckExpr(Version, AtLoc, RParen, Context.BoolTy);
+  return new (Context) ObjCAvailabilityCheckExpr(Version, VariantVersion, AtLoc,
+                                                 RParen, Context.BoolTy);
 }
 
 /// Prepare a conversion of the given expression to an ObjC object
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index 43129800e9813..e0024e4828095 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -4766,7 +4766,7 @@ void TypoCorrectionConsumer::addCorrection(TypoCorrection Correction) {
 
       auto IsDeprecated = [](Decl *D) {
         while (D) {
-          if (D->isDeprecated())
+          if (D->isDeprecatedInAnyTargetPlatform())
             return true;
           D = llvm::dyn_cast_or_null<NamespaceDecl>(D->getDeclContext());
         }
diff --git a/clang/lib/Sema/SemaObjCProperty.cpp b/clang/lib/Sema/SemaObjCProperty.cpp
index 4d7efed23939e..73985e6964ed9 100644
--- a/clang/lib/Sema/SemaObjCProperty.cpp
+++ b/clang/lib/Sema/SemaObjCProperty.cpp
@@ -1927,7 +1927,7 @@ void SemaObjC::DefaultSynthesizeProperties(Scope *S, ObjCImplDecl *IMPDecl,
                             /* property = */ Prop->getIdentifier(),
                             /* ivar = */ Prop->getDefaultSynthIvarName(Context),
                             Prop->getLocation(), Prop->getQueryKind()));
-    if (PIDecl && !Prop->isUnavailable()) {
+    if (PIDecl && !Prop->isUnavailabledForAllTargetPlatforms()) {
       Diag(Prop->getLocation(), diag::warn_missing_explicit_synthesis);
       Diag(IMPDecl->getLocation(), diag::note_while_in_implementation);
     }
@@ -2075,8 +2075,7 @@ void SemaObjC::DiagnoseUnimplementedProperties(Scope *S, ObjCImplDecl *IMPDecl,
     // Is there a matching property synthesize/dynamic?
     if (Prop->isInvalidDecl() ||
         Prop->getPropertyImplementation() == ObjCPropertyDecl::Optional ||
-        PropImplMap.count(Prop) ||
-        Prop->getAvailability() == AR_Unavailable)
+        PropImplMap.count(Prop) || Prop->isUnavailabledForAllTargetPlatforms())
       continue;
 
     // Diagnose unimplemented getters and setters.
@@ -2291,12 +2290,12 @@ void SemaObjC::DiagnoseMissingDesignatedInitOverrides(
       // marked as unavailable.
       bool Ignore = false;
       if (auto *IMD = IFD->getInstanceMethod(MD->getSelector())) {
-        Ignore = IMD->isUnavailable();
+        Ignore = IMD->isUnavailabledForAllTargetPlatforms();
       } else {
         // Check the methods declared in the class extensions too.
         for (auto *Ext : IFD->visible_extensions())
           if (auto *IMD = Ext->getInstanceMethod(MD->getSelector())) {
-            Ignore = IMD->isUnavailable();
+            Ignore = IMD->isUnavailabledForAllTargetPlatforms();
             break;
           }
       }
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 21781f7474e56..b0eac60ce8119 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -4172,8 +4172,13 @@ bool Sema::InstantiateClassTemplateSpecialization(
   if (AlreadyInstantiating)
     return false;
 
+  const TargetInfo &TI = Context.getTargetInfo();
+  StringRef PlatformName = TI.getPlatformName();
+  const VersionTuple &PlatformMinVersion = TI.getPlatformMinVersion();
+
   bool HadAvaibilityWarning =
-      ShouldDiagnoseAvailabilityOfDecl(ClassTemplateSpec, nullptr, nullptr)
+      ShouldDiagnoseAvailabilityOfDecl(ClassTemplateSpec, PlatformName,
+                                       PlatformMinVersion, nullptr, nullptr)
           .first != AR_Available;
 
   ActionResult<CXXRecordDecl *> Pattern =
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 92c555dc427b3..8fb690d800143 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -1743,6 +1743,7 @@ void ASTStmtReader::VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E)
   E->AtLoc = R.getBegin();
   E->RParen = R.getEnd();
   E->VersionToCheck = Record.readVersionTuple();
+  E->VariantVersionToCheck = Record.readVersionTuple();
 }
 
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 782fecdbd0c80..c1c9cf77feddd 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -1716,6 +1716,7 @@ void ASTStmtWriter::VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E)
   VisitExpr(E);
   Record.AddSourceRange(E->getSourceRange());
   Record.AddVersionTuple(E->getVersion());
+  Record.AddVersionTuple(E->getVariantVersion());
   Code = serialization::EXPR_OBJC_AVAILABILITY_CHECK;
 }
 
diff --git a/clang/test/CodeGenObjC/availability-check-zippered.m b/clang/test/CodeGenObjC/availability-check-zippered.m
new file mode 100644
index 0000000000000..cde108307d96b
--- /dev/null
+++ b/clang/test/CodeGenObjC/availability-check-zippered.m
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -triple x86_64-apple-macosx10.14 -darwin-target-variant-triple x86_64-apple-ios12-macabi -emit-llvm -o - %s | FileCheck %s
+
+// XFAIL: *
+void use_at_available() {
+
+  // CHECK: call i32 @__isOSVersionAtLeast(i32 10, i32 15, i32 0)
+  // CHECK-NEXT: call i32 @__isTargetPlatformNative()
+  // CHECK-NEXT: icmp ne
+  // CHECK-NEXT: select
+  // CHECK-NEXT: icmp ne
+  if (@available(macos 10.15, *))
+    ;
+
+  // CHECK: call i32 @__isOSVersionAtLeast(i32 10, i32 15, i32 0)
+  // CHECK-NEXT: call i32 @__isTargetVariantOSVersionAtLeast(i32 13, i32 0, i32 0)
+  // CHECK-NEXT: call i32 @__isTargetPlatformNative()
+  // CHECK-NEXT: icmp ne
+  // CHECK-NEXT: select
+  // CHECK-NEXT: icmp ne
+  if (@available(macos 10.15, iosmac 13, *))
+   ;
+
+  // CHECK: call i32 @__isTargetVariantOSVersionAtLeast(i32 13, i32 0, i32 0)
+  // CHECK-NEXT: call i32 @__isTargetPlatformNative()
+  // CHECK-NEXT: icmp ne
+  // CHECK-NEXT: select
+  // CHECK-NEXT: icmp ne
+  if (@available(ios 13, *))
+   ;
+
+  // This check should be folded: our deployment target is 10.11.
+  // CHECK-NOT: call i32 @__isOSVersionAtLeast
+  // CHECK-NOT: call i32 @__isTargetVariantOSVersionAtLeast
+  // CHECK: br i1 true
+  if (__builtin_available(macos 10.11, ios 11, *))
+    ;
+}
+
+// CHECK: declare i32 @__isOSVersionAtLeast(i32, i32, i32)
+// CHECK: declare i32 @__isTargetPlatformNative
+// CHECK: declare i32 @__isTargetVariantOSVersionAtLeast(i32, i32, i32)
diff --git a/clang/test/FixIt/fixit-availability-zippered.m b/clang/test/FixIt/fixit-availability-zippered.m
new file mode 100644
index 0000000000000..82fc8397ff21d
--- /dev/null
+++ b/clang/test/FixIt/fixit-availability-zippered.m
@@ -0,0 +1,23 @@
+// RUN: %clang_cc1 -fsyntax-only -Wunguarded-availability -fdiagnostics-parseable-fixits -triple x86_64-apple-macos10.14 -darwin-target-variant-triple x86_64-apple-ios12-macabi %s 2>&1 | FileCheck %s
+
+// XFAIL: *
+__attribute__((availability(macOS, introduced=10.15))) __attribute__((availability(ios, introduced=13)))
+int function(void);
+
+int use() {
+  function();
+// CHECK: fix-it:{{.*}}:{[[@LINE-1]]:3-[[@LINE-1]]:3}:"if (@available(macOS 10.15, iOSMac 13, *)) {\n      "
+// CHECK-NEXT: fix-it:{{.*}}:{[[@LINE-2]]:14-[[@LINE-2]]:14}:"\n  } else {\n      // Fallback on earlier versions\n  }"
+}
+
+#define API_AVAILABLE(...) __attribute__((availability(macOS, introduced=10.15))) __attribute__((availability(ios, introduced=13))) // dummy macro
+
+API_AVAILABLE(macos(10.15), ios(13))
+ at interface NewClass
+ at end
+
+ at interface OldButOfferFixit
+ at property(copy) NewClass *prop;
+// CHECK: fix-it:{{.*}}:{[[@LINE-2]]:1-[[@LINE-2]]:1}:"API_AVAILABLE(macos(10.15), iosmac(13))\n"
+
+ at end
diff --git a/clang/test/Sema/attr-availability-zippered.c b/clang/test/Sema/attr-availability-zippered.c
new file mode 100644
index 0000000000000..37a3e6c157722
--- /dev/null
+++ b/clang/test/Sema/attr-availability-zippered.c
@@ -0,0 +1,133 @@
+// RUN: %clang_cc1 "-triple" "x86_64-apple-macos10.14" -darwin-target-variant-triple x86_64-apple-ios12-macabi -fsyntax-only -verify %s
+
+// XFAIL: *
+__attribute__((availability(macos, introduced=10.10, deprecated=10.14), availability(ios, introduced=9, deprecated=12)))
+void bothDeprecated() { // expected-note {{'bothDeprecated' has been explicitly marked deprecated here}}
+}
+
+__attribute__((availability(macos, introduced=10.10, deprecated=10.14), availability(ios, introduced=9, deprecated=13)))
+void macOSDeprecated() { // expected-note {{'macOSDeprecated' has been explicitly marked deprecated here}}
+}
+
+__attribute__((availability(macos, introduced=10.10, deprecated=10.15), availability(ios, introduced=9, deprecated=12)))
+void iOSDeprecated() { // expected-note {{'iOSDeprecated' has been explicitly marked deprecated here}}
+}
+
+__attribute__((availability(macos, introduced=10.10, deprecated=10.15), availability(ios, introduced=9, deprecated=13)))
+void bothNotDeprecated() {
+}
+
+void checkDeprecated() {
+  // The compiler may warn here if the deployment target is older than the version introduced
+  bothDeprecated();  // expected-warning {{'bothDeprecated' is deprecated: first deprecated in macOS 10.14 and first deprecated in iOS (on macOS) 12}}
+  macOSDeprecated(); // expected-warning {{'macOSDeprecated' is deprecated: first deprecated in macOS 10.14}}
+  iOSDeprecated();   // expected-warning {{'iOSDeprecated' is deprecated: first deprecated in iOS (on macOS) 12}}
+  bothNotDeprecated();
+}
+
+__attribute__((availability(macos, unavailable), availability(ios, unavailable)))
+void bothUnavailable() { // expected-note {{'bothUnavailable' has been explicitly marked unavailable here}}
+}
+
+__attribute__((availability(macos, introduced=10.10), availability(ios, unavailable)))
+void iosUnavailable() { // expected-note {{'iosUnavailable' has been explicitly marked unavailable here}}
+}
+
+__attribute__((availability(macos, unavailable), availability(ios, introduced=11)))
+void macOSUnavailable() { // expected-note {{'macOSUnavailable' has been explicitly marked unavailable here}}
+}
+
+__attribute__((availability(macos, introduced=10.10), availability(ios, introduced=11)))
+void bothAvailable() { }
+
+__attribute__((availability(macos, introduced=10.10, obsoleted=10.14), availability(ios, introduced=9, obsoleted=12)))
+void bothObsoleted() { // expected-note {{'bothObsoleted' has been explicitly marked unavailable here}}
+}
+
+void checkUnavailable() {
+  bothUnavailable();  // expected-error {{'bothUnavailable' is unavailable: not available on macOS and not available on iOS (on macOS)}}
+  iosUnavailable(); // expected-error {{'iosUnavailable' is unavailable: not available on iOS (on macOS)}}
+  macOSUnavailable(); // expected-error {{'macOSUnavailable' is unavailable: not available on macOS}}
+  bothAvailable();
+  
+  bothObsoleted(); // expected-error {{'bothObsoleted' is unavailable: obsoleted in macOS 10.14 and obsoleted in iOS (on macOS) 12}}
+}
+
+__attribute__((availability(macos, introduced=10.10, deprecated=10.14)))
+typedef struct ZipperedTypedefDifferentOffendingDecl { // expected-note {{'ZipperedTypedefDifferentOffendingDecl' has been explicitly marked deprecated here}}
+// expected-warning at -1 {{'ZipperedTypedefDifferentOffendingDecl' is deprecated: first deprecated in iOS (on macOS) 12}}
+// expected-note at -2 {{'ZipperedTypedefDifferentOffendingDecl' has been explicitly marked deprecated here}}
+  int x;
+} __attribute__((availability(ios, introduced=9, deprecated=12))) ZipperedTypedefDifferentOffendingDecl_t; // expected-note {{'ZipperedTypedefDifferentOffendingDecl_t' has been explicitly marked deprecated here}}
+
+void checkZipperDiffOffendingDecl() {
+  ZipperedTypedefDifferentOffendingDecl_t tt; // expected-warning {{'ZipperedTypedefDifferentOffendingDecl_t' is deprecated: first deprecated in macOS 10.14 and first deprecated in iOS (on macOS) 12}}
+}
+
+__attribute__((availability(macos, introduced=10.10, deprecated=10.14), availability(ios, unavailable)))
+void mixUnavailableAndDeprecated() {
+  // expected-note at -1 {{'mixUnavailableAndDeprecated' has been explicitly marked unavailable here}}
+  // expected-note at -2 {{'mixUnavailableAndDeprecated' has been explicitly marked deprecated here}}
+}
+
+__attribute__((availability(macos, introduced=10.10, deprecated=10.15), availability(ios, unavailable)))
+void iOSUnavailableMacGood() {
+  // expected-note at -1 {{'iOSUnavailableMacGood' has been explicitly marked unavailable here}}
+}
+
+__attribute__((availability(macos, introduced=10.10, deprecated=10.14), availability(ios, introduced=11)))
+void macOSDeprecatedIOSGood() {
+  // expected-note at -1 {{'macOSDeprecatedIOSGood' has been explicitly marked deprecated here}}
+}
+
+__attribute__((availability(macos, introduced=10.10, deprecated=10.14, replacement="foo"), availability(ios, introduced=9, deprecated=12, replacement="bar")))
+void bothDeprecatedDiffReplacement() { // expected-note {{'bothDeprecatedDiffReplacement' has been explicitly marked deprecated here}}
+}
+
+__attribute__((availability(macos, unavailable, replacement="bar"), availability(ios, unavailable, replacement="foo")))
+void bothUnavailableSameReplacement() { // expected-note {{'bothUnavailableSameReplacement' has been explicitly marked unavailable here}}
+}
+
+__attribute__((availability(ios, unavailable)))
+void justIosUnavailable() { // expected-note {{'justIosUnavailable' has been explicitly marked unavailable here}}
+}
+
+__attribute__((availability(macos, introduced=10.10, deprecated=10.14)))
+void justMacOSDeprecated() { // expected-note {{'justMacOSDeprecated' has been explicitly marked deprecated here}}
+}
+
+void checkMix() {
+  mixUnavailableAndDeprecated();
+  // expected-error at -1 {{'mixUnavailableAndDeprecated' is unavailable: not available on iOS (on macOS)}}
+  // expected-warning at -2 {{'mixUnavailableAndDeprecated' is deprecated: first deprecated in macOS 10.14}}
+  
+  iOSUnavailableMacGood();
+  // expected-error at -1 {{'iOSUnavailableMacGood' is unavailable: not available on iOS (on macOS)}}
+  
+  macOSDeprecatedIOSGood();
+  // expected-warning at -1 {{'macOSDeprecatedIOSGood' is deprecated: first deprecated in macOS 10.14}}
+
+  bothDeprecatedDiffReplacement(); // expected-warning {{'bothDeprecatedDiffReplacement' is deprecated: first deprecated in macOS 10.14 and first deprecated in iOS (on macOS) 12}}
+  
+  bothUnavailableSameReplacement(); // expected-error {{'bothUnavailableSameReplacement' is unavailable: not available on macOS and not available on iOS (on macOS)}}
+  
+  justIosUnavailable(); // expected-error {{'justIosUnavailable' is unavailable: not available on iOS (on macOS)}}
+  
+  justMacOSDeprecated(); // expected-warning {{'justMacOSDeprecated' is deprecated: first deprecated in macOS 10.14}}
+}
+
+int deprecatedFunc() __attribute__((deprecated)); // expected-note {{'deprecatedFunc' has been explicitly marked deprecated here}}
+int unavailFunc() __attribute__((unavailable)); // expected-note {{'unavailFunc' has been explicitly marked unavailable here}}
+
+void a() {
+  int (*ptr)() = deprecatedFunc; // expected-warning {{'deprecatedFunc' is deprecated}}
+  int (*ptr2)() = unavailFunc; // expected-error {{'unavailFunc' is unavailable}}
+}
+
+
+struct IntroducedLaterBoth { } __attribute__((availability(macos, introduced=10.15), availability(ios, introduced=13)));
+// expected-note at -1 {{'IntroducedLaterBoth' has been explicitly marked partial here}}
+
+struct NotYetIntroduced { // expected-note {{annotate 'NotYetIntroduced' with an availability attribute to silence this warning}}
+  struct IntroducedLaterBoth x; // expected-warning {{'IntroducedLaterBoth' is only available on macOS 10.15 and iOS (on macOS) 13 or newer}}
+};
diff --git a/clang/test/SemaObjC/unguarded-availability-zippered.m b/clang/test/SemaObjC/unguarded-availability-zippered.m
new file mode 100644
index 0000000000000..f90c4a7242126
--- /dev/null
+++ b/clang/test/SemaObjC/unguarded-availability-zippered.m
@@ -0,0 +1,42 @@
+// RUN: %clang_cc1 -triple x86_64-apple-macos10.14 -darwin-target-variant-triple x86_64-apple-ios12-macabi -fblocks -fsyntax-only -verify %s
+
+// XFAIL: *
+#ifdef NO_WARNING
+  // expected-no-diagnostics
+#endif
+
+
+#define AVAILABLE_PREV_MAC __attribute__((availability(macos, introduced = 10.13)))
+#define AVAILABLE_CURRENT_MAC __attribute__((availability(macos, introduced = 10.14)))
+#define AVAILABLE_NEXT_MAC __attribute__((availability(macos, introduced = 10.15)))
+
+#define AVAILABLE_PREV_IOS __attribute__((availability(ios, introduced = 11)))
+#define AVAILABLE_CURRENT_IOS __attribute__((availability(iOSMac, introduced = 12)))
+#define AVAILABLE_NEXT_IOS __attribute__((availability(ios, introduced = 13)))
+
+void bothPreviouslyAvailable() AVAILABLE_PREV_MAC AVAILABLE_PREV_IOS;
+void bothCurrentlyAvailable() AVAILABLE_CURRENT_MAC AVAILABLE_CURRENT_IOS;
+void bothWillBeAvailable() AVAILABLE_NEXT_MAC AVAILABLE_NEXT_IOS;
+// expected-note at -1 3 {{'bothWillBeAvailable' has been explicitly marked partial here}}
+
+void macOSCurrentlyAvailable() AVAILABLE_CURRENT_MAC AVAILABLE_NEXT_IOS;
+// expected-note at -1 {{'macOSCurrentlyAvailable' has been explicitly marked partial here}}
+
+void test() {
+  bothPreviouslyAvailable();
+  bothCurrentlyAvailable();
+  bothWillBeAvailable(); // expected-warning {{'bothWillBeAvailable' is only available on macOS 10.15 and iOS (on macOS) 13 or newer}}
+  // expected-note at -1{{enclose 'bothWillBeAvailable' in an @available check to silence this warning}}
+  
+  macOSCurrentlyAvailable(); // expected-warning {{'macOSCurrentlyAvailable' is only available on iOS (on macOS) 13 or newer}}
+  // expected-note at -1 {{enclose 'macOSCurrentlyAvailable' in an @available check to silence this warning}}
+  
+  if (@available(ios 13, macos 10.15, *))
+    bothWillBeAvailable();
+  if (@available(ios 13, *))
+    bothWillBeAvailable(); // expected-warning {{'bothWillBeAvailable' is only available on macOS 10.15 or newer}}
+  // expected-note at -1 {{enclose}}
+  if (@available(macos 10.15, *))
+    bothWillBeAvailable(); // expected-warning {{'bothWillBeAvailable' is only available on iOS (on macOS) 13 or newer}}
+  // expected-note at -1 {{enclose}}
+}
diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp
index 47b641446576c..0dff1054f3b1e 100644
--- a/clang/tools/libclang/CIndex.cpp
+++ b/clang/tools/libclang/CIndex.cpp
@@ -8893,7 +8893,10 @@ static CXAvailabilityKind getCursorAvailabilityForDecl(const Decl *D) {
   if (isa<FunctionDecl>(D) && cast<FunctionDecl>(D)->isDeleted())
     return CXAvailability_NotAvailable;
 
-  switch (D->getAvailability()) {
+  const TargetInfo &TI =
+      D->getTranslationUnitDecl()->getASTContext().getTargetInfo();
+  switch (
+      D->getAvailability(TI.getPlatformName(), TI.getPlatformMinVersion())) {
   case AR_Available:
   case AR_NotYetIntroduced:
     if (const EnumConstantDecl *EnumConst = dyn_cast<EnumConstantDecl>(D))



More information about the cfe-commits mailing list