[clang] [APINotes] Diagnose invalid Where.Parameters selectors (PR #209408)

via cfe-commits cfe-commits at lists.llvm.org
Tue Jul 28 10:53:41 PDT 2026


https://github.com/StoeckOverflow updated https://github.com/llvm/llvm-project/pull/209408

>From 7030c9531c066bb8784faea250475e0a1bfa897d Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Fri, 19 Jun 2026 12:57:27 +0200
Subject: [PATCH 01/12] [APINotes] Diagnose invalid Where.Parameters selectors

---
 clang/include/clang/APINotes/APINotesReader.h |  12 ++
 clang/lib/APINotes/APINotesReader.cpp         |  79 +++++++++
 clang/lib/APINotes/APINotesYAMLCompiler.cpp   |  71 +++++++-
 clang/lib/Sema/SemaAPINotes.cpp               | 155 ++++++++++++++++++
 .../WhereParametersDiagnostics.apinotes       |  50 ++++++
 .../Headers/WhereParametersDiagnostics.h      |  18 ++
 .../APINotes/Inputs/Headers/module.modulemap  |   5 +
 .../APINotes.apinotes                         |  66 ++++++++
 .../WhereParametersDiagnostics.h              |  16 ++
 .../APINotes/where-parameters-diagnostics.cpp |  41 +++++
 10 files changed, 511 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/APINotes/Inputs/Headers/WhereParametersDiagnostics.apinotes
 create mode 100644 clang/test/APINotes/Inputs/Headers/WhereParametersDiagnostics.h
 create mode 100644 clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/APINotes.apinotes
 create mode 100644 clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/WhereParametersDiagnostics.h
 create mode 100644 clang/test/APINotes/where-parameters-diagnostics.cpp

diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h
index 761745e20b61a..640be8ad5acb3 100644
--- a/clang/include/clang/APINotes/APINotesReader.h
+++ b/clang/include/clang/APINotes/APINotesReader.h
@@ -17,6 +17,7 @@
 
 #include "clang/APINotes/Types.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/VersionTuple.h"
@@ -169,6 +170,11 @@ class APINotesReader {
   lookupCXXMethod(ContextID CtxID, llvm::StringRef Name,
                   llvm::ArrayRef<std::string> Parameters);
 
+  /// Collect exact parameter selectors stored for the given C++ method.
+  void collectCXXMethodParameterSelectors(
+      ContextID CtxID, llvm::StringRef Name,
+      llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors);
+
   /// Look for information regarding the given global variable.
   ///
   /// \param Name The name of the global variable.
@@ -195,6 +201,12 @@ class APINotesReader {
                        llvm::ArrayRef<std::string> Parameters,
                        std::optional<Context> Ctx = std::nullopt);
 
+  /// Collect exact parameter selectors stored for the given global function.
+  void collectGlobalFunctionParameterSelectors(
+      llvm::StringRef Name,
+      llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors,
+      std::optional<Context> Ctx = std::nullopt);
+
   /// Look for information regarding the given enumerator.
   ///
   /// \param Name The name of the enumerator.
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index 888844fbb6cd9..944beccb9d2df 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -19,6 +19,8 @@
 #include "llvm/Bitstream/BitstreamReader.h"
 #include "llvm/Support/DJB.h"
 #include "llvm/Support/OnDiskHashTable.h"
+#include <string>
+#include <utility>
 
 namespace clang {
 namespace api_notes {
@@ -833,6 +835,16 @@ class APINotesReader::Implementation {
   /// optional if the string is unknown.
   std::optional<IdentifierID> getIdentifier(llvm::StringRef Str);
 
+  /// Retrieve the identifier string for the given ID, or an empty optional if
+  /// the ID is unknown.
+  std::optional<llvm::StringRef> getIdentifierString(IdentifierID ID);
+
+  /// Collect exact parameter selectors stored in the given function-like table.
+  template <typename TableT>
+  void collectFunctionParameterSelectors(
+      TableT *Table, uint32_t ParentContextID, llvm::StringRef Name,
+      llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors);
+
   /// Retrieve the selector ID for the given selector, or an empty
   /// optional if the string is unknown.
   std::optional<SelectorID> getSelector(ObjCSelectorRef Selector);
@@ -893,6 +905,56 @@ APINotesReader::Implementation::getIdentifier(llvm::StringRef Str) {
   return *Known;
 }
 
+std::optional<llvm::StringRef>
+APINotesReader::Implementation::getIdentifierString(IdentifierID ID) {
+  if (!IdentifierTable)
+    return std::nullopt;
+
+  if (ID == IdentifierID(0))
+    return llvm::StringRef();
+
+  for (llvm::StringRef Identifier : IdentifierTable->keys()) {
+    auto KnownID = IdentifierTable->find(Identifier);
+    if (KnownID != IdentifierTable->end() && *KnownID == ID)
+      return Identifier;
+  }
+
+  return std::nullopt;
+}
+
+template <typename TableT>
+void APINotesReader::Implementation::collectFunctionParameterSelectors(
+    TableT *Table, uint32_t ParentContextID, llvm::StringRef Name,
+    llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors) {
+  if (!Table)
+    return;
+
+  std::optional<IdentifierID> NameID = getIdentifier(Name);
+  if (!NameID)
+    return;
+
+  for (auto I = Table->key_begin(), E = Table->key_end(); I != E; ++I) {
+    FunctionTableKey Key = I.getInternalKey();
+    if (Key.parentContextID != ParentContextID ||
+        Key.nameID != static_cast<unsigned>(*NameID) || !Key.parameterTypeIDs)
+      continue;
+
+    llvm::SmallVector<std::string, 4> ParameterSelector;
+    ParameterSelector.reserve(Key.parameterTypeIDs->size());
+    bool Failed = false;
+    for (IdentifierID TypeID : *Key.parameterTypeIDs) {
+      std::optional<llvm::StringRef> TypeName = getIdentifierString(TypeID);
+      if (!TypeName) {
+        Failed = true;
+        break;
+      }
+      ParameterSelector.push_back(TypeName->str());
+    }
+    if (!Failed)
+      Selectors.push_back(std::move(ParameterSelector));
+  }
+}
+
 std::optional<FunctionTableKey>
 APINotesReader::Implementation::getFunctionKey(uint32_t ParentContextID,
                                                llvm::StringRef Name) {
@@ -2351,6 +2413,13 @@ auto APINotesReader::lookupCXXMethod(ContextID CtxID, llvm::StringRef Name,
   return lookupCXXMethodImpl(CtxID, Name, Parameters);
 }
 
+void APINotesReader::collectCXXMethodParameterSelectors(
+    ContextID CtxID, llvm::StringRef Name,
+    llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors) {
+  Implementation->collectFunctionParameterSelectors(
+      Implementation->CXXMethodTable.get(), CtxID.Value, Name, Selectors);
+}
+
 auto APINotesReader::lookupCXXMethodImpl(ContextID CtxID, llvm::StringRef Name)
     -> VersionedInfo<CXXMethodInfo> {
   if (!Implementation->CXXMethodTable)
@@ -2418,6 +2487,16 @@ auto APINotesReader::lookupGlobalFunction(
   return lookupGlobalFunctionImpl(Name, Parameters, Ctx);
 }
 
+void APINotesReader::collectGlobalFunctionParameterSelectors(
+    llvm::StringRef Name,
+    llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors,
+    std::optional<Context> Ctx) {
+  uint32_t ParentContextID = Ctx ? Ctx->id.Value : static_cast<uint32_t>(-1);
+  Implementation->collectFunctionParameterSelectors(
+      Implementation->GlobalFunctionTable.get(), ParentContextID, Name,
+      Selectors);
+}
+
 auto APINotesReader::lookupGlobalFunctionImpl(llvm::StringRef Name,
                                               std::optional<Context> Ctx)
     -> VersionedInfo<GlobalFunctionInfo> {
diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
index 1c3a59873db25..058f9da338661 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -18,6 +18,7 @@
 #include "clang/APINotes/Types.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/Specifiers.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/VersionTuple.h"
@@ -788,6 +789,47 @@ bool clang::api_notes::parseAndDumpAPINotes(StringRef YI,
 namespace {
 using namespace api_notes;
 
+struct KnownFunctionSelector {
+  llvm::StringRef Name;
+  llvm::ArrayRef<llvm::StringRef> Parameters;
+};
+
+static bool equalParameterSelectors(llvm::ArrayRef<llvm::StringRef> LHS,
+                                    llvm::ArrayRef<llvm::StringRef> RHS) {
+  if (LHS.size() != RHS.size())
+    return false;
+
+  for (unsigned I = 0, E = LHS.size(); I != E; ++I)
+    if (LHS[I] != RHS[I])
+      return false;
+
+  return true;
+}
+
+static bool
+hasFunctionSelector(llvm::ArrayRef<KnownFunctionSelector> KnownSelectors,
+                    llvm::StringRef Name,
+                    llvm::ArrayRef<llvm::StringRef> Parameters) {
+  for (const KnownFunctionSelector &Known : KnownSelectors)
+    if (Known.Name == Name &&
+        equalParameterSelectors(Known.Parameters, Parameters))
+      return true;
+
+  return false;
+}
+
+static std::string
+formatWhereParameters(llvm::ArrayRef<llvm::StringRef> Parameters) {
+  std::string Result = "[";
+  for (unsigned I = 0, E = Parameters.size(); I != E; ++I) {
+    if (I)
+      Result += ", ";
+    Result += Parameters[I];
+  }
+  Result += "]";
+  return Result;
+}
+
 class YAMLConverter {
   const Module &M;
   APINotesWriter Writer;
@@ -1162,11 +1204,24 @@ class YAMLConverter {
       Writer.addField(TagCtxID, Field.Name, FI, SwiftVersion);
     }
 
+    llvm::SmallVector<KnownFunctionSelector, 4> KnownMethodSelectors;
     for (const auto &CXXMethod : T.Methods) {
       auto WhereParameters = getWhereParameters(CXXMethod);
       if (!WhereParameters.first)
         continue;
 
+      if (WhereParameters.second) {
+        if (hasFunctionSelector(KnownMethodSelectors, CXXMethod.Name,
+                                *WhereParameters.second)) {
+          emitError(llvm::Twine("duplicate definition of C++ method '") +
+                    CXXMethod.Name + "' with Where.Parameters " +
+                    formatWhereParameters(*WhereParameters.second));
+          continue;
+        }
+        KnownMethodSelectors.push_back(
+            {CXXMethod.Name, *WhereParameters.second});
+      }
+
       CXXMethodInfo MI;
       convertFunction(CXXMethod, MI);
       if (WhereParameters.second)
@@ -1243,13 +1298,25 @@ class YAMLConverter {
 
     // Write all global functions.
     llvm::StringSet<> KnownNameOnlyFunctions;
+    llvm::SmallVector<KnownFunctionSelector, 4> KnownFunctionSelectors;
     for (const auto &Function : TLItems.Functions) {
       auto WhereParameters = getWhereParameters(Function);
       if (!WhereParameters.first)
         continue;
 
-      // Check for duplicate name-only global functions. Selector-aware
-      // duplicate diagnostics are handled by a later overload-matching PR.
+      if (WhereParameters.second) {
+        if (hasFunctionSelector(KnownFunctionSelectors, Function.Name,
+                                *WhereParameters.second)) {
+          emitError(llvm::Twine("duplicate definition of global function '") +
+                    Function.Name + "' with Where.Parameters " +
+                    formatWhereParameters(*WhereParameters.second));
+          continue;
+        }
+        KnownFunctionSelectors.push_back(
+            {Function.Name, *WhereParameters.second});
+      }
+
+      // Check for duplicate name-only global functions.
       if (!WhereParameters.second &&
           !KnownNameOnlyFunctions.insert(Function.Name).second) {
         emitError(llvm::Twine("multiple definitions of global function '") +
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 1ab1eac4a5434..e9e90c93755d3 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -1017,6 +1017,50 @@ struct APINotesParameterSelectorCandidates {
   std::optional<APINotesParameterSelector> Desugared;
 };
 
+struct APINotesParameterSelectorSet {
+  SmallVector<APINotesParameterSelector, 4> Selectors;
+
+  void add(const APINotesParameterSelector &Selector) {
+    Selectors.push_back(Selector);
+  }
+
+  void add(ArrayRef<std::string> Parameters) {
+    APINotesParameterSelector Selector;
+    Selector.Parameters.append(Parameters.begin(), Parameters.end());
+    add(Selector);
+  }
+
+  void add(const APINotesParameterSelectorCandidates &Candidates) {
+    add(Candidates.Source);
+    if (Candidates.Desugared)
+      add(*Candidates.Desugared);
+  }
+
+  bool contains(ArrayRef<std::string> Parameters) const {
+    for (const APINotesParameterSelector &Selector : Selectors) {
+      if (Selector.Parameters.size() != Parameters.size())
+        continue;
+
+      bool Matches = true;
+      for (unsigned I = 0, E = Parameters.size(); I != E; ++I) {
+        if (Selector.Parameters[I] == Parameters[I])
+          continue;
+        Matches = false;
+        break;
+      }
+      if (Matches)
+        return true;
+    }
+
+    return false;
+  }
+
+  bool empty() const { return Selectors.empty(); }
+
+  auto begin() const { return Selectors.begin(); }
+  auto end() const { return Selectors.end(); }
+};
+
 static PrintingPolicy
 getAPINotesParameterSelectorPrintingPolicy(const ASTContext &Context) {
   PrintingPolicy Policy(Context.getLangOpts());
@@ -1072,6 +1116,84 @@ getAPINotesParameterSelectorCandidates(const Sema &S, const FunctionDecl *FD) {
   return Candidates;
 }
 
+static APINotesParameterSelectorSet
+makeParameterSelectorSet(ArrayRef<SmallVector<std::string, 4>> Selectors) {
+  APINotesParameterSelectorSet Set;
+  for (const SmallVector<std::string, 4> &Selector : Selectors)
+    Set.add(Selector);
+  return Set;
+}
+
+static std::string
+formatParameterSelectorForDiagnostic(ArrayRef<std::string> Parameters) {
+  std::string Result = "[";
+  for (unsigned I = 0, E = Parameters.size(); I != E; ++I) {
+    if (I)
+      Result += ", ";
+    Result += Parameters[I];
+  }
+  Result += "]";
+  return Result;
+}
+
+static void collectOverloadParameterSelectors(const Sema &S,
+                                              const FunctionDecl *FD,
+                                              APINotesParameterSelectorSet &Set,
+                                              bool &IsRepresentative) {
+  IsRepresentative = false;
+  bool FoundRepresentative = false;
+
+  auto AddCandidate = [&](const FunctionDecl *Candidate) {
+    if (!FoundRepresentative) {
+      IsRepresentative =
+          Candidate->getCanonicalDecl() == FD->getCanonicalDecl();
+      FoundRepresentative = true;
+    }
+
+    if (auto Candidates = getAPINotesParameterSelectorCandidates(S, Candidate))
+      Set.add(*Candidates);
+  };
+
+  for (NamedDecl *ND : FD->getDeclContext()->lookup(FD->getDeclName())) {
+    if (auto *Candidate = dyn_cast<FunctionDecl>(ND))
+      AddCandidate(Candidate);
+  }
+
+  if (FoundRepresentative)
+    return;
+
+  for (Decl *D : FD->getDeclContext()->decls()) {
+    auto *ND = dyn_cast<NamedDecl>(D);
+    if (!ND || ND->getDeclName() != FD->getDeclName())
+      continue;
+
+    if (auto *Candidate = dyn_cast<FunctionDecl>(ND))
+      AddCandidate(Candidate);
+  }
+
+  if (!FoundRepresentative)
+    AddCandidate(FD);
+}
+
+static void diagnoseUnmatchedParameterSelectors(
+    Sema &S, SourceLocation Loc, StringRef Name,
+    const APINotesParameterSelectorSet &APINotesSelectors,
+    const APINotesParameterSelectorSet &DeclarationSelectors) {
+  if (DeclarationSelectors.empty())
+    return;
+
+  for (const APINotesParameterSelector &APINotesSelector : APINotesSelectors) {
+    if (DeclarationSelectors.contains(APINotesSelector.Parameters))
+      continue;
+
+    S.Diag(Loc, diag::warn_apinotes_message)
+        << (llvm::Twine("API notes entry for '") + Name +
+            "' has unmatched Where.Parameters " +
+            formatParameterSelectorForDiagnostic(APINotesSelector.Parameters))
+               .str();
+  }
+}
+
 // Apply the first exact selector entry found. This preserves source-spelling
 // precedence over the desugared fallback and avoids applying multiple exact
 // entries for the same declaration.
@@ -1131,6 +1253,12 @@ void Sema::ProcessAPINotes(Decl *D) {
       if (FD->getDeclName().isIdentifier()) {
         auto ParameterSelectorCandidates =
             getAPINotesParameterSelectorCandidates(*this, FD);
+
+        APINotesParameterSelectorSet DeclarationSelectors;
+        bool DiagnoseUnmatchedSelectors = false;
+        collectOverloadParameterSelectors(*this, FD, DeclarationSelectors,
+                                          DiagnoseUnmatchedSelectors);
+
         for (auto Reader : Readers) {
           auto Info =
               Reader->lookupGlobalFunction(FD->getName(), APINotesContext);
@@ -1143,6 +1271,17 @@ void Sema::ProcessAPINotes(Decl *D) {
                   return Reader->lookupGlobalFunction(FD->getName(), Parameters,
                                                       APINotesContext);
                 });
+
+          if (DiagnoseUnmatchedSelectors && !DeclarationSelectors.empty()) {
+            SmallVector<SmallVector<std::string, 4>, 4> RawAPINotesSelectors;
+            Reader->collectGlobalFunctionParameterSelectors(
+                FD->getName(), RawAPINotesSelectors, APINotesContext);
+            APINotesParameterSelectorSet APINotesSelectors =
+                makeParameterSelectorSet(RawAPINotesSelectors);
+            diagnoseUnmatchedParameterSelectors(
+                *this, FD->getLocation(), FD->getName(), APINotesSelectors,
+                DeclarationSelectors);
+          }
         }
       }
 
@@ -1348,6 +1487,22 @@ void Sema::ProcessAPINotes(Decl *D) {
                     return Reader->lookupCXXMethod(Context->id, MethodName,
                                                    Parameters);
                   });
+
+            APINotesParameterSelectorSet DeclarationSelectors;
+            bool DiagnoseUnmatchedSelectors = false;
+            collectOverloadParameterSelectors(*this, CXXMethod,
+                                              DeclarationSelectors,
+                                              DiagnoseUnmatchedSelectors);
+            if (DiagnoseUnmatchedSelectors && !DeclarationSelectors.empty()) {
+              SmallVector<SmallVector<std::string, 4>, 4> RawAPINotesSelectors;
+              Reader->collectCXXMethodParameterSelectors(
+                  Context->id, MethodName, RawAPINotesSelectors);
+              APINotesParameterSelectorSet APINotesSelectors =
+                  makeParameterSelectorSet(RawAPINotesSelectors);
+              diagnoseUnmatchedParameterSelectors(
+                  *this, CXXMethod->getLocation(), MethodName,
+                  APINotesSelectors, DeclarationSelectors);
+            }
           }
         }
       }
diff --git a/clang/test/APINotes/Inputs/Headers/WhereParametersDiagnostics.apinotes b/clang/test/APINotes/Inputs/Headers/WhereParametersDiagnostics.apinotes
new file mode 100644
index 0000000000000..2cf62c2b5e6be
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/WhereParametersDiagnostics.apinotes
@@ -0,0 +1,50 @@
+---
+Name: WhereParametersDiagnostics
+Functions:
+- Name: unmatchedGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: shouldNotApplyGlobal(_:)
+- Name: diagnosticBroadGlobal
+  SwiftPrivate: true
+- Name: diagnosticBroadGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: shouldNotApplyBroadGlobal(_:)
+- Name: diagnosticMatchedGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: diagnosticMatchedGlobal(_:)
+- Name: diagnosticAliasMatchedGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: diagnosticAliasMatchedGlobal(_:)
+Tags:
+- Name: DiagnosticWidget
+  Methods:
+  - Name: unmatchedMethod
+    Where:
+      Parameters:
+      - int
+    SwiftName: shouldNotApplyMethod(_:)
+  - Name: diagnosticBroadMethod
+    SwiftPrivate: true
+  - Name: diagnosticBroadMethod
+    Where:
+      Parameters:
+      - int
+    SwiftName: shouldNotApplyBroadMethod(_:)
+  - Name: diagnosticMatchedMethod
+    Where:
+      Parameters:
+      - int
+    SwiftName: diagnosticMatchedMethod(_:)
+  - Name: diagnosticAliasMatchedMethod
+    Where:
+      Parameters:
+      - int
+    SwiftName: diagnosticAliasMatchedMethod(_:)
diff --git a/clang/test/APINotes/Inputs/Headers/WhereParametersDiagnostics.h b/clang/test/APINotes/Inputs/Headers/WhereParametersDiagnostics.h
new file mode 100644
index 0000000000000..c39ccd57541aa
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/WhereParametersDiagnostics.h
@@ -0,0 +1,18 @@
+#ifndef WHERE_PARAMETERS_DIAGNOSTICS_H
+#define WHERE_PARAMETERS_DIAGNOSTICS_H
+
+using DiagnosticAliasInt = int;
+
+void unmatchedGlobal(float);
+void diagnosticBroadGlobal(float);
+void diagnosticMatchedGlobal(int);
+void diagnosticAliasMatchedGlobal(DiagnosticAliasInt);
+
+struct DiagnosticWidget {
+  void unmatchedMethod(float);
+  void diagnosticBroadMethod(float);
+  void diagnosticMatchedMethod(int);
+  void diagnosticAliasMatchedMethod(DiagnosticAliasInt);
+};
+
+#endif // WHERE_PARAMETERS_DIAGNOSTICS_H
diff --git a/clang/test/APINotes/Inputs/Headers/module.modulemap b/clang/test/APINotes/Inputs/Headers/module.modulemap
index 592d482ea7a57..d00b96c55b839 100644
--- a/clang/test/APINotes/Inputs/Headers/module.modulemap
+++ b/clang/test/APINotes/Inputs/Headers/module.modulemap
@@ -75,3 +75,8 @@ module WhereParametersSema {
   header "WhereParametersSema.h"
   export *
 }
+
+module WhereParametersDiagnostics {
+  header "WhereParametersDiagnostics.h"
+  export *
+}
diff --git a/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/APINotes.apinotes b/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/APINotes.apinotes
new file mode 100644
index 0000000000000..d99ea9c0f4ce9
--- /dev/null
+++ b/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/APINotes.apinotes
@@ -0,0 +1,66 @@
+---
+Name: WhereParametersDiagnostics
+Functions:
+- Name: duplicateGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: duplicateGlobalA(_:)
+- Name: duplicateGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: duplicateGlobalB(_:)
+- Name: duplicateEmpty
+  Where:
+    Parameters: []
+  SwiftName: duplicateEmptyA()
+- Name: duplicateEmpty
+  Where:
+    Parameters: []
+  SwiftName: duplicateEmptyB()
+- Name: allowedGlobal
+  SwiftPrivate: true
+- Name: allowedGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: allowedGlobalInt(_:)
+- Name: allowedGlobal
+  Where:
+    Parameters:
+    - double
+  SwiftName: allowedGlobalDouble(_:)
+Tags:
+- Name: DiagnosticWidget
+  Methods:
+  - Name: duplicateMethod
+    Where:
+      Parameters:
+      - int
+    SwiftName: duplicateMethodA(_:)
+  - Name: duplicateMethod
+    Where:
+      Parameters:
+      - int
+    SwiftName: duplicateMethodB(_:)
+  - Name: duplicateEmpty
+    Where:
+      Parameters: []
+    SwiftName: duplicateEmptyA()
+  - Name: duplicateEmpty
+    Where:
+      Parameters: []
+    SwiftName: duplicateEmptyB()
+  - Name: allowed
+    SwiftPrivate: true
+  - Name: allowed
+    Where:
+      Parameters:
+      - int
+    SwiftName: allowedInt(_:)
+  - Name: allowed
+    Where:
+      Parameters:
+      - double
+    SwiftName: allowedDouble(_:)
diff --git a/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/WhereParametersDiagnostics.h b/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/WhereParametersDiagnostics.h
new file mode 100644
index 0000000000000..406a848a04b03
--- /dev/null
+++ b/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/WhereParametersDiagnostics.h
@@ -0,0 +1,16 @@
+#ifndef WHERE_PARAMETERS_DIAGNOSTICS_H
+#define WHERE_PARAMETERS_DIAGNOSTICS_H
+
+void duplicateGlobal(int);
+void duplicateEmpty();
+void allowedGlobal(int);
+void allowedGlobal(double);
+
+struct DiagnosticWidget {
+  void duplicateMethod(int);
+  void duplicateEmpty();
+  void allowed(int);
+  void allowed(double);
+};
+
+#endif // WHERE_PARAMETERS_DIAGNOSTICS_H
diff --git a/clang/test/APINotes/where-parameters-diagnostics.cpp b/clang/test/APINotes/where-parameters-diagnostics.cpp
new file mode 100644
index 0000000000000..7bb033973f728
--- /dev/null
+++ b/clang/test/APINotes/where-parameters-diagnostics.cpp
@@ -0,0 +1,41 @@
+// RUN: not %clang_cc1 -fsyntax-only -fapinotes %s -I %S/Inputs/WhereParametersDuplicateSelectorDiag 2>&1 | FileCheck %s --check-prefix=DUPLICATE
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wapinotes -fsyntax-only -I %S/Inputs/Headers %s -x c++ 2>&1 | FileCheck %s --check-prefix=UNMATCHED --implicit-check-not=diagnosticMatchedGlobal --implicit-check-not=diagnosticAliasMatchedGlobal --implicit-check-not=diagnosticMatchedMethod --implicit-check-not=diagnosticAliasMatchedMethod
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter diagnosticBroadGlobal -x c++ | FileCheck %s --check-prefix=BROAD-GLOBAL
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticBroadMethod -x c++ | FileCheck %s --check-prefix=BROAD-METHOD
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter diagnosticMatchedGlobal -x c++ | FileCheck %s --check-prefix=MATCHED-GLOBAL
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter diagnosticAliasMatchedGlobal -x c++ | FileCheck %s --check-prefix=ALIAS-MATCHED-GLOBAL
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticMatchedMethod -x c++ | FileCheck %s --check-prefix=MATCHED-METHOD
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticAliasMatchedMethod -x c++ | FileCheck %s --check-prefix=ALIAS-MATCHED-METHOD
+
+#include "WhereParametersDiagnostics.h"
+
+// DUPLICATE: error: duplicate definition of global function 'duplicateGlobal' with Where.Parameters [int]
+// DUPLICATE: error: duplicate definition of global function 'duplicateEmpty' with Where.Parameters []
+// DUPLICATE: error: duplicate definition of C++ method 'duplicateMethod' with Where.Parameters [int]
+// DUPLICATE: error: duplicate definition of C++ method 'duplicateEmpty' with Where.Parameters []
+
+// UNMATCHED-DAG: warning: API notes entry for 'unmatchedGlobal' has unmatched Where.Parameters [int]
+// UNMATCHED-DAG: warning: API notes entry for 'diagnosticBroadGlobal' has unmatched Where.Parameters [int]
+// UNMATCHED-DAG: warning: API notes entry for 'unmatchedMethod' has unmatched Where.Parameters [int]
+// UNMATCHED-DAG: warning: API notes entry for 'diagnosticBroadMethod' has unmatched Where.Parameters [int]
+
+// BROAD-GLOBAL: FunctionDecl {{.+}} diagnosticBroadGlobal 'void (float)'
+// BROAD-GLOBAL: SwiftPrivateAttr
+// BROAD-GLOBAL-NOT: SwiftNameAttr
+
+// BROAD-METHOD: CXXMethodDecl {{.+}} diagnosticBroadMethod 'void (float)'
+// BROAD-METHOD: SwiftPrivateAttr
+// BROAD-METHOD-NOT: SwiftNameAttr
+
+// MATCHED-GLOBAL: FunctionDecl {{.+}} diagnosticMatchedGlobal 'void (int)'
+// MATCHED-GLOBAL: SwiftNameAttr {{.+}} "diagnosticMatchedGlobal(_:)"
+
+// ALIAS-MATCHED-GLOBAL: FunctionDecl {{.+}} diagnosticAliasMatchedGlobal 'void (DiagnosticAliasInt)'
+// ALIAS-MATCHED-GLOBAL: SwiftNameAttr {{.+}} "diagnosticAliasMatchedGlobal(_:)"
+
+// MATCHED-METHOD: CXXMethodDecl {{.+}} diagnosticMatchedMethod 'void (int)'
+// MATCHED-METHOD: SwiftNameAttr {{.+}} "diagnosticMatchedMethod(_:)"
+
+// ALIAS-MATCHED-METHOD: CXXMethodDecl {{.+}} diagnosticAliasMatchedMethod 'void (DiagnosticAliasInt)'
+// ALIAS-MATCHED-METHOD: SwiftNameAttr {{.+}} "diagnosticAliasMatchedMethod(_:)"

>From c15652651dc55759ea84ee06245743af9f48a734 Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Tue, 14 Jul 2026 14:05:04 +0200
Subject: [PATCH 02/12] [APINotes] Make Where.Parameters diagnostics
 non-loading and and address reviewer feedback

---
 clang/include/clang/APINotes/APINotesReader.h |  4 +-
 clang/include/clang/APINotes/Types.h          |  7 +++
 clang/lib/APINotes/APINotesReader.cpp         | 31 +++++-----
 clang/lib/APINotes/APINotesTypes.cpp          | 25 ++++++++
 clang/lib/APINotes/APINotesYAMLCompiler.cpp   | 31 +---------
 clang/lib/Sema/SemaAPINotes.cpp               | 60 +++++++------------
 6 files changed, 72 insertions(+), 86 deletions(-)

diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h
index 640be8ad5acb3..1289c1366437c 100644
--- a/clang/include/clang/APINotes/APINotesReader.h
+++ b/clang/include/clang/APINotes/APINotesReader.h
@@ -171,7 +171,7 @@ class APINotesReader {
                   llvm::ArrayRef<std::string> Parameters);
 
   /// Collect exact parameter selectors stored for the given C++ method.
-  void collectCXXMethodParameterSelectors(
+  bool collectCXXMethodParameterSelectors(
       ContextID CtxID, llvm::StringRef Name,
       llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors);
 
@@ -202,7 +202,7 @@ class APINotesReader {
                        std::optional<Context> Ctx = std::nullopt);
 
   /// Collect exact parameter selectors stored for the given global function.
-  void collectGlobalFunctionParameterSelectors(
+  bool collectGlobalFunctionParameterSelectors(
       llvm::StringRef Name,
       llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors,
       std::optional<Context> Ctx = std::nullopt);
diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index db00c42f4561b..f0b6f589a7c5e 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -14,6 +14,7 @@
 #include "llvm/ADT/StringRef.h"
 #include <climits>
 #include <optional>
+#include <string>
 #include <vector>
 
 namespace llvm {
@@ -22,6 +23,12 @@ class raw_ostream;
 
 namespace clang {
 namespace api_notes {
+
+std::string
+formatAPINotesParameterSelector(llvm::ArrayRef<llvm::StringRef> Parameters);
+std::string
+formatAPINotesParameterSelector(llvm::ArrayRef<std::string> Parameters);
+
 enum class RetainCountConventionKind {
   None,
   CFReturnsRetained,
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index 944beccb9d2df..9520fbac75b70 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -841,7 +841,7 @@ class APINotesReader::Implementation {
 
   /// Collect exact parameter selectors stored in the given function-like table.
   template <typename TableT>
-  void collectFunctionParameterSelectors(
+  bool collectFunctionParameterSelectors(
       TableT *Table, uint32_t ParentContextID, llvm::StringRef Name,
       llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors);
 
@@ -923,36 +923,33 @@ APINotesReader::Implementation::getIdentifierString(IdentifierID ID) {
 }
 
 template <typename TableT>
-void APINotesReader::Implementation::collectFunctionParameterSelectors(
+bool APINotesReader::Implementation::collectFunctionParameterSelectors(
     TableT *Table, uint32_t ParentContextID, llvm::StringRef Name,
     llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors) {
   if (!Table)
-    return;
+    return true;
 
   std::optional<IdentifierID> NameID = getIdentifier(Name);
   if (!NameID)
-    return;
+    return true;
 
-  for (auto I = Table->key_begin(), E = Table->key_end(); I != E; ++I) {
-    FunctionTableKey Key = I.getInternalKey();
+  for (FunctionTableKey Key : Table->keys()) {
     if (Key.parentContextID != ParentContextID ||
         Key.nameID != static_cast<unsigned>(*NameID) || !Key.parameterTypeIDs)
       continue;
 
     llvm::SmallVector<std::string, 4> ParameterSelector;
     ParameterSelector.reserve(Key.parameterTypeIDs->size());
-    bool Failed = false;
     for (IdentifierID TypeID : *Key.parameterTypeIDs) {
       std::optional<llvm::StringRef> TypeName = getIdentifierString(TypeID);
-      if (!TypeName) {
-        Failed = true;
-        break;
-      }
+      if (!TypeName)
+        return false;
       ParameterSelector.push_back(TypeName->str());
     }
-    if (!Failed)
-      Selectors.push_back(std::move(ParameterSelector));
+    Selectors.push_back(std::move(ParameterSelector));
   }
+
+  return true;
 }
 
 std::optional<FunctionTableKey>
@@ -2413,10 +2410,10 @@ auto APINotesReader::lookupCXXMethod(ContextID CtxID, llvm::StringRef Name,
   return lookupCXXMethodImpl(CtxID, Name, Parameters);
 }
 
-void APINotesReader::collectCXXMethodParameterSelectors(
+bool APINotesReader::collectCXXMethodParameterSelectors(
     ContextID CtxID, llvm::StringRef Name,
     llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors) {
-  Implementation->collectFunctionParameterSelectors(
+  return Implementation->collectFunctionParameterSelectors(
       Implementation->CXXMethodTable.get(), CtxID.Value, Name, Selectors);
 }
 
@@ -2487,12 +2484,12 @@ auto APINotesReader::lookupGlobalFunction(
   return lookupGlobalFunctionImpl(Name, Parameters, Ctx);
 }
 
-void APINotesReader::collectGlobalFunctionParameterSelectors(
+bool APINotesReader::collectGlobalFunctionParameterSelectors(
     llvm::StringRef Name,
     llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors,
     std::optional<Context> Ctx) {
   uint32_t ParentContextID = Ctx ? Ctx->id.Value : static_cast<uint32_t>(-1);
-  Implementation->collectFunctionParameterSelectors(
+  return Implementation->collectFunctionParameterSelectors(
       Implementation->GlobalFunctionTable.get(), ParentContextID, Name,
       Selectors);
 }
diff --git a/clang/lib/APINotes/APINotesTypes.cpp b/clang/lib/APINotes/APINotesTypes.cpp
index 96dd722587c10..7e3f71f719763 100644
--- a/clang/lib/APINotes/APINotesTypes.cpp
+++ b/clang/lib/APINotes/APINotesTypes.cpp
@@ -7,10 +7,35 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/APINotes/Types.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/raw_ostream.h"
 
+namespace {
+template <typename ParameterT>
+std::string
+formatAPINotesParameterSelectorImpl(llvm::ArrayRef<ParameterT> Parameters) {
+  std::string Result;
+  llvm::raw_string_ostream OS(Result);
+  OS << "[";
+  llvm::interleaveComma(Parameters, OS);
+  OS << "]";
+  return Result;
+}
+} // namespace
+
 namespace clang {
 namespace api_notes {
+
+std::string
+formatAPINotesParameterSelector(llvm::ArrayRef<llvm::StringRef> Parameters) {
+  return formatAPINotesParameterSelectorImpl(Parameters);
+}
+
+std::string
+formatAPINotesParameterSelector(llvm::ArrayRef<std::string> Parameters) {
+  return formatAPINotesParameterSelectorImpl(Parameters);
+}
+
 LLVM_DUMP_METHOD void CommonEntityInfo::dump(llvm::raw_ostream &OS) const {
   if (Unavailable)
     OS << "[Unavailable] (" << UnavailableMsg << ")" << ' ';
diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
index 058f9da338661..10db3fbb8e659 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -794,42 +794,17 @@ struct KnownFunctionSelector {
   llvm::ArrayRef<llvm::StringRef> Parameters;
 };
 
-static bool equalParameterSelectors(llvm::ArrayRef<llvm::StringRef> LHS,
-                                    llvm::ArrayRef<llvm::StringRef> RHS) {
-  if (LHS.size() != RHS.size())
-    return false;
-
-  for (unsigned I = 0, E = LHS.size(); I != E; ++I)
-    if (LHS[I] != RHS[I])
-      return false;
-
-  return true;
-}
-
 static bool
 hasFunctionSelector(llvm::ArrayRef<KnownFunctionSelector> KnownSelectors,
                     llvm::StringRef Name,
                     llvm::ArrayRef<llvm::StringRef> Parameters) {
   for (const KnownFunctionSelector &Known : KnownSelectors)
-    if (Known.Name == Name &&
-        equalParameterSelectors(Known.Parameters, Parameters))
+    if (Known.Name == Name && Known.Parameters == Parameters)
       return true;
 
   return false;
 }
 
-static std::string
-formatWhereParameters(llvm::ArrayRef<llvm::StringRef> Parameters) {
-  std::string Result = "[";
-  for (unsigned I = 0, E = Parameters.size(); I != E; ++I) {
-    if (I)
-      Result += ", ";
-    Result += Parameters[I];
-  }
-  Result += "]";
-  return Result;
-}
-
 class YAMLConverter {
   const Module &M;
   APINotesWriter Writer;
@@ -1215,7 +1190,7 @@ class YAMLConverter {
                                 *WhereParameters.second)) {
           emitError(llvm::Twine("duplicate definition of C++ method '") +
                     CXXMethod.Name + "' with Where.Parameters " +
-                    formatWhereParameters(*WhereParameters.second));
+                    formatAPINotesParameterSelector(*WhereParameters.second));
           continue;
         }
         KnownMethodSelectors.push_back(
@@ -1309,7 +1284,7 @@ class YAMLConverter {
                                 *WhereParameters.second)) {
           emitError(llvm::Twine("duplicate definition of global function '") +
                     Function.Name + "' with Where.Parameters " +
-                    formatWhereParameters(*WhereParameters.second));
+                    formatAPINotesParameterSelector(*WhereParameters.second));
           continue;
         }
         KnownFunctionSelectors.push_back(
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index e9e90c93755d3..043af7e57165b 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -1124,20 +1124,7 @@ makeParameterSelectorSet(ArrayRef<SmallVector<std::string, 4>> Selectors) {
   return Set;
 }
 
-static std::string
-formatParameterSelectorForDiagnostic(ArrayRef<std::string> Parameters) {
-  std::string Result = "[";
-  for (unsigned I = 0, E = Parameters.size(); I != E; ++I) {
-    if (I)
-      Result += ", ";
-    Result += Parameters[I];
-  }
-  Result += "]";
-  return Result;
-}
-
-static void collectOverloadParameterSelectors(const Sema &S,
-                                              const FunctionDecl *FD,
+static void collectOverloadParameterSelectors(const Sema &S, FunctionDecl *FD,
                                               APINotesParameterSelectorSet &Set,
                                               bool &IsRepresentative) {
   IsRepresentative = false;
@@ -1153,16 +1140,8 @@ static void collectOverloadParameterSelectors(const Sema &S,
     if (auto Candidates = getAPINotesParameterSelectorCandidates(S, Candidate))
       Set.add(*Candidates);
   };
-
-  for (NamedDecl *ND : FD->getDeclContext()->lookup(FD->getDeclName())) {
-    if (auto *Candidate = dyn_cast<FunctionDecl>(ND))
-      AddCandidate(Candidate);
-  }
-
-  if (FoundRepresentative)
-    return;
-
-  for (Decl *D : FD->getDeclContext()->decls()) {
+  
+  for (Decl *D : FD->getDeclContext()->noload_decls()) {
     auto *ND = dyn_cast<NamedDecl>(D);
     if (!ND || ND->getDeclName() != FD->getDeclName())
       continue;
@@ -1189,7 +1168,8 @@ static void diagnoseUnmatchedParameterSelectors(
     S.Diag(Loc, diag::warn_apinotes_message)
         << (llvm::Twine("API notes entry for '") + Name +
             "' has unmatched Where.Parameters " +
-            formatParameterSelectorForDiagnostic(APINotesSelector.Parameters))
+            api_notes::formatAPINotesParameterSelector(
+                APINotesSelector.Parameters))
                .str();
   }
 }
@@ -1274,13 +1254,14 @@ void Sema::ProcessAPINotes(Decl *D) {
 
           if (DiagnoseUnmatchedSelectors && !DeclarationSelectors.empty()) {
             SmallVector<SmallVector<std::string, 4>, 4> RawAPINotesSelectors;
-            Reader->collectGlobalFunctionParameterSelectors(
-                FD->getName(), RawAPINotesSelectors, APINotesContext);
-            APINotesParameterSelectorSet APINotesSelectors =
-                makeParameterSelectorSet(RawAPINotesSelectors);
-            diagnoseUnmatchedParameterSelectors(
-                *this, FD->getLocation(), FD->getName(), APINotesSelectors,
-                DeclarationSelectors);
+            if (Reader->collectGlobalFunctionParameterSelectors(
+                    FD->getName(), RawAPINotesSelectors, APINotesContext)) {
+              APINotesParameterSelectorSet APINotesSelectors =
+                  makeParameterSelectorSet(RawAPINotesSelectors);
+              diagnoseUnmatchedParameterSelectors(
+                  *this, FD->getLocation(), FD->getName(), APINotesSelectors,
+                  DeclarationSelectors);
+            }
           }
         }
       }
@@ -1495,13 +1476,14 @@ void Sema::ProcessAPINotes(Decl *D) {
                                               DiagnoseUnmatchedSelectors);
             if (DiagnoseUnmatchedSelectors && !DeclarationSelectors.empty()) {
               SmallVector<SmallVector<std::string, 4>, 4> RawAPINotesSelectors;
-              Reader->collectCXXMethodParameterSelectors(
-                  Context->id, MethodName, RawAPINotesSelectors);
-              APINotesParameterSelectorSet APINotesSelectors =
-                  makeParameterSelectorSet(RawAPINotesSelectors);
-              diagnoseUnmatchedParameterSelectors(
-                  *this, CXXMethod->getLocation(), MethodName,
-                  APINotesSelectors, DeclarationSelectors);
+              if (Reader->collectCXXMethodParameterSelectors(
+                      Context->id, MethodName, RawAPINotesSelectors)) {
+                APINotesParameterSelectorSet APINotesSelectors =
+                    makeParameterSelectorSet(RawAPINotesSelectors);
+                diagnoseUnmatchedParameterSelectors(
+                    *this, CXXMethod->getLocation(), MethodName,
+                    APINotesSelectors, DeclarationSelectors);
+              }
             }
           }
         }

>From de864e7d1286f86201e0555a8500a3c39cdcf1f3 Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Tue, 14 Jul 2026 14:12:05 +0200
Subject: [PATCH 03/12] [APINotes] Fix formatting in Where.Parameters
 diagnostics

---
 clang/lib/Sema/SemaAPINotes.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 043af7e57165b..c2ec720b7120e 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -1140,7 +1140,7 @@ static void collectOverloadParameterSelectors(const Sema &S, FunctionDecl *FD,
     if (auto Candidates = getAPINotesParameterSelectorCandidates(S, Candidate))
       Set.add(*Candidates);
   };
-  
+
   for (Decl *D : FD->getDeclContext()->noload_decls()) {
     auto *ND = dyn_cast<NamedDecl>(D);
     if (!ND || ND->getDeclName() != FD->getDeclName())

>From 839e941b7e4600d22f4975a610243c3150cccdc0 Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Thu, 16 Jul 2026 18:49:57 +0200
Subject: [PATCH 04/12] [APINotes] Simplify Where.Parameters diagnostic
 selector matching

---
 clang/lib/Sema/SemaAPINotes.cpp | 36 ++++++++++-----------------------
 1 file changed, 11 insertions(+), 25 deletions(-)

diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index c2ec720b7120e..a8d1babe5dbd9 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -1036,21 +1036,15 @@ struct APINotesParameterSelectorSet {
       add(*Candidates.Desugared);
   }
 
+  void add(ArrayRef<SmallVector<std::string, 4>> ParameterSelectors) {
+    for (const auto &Selector : ParameterSelectors)
+      add(ArrayRef<std::string>(Selector));
+  }
+
   bool contains(ArrayRef<std::string> Parameters) const {
-    for (const APINotesParameterSelector &Selector : Selectors) {
-      if (Selector.Parameters.size() != Parameters.size())
-        continue;
-
-      bool Matches = true;
-      for (unsigned I = 0, E = Parameters.size(); I != E; ++I) {
-        if (Selector.Parameters[I] == Parameters[I])
-          continue;
-        Matches = false;
-        break;
-      }
-      if (Matches)
+    for (const APINotesParameterSelector &Selector : Selectors)
+      if (ArrayRef<std::string>(Selector.Parameters) == Parameters)
         return true;
-    }
 
     return false;
   }
@@ -1116,14 +1110,6 @@ getAPINotesParameterSelectorCandidates(const Sema &S, const FunctionDecl *FD) {
   return Candidates;
 }
 
-static APINotesParameterSelectorSet
-makeParameterSelectorSet(ArrayRef<SmallVector<std::string, 4>> Selectors) {
-  APINotesParameterSelectorSet Set;
-  for (const SmallVector<std::string, 4> &Selector : Selectors)
-    Set.add(Selector);
-  return Set;
-}
-
 static void collectOverloadParameterSelectors(const Sema &S, FunctionDecl *FD,
                                               APINotesParameterSelectorSet &Set,
                                               bool &IsRepresentative) {
@@ -1256,8 +1242,8 @@ void Sema::ProcessAPINotes(Decl *D) {
             SmallVector<SmallVector<std::string, 4>, 4> RawAPINotesSelectors;
             if (Reader->collectGlobalFunctionParameterSelectors(
                     FD->getName(), RawAPINotesSelectors, APINotesContext)) {
-              APINotesParameterSelectorSet APINotesSelectors =
-                  makeParameterSelectorSet(RawAPINotesSelectors);
+              APINotesParameterSelectorSet APINotesSelectors;
+              APINotesSelectors.add(RawAPINotesSelectors);
               diagnoseUnmatchedParameterSelectors(
                   *this, FD->getLocation(), FD->getName(), APINotesSelectors,
                   DeclarationSelectors);
@@ -1478,8 +1464,8 @@ void Sema::ProcessAPINotes(Decl *D) {
               SmallVector<SmallVector<std::string, 4>, 4> RawAPINotesSelectors;
               if (Reader->collectCXXMethodParameterSelectors(
                       Context->id, MethodName, RawAPINotesSelectors)) {
-                APINotesParameterSelectorSet APINotesSelectors =
-                    makeParameterSelectorSet(RawAPINotesSelectors);
+                APINotesParameterSelectorSet APINotesSelectors;
+                APINotesSelectors.add(RawAPINotesSelectors);
                 diagnoseUnmatchedParameterSelectors(
                     *this, CXXMethod->getLocation(), MethodName,
                     APINotesSelectors, DeclarationSelectors);

>From b38e259a8477daa6cade404b2e8bfdd51032c586 Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Tue, 21 Jul 2026 15:00:55 +0200
Subject: [PATCH 05/12] [APINotes] Track unmatched parameter selectors by state

---
 clang/include/clang/APINotes/APINotesReader.h |  33 ++-
 clang/include/clang/APINotes/Types.h          |  67 ++++++
 clang/include/clang/Sema/Sema.h               |   7 +
 clang/lib/APINotes/APINotesReader.cpp         | 135 ++++++++---
 clang/lib/APINotes/APINotesYAMLCompiler.cpp   |  51 ++--
 clang/lib/Sema/Sema.cpp                       |   2 +
 clang/lib/Sema/SemaAPINotes.cpp               | 222 +++++++++---------
 clang/lib/Sema/SemaAPINotesInternal.h         |  52 ++++
 8 files changed, 393 insertions(+), 176 deletions(-)
 create mode 100644 clang/lib/Sema/SemaAPINotesInternal.h

diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h
index 1289c1366437c..3b05dd2ef8cbe 100644
--- a/clang/include/clang/APINotes/APINotesReader.h
+++ b/clang/include/clang/APINotes/APINotesReader.h
@@ -170,10 +170,15 @@ class APINotesReader {
   lookupCXXMethod(ContextID CtxID, llvm::StringRef Name,
                   llvm::ArrayRef<std::string> Parameters);
 
-  /// Collect exact parameter selectors stored for the given C++ method.
-  bool collectCXXMethodParameterSelectors(
-      ContextID CtxID, llvm::StringRef Name,
-      llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors);
+  /// Build the selector key for the given C++ method.
+  std::optional<APINotesFunctionSelectorKey>
+  getCXXMethodSelectorKey(ContextID CtxID, llvm::StringRef Name);
+
+  /// Build the selector key for the given C++ method with an exact parameter
+  /// selector.
+  std::optional<APINotesFunctionSelectorKey>
+  getCXXMethodSelectorKey(ContextID CtxID, llvm::StringRef Name,
+                          llvm::ArrayRef<std::string> Parameters);
 
   /// Look for information regarding the given global variable.
   ///
@@ -201,11 +206,21 @@ class APINotesReader {
                        llvm::ArrayRef<std::string> Parameters,
                        std::optional<Context> Ctx = std::nullopt);
 
-  /// Collect exact parameter selectors stored for the given global function.
-  bool collectGlobalFunctionParameterSelectors(
-      llvm::StringRef Name,
-      llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors,
-      std::optional<Context> Ctx = std::nullopt);
+  /// Build the selector key for the given global function.
+  std::optional<APINotesFunctionSelectorKey>
+  getGlobalFunctionSelectorKey(llvm::StringRef Name,
+                               std::optional<Context> Ctx = std::nullopt);
+
+  /// Build the selector key for the given global function with an exact
+  /// parameter selector.
+  std::optional<APINotesFunctionSelectorKey>
+  getGlobalFunctionSelectorKey(llvm::StringRef Name,
+                               llvm::ArrayRef<std::string> Parameters,
+                               std::optional<Context> Ctx = std::nullopt);
+
+  /// Collect exact parameter selector keys stored by this reader.
+  bool collectExactFunctionParameterSelectors(
+      llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors);
 
   /// Look for information regarding the given enumerator.
   ///
diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index f0b6f589a7c5e..39d6853574b2c 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -11,6 +11,9 @@
 
 #include "clang/Basic/Specifiers.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMapInfo.h"
+#include "llvm/ADT/Hashing.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 #include <climits>
 #include <optional>
@@ -29,6 +32,43 @@ formatAPINotesParameterSelector(llvm::ArrayRef<llvm::StringRef> Parameters);
 std::string
 formatAPINotesParameterSelector(llvm::ArrayRef<std::string> Parameters);
 
+/// Stable reader-facing identity for an API notes function selector entry.
+///
+/// This mirrors the serialized function table key closely enough for Sema-side
+/// diagnostics to use it as a DenseMap key, without exposing the reader's
+/// private FunctionTableKey implementation type.
+struct APINotesFunctionSelectorKey {
+  bool IsCXXMethod = false;
+  uint32_t ParentContextID = 0;
+  uint32_t NameID = 0;
+  std::optional<llvm::SmallVector<uint32_t, 2>> ParameterTypeIDs;
+
+  llvm::hash_code hashValue() const {
+    auto Hash = llvm::hash_combine(IsCXXMethod, ParentContextID, NameID,
+                                   static_cast<bool>(ParameterTypeIDs));
+    if (ParameterTypeIDs) {
+      Hash = llvm::hash_combine(Hash, ParameterTypeIDs->size());
+      for (uint32_t TypeID : *ParameterTypeIDs)
+        Hash = llvm::hash_combine(Hash, TypeID);
+    }
+    return Hash;
+  }
+};
+
+inline bool operator==(const APINotesFunctionSelectorKey &LHS,
+                       const APINotesFunctionSelectorKey &RHS) {
+  return LHS.IsCXXMethod == RHS.IsCXXMethod &&
+         LHS.ParentContextID == RHS.ParentContextID &&
+         LHS.NameID == RHS.NameID &&
+         LHS.ParameterTypeIDs == RHS.ParameterTypeIDs;
+}
+
+/// Exact function selector key plus parameter spelling used for diagnostics.
+struct APINotesFunctionSelector {
+  APINotesFunctionSelectorKey Key;
+  llvm::SmallVector<std::string, 4> Parameters;
+};
+
 enum class RetainCountConventionKind {
   None,
   CFReturnsRetained,
@@ -1008,4 +1048,31 @@ struct ObjCSelectorRef {
 } // namespace api_notes
 } // namespace clang
 
+namespace llvm {
+template <> struct DenseMapInfo<clang::api_notes::APINotesFunctionSelectorKey> {
+  static clang::api_notes::APINotesFunctionSelectorKey getEmptyKey() {
+    clang::api_notes::APINotesFunctionSelectorKey Key;
+    Key.NameID = ~uint32_t(0);
+    return Key;
+  }
+
+  static clang::api_notes::APINotesFunctionSelectorKey getTombstoneKey() {
+    clang::api_notes::APINotesFunctionSelectorKey Key;
+    Key.NameID = ~uint32_t(0) - 1;
+    return Key;
+  }
+
+  static unsigned
+  getHashValue(const clang::api_notes::APINotesFunctionSelectorKey &Key) {
+    return Key.hashValue();
+  }
+
+  static bool
+  isEqual(const clang::api_notes::APINotesFunctionSelectorKey &LHS,
+          const clang::api_notes::APINotesFunctionSelectorKey &RHS) {
+    return LHS == RHS;
+  }
+};
+} // namespace llvm
+
 #endif
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index d46edeb0d2872..778c1a2f5c427 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -156,6 +156,7 @@ enum class OverloadCandidateParamOrder : char;
 enum OverloadCandidateRewriteKind : unsigned;
 class OverloadCandidateSet;
 class Preprocessor;
+struct APINotesSelectorDiagnosticState;
 class SemaAMDGPU;
 class SemaARM;
 class SemaAVR;
@@ -1312,6 +1313,8 @@ class Sema final : public SemaBase {
   SourceManager &SourceMgr;
   api_notes::APINotesManager APINotes;
 
+  std::unique_ptr<APINotesSelectorDiagnosticState> APINotesSelectorDiagnostics;
+
   /// A RAII object to enter scope of a compound statement.
   class CompoundScopeRAII {
   public:
@@ -1664,6 +1667,10 @@ class Sema final : public SemaBase {
   /// Apply the 'Type:' annotation to the specified declaration
   void ApplyAPINotesType(Decl *D, StringRef TypeString);
 
+  /// Diagnose exact API notes selectors that were not matched by any
+  /// declaration processed in this translation unit.
+  void DiagnoseUnusedAPINotesSelectors();
+
   /// Whether APINotes should be gathered for all applicable Swift language
   /// versions, without being applied. Leaving clients of the current module
   /// to select and apply the correct version.
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index 9520fbac75b70..fec23ac955a2a 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -760,6 +760,11 @@ class APINotesReader::Implementation {
   /// The identifier table.
   std::unique_ptr<SerializedIdentifierTable> IdentifierTable;
 
+  /// Lazy reverse lookup cache for identifier IDs. Identifier IDs are dense and
+  /// start at 1. Slot 0 is reserved for the empty identifier.
+  bool IdentifierStringsInitialized = false;
+  llvm::SmallVector<std::optional<llvm::StringRef>, 0> IdentifierStrings;
+
   using SerializedContextIDTable =
       llvm::OnDiskIterableChainedHashTable<ContextIDTableInfo>;
 
@@ -839,11 +844,12 @@ class APINotesReader::Implementation {
   /// the ID is unknown.
   std::optional<llvm::StringRef> getIdentifierString(IdentifierID ID);
 
-  /// Collect exact parameter selectors stored in the given function-like table.
+  /// Collect exact parameter selector keys stored in the given function-like
+  /// table.
   template <typename TableT>
-  bool collectFunctionParameterSelectors(
-      TableT *Table, uint32_t ParentContextID, llvm::StringRef Name,
-      llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors);
+  bool collectExactFunctionParameterSelectors(
+      TableT *Table, bool IsCXXMethod,
+      llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors);
 
   /// Retrieve the selector ID for the given selector, or an empty
   /// optional if the string is unknown.
@@ -913,40 +919,67 @@ APINotesReader::Implementation::getIdentifierString(IdentifierID ID) {
   if (ID == IdentifierID(0))
     return llvm::StringRef();
 
-  for (llvm::StringRef Identifier : IdentifierTable->keys()) {
-    auto KnownID = IdentifierTable->find(Identifier);
-    if (KnownID != IdentifierTable->end() && *KnownID == ID)
-      return Identifier;
-  }
+  if (!IdentifierStringsInitialized) {
+    IdentifierStringsInitialized = true;
+    // keys() and data() iterate over the same serialized entries in lockstep,
+    // so build the reverse cache without doing a lookup for each key.
+    auto Identifiers = IdentifierTable->keys();
+    auto IDs = IdentifierTable->data();
+    auto Identifier = Identifiers.begin();
+    auto KnownID = IDs.begin();
+    auto IdentifierEnd = Identifiers.end();
+    auto KnownIDEnd = IDs.end();
+    for (; Identifier != IdentifierEnd && KnownID != KnownIDEnd;
+         ++Identifier, ++KnownID) {
+      unsigned Index = static_cast<unsigned>(*KnownID);
+      if (IdentifierStrings.size() <= Index)
+        IdentifierStrings.resize(Index + 1);
+      IdentifierStrings[Index] = *Identifier;
+    }
+  }
+
+  unsigned Index = static_cast<unsigned>(ID);
+  if (Index >= IdentifierStrings.size())
+    return std::nullopt;
+  return IdentifierStrings[Index];
+}
 
-  return std::nullopt;
+static APINotesFunctionSelectorKey
+toAPINotesFunctionSelectorKey(const FunctionTableKey &Key, bool IsCXXMethod) {
+  APINotesFunctionSelectorKey SelectorKey;
+  SelectorKey.IsCXXMethod = IsCXXMethod;
+  SelectorKey.ParentContextID = Key.parentContextID;
+  SelectorKey.NameID = Key.nameID;
+  if (Key.parameterTypeIDs) {
+    SelectorKey.ParameterTypeIDs.emplace();
+    SelectorKey.ParameterTypeIDs->reserve(Key.parameterTypeIDs->size());
+    for (IdentifierID TypeID : *Key.parameterTypeIDs)
+      SelectorKey.ParameterTypeIDs->push_back(static_cast<unsigned>(TypeID));
+  }
+  return SelectorKey;
 }
 
 template <typename TableT>
-bool APINotesReader::Implementation::collectFunctionParameterSelectors(
-    TableT *Table, uint32_t ParentContextID, llvm::StringRef Name,
-    llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors) {
+bool APINotesReader::Implementation::collectExactFunctionParameterSelectors(
+    TableT *Table, bool IsCXXMethod,
+    llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors) {
   if (!Table)
     return true;
 
-  std::optional<IdentifierID> NameID = getIdentifier(Name);
-  if (!NameID)
-    return true;
-
   for (FunctionTableKey Key : Table->keys()) {
-    if (Key.parentContextID != ParentContextID ||
-        Key.nameID != static_cast<unsigned>(*NameID) || !Key.parameterTypeIDs)
+    if (!Key.parameterTypeIDs)
       continue;
 
-    llvm::SmallVector<std::string, 4> ParameterSelector;
-    ParameterSelector.reserve(Key.parameterTypeIDs->size());
+    APINotesFunctionSelector Selector;
+    Selector.Key = toAPINotesFunctionSelectorKey(Key, IsCXXMethod);
+    Selector.Parameters.reserve(Key.parameterTypeIDs->size());
     for (IdentifierID TypeID : *Key.parameterTypeIDs) {
       std::optional<llvm::StringRef> TypeName = getIdentifierString(TypeID);
       if (!TypeName)
         return false;
-      ParameterSelector.push_back(TypeName->str());
+      Selector.Parameters.push_back(TypeName->str());
     }
-    Selectors.push_back(std::move(ParameterSelector));
+    Selectors.push_back(std::move(Selector));
   }
 
   return true;
@@ -2410,11 +2443,24 @@ auto APINotesReader::lookupCXXMethod(ContextID CtxID, llvm::StringRef Name,
   return lookupCXXMethodImpl(CtxID, Name, Parameters);
 }
 
-bool APINotesReader::collectCXXMethodParameterSelectors(
+std::optional<APINotesFunctionSelectorKey>
+APINotesReader::getCXXMethodSelectorKey(ContextID CtxID, llvm::StringRef Name) {
+  std::optional<FunctionTableKey> Key =
+      Implementation->getFunctionKey(CtxID.Value, Name);
+  if (!Key)
+    return std::nullopt;
+  return toAPINotesFunctionSelectorKey(*Key, /*IsCXXMethod=*/true);
+}
+
+std::optional<APINotesFunctionSelectorKey>
+APINotesReader::getCXXMethodSelectorKey(
     ContextID CtxID, llvm::StringRef Name,
-    llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors) {
-  return Implementation->collectFunctionParameterSelectors(
-      Implementation->CXXMethodTable.get(), CtxID.Value, Name, Selectors);
+    llvm::ArrayRef<std::string> Parameters) {
+  std::optional<FunctionTableKey> Key =
+      Implementation->getFunctionKey(CtxID.Value, Name, Parameters);
+  if (!Key)
+    return std::nullopt;
+  return toAPINotesFunctionSelectorKey(*Key, /*IsCXXMethod=*/true);
 }
 
 auto APINotesReader::lookupCXXMethodImpl(ContextID CtxID, llvm::StringRef Name)
@@ -2484,14 +2530,35 @@ auto APINotesReader::lookupGlobalFunction(
   return lookupGlobalFunctionImpl(Name, Parameters, Ctx);
 }
 
-bool APINotesReader::collectGlobalFunctionParameterSelectors(
-    llvm::StringRef Name,
-    llvm::SmallVectorImpl<llvm::SmallVector<std::string, 4>> &Selectors,
+std::optional<APINotesFunctionSelectorKey>
+APINotesReader::getGlobalFunctionSelectorKey(llvm::StringRef Name,
+                                             std::optional<Context> Ctx) {
+  std::optional<FunctionTableKey> Key =
+      Implementation->getFunctionKey(Ctx, Name);
+  if (!Key)
+    return std::nullopt;
+  return toAPINotesFunctionSelectorKey(*Key, /*IsCXXMethod=*/false);
+}
+
+std::optional<APINotesFunctionSelectorKey>
+APINotesReader::getGlobalFunctionSelectorKey(
+    llvm::StringRef Name, llvm::ArrayRef<std::string> Parameters,
     std::optional<Context> Ctx) {
-  uint32_t ParentContextID = Ctx ? Ctx->id.Value : static_cast<uint32_t>(-1);
-  return Implementation->collectFunctionParameterSelectors(
-      Implementation->GlobalFunctionTable.get(), ParentContextID, Name,
-      Selectors);
+  std::optional<FunctionTableKey> Key =
+      Implementation->getFunctionKey(Ctx, Name, Parameters);
+  if (!Key)
+    return std::nullopt;
+  return toAPINotesFunctionSelectorKey(*Key, /*IsCXXMethod=*/false);
+}
+
+bool APINotesReader::collectExactFunctionParameterSelectors(
+    llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors) {
+  return Implementation->collectExactFunctionParameterSelectors(
+             Implementation->GlobalFunctionTable.get(), /*IsCXXMethod=*/false,
+             Selectors) &&
+         Implementation->collectExactFunctionParameterSelectors(
+             Implementation->CXXMethodTable.get(), /*IsCXXMethod=*/true,
+             Selectors);
 }
 
 auto APINotesReader::lookupGlobalFunctionImpl(llvm::StringRef Name,
diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
index 10db3fbb8e659..e01dc84dde941 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -18,12 +18,16 @@
 #include "clang/APINotes/Types.h"
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/Specifiers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/VersionTuple.h"
 #include "llvm/Support/YAMLTraits.h"
+#include "llvm/Support/raw_ostream.h"
 #include <optional>
+#include <string>
 #include <type_traits>
 #include <vector>
 
@@ -789,20 +793,19 @@ bool clang::api_notes::parseAndDumpAPINotes(StringRef YI,
 namespace {
 using namespace api_notes;
 
-struct KnownFunctionSelector {
-  llvm::StringRef Name;
-  llvm::ArrayRef<llvm::StringRef> Parameters;
-};
-
-static bool
-hasFunctionSelector(llvm::ArrayRef<KnownFunctionSelector> KnownSelectors,
-                    llvm::StringRef Name,
-                    llvm::ArrayRef<llvm::StringRef> Parameters) {
-  for (const KnownFunctionSelector &Known : KnownSelectors)
-    if (Known.Name == Name && Known.Parameters == Parameters)
-      return true;
-
-  return false;
+static std::string
+getFunctionSelectorKey(llvm::StringRef Name,
+                       llvm::ArrayRef<llvm::StringRef> Parameters) {
+  llvm::SmallString<64> Key;
+  llvm::raw_svector_ostream OS(Key);
+  auto AppendKeyPart = [&OS](llvm::StringRef Part) {
+    OS << Part.size() << ':' << Part;
+  };
+
+  AppendKeyPart(Name);
+  OS << ';';
+  llvm::interleave(Parameters, OS, AppendKeyPart, "");
+  return Key.str().str();
 }
 
 class YAMLConverter {
@@ -1179,22 +1182,22 @@ class YAMLConverter {
       Writer.addField(TagCtxID, Field.Name, FI, SwiftVersion);
     }
 
-    llvm::SmallVector<KnownFunctionSelector, 4> KnownMethodSelectors;
+    llvm::StringSet<> KnownMethodSelectors;
     for (const auto &CXXMethod : T.Methods) {
       auto WhereParameters = getWhereParameters(CXXMethod);
       if (!WhereParameters.first)
         continue;
 
       if (WhereParameters.second) {
-        if (hasFunctionSelector(KnownMethodSelectors, CXXMethod.Name,
-                                *WhereParameters.second)) {
+        if (!KnownMethodSelectors
+                 .insert(getFunctionSelectorKey(CXXMethod.Name,
+                                                *WhereParameters.second))
+                 .second) {
           emitError(llvm::Twine("duplicate definition of C++ method '") +
                     CXXMethod.Name + "' with Where.Parameters " +
                     formatAPINotesParameterSelector(*WhereParameters.second));
           continue;
         }
-        KnownMethodSelectors.push_back(
-            {CXXMethod.Name, *WhereParameters.second});
       }
 
       CXXMethodInfo MI;
@@ -1273,22 +1276,22 @@ class YAMLConverter {
 
     // Write all global functions.
     llvm::StringSet<> KnownNameOnlyFunctions;
-    llvm::SmallVector<KnownFunctionSelector, 4> KnownFunctionSelectors;
+    llvm::StringSet<> KnownFunctionSelectors;
     for (const auto &Function : TLItems.Functions) {
       auto WhereParameters = getWhereParameters(Function);
       if (!WhereParameters.first)
         continue;
 
       if (WhereParameters.second) {
-        if (hasFunctionSelector(KnownFunctionSelectors, Function.Name,
-                                *WhereParameters.second)) {
+        if (!KnownFunctionSelectors
+                 .insert(getFunctionSelectorKey(Function.Name,
+                                                *WhereParameters.second))
+                 .second) {
           emitError(llvm::Twine("duplicate definition of global function '") +
                     Function.Name + "' with Where.Parameters " +
                     formatAPINotesParameterSelector(*WhereParameters.second));
           continue;
         }
-        KnownFunctionSelectors.push_back(
-            {Function.Name, *WhereParameters.second});
       }
 
       // Check for duplicate name-only global functions.
diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp
index 9f962912148ab..3b987bb308d32 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -11,6 +11,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "SemaAPINotesInternal.h"
 #include "UsedDeclVisitor.h"
 #include "clang/AST/ASTContext.h"
 #include "clang/AST/ASTDiagnostic.h"
@@ -1327,6 +1328,7 @@ void Sema::ActOnEndOfTranslationUnit() {
   DiagnoseUnterminatedPragmaAttribute();
   OpenMP().DiagnoseUnterminatedOpenMPDeclareTarget();
   DiagnosePrecisionLossInComplexDivision();
+  DiagnoseUnusedAPINotesSelectors();
 
   // All delayed member exception specs should be checked or we end up accepting
   // incompatible declarations.
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index a8d1babe5dbd9..92b5d7080b7ac 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -10,6 +10,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "SemaAPINotesInternal.h"
 #include "TypeLocBuilder.h"
 #include "clang/APINotes/APINotesReader.h"
 #include "clang/APINotes/Types.h"
@@ -1017,44 +1018,6 @@ struct APINotesParameterSelectorCandidates {
   std::optional<APINotesParameterSelector> Desugared;
 };
 
-struct APINotesParameterSelectorSet {
-  SmallVector<APINotesParameterSelector, 4> Selectors;
-
-  void add(const APINotesParameterSelector &Selector) {
-    Selectors.push_back(Selector);
-  }
-
-  void add(ArrayRef<std::string> Parameters) {
-    APINotesParameterSelector Selector;
-    Selector.Parameters.append(Parameters.begin(), Parameters.end());
-    add(Selector);
-  }
-
-  void add(const APINotesParameterSelectorCandidates &Candidates) {
-    add(Candidates.Source);
-    if (Candidates.Desugared)
-      add(*Candidates.Desugared);
-  }
-
-  void add(ArrayRef<SmallVector<std::string, 4>> ParameterSelectors) {
-    for (const auto &Selector : ParameterSelectors)
-      add(ArrayRef<std::string>(Selector));
-  }
-
-  bool contains(ArrayRef<std::string> Parameters) const {
-    for (const APINotesParameterSelector &Selector : Selectors)
-      if (ArrayRef<std::string>(Selector.Parameters) == Parameters)
-        return true;
-
-    return false;
-  }
-
-  bool empty() const { return Selectors.empty(); }
-
-  auto begin() const { return Selectors.begin(); }
-  auto end() const { return Selectors.end(); }
-};
-
 static PrintingPolicy
 getAPINotesParameterSelectorPrintingPolicy(const ASTContext &Context) {
   PrintingPolicy Policy(Context.getLangOpts());
@@ -1110,53 +1073,66 @@ getAPINotesParameterSelectorCandidates(const Sema &S, const FunctionDecl *FD) {
   return Candidates;
 }
 
-static void collectOverloadParameterSelectors(const Sema &S, FunctionDecl *FD,
-                                              APINotesParameterSelectorSet &Set,
-                                              bool &IsRepresentative) {
-  IsRepresentative = false;
-  bool FoundRepresentative = false;
-
-  auto AddCandidate = [&](const FunctionDecl *Candidate) {
-    if (!FoundRepresentative) {
-      IsRepresentative =
-          Candidate->getCanonicalDecl() == FD->getCanonicalDecl();
-      FoundRepresentative = true;
-    }
-
-    if (auto Candidates = getAPINotesParameterSelectorCandidates(S, Candidate))
-      Set.add(*Candidates);
-  };
-
-  for (Decl *D : FD->getDeclContext()->noload_decls()) {
-    auto *ND = dyn_cast<NamedDecl>(D);
-    if (!ND || ND->getDeclName() != FD->getDeclName())
-      continue;
+static api_notes::APINotesFunctionSelectorKey
+getBroadAPINotesSelectorKey(api_notes::APINotesFunctionSelectorKey Key) {
+  Key.ParameterTypeIDs = std::nullopt;
+  return Key;
+}
 
-    if (auto *Candidate = dyn_cast<FunctionDecl>(ND))
-      AddCandidate(Candidate);
+static APINotesSelectorDiagnosticReaderState &
+getAPINotesSelectorDiagnosticState(Sema &S, api_notes::APINotesReader *Reader) {
+  if (!S.APINotesSelectorDiagnostics)
+    S.APINotesSelectorDiagnostics =
+        std::make_unique<APINotesSelectorDiagnosticState>();
+  auto &State = S.APINotesSelectorDiagnostics->Readers[Reader];
+  if (State.Initialized)
+    return State;
+
+  State.Initialized = true;
+  SmallVector<api_notes::APINotesFunctionSelector, 4> Selectors;
+  if (!Reader->collectExactFunctionParameterSelectors(Selectors))
+    return State;
+
+  State.Selectors.reserve(Selectors.size());
+  State.SelectorIndices.reserve(Selectors.size());
+  State.SeenNames.reserve(Selectors.size());
+  for (auto &Selector : Selectors) {
+    unsigned Index = State.Selectors.size();
+    APINotesSelectorDiagnosticEntry Entry;
+    Entry.BroadKey = getBroadAPINotesSelectorKey(Selector.Key);
+    Entry.Parameters = std::move(Selector.Parameters);
+    State.Selectors.push_back(std::move(Entry));
+    State.SelectorIndices.insert({Selector.Key, Index});
   }
-
-  if (!FoundRepresentative)
-    AddCandidate(FD);
+  return State;
 }
 
-static void diagnoseUnmatchedParameterSelectors(
-    Sema &S, SourceLocation Loc, StringRef Name,
-    const APINotesParameterSelectorSet &APINotesSelectors,
-    const APINotesParameterSelectorSet &DeclarationSelectors) {
-  if (DeclarationSelectors.empty())
-    return;
+static void noteSeenAPINotesSelectorName(
+    APINotesSelectorDiagnosticReaderState &State,
+    const api_notes::APINotesFunctionSelectorKey &BroadKey, StringRef Name,
+    SourceLocation Loc) {
+  State.SeenNames.insert({BroadKey, {Loc, Name.str()}});
+}
 
-  for (const APINotesParameterSelector &APINotesSelector : APINotesSelectors) {
-    if (DeclarationSelectors.contains(APINotesSelector.Parameters))
-      continue;
+static void
+markAPINotesSelectorUsed(APINotesSelectorDiagnosticReaderState &State,
+                         const api_notes::APINotesFunctionSelectorKey &Key) {
+  auto KnownSelector = State.SelectorIndices.find(Key);
+  if (KnownSelector != State.SelectorIndices.end())
+    State.Selectors[KnownSelector->second].Used = true;
+}
 
-    S.Diag(Loc, diag::warn_apinotes_message)
-        << (llvm::Twine("API notes entry for '") + Name +
-            "' has unmatched Where.Parameters " +
-            api_notes::formatAPINotesParameterSelector(
-                APINotesSelector.Parameters))
-               .str();
+static void markAPINotesSelectorCandidatesUsed(
+    APINotesSelectorDiagnosticReaderState &State,
+    llvm::function_ref<std::optional<api_notes::APINotesFunctionSelectorKey>(
+        ArrayRef<std::string>)>
+        GetSelectorKey,
+    const APINotesParameterSelectorCandidates &Candidates) {
+  if (auto Key = GetSelectorKey(Candidates.Source.Parameters))
+    markAPINotesSelectorUsed(State, *Key);
+  if (Candidates.Desugared) {
+    if (auto Key = GetSelectorKey(Candidates.Desugared->Parameters))
+      markAPINotesSelectorUsed(State, *Key);
   }
 }
 
@@ -1220,11 +1196,6 @@ void Sema::ProcessAPINotes(Decl *D) {
         auto ParameterSelectorCandidates =
             getAPINotesParameterSelectorCandidates(*this, FD);
 
-        APINotesParameterSelectorSet DeclarationSelectors;
-        bool DiagnoseUnmatchedSelectors = false;
-        collectOverloadParameterSelectors(*this, FD, DeclarationSelectors,
-                                          DiagnoseUnmatchedSelectors);
-
         for (auto Reader : Readers) {
           auto Info =
               Reader->lookupGlobalFunction(FD->getName(), APINotesContext);
@@ -1238,16 +1209,22 @@ void Sema::ProcessAPINotes(Decl *D) {
                                                       APINotesContext);
                 });
 
-          if (DiagnoseUnmatchedSelectors && !DeclarationSelectors.empty()) {
-            SmallVector<SmallVector<std::string, 4>, 4> RawAPINotesSelectors;
-            if (Reader->collectGlobalFunctionParameterSelectors(
-                    FD->getName(), RawAPINotesSelectors, APINotesContext)) {
-              APINotesParameterSelectorSet APINotesSelectors;
-              APINotesSelectors.add(RawAPINotesSelectors);
-              diagnoseUnmatchedParameterSelectors(
-                  *this, FD->getLocation(), FD->getName(), APINotesSelectors,
-                  DeclarationSelectors);
-            }
+          if (ParameterSelectorCandidates &&
+              !Diags.isIgnored(diag::warn_apinotes_message,
+                               FD->getLocation())) {
+            auto &DiagnosticState =
+                getAPINotesSelectorDiagnosticState(*this, Reader);
+            if (auto BroadKey = Reader->getGlobalFunctionSelectorKey(
+                    FD->getName(), APINotesContext))
+              noteSeenAPINotesSelectorName(DiagnosticState, *BroadKey,
+                                           FD->getName(), FD->getLocation());
+            markAPINotesSelectorCandidatesUsed(
+                DiagnosticState,
+                [&](ArrayRef<std::string> Parameters) {
+                  return Reader->getGlobalFunctionSelectorKey(
+                      FD->getName(), Parameters, APINotesContext);
+                },
+                *ParameterSelectorCandidates);
           }
         }
       }
@@ -1455,21 +1432,23 @@ void Sema::ProcessAPINotes(Decl *D) {
                                                    Parameters);
                   });
 
-            APINotesParameterSelectorSet DeclarationSelectors;
-            bool DiagnoseUnmatchedSelectors = false;
-            collectOverloadParameterSelectors(*this, CXXMethod,
-                                              DeclarationSelectors,
-                                              DiagnoseUnmatchedSelectors);
-            if (DiagnoseUnmatchedSelectors && !DeclarationSelectors.empty()) {
-              SmallVector<SmallVector<std::string, 4>, 4> RawAPINotesSelectors;
-              if (Reader->collectCXXMethodParameterSelectors(
-                      Context->id, MethodName, RawAPINotesSelectors)) {
-                APINotesParameterSelectorSet APINotesSelectors;
-                APINotesSelectors.add(RawAPINotesSelectors);
-                diagnoseUnmatchedParameterSelectors(
-                    *this, CXXMethod->getLocation(), MethodName,
-                    APINotesSelectors, DeclarationSelectors);
-              }
+            if (ParameterSelectorCandidates &&
+                !Diags.isIgnored(diag::warn_apinotes_message,
+                                 CXXMethod->getLocation())) {
+              auto &DiagnosticState =
+                  getAPINotesSelectorDiagnosticState(*this, Reader);
+              if (auto BroadKey =
+                      Reader->getCXXMethodSelectorKey(Context->id, MethodName))
+                noteSeenAPINotesSelectorName(DiagnosticState, *BroadKey,
+                                             MethodName,
+                                             CXXMethod->getLocation());
+              markAPINotesSelectorCandidatesUsed(
+                  DiagnosticState,
+                  [&](ArrayRef<std::string> Parameters) {
+                    return Reader->getCXXMethodSelectorKey(
+                        Context->id, MethodName, Parameters);
+                  },
+                  *ParameterSelectorCandidates);
             }
           }
         }
@@ -1497,3 +1476,28 @@ void Sema::ProcessAPINotes(Decl *D) {
     }
   }
 }
+
+void Sema::DiagnoseUnusedAPINotesSelectors() {
+  if (!APINotesSelectorDiagnostics)
+    return;
+
+  for (auto &ReaderSelectors : APINotesSelectorDiagnostics->Readers) {
+    auto &State = ReaderSelectors.second;
+    for (const auto &Selector : State.Selectors) {
+      if (Selector.Used)
+        continue;
+
+      auto SeenName = State.SeenNames.find(Selector.BroadKey);
+      if (SeenName == State.SeenNames.end())
+        continue;
+
+      Diag(SeenName->second.Loc, diag::warn_apinotes_message)
+          << (llvm::Twine("API notes entry for '") + SeenName->second.Name +
+              "' has unmatched Where.Parameters " +
+              api_notes::formatAPINotesParameterSelector(Selector.Parameters))
+                 .str();
+    }
+  }
+
+  APINotesSelectorDiagnostics.reset();
+}
diff --git a/clang/lib/Sema/SemaAPINotesInternal.h b/clang/lib/Sema/SemaAPINotesInternal.h
new file mode 100644
index 0000000000000..1b9e2ffa60bb8
--- /dev/null
+++ b/clang/lib/Sema/SemaAPINotesInternal.h
@@ -0,0 +1,52 @@
+//===--- SemaAPINotesInternal.h - API Notes Sema Internals ------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_SEMA_SEMAAPINOTESINTERNAL_H
+#define LLVM_CLANG_LIB_SEMA_SEMAAPINOTESINTERNAL_H
+
+#include "clang/APINotes/Types.h"
+#include "clang/Basic/SourceLocation.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include <string>
+
+namespace clang {
+namespace api_notes {
+class APINotesReader;
+}
+
+struct APINotesSelectorDiagnosticEntry {
+  api_notes::APINotesFunctionSelectorKey BroadKey;
+  llvm::SmallVector<std::string, 4> Parameters;
+  bool Used = false;
+};
+
+struct APINotesSelectorDiagnosticName {
+  SourceLocation Loc;
+  std::string Name;
+};
+
+struct APINotesSelectorDiagnosticReaderState {
+  bool Initialized = false;
+  llvm::DenseMap<api_notes::APINotesFunctionSelectorKey, unsigned>
+      SelectorIndices;
+  llvm::DenseMap<api_notes::APINotesFunctionSelectorKey,
+                 APINotesSelectorDiagnosticName>
+      SeenNames;
+  llvm::SmallVector<APINotesSelectorDiagnosticEntry, 4> Selectors;
+};
+
+struct APINotesSelectorDiagnosticState {
+  llvm::DenseMap<api_notes::APINotesReader *,
+                 APINotesSelectorDiagnosticReaderState>
+      Readers;
+};
+
+} // namespace clang
+
+#endif // LLVM_CLANG_LIB_SEMA_SEMAAPINOTESINTERNAL_H

>From 30f2f9a03fca93e5c741b5d40c95a9cb4e084475 Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Fri, 24 Jul 2026 15:38:51 +0200
Subject: [PATCH 06/12] [APINotes] Address selector diagnostic review feedback

---
 clang/include/clang/APINotes/Types.h          | 147 +++++++++++++-----
 clang/lib/APINotes/APINotesFormat.h           |  61 --------
 clang/lib/APINotes/APINotesReader.cpp         |  54 +++----
 clang/lib/APINotes/APINotesYAMLCompiler.cpp   |   9 +-
 clang/lib/Sema/SemaAPINotes.cpp               | 117 ++++++--------
 clang/lib/Sema/SemaAPINotesInternal.h         |  63 +++++++-
 .../APINotes.apinotes                         |  66 --------
 .../WhereParametersDiagnostics.h              |  16 --
 .../APINotes/where-parameters-diagnostics.cpp | 118 ++++++++++++--
 9 files changed, 345 insertions(+), 306 deletions(-)
 delete mode 100644 clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/APINotes.apinotes
 delete mode 100644 clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/WhereParametersDiagnostics.h

diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index 39d6853574b2c..a32c42b829119 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -13,6 +13,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMapInfo.h"
 #include "llvm/ADT/Hashing.h"
+#include "llvm/ADT/PointerEmbeddedInt.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 #include <climits>
@@ -32,43 +33,6 @@ formatAPINotesParameterSelector(llvm::ArrayRef<llvm::StringRef> Parameters);
 std::string
 formatAPINotesParameterSelector(llvm::ArrayRef<std::string> Parameters);
 
-/// Stable reader-facing identity for an API notes function selector entry.
-///
-/// This mirrors the serialized function table key closely enough for Sema-side
-/// diagnostics to use it as a DenseMap key, without exposing the reader's
-/// private FunctionTableKey implementation type.
-struct APINotesFunctionSelectorKey {
-  bool IsCXXMethod = false;
-  uint32_t ParentContextID = 0;
-  uint32_t NameID = 0;
-  std::optional<llvm::SmallVector<uint32_t, 2>> ParameterTypeIDs;
-
-  llvm::hash_code hashValue() const {
-    auto Hash = llvm::hash_combine(IsCXXMethod, ParentContextID, NameID,
-                                   static_cast<bool>(ParameterTypeIDs));
-    if (ParameterTypeIDs) {
-      Hash = llvm::hash_combine(Hash, ParameterTypeIDs->size());
-      for (uint32_t TypeID : *ParameterTypeIDs)
-        Hash = llvm::hash_combine(Hash, TypeID);
-    }
-    return Hash;
-  }
-};
-
-inline bool operator==(const APINotesFunctionSelectorKey &LHS,
-                       const APINotesFunctionSelectorKey &RHS) {
-  return LHS.IsCXXMethod == RHS.IsCXXMethod &&
-         LHS.ParentContextID == RHS.ParentContextID &&
-         LHS.NameID == RHS.NameID &&
-         LHS.ParameterTypeIDs == RHS.ParameterTypeIDs;
-}
-
-/// Exact function selector key plus parameter spelling used for diagnostics.
-struct APINotesFunctionSelector {
-  APINotesFunctionSelectorKey Key;
-  llvm::SmallVector<std::string, 4> Parameters;
-};
-
 enum class RetainCountConventionKind {
   None,
   CFReturnsRetained,
@@ -1035,6 +999,89 @@ struct Context {
   Context(ContextID id, ContextKind kind) : id(id), kind(kind) {}
 };
 
+using IdentifierID = llvm::PointerEmbeddedInt<unsigned, 31>;
+
+/// A key for a stored global-function or C++-method API notes entry.
+///
+/// The key is represented by the ID of its parent context, the declaration
+/// name, and optional exact parameter types.
+struct FunctionTableKey {
+  uint32_t parentContextID;
+  uint32_t nameID;
+  std::optional<llvm::SmallVector<IdentifierID, 2>> parameterTypeIDs;
+
+  FunctionTableKey() : parentContextID(-1), nameID(-1) {}
+
+  FunctionTableKey(uint32_t ParentContextID, uint32_t NameID)
+      : parentContextID(ParentContextID), nameID(NameID) {}
+
+  FunctionTableKey(uint32_t ParentContextID, uint32_t NameID,
+                   const llvm::SmallVectorImpl<IdentifierID> &ParameterTypeIDs)
+      : parentContextID(ParentContextID), nameID(NameID) {
+    parameterTypeIDs.emplace(ParameterTypeIDs.begin(), ParameterTypeIDs.end());
+  }
+
+  FunctionTableKey(std::optional<Context> ParentCtx, IdentifierID NameID)
+      : parentContextID(ParentCtx ? ParentCtx->id.Value
+                                  : static_cast<uint32_t>(-1)),
+        nameID(NameID) {}
+
+  FunctionTableKey(std::optional<Context> ParentCtx, IdentifierID NameID,
+                   const llvm::SmallVectorImpl<IdentifierID> &ParameterTypeIDs)
+      : parentContextID(ParentCtx ? ParentCtx->id.Value
+                                  : static_cast<uint32_t>(-1)),
+        nameID(NameID) {
+    parameterTypeIDs.emplace(ParameterTypeIDs.begin(), ParameterTypeIDs.end());
+  }
+
+  llvm::hash_code hashValue() const {
+    auto Hash = llvm::hash_combine(parentContextID, nameID,
+                                   static_cast<bool>(parameterTypeIDs));
+    if (parameterTypeIDs) {
+      Hash = llvm::hash_combine(Hash, parameterTypeIDs->size());
+      for (IdentifierID TypeID : *parameterTypeIDs)
+        Hash = llvm::hash_combine(Hash, static_cast<unsigned>(TypeID));
+    }
+    return Hash;
+  }
+};
+
+inline bool operator==(const FunctionTableKey &LHS,
+                       const FunctionTableKey &RHS) {
+  return LHS.parentContextID == RHS.parentContextID &&
+         LHS.nameID == RHS.nameID &&
+         LHS.parameterTypeIDs == RHS.parameterTypeIDs;
+}
+
+/// Stable reader-facing identity for an API notes function selector entry.
+///
+/// The key keeps the serialized function table identity together with the table
+/// kind. parentContextID names only the declaration context and does not say
+/// whether the entry came from the global-function or C++-method table.
+struct APINotesFunctionSelectorKey {
+  FunctionTableKey Key;
+  bool IsCXXMethod = false;
+
+  APINotesFunctionSelectorKey getWithoutParameterSelector() const {
+    return {FunctionTableKey(Key.parentContextID, Key.nameID), IsCXXMethod};
+  }
+
+  llvm::hash_code hashValue() const {
+    return llvm::hash_combine(Key.hashValue(), IsCXXMethod);
+  }
+};
+
+inline bool operator==(const APINotesFunctionSelectorKey &LHS,
+                       const APINotesFunctionSelectorKey &RHS) {
+  return LHS.Key == RHS.Key && LHS.IsCXXMethod == RHS.IsCXXMethod;
+}
+
+/// Exact function selector key plus parameter spellings used for diagnostics.
+struct APINotesFunctionSelector {
+  APINotesFunctionSelectorKey Key;
+  llvm::SmallVector<std::string, 4> Parameters;
+};
+
 /// A temporary reference to an Objective-C selector, suitable for
 /// referencing selector data on the stack.
 ///
@@ -1049,16 +1096,38 @@ struct ObjCSelectorRef {
 } // namespace clang
 
 namespace llvm {
+template <> struct DenseMapInfo<clang::api_notes::FunctionTableKey> {
+  static clang::api_notes::FunctionTableKey getEmptyKey() {
+    return {0, ~uint32_t(0)};
+  }
+
+  static clang::api_notes::FunctionTableKey getTombstoneKey() {
+    return {0, ~uint32_t(0) - 1};
+  }
+
+  static unsigned getHashValue(const clang::api_notes::FunctionTableKey &Key) {
+    return Key.hashValue();
+  }
+
+  static bool isEqual(const clang::api_notes::FunctionTableKey &LHS,
+                      const clang::api_notes::FunctionTableKey &RHS) {
+    return LHS == RHS;
+  }
+};
+
 template <> struct DenseMapInfo<clang::api_notes::APINotesFunctionSelectorKey> {
+  // The local Key values below are DenseMap sentinels for the wrapper type.
+  // Key.Key stores the wrapped FunctionTableKey sentinel.
   static clang::api_notes::APINotesFunctionSelectorKey getEmptyKey() {
     clang::api_notes::APINotesFunctionSelectorKey Key;
-    Key.NameID = ~uint32_t(0);
+    Key.Key = DenseMapInfo<clang::api_notes::FunctionTableKey>::getEmptyKey();
     return Key;
   }
 
   static clang::api_notes::APINotesFunctionSelectorKey getTombstoneKey() {
     clang::api_notes::APINotesFunctionSelectorKey Key;
-    Key.NameID = ~uint32_t(0) - 1;
+    Key.Key =
+        DenseMapInfo<clang::api_notes::FunctionTableKey>::getTombstoneKey();
     return Key;
   }
 
diff --git a/clang/lib/APINotes/APINotesFormat.h b/clang/lib/APINotes/APINotesFormat.h
index f34ca2e2e363f..30fc8599349bf 100644
--- a/clang/lib/APINotes/APINotesFormat.h
+++ b/clang/lib/APINotes/APINotesFormat.h
@@ -35,7 +35,6 @@ const uint16_t VERSION_MINOR = 41; // 39 for BoundsSafety;
 const uint8_t kSwiftConforms = 1;
 const uint8_t kSwiftDoesNotConform = 2;
 
-using IdentifierID = llvm::PointerEmbeddedInt<unsigned, 31>;
 using IdentifierIDField = llvm::BCVBR<16>;
 
 using SelectorID = llvm::PointerEmbeddedInt<unsigned, 31>;
@@ -365,47 +364,6 @@ constexpr uint8_t FunctionKeyHasParameterSelector = 0x01;
 constexpr unsigned FunctionTableKeyBaseLength =
     sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t) + sizeof(uint16_t);
 
-struct FunctionTableKey {
-  uint32_t parentContextID;
-  uint32_t nameID;
-  std::optional<llvm::SmallVector<IdentifierID, 2>> parameterTypeIDs;
-
-  FunctionTableKey() : parentContextID(-1), nameID(-1) {}
-
-  FunctionTableKey(uint32_t ParentContextID, uint32_t NameID)
-      : parentContextID(ParentContextID), nameID(NameID) {}
-
-  FunctionTableKey(uint32_t ParentContextID, uint32_t NameID,
-                   const llvm::SmallVectorImpl<IdentifierID> &ParameterTypeIDs)
-      : parentContextID(ParentContextID), nameID(NameID) {
-    parameterTypeIDs.emplace(ParameterTypeIDs.begin(), ParameterTypeIDs.end());
-  }
-
-  FunctionTableKey(std::optional<Context> ParentCtx, IdentifierID NameID)
-      : parentContextID(ParentCtx ? ParentCtx->id.Value
-                                  : static_cast<uint32_t>(-1)),
-        nameID(NameID) {}
-
-  FunctionTableKey(std::optional<Context> ParentCtx, IdentifierID NameID,
-                   const llvm::SmallVectorImpl<IdentifierID> &ParameterTypeIDs)
-      : parentContextID(ParentCtx ? ParentCtx->id.Value
-                                  : static_cast<uint32_t>(-1)),
-        nameID(NameID) {
-    parameterTypeIDs.emplace(ParameterTypeIDs.begin(), ParameterTypeIDs.end());
-  }
-
-  llvm::hash_code hashValue() const {
-    auto Hash = llvm::hash_combine(parentContextID, nameID,
-                                   static_cast<bool>(parameterTypeIDs));
-    if (parameterTypeIDs) {
-      Hash = llvm::hash_combine(Hash, parameterTypeIDs->size());
-      for (IdentifierID TypeID : *parameterTypeIDs)
-        Hash = llvm::hash_combine(Hash, static_cast<unsigned>(TypeID));
-    }
-    return Hash;
-  }
-};
-
 template <typename GetIdentifierFn>
 std::optional<FunctionTableKey>
 getFunctionKeyImpl(uint32_t ParentContextID, llvm::StringRef Name,
@@ -438,13 +396,6 @@ getFunctionKeyImpl(uint32_t ParentContextID, llvm::StringRef Name,
   return FunctionTableKey(ParentContextID, *NameID, ParameterTypeIDs);
 }
 
-inline bool operator==(const FunctionTableKey &lhs,
-                       const FunctionTableKey &rhs) {
-  return lhs.parentContextID == rhs.parentContextID &&
-         lhs.nameID == rhs.nameID &&
-         lhs.parameterTypeIDs == rhs.parameterTypeIDs;
-}
-
 } // namespace api_notes
 } // namespace clang
 
@@ -492,18 +443,6 @@ template <> struct DenseMapInfo<clang::api_notes::SingleDeclTableKey> {
   }
 };
 
-template <> struct DenseMapInfo<clang::api_notes::FunctionTableKey> {
-  static unsigned
-  getHashValue(const clang::api_notes::FunctionTableKey &value) {
-    return value.hashValue();
-  }
-
-  static bool isEqual(const clang::api_notes::FunctionTableKey &lhs,
-                      const clang::api_notes::FunctionTableKey &rhs) {
-    return lhs == rhs;
-  }
-};
-
 } // namespace llvm
 
 #endif
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index fec23ac955a2a..16951d185f124 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -15,6 +15,7 @@
 #include "clang/APINotes/APINotesReader.h"
 #include "APINotesFormat.h"
 #include "clang/APINotes/Types.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/Hashing.h"
 #include "llvm/Bitstream/BitstreamReader.h"
 #include "llvm/Support/DJB.h"
@@ -760,10 +761,9 @@ class APINotesReader::Implementation {
   /// The identifier table.
   std::unique_ptr<SerializedIdentifierTable> IdentifierTable;
 
-  /// Lazy reverse lookup cache for identifier IDs. Identifier IDs are dense and
-  /// start at 1. Slot 0 is reserved for the empty identifier.
+  /// Lazy reverse lookup cache from identifier ID to string.
   bool IdentifierStringsInitialized = false;
-  llvm::SmallVector<std::optional<llvm::StringRef>, 0> IdentifierStrings;
+  llvm::DenseMap<uint32_t, llvm::StringRef> IdentifierStrings;
 
   using SerializedContextIDTable =
       llvm::OnDiskIterableChainedHashTable<ContextIDTableInfo>;
@@ -921,8 +921,10 @@ APINotesReader::Implementation::getIdentifierString(IdentifierID ID) {
 
   if (!IdentifierStringsInitialized) {
     IdentifierStringsInitialized = true;
-    // keys() and data() iterate over the same serialized entries in lockstep,
-    // so build the reverse cache without doing a lookup for each key.
+    // keys() and data() iterate over the same serialized entries in lockstep.
+    // The serialized hash-table order is not guaranteed to be identifier-ID
+    // order, so keep an explicit ID-to-string map rather than indexing a vector
+    // by ID.
     auto Identifiers = IdentifierTable->keys();
     auto IDs = IdentifierTable->data();
     auto Identifier = Identifiers.begin();
@@ -930,33 +932,15 @@ APINotesReader::Implementation::getIdentifierString(IdentifierID ID) {
     auto IdentifierEnd = Identifiers.end();
     auto KnownIDEnd = IDs.end();
     for (; Identifier != IdentifierEnd && KnownID != KnownIDEnd;
-         ++Identifier, ++KnownID) {
-      unsigned Index = static_cast<unsigned>(*KnownID);
-      if (IdentifierStrings.size() <= Index)
-        IdentifierStrings.resize(Index + 1);
-      IdentifierStrings[Index] = *Identifier;
-    }
+         ++Identifier, ++KnownID)
+      IdentifierStrings.try_emplace(static_cast<uint32_t>(*KnownID),
+                                    *Identifier);
   }
 
-  unsigned Index = static_cast<unsigned>(ID);
-  if (Index >= IdentifierStrings.size())
+  auto Known = IdentifierStrings.find(static_cast<uint32_t>(ID));
+  if (Known == IdentifierStrings.end())
     return std::nullopt;
-  return IdentifierStrings[Index];
-}
-
-static APINotesFunctionSelectorKey
-toAPINotesFunctionSelectorKey(const FunctionTableKey &Key, bool IsCXXMethod) {
-  APINotesFunctionSelectorKey SelectorKey;
-  SelectorKey.IsCXXMethod = IsCXXMethod;
-  SelectorKey.ParentContextID = Key.parentContextID;
-  SelectorKey.NameID = Key.nameID;
-  if (Key.parameterTypeIDs) {
-    SelectorKey.ParameterTypeIDs.emplace();
-    SelectorKey.ParameterTypeIDs->reserve(Key.parameterTypeIDs->size());
-    for (IdentifierID TypeID : *Key.parameterTypeIDs)
-      SelectorKey.ParameterTypeIDs->push_back(static_cast<unsigned>(TypeID));
-  }
-  return SelectorKey;
+  return Known->second;
 }
 
 template <typename TableT>
@@ -966,12 +950,12 @@ bool APINotesReader::Implementation::collectExactFunctionParameterSelectors(
   if (!Table)
     return true;
 
-  for (FunctionTableKey Key : Table->keys()) {
+  for (const FunctionTableKey &Key : Table->keys()) {
     if (!Key.parameterTypeIDs)
       continue;
 
     APINotesFunctionSelector Selector;
-    Selector.Key = toAPINotesFunctionSelectorKey(Key, IsCXXMethod);
+    Selector.Key = {Key, IsCXXMethod};
     Selector.Parameters.reserve(Key.parameterTypeIDs->size());
     for (IdentifierID TypeID : *Key.parameterTypeIDs) {
       std::optional<llvm::StringRef> TypeName = getIdentifierString(TypeID);
@@ -2449,7 +2433,7 @@ APINotesReader::getCXXMethodSelectorKey(ContextID CtxID, llvm::StringRef Name) {
       Implementation->getFunctionKey(CtxID.Value, Name);
   if (!Key)
     return std::nullopt;
-  return toAPINotesFunctionSelectorKey(*Key, /*IsCXXMethod=*/true);
+  return APINotesFunctionSelectorKey{*Key, /*IsCXXMethod=*/true};
 }
 
 std::optional<APINotesFunctionSelectorKey>
@@ -2460,7 +2444,7 @@ APINotesReader::getCXXMethodSelectorKey(
       Implementation->getFunctionKey(CtxID.Value, Name, Parameters);
   if (!Key)
     return std::nullopt;
-  return toAPINotesFunctionSelectorKey(*Key, /*IsCXXMethod=*/true);
+  return APINotesFunctionSelectorKey{*Key, /*IsCXXMethod=*/true};
 }
 
 auto APINotesReader::lookupCXXMethodImpl(ContextID CtxID, llvm::StringRef Name)
@@ -2537,7 +2521,7 @@ APINotesReader::getGlobalFunctionSelectorKey(llvm::StringRef Name,
       Implementation->getFunctionKey(Ctx, Name);
   if (!Key)
     return std::nullopt;
-  return toAPINotesFunctionSelectorKey(*Key, /*IsCXXMethod=*/false);
+  return APINotesFunctionSelectorKey{*Key, /*IsCXXMethod=*/false};
 }
 
 std::optional<APINotesFunctionSelectorKey>
@@ -2548,7 +2532,7 @@ APINotesReader::getGlobalFunctionSelectorKey(
       Implementation->getFunctionKey(Ctx, Name, Parameters);
   if (!Key)
     return std::nullopt;
-  return toAPINotesFunctionSelectorKey(*Key, /*IsCXXMethod=*/false);
+  return APINotesFunctionSelectorKey{*Key, /*IsCXXMethod=*/false};
 }
 
 bool APINotesReader::collectExactFunctionParameterSelectors(
diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
index e01dc84dde941..4d75bea792e3b 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -1193,7 +1193,7 @@ class YAMLConverter {
                  .insert(getFunctionSelectorKey(CXXMethod.Name,
                                                 *WhereParameters.second))
                  .second) {
-          emitError(llvm::Twine("duplicate definition of C++ method '") +
+          emitError(llvm::Twine("multiple API notes entries for C++ method '") +
                     CXXMethod.Name + "' with Where.Parameters " +
                     formatAPINotesParameterSelector(*WhereParameters.second));
           continue;
@@ -1287,9 +1287,10 @@ class YAMLConverter {
                  .insert(getFunctionSelectorKey(Function.Name,
                                                 *WhereParameters.second))
                  .second) {
-          emitError(llvm::Twine("duplicate definition of global function '") +
-                    Function.Name + "' with Where.Parameters " +
-                    formatAPINotesParameterSelector(*WhereParameters.second));
+          emitError(
+              llvm::Twine("multiple API notes entries for global function '") +
+              Function.Name + "' with Where.Parameters " +
+              formatAPINotesParameterSelector(*WhereParameters.second));
           continue;
         }
       }
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 92b5d7080b7ac..80dc09ceda252 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -1001,6 +1001,7 @@ static void stripAPINotesParameterNullability(QualType &ParamType) {
   }
 }
 
+namespace clang {
 struct APINotesParameterSelector {
   SmallVector<std::string, 4> Parameters;
 
@@ -1017,6 +1018,7 @@ struct APINotesParameterSelectorCandidates {
   APINotesParameterSelector Source;
   std::optional<APINotesParameterSelector> Desugared;
 };
+} // namespace clang
 
 static PrintingPolicy
 getAPINotesParameterSelectorPrintingPolicy(const ASTContext &Context) {
@@ -1073,66 +1075,41 @@ getAPINotesParameterSelectorCandidates(const Sema &S, const FunctionDecl *FD) {
   return Candidates;
 }
 
-static api_notes::APINotesFunctionSelectorKey
-getBroadAPINotesSelectorKey(api_notes::APINotesFunctionSelectorKey Key) {
-  Key.ParameterTypeIDs = std::nullopt;
-  return Key;
-}
-
-static APINotesSelectorDiagnosticReaderState &
-getAPINotesSelectorDiagnosticState(Sema &S, api_notes::APINotesReader *Reader) {
-  if (!S.APINotesSelectorDiagnostics)
-    S.APINotesSelectorDiagnostics =
-        std::make_unique<APINotesSelectorDiagnosticState>();
-  auto &State = S.APINotesSelectorDiagnostics->Readers[Reader];
-  if (State.Initialized)
+APINotesSelectorDiagnosticReaderState &
+APINotesSelectorDiagnosticState::getOrCreateReaderState(
+    api_notes::APINotesReader &Reader) {
+  auto [StateIt, Inserted] = Readers.try_emplace(&Reader);
+  APINotesSelectorDiagnosticReaderState &State = StateIt->second;
+  if (!Inserted)
     return State;
 
-  State.Initialized = true;
   SmallVector<api_notes::APINotesFunctionSelector, 4> Selectors;
-  if (!Reader->collectExactFunctionParameterSelectors(Selectors))
+  if (!Reader.collectExactFunctionParameterSelectors(Selectors))
     return State;
 
-  State.Selectors.reserve(Selectors.size());
-  State.SelectorIndices.reserve(Selectors.size());
-  State.SeenNames.reserve(Selectors.size());
-  for (auto &Selector : Selectors) {
-    unsigned Index = State.Selectors.size();
-    APINotesSelectorDiagnosticEntry Entry;
-    Entry.BroadKey = getBroadAPINotesSelectorKey(Selector.Key);
-    Entry.Parameters = std::move(Selector.Parameters);
-    State.Selectors.push_back(std::move(Entry));
-    State.SelectorIndices.insert({Selector.Key, Index});
-  }
+  State.addSelectors(Selectors);
   return State;
 }
 
-static void noteSeenAPINotesSelectorName(
-    APINotesSelectorDiagnosticReaderState &State,
-    const api_notes::APINotesFunctionSelectorKey &BroadKey, StringRef Name,
-    SourceLocation Loc) {
-  State.SeenNames.insert({BroadKey, {Loc, Name.str()}});
-}
+static APINotesSelectorDiagnosticReaderState &
+getAPINotesSelectorDiagnosticState(Sema &S, api_notes::APINotesReader *Reader) {
+  if (!S.APINotesSelectorDiagnostics)
+    S.APINotesSelectorDiagnostics =
+        std::make_unique<APINotesSelectorDiagnosticState>();
 
-static void
-markAPINotesSelectorUsed(APINotesSelectorDiagnosticReaderState &State,
-                         const api_notes::APINotesFunctionSelectorKey &Key) {
-  auto KnownSelector = State.SelectorIndices.find(Key);
-  if (KnownSelector != State.SelectorIndices.end())
-    State.Selectors[KnownSelector->second].Used = true;
+  return S.APINotesSelectorDiagnostics->getOrCreateReaderState(*Reader);
 }
 
-static void markAPINotesSelectorCandidatesUsed(
-    APINotesSelectorDiagnosticReaderState &State,
+void APINotesSelectorDiagnosticReaderState::markCandidatesUsed(
     llvm::function_ref<std::optional<api_notes::APINotesFunctionSelectorKey>(
         ArrayRef<std::string>)>
         GetSelectorKey,
     const APINotesParameterSelectorCandidates &Candidates) {
   if (auto Key = GetSelectorKey(Candidates.Source.Parameters))
-    markAPINotesSelectorUsed(State, *Key);
+    markUsed(*Key);
   if (Candidates.Desugared) {
     if (auto Key = GetSelectorKey(Candidates.Desugared->Parameters))
-      markAPINotesSelectorUsed(State, *Key);
+      markUsed(*Key);
   }
 }
 
@@ -1216,10 +1193,9 @@ void Sema::ProcessAPINotes(Decl *D) {
                 getAPINotesSelectorDiagnosticState(*this, Reader);
             if (auto BroadKey = Reader->getGlobalFunctionSelectorKey(
                     FD->getName(), APINotesContext))
-              noteSeenAPINotesSelectorName(DiagnosticState, *BroadKey,
-                                           FD->getName(), FD->getLocation());
-            markAPINotesSelectorCandidatesUsed(
-                DiagnosticState,
+              DiagnosticState.noteSeenDeclaration(*BroadKey, FD->getName(),
+                                                  FD->getLocation());
+            DiagnosticState.markCandidatesUsed(
                 [&](ArrayRef<std::string> Parameters) {
                   return Reader->getGlobalFunctionSelectorKey(
                       FD->getName(), Parameters, APINotesContext);
@@ -1439,11 +1415,9 @@ void Sema::ProcessAPINotes(Decl *D) {
                   getAPINotesSelectorDiagnosticState(*this, Reader);
               if (auto BroadKey =
                       Reader->getCXXMethodSelectorKey(Context->id, MethodName))
-                noteSeenAPINotesSelectorName(DiagnosticState, *BroadKey,
-                                             MethodName,
-                                             CXXMethod->getLocation());
-              markAPINotesSelectorCandidatesUsed(
-                  DiagnosticState,
+                DiagnosticState.noteSeenDeclaration(*BroadKey, MethodName,
+                                                    CXXMethod->getLocation());
+              DiagnosticState.markCandidatesUsed(
                   [&](ArrayRef<std::string> Parameters) {
                     return Reader->getCXXMethodSelectorKey(
                         Context->id, MethodName, Parameters);
@@ -1477,27 +1451,32 @@ void Sema::ProcessAPINotes(Decl *D) {
   }
 }
 
+void APINotesSelectorDiagnosticReaderState::diagnoseUnused(Sema &S) const {
+  for (const auto &Selector : Selectors) {
+    if (Selector.Used)
+      continue;
+
+    auto SeenName = SeenNames.find(Selector.BroadKey);
+    if (SeenName == SeenNames.end())
+      continue;
+
+    S.Diag(SeenName->second.Loc, diag::warn_apinotes_message)
+        << (llvm::Twine("API notes entry for '") + SeenName->second.Name +
+            "' has unmatched Where.Parameters " +
+            api_notes::formatAPINotesParameterSelector(Selector.Parameters))
+               .str();
+  }
+}
+
+void APINotesSelectorDiagnosticState::diagnoseUnused(Sema &S) const {
+  for (const auto &ReaderSelectors : Readers)
+    ReaderSelectors.second.diagnoseUnused(S);
+}
+
 void Sema::DiagnoseUnusedAPINotesSelectors() {
   if (!APINotesSelectorDiagnostics)
     return;
 
-  for (auto &ReaderSelectors : APINotesSelectorDiagnostics->Readers) {
-    auto &State = ReaderSelectors.second;
-    for (const auto &Selector : State.Selectors) {
-      if (Selector.Used)
-        continue;
-
-      auto SeenName = State.SeenNames.find(Selector.BroadKey);
-      if (SeenName == State.SeenNames.end())
-        continue;
-
-      Diag(SeenName->second.Loc, diag::warn_apinotes_message)
-          << (llvm::Twine("API notes entry for '") + SeenName->second.Name +
-              "' has unmatched Where.Parameters " +
-              api_notes::formatAPINotesParameterSelector(Selector.Parameters))
-                 .str();
-    }
-  }
-
+  APINotesSelectorDiagnostics->diagnoseUnused(*this);
   APINotesSelectorDiagnostics.reset();
 }
diff --git a/clang/lib/Sema/SemaAPINotesInternal.h b/clang/lib/Sema/SemaAPINotesInternal.h
index 1b9e2ffa60bb8..0192bbf0e5da3 100644
--- a/clang/lib/Sema/SemaAPINotesInternal.h
+++ b/clang/lib/Sema/SemaAPINotesInternal.h
@@ -11,40 +11,101 @@
 
 #include "clang/APINotes/Types.h"
 #include "clang/Basic/SourceLocation.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
 #include <string>
+#include <utility>
 
 namespace clang {
+class Sema;
+struct APINotesParameterSelectorCandidates;
 namespace api_notes {
 class APINotesReader;
 }
 
+/// One stored exact Where.Parameters selector tracked for diagnostics.
 struct APINotesSelectorDiagnosticEntry {
   api_notes::APINotesFunctionSelectorKey BroadKey;
   llvm::SmallVector<std::string, 4> Parameters;
   bool Used = false;
 };
 
+/// Source name and location for a declaration seen by Sema.
 struct APINotesSelectorDiagnosticName {
   SourceLocation Loc;
   std::string Name;
 };
 
+/// Tracks exact Where.Parameters selectors from one API notes reader.
+///
+/// Sema marks selectors as used when a visible declaration matches them. It
+/// also records broad/name-only declarations seen in the translation unit, so
+/// end-of-TU diagnostics can warn about exact selectors for known names that
+/// were never matched.
 struct APINotesSelectorDiagnosticReaderState {
-  bool Initialized = false;
+  /// Maps exact selector keys to entries in Selectors.
   llvm::DenseMap<api_notes::APINotesFunctionSelectorKey, unsigned>
       SelectorIndices;
+
+  /// Maps broad/name-only keys to a declaration location/name used for
+  /// diagnostics.
   llvm::DenseMap<api_notes::APINotesFunctionSelectorKey,
                  APINotesSelectorDiagnosticName>
       SeenNames;
+
   llvm::SmallVector<APINotesSelectorDiagnosticEntry, 4> Selectors;
+
+  void addSelector(api_notes::APINotesFunctionSelector Selector) {
+    unsigned Index = Selectors.size();
+    APINotesSelectorDiagnosticEntry Entry;
+    Entry.BroadKey = Selector.Key.getWithoutParameterSelector();
+    Entry.Parameters = std::move(Selector.Parameters);
+    Selectors.push_back(std::move(Entry));
+    SelectorIndices.insert({Selector.Key, Index});
+  }
+
+  void addSelectors(llvm::SmallVectorImpl<api_notes::APINotesFunctionSelector>
+                        &NewSelectors) {
+    Selectors.reserve(NewSelectors.size());
+    SelectorIndices.reserve(NewSelectors.size());
+    SeenNames.reserve(NewSelectors.size());
+    for (auto &Selector : NewSelectors)
+      addSelector(std::move(Selector));
+  }
+
+  void noteSeenDeclaration(const api_notes::APINotesFunctionSelectorKey &Key,
+                           llvm::StringRef Name, SourceLocation Loc) {
+    SeenNames.insert({Key.getWithoutParameterSelector(), {Loc, Name.str()}});
+  }
+
+  void markUsed(const api_notes::APINotesFunctionSelectorKey &Key) {
+    auto KnownSelector = SelectorIndices.find(Key);
+    if (KnownSelector != SelectorIndices.end())
+      Selectors[KnownSelector->second].Used = true;
+  }
+
+  void markCandidatesUsed(
+      llvm::function_ref<std::optional<api_notes::APINotesFunctionSelectorKey>(
+          llvm::ArrayRef<std::string>)>
+          GetSelectorKey,
+      const APINotesParameterSelectorCandidates &Candidates);
+
+  void diagnoseUnused(Sema &S) const;
 };
 
+/// Selector diagnostic state for all API notes readers used by one Sema.
 struct APINotesSelectorDiagnosticState {
   llvm::DenseMap<api_notes::APINotesReader *,
                  APINotesSelectorDiagnosticReaderState>
       Readers;
+
+  APINotesSelectorDiagnosticReaderState &
+  getOrCreateReaderState(api_notes::APINotesReader &Reader);
+
+  void diagnoseUnused(Sema &S) const;
 };
 
 } // namespace clang
diff --git a/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/APINotes.apinotes b/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/APINotes.apinotes
deleted file mode 100644
index d99ea9c0f4ce9..0000000000000
--- a/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/APINotes.apinotes
+++ /dev/null
@@ -1,66 +0,0 @@
----
-Name: WhereParametersDiagnostics
-Functions:
-- Name: duplicateGlobal
-  Where:
-    Parameters:
-    - int
-  SwiftName: duplicateGlobalA(_:)
-- Name: duplicateGlobal
-  Where:
-    Parameters:
-    - int
-  SwiftName: duplicateGlobalB(_:)
-- Name: duplicateEmpty
-  Where:
-    Parameters: []
-  SwiftName: duplicateEmptyA()
-- Name: duplicateEmpty
-  Where:
-    Parameters: []
-  SwiftName: duplicateEmptyB()
-- Name: allowedGlobal
-  SwiftPrivate: true
-- Name: allowedGlobal
-  Where:
-    Parameters:
-    - int
-  SwiftName: allowedGlobalInt(_:)
-- Name: allowedGlobal
-  Where:
-    Parameters:
-    - double
-  SwiftName: allowedGlobalDouble(_:)
-Tags:
-- Name: DiagnosticWidget
-  Methods:
-  - Name: duplicateMethod
-    Where:
-      Parameters:
-      - int
-    SwiftName: duplicateMethodA(_:)
-  - Name: duplicateMethod
-    Where:
-      Parameters:
-      - int
-    SwiftName: duplicateMethodB(_:)
-  - Name: duplicateEmpty
-    Where:
-      Parameters: []
-    SwiftName: duplicateEmptyA()
-  - Name: duplicateEmpty
-    Where:
-      Parameters: []
-    SwiftName: duplicateEmptyB()
-  - Name: allowed
-    SwiftPrivate: true
-  - Name: allowed
-    Where:
-      Parameters:
-      - int
-    SwiftName: allowedInt(_:)
-  - Name: allowed
-    Where:
-      Parameters:
-      - double
-    SwiftName: allowedDouble(_:)
diff --git a/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/WhereParametersDiagnostics.h b/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/WhereParametersDiagnostics.h
deleted file mode 100644
index 406a848a04b03..0000000000000
--- a/clang/test/APINotes/Inputs/WhereParametersDuplicateSelectorDiag/WhereParametersDiagnostics.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#ifndef WHERE_PARAMETERS_DIAGNOSTICS_H
-#define WHERE_PARAMETERS_DIAGNOSTICS_H
-
-void duplicateGlobal(int);
-void duplicateEmpty();
-void allowedGlobal(int);
-void allowedGlobal(double);
-
-struct DiagnosticWidget {
-  void duplicateMethod(int);
-  void duplicateEmpty();
-  void allowed(int);
-  void allowed(double);
-};
-
-#endif // WHERE_PARAMETERS_DIAGNOSTICS_H
diff --git a/clang/test/APINotes/where-parameters-diagnostics.cpp b/clang/test/APINotes/where-parameters-diagnostics.cpp
index 7bb033973f728..58bcaa7e114d8 100644
--- a/clang/test/APINotes/where-parameters-diagnostics.cpp
+++ b/clang/test/APINotes/where-parameters-diagnostics.cpp
@@ -1,19 +1,14 @@
-// RUN: not %clang_cc1 -fsyntax-only -fapinotes %s -I %S/Inputs/WhereParametersDuplicateSelectorDiag 2>&1 | FileCheck %s --check-prefix=DUPLICATE
-// RUN: rm -rf %t && mkdir -p %t
-// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wapinotes -fsyntax-only -I %S/Inputs/Headers %s -x c++ 2>&1 | FileCheck %s --check-prefix=UNMATCHED --implicit-check-not=diagnosticMatchedGlobal --implicit-check-not=diagnosticAliasMatchedGlobal --implicit-check-not=diagnosticMatchedMethod --implicit-check-not=diagnosticAliasMatchedMethod
-// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter diagnosticBroadGlobal -x c++ | FileCheck %s --check-prefix=BROAD-GLOBAL
-// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticBroadMethod -x c++ | FileCheck %s --check-prefix=BROAD-METHOD
-// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter diagnosticMatchedGlobal -x c++ | FileCheck %s --check-prefix=MATCHED-GLOBAL
-// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter diagnosticAliasMatchedGlobal -x c++ | FileCheck %s --check-prefix=ALIAS-MATCHED-GLOBAL
-// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticMatchedMethod -x c++ | FileCheck %s --check-prefix=MATCHED-METHOD
-// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticAliasMatchedMethod -x c++ | FileCheck %s --check-prefix=ALIAS-MATCHED-METHOD
+// RUN: rm -rf %t && split-file %s %t
+// RUN: not %clang_cc1 -fsyntax-only -fapinotes %t/diagnostics.cpp -I %t/WhereParametersDuplicateSelectorDiag 2>&1 | FileCheck %t/WhereParametersDuplicateSelectorDiag/APINotes.apinotes --check-prefix=DUPLICATE
+// RUN: rm -rf %t/ModulesCache && mkdir -p %t/ModulesCache
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wapinotes -fsyntax-only -I %S/Inputs/Headers %t/diagnostics.cpp -x c++ 2>&1 | FileCheck %s --check-prefix=UNMATCHED --implicit-check-not=diagnosticMatchedGlobal --implicit-check-not=diagnosticAliasMatchedGlobal --implicit-check-not=diagnosticMatchedMethod --implicit-check-not=diagnosticAliasMatchedMethod
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %t/diagnostics.cpp -ast-dump -ast-dump-filter diagnosticBroadGlobal -x c++ | FileCheck %s --check-prefix=BROAD-GLOBAL
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %t/diagnostics.cpp -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticBroadMethod -x c++ | FileCheck %s --check-prefix=BROAD-METHOD
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %t/diagnostics.cpp -ast-dump -ast-dump-filter diagnosticMatchedGlobal -x c++ | FileCheck %s --check-prefix=MATCHED-GLOBAL
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %t/diagnostics.cpp -ast-dump -ast-dump-filter diagnosticAliasMatchedGlobal -x c++ | FileCheck %s --check-prefix=ALIAS-MATCHED-GLOBAL
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %t/diagnostics.cpp -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticMatchedMethod -x c++ | FileCheck %s --check-prefix=MATCHED-METHOD
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %t/diagnostics.cpp -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticAliasMatchedMethod -x c++ | FileCheck %s --check-prefix=ALIAS-MATCHED-METHOD
 
-#include "WhereParametersDiagnostics.h"
-
-// DUPLICATE: error: duplicate definition of global function 'duplicateGlobal' with Where.Parameters [int]
-// DUPLICATE: error: duplicate definition of global function 'duplicateEmpty' with Where.Parameters []
-// DUPLICATE: error: duplicate definition of C++ method 'duplicateMethod' with Where.Parameters [int]
-// DUPLICATE: error: duplicate definition of C++ method 'duplicateEmpty' with Where.Parameters []
 
 // UNMATCHED-DAG: warning: API notes entry for 'unmatchedGlobal' has unmatched Where.Parameters [int]
 // UNMATCHED-DAG: warning: API notes entry for 'diagnosticBroadGlobal' has unmatched Where.Parameters [int]
@@ -39,3 +34,96 @@
 
 // ALIAS-MATCHED-METHOD: CXXMethodDecl {{.+}} diagnosticAliasMatchedMethod 'void (DiagnosticAliasInt)'
 // ALIAS-MATCHED-METHOD: SwiftNameAttr {{.+}} "diagnosticAliasMatchedMethod(_:)"
+
+//--- diagnostics.cpp
+#include "WhereParametersDiagnostics.h"
+
+//--- WhereParametersDuplicateSelectorDiag/WhereParametersDiagnostics.h
+#ifndef WHERE_PARAMETERS_DIAGNOSTICS_H
+#define WHERE_PARAMETERS_DIAGNOSTICS_H
+
+void duplicateGlobal(int);
+void duplicateEmpty();
+void allowedGlobal(int);
+void allowedGlobal(double);
+
+struct DiagnosticWidget {
+  void duplicateMethod(int);
+  void duplicateEmpty();
+  void allowed(int);
+  void allowed(double);
+};
+
+#endif // WHERE_PARAMETERS_DIAGNOSTICS_H
+
+//--- WhereParametersDuplicateSelectorDiag/APINotes.apinotes
+---
+Name: WhereParametersDiagnostics
+Functions:
+- Name: duplicateGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: duplicateGlobalA(_:)
+- Name: duplicateGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: duplicateGlobalB(_:)
+# DUPLICATE: error: multiple API notes entries for global function 'duplicateGlobal' with Where.Parameters [int]
+- Name: duplicateEmpty
+  Where:
+    Parameters: []
+  SwiftName: duplicateEmptyA()
+- Name: duplicateEmpty
+  Where:
+    Parameters: []
+  SwiftName: duplicateEmptyB()
+# DUPLICATE: error: multiple API notes entries for global function 'duplicateEmpty' with Where.Parameters []
+- Name: allowedGlobal
+  SwiftPrivate: true
+- Name: allowedGlobal
+  Where:
+    Parameters:
+    - int
+  SwiftName: allowedGlobalInt(_:)
+- Name: allowedGlobal
+  Where:
+    Parameters:
+    - double
+  SwiftName: allowedGlobalDouble(_:)
+Tags:
+- Name: DiagnosticWidget
+  Methods:
+  - Name: duplicateMethod
+    Where:
+      Parameters:
+      - int
+    SwiftName: duplicateMethodA(_:)
+  - Name: duplicateMethod
+    Where:
+      Parameters:
+      - int
+    SwiftName: duplicateMethodB(_:)
+# DUPLICATE: error: multiple API notes entries for C++ method 'duplicateMethod' with Where.Parameters [int]
+  - Name: duplicateEmpty
+    Where:
+      Parameters: []
+    SwiftName: duplicateEmptyA()
+  - Name: duplicateEmpty
+    Where:
+      Parameters: []
+    SwiftName: duplicateEmptyB()
+# DUPLICATE: error: multiple API notes entries for C++ method 'duplicateEmpty' with Where.Parameters []
+  - Name: allowed
+    SwiftPrivate: true
+  - Name: allowed
+    Where:
+      Parameters:
+      - int
+    SwiftName: allowedInt(_:)
+  - Name: allowed
+    Where:
+      Parameters:
+      - double
+    SwiftName: allowedDouble(_:)

>From 004d68ce68f18da3e7e23f761048ec4b470fcb8b Mon Sep 17 00:00:00 2001
From: StoeckOverflow <95052643+StoeckOverflow at users.noreply.github.com>
Date: Mon, 27 Jul 2026 11:53:50 +0200
Subject: [PATCH 07/12] [APINotes] Update clang/lib/APINotes/APINotesReader.cpp

Co-authored-by: John Hui <updog at j-hui.com>
---
 clang/lib/APINotes/APINotesReader.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index 16951d185f124..fe434ca8342f6 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -762,8 +762,7 @@ class APINotesReader::Implementation {
   std::unique_ptr<SerializedIdentifierTable> IdentifierTable;
 
   /// Lazy reverse lookup cache from identifier ID to string.
-  bool IdentifierStringsInitialized = false;
-  llvm::DenseMap<uint32_t, llvm::StringRef> IdentifierStrings;
+  std::optional<llvm::DenseMap<uint32_t, llvm::StringRef>> IdentifierStrings;
 
   using SerializedContextIDTable =
       llvm::OnDiskIterableChainedHashTable<ContextIDTableInfo>;

>From 746caf357172c3c1bce1a653063d283f1df45fa2 Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Mon, 27 Jul 2026 12:23:46 +0200
Subject: [PATCH 08/12] [APINotes] Clean up selector diagnostic state

---
 clang/lib/APINotes/APINotesReader.cpp | 29 +++++++++++++++------------
 1 file changed, 16 insertions(+), 13 deletions(-)

diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index fe434ca8342f6..b2aa67ee4f85e 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -21,6 +21,7 @@
 #include "llvm/Support/DJB.h"
 #include "llvm/Support/OnDiskHashTable.h"
 #include <string>
+#include <type_traits>
 #include <utility>
 
 namespace clang {
@@ -847,7 +848,7 @@ class APINotesReader::Implementation {
   /// table.
   template <typename TableT>
   bool collectExactFunctionParameterSelectors(
-      TableT *Table, bool IsCXXMethod,
+      TableT *Table,
       llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors);
 
   /// Retrieve the selector ID for the given selector, or an empty
@@ -918,8 +919,8 @@ APINotesReader::Implementation::getIdentifierString(IdentifierID ID) {
   if (ID == IdentifierID(0))
     return llvm::StringRef();
 
-  if (!IdentifierStringsInitialized) {
-    IdentifierStringsInitialized = true;
+  if (!IdentifierStrings) {
+    IdentifierStrings.emplace();
     // keys() and data() iterate over the same serialized entries in lockstep.
     // The serialized hash-table order is not guaranteed to be identifier-ID
     // order, so keep an explicit ID-to-string map rather than indexing a vector
@@ -932,20 +933,24 @@ APINotesReader::Implementation::getIdentifierString(IdentifierID ID) {
     auto KnownIDEnd = IDs.end();
     for (; Identifier != IdentifierEnd && KnownID != KnownIDEnd;
          ++Identifier, ++KnownID)
-      IdentifierStrings.try_emplace(static_cast<uint32_t>(*KnownID),
-                                    *Identifier);
+      IdentifierStrings->try_emplace(static_cast<uint32_t>(*KnownID),
+                                     *Identifier);
   }
 
-  auto Known = IdentifierStrings.find(static_cast<uint32_t>(ID));
-  if (Known == IdentifierStrings.end())
+  auto Known = IdentifierStrings->find(static_cast<uint32_t>(ID));
+  if (Known == IdentifierStrings->end())
     return std::nullopt;
   return Known->second;
 }
 
 template <typename TableT>
 bool APINotesReader::Implementation::collectExactFunctionParameterSelectors(
-    TableT *Table, bool IsCXXMethod,
-    llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors) {
+    TableT *Table, llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors) {
+  static_assert(std::is_same_v<TableT, SerializedGlobalFunctionTable> ||
+                std::is_same_v<TableT, SerializedCXXMethodTable>);
+  constexpr bool IsCXXMethod =
+      std::is_same_v<TableT, SerializedCXXMethodTable>;
+
   if (!Table)
     return true;
 
@@ -2537,11 +2542,9 @@ APINotesReader::getGlobalFunctionSelectorKey(
 bool APINotesReader::collectExactFunctionParameterSelectors(
     llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors) {
   return Implementation->collectExactFunctionParameterSelectors(
-             Implementation->GlobalFunctionTable.get(), /*IsCXXMethod=*/false,
-             Selectors) &&
+             Implementation->GlobalFunctionTable.get(), Selectors) &&
          Implementation->collectExactFunctionParameterSelectors(
-             Implementation->CXXMethodTable.get(), /*IsCXXMethod=*/true,
-             Selectors);
+             Implementation->CXXMethodTable.get(), Selectors);
 }
 
 auto APINotesReader::lookupGlobalFunctionImpl(llvm::StringRef Name,

>From 42b8a13e611358d999084ffa1dfc23e0a4eaabcc Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Mon, 27 Jul 2026 12:26:59 +0200
Subject: [PATCH 09/12] [APINotes] Format selector diagnostic cleanup

---
 clang/lib/APINotes/APINotesReader.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index b2aa67ee4f85e..afd4aa1dffd01 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -948,8 +948,7 @@ bool APINotesReader::Implementation::collectExactFunctionParameterSelectors(
     TableT *Table, llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors) {
   static_assert(std::is_same_v<TableT, SerializedGlobalFunctionTable> ||
                 std::is_same_v<TableT, SerializedCXXMethodTable>);
-  constexpr bool IsCXXMethod =
-      std::is_same_v<TableT, SerializedCXXMethodTable>;
+  constexpr bool IsCXXMethod = std::is_same_v<TableT, SerializedCXXMethodTable>;
 
   if (!Table)
     return true;

>From 1c315701ebaf52ee2f138ac566df36472c0e435d Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Tue, 28 Jul 2026 10:54:58 +0200
Subject: [PATCH 10/12] [APINotes] Address selector diagnostics naming nits

---
 clang/include/clang/APINotes/Types.h  | 4 ++--
 clang/lib/APINotes/APINotesReader.cpp | 4 ++--
 clang/lib/Sema/SemaAPINotes.cpp       | 3 ++-
 clang/lib/Sema/SemaAPINotesInternal.h | 6 +++---
 4 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index a32c42b829119..351eeab8f6ec1 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -1076,10 +1076,10 @@ inline bool operator==(const APINotesFunctionSelectorKey &LHS,
   return LHS.Key == RHS.Key && LHS.IsCXXMethod == RHS.IsCXXMethod;
 }
 
-/// Exact function selector key plus parameter spellings used for diagnostics.
 struct APINotesFunctionSelector {
   APINotesFunctionSelectorKey Key;
-  llvm::SmallVector<std::string, 4> Parameters;
+  /// Original parameter selector strings used for diagnostics.
+  llvm::SmallVector<std::string, 4> ParameterSpellings;
 };
 
 /// A temporary reference to an Objective-C selector, suitable for
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index afd4aa1dffd01..97f7ed1dc06d9 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -959,12 +959,12 @@ bool APINotesReader::Implementation::collectExactFunctionParameterSelectors(
 
     APINotesFunctionSelector Selector;
     Selector.Key = {Key, IsCXXMethod};
-    Selector.Parameters.reserve(Key.parameterTypeIDs->size());
+    Selector.ParameterSpellings.reserve(Key.parameterTypeIDs->size());
     for (IdentifierID TypeID : *Key.parameterTypeIDs) {
       std::optional<llvm::StringRef> TypeName = getIdentifierString(TypeID);
       if (!TypeName)
         return false;
-      Selector.Parameters.push_back(TypeName->str());
+      Selector.ParameterSpellings.push_back(TypeName->str());
     }
     Selectors.push_back(std::move(Selector));
   }
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 80dc09ceda252..6bd88dccf9f65 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -1463,7 +1463,8 @@ void APINotesSelectorDiagnosticReaderState::diagnoseUnused(Sema &S) const {
     S.Diag(SeenName->second.Loc, diag::warn_apinotes_message)
         << (llvm::Twine("API notes entry for '") + SeenName->second.Name +
             "' has unmatched Where.Parameters " +
-            api_notes::formatAPINotesParameterSelector(Selector.Parameters))
+            api_notes::formatAPINotesParameterSelector(
+                Selector.ParameterSpellings))
                .str();
   }
 }
diff --git a/clang/lib/Sema/SemaAPINotesInternal.h b/clang/lib/Sema/SemaAPINotesInternal.h
index 0192bbf0e5da3..0ada6e4f74a30 100644
--- a/clang/lib/Sema/SemaAPINotesInternal.h
+++ b/clang/lib/Sema/SemaAPINotesInternal.h
@@ -24,12 +24,12 @@ class Sema;
 struct APINotesParameterSelectorCandidates;
 namespace api_notes {
 class APINotesReader;
-}
+} // namespace api_notes
 
 /// One stored exact Where.Parameters selector tracked for diagnostics.
 struct APINotesSelectorDiagnosticEntry {
   api_notes::APINotesFunctionSelectorKey BroadKey;
-  llvm::SmallVector<std::string, 4> Parameters;
+  llvm::SmallVector<std::string, 4> ParameterSpellings;
   bool Used = false;
 };
 
@@ -62,7 +62,7 @@ struct APINotesSelectorDiagnosticReaderState {
     unsigned Index = Selectors.size();
     APINotesSelectorDiagnosticEntry Entry;
     Entry.BroadKey = Selector.Key.getWithoutParameterSelector();
-    Entry.Parameters = std::move(Selector.Parameters);
+    Entry.ParameterSpellings = std::move(Selector.ParameterSpellings);
     Selectors.push_back(std::move(Entry));
     SelectorIndices.insert({Selector.Key, Index});
   }

>From 7a0edf6b6ffb5b29185606beedcddcddfb809bd3 Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Tue, 28 Jul 2026 12:02:42 +0200
Subject: [PATCH 11/12] [APINotes] Track unmatched selectors by key

---
 clang/include/clang/APINotes/APINotesReader.h |  7 ++-
 clang/include/clang/APINotes/Types.h          |  6 ---
 clang/lib/APINotes/APINotesReader.cpp         | 36 +++++++++------
 clang/lib/Sema/SemaAPINotes.cpp               | 22 +++++----
 clang/lib/Sema/SemaAPINotesInternal.h         | 46 +++++++------------
 5 files changed, 59 insertions(+), 58 deletions(-)

diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h
index 3b05dd2ef8cbe..4f60a25b55472 100644
--- a/clang/include/clang/APINotes/APINotesReader.h
+++ b/clang/include/clang/APINotes/APINotesReader.h
@@ -220,7 +220,12 @@ class APINotesReader {
 
   /// Collect exact parameter selector keys stored by this reader.
   bool collectExactFunctionParameterSelectors(
-      llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors);
+      llvm::SmallVectorImpl<APINotesFunctionSelectorKey> &Selectors);
+
+  /// Reconstruct parameter selector strings for a stored exact selector key.
+  std::optional<llvm::SmallVector<std::string, 4>>
+  getParameterSelectorSpellingsForDiagnostics(
+      const APINotesFunctionSelectorKey &Key);
 
   /// Look for information regarding the given enumerator.
   ///
diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index 351eeab8f6ec1..0852d7d6c3487 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -1076,12 +1076,6 @@ inline bool operator==(const APINotesFunctionSelectorKey &LHS,
   return LHS.Key == RHS.Key && LHS.IsCXXMethod == RHS.IsCXXMethod;
 }
 
-struct APINotesFunctionSelector {
-  APINotesFunctionSelectorKey Key;
-  /// Original parameter selector strings used for diagnostics.
-  llvm::SmallVector<std::string, 4> ParameterSpellings;
-};
-
 /// A temporary reference to an Objective-C selector, suitable for
 /// referencing selector data on the stack.
 ///
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index 97f7ed1dc06d9..2d5edaea4cdf5 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -849,7 +849,7 @@ class APINotesReader::Implementation {
   template <typename TableT>
   bool collectExactFunctionParameterSelectors(
       TableT *Table,
-      llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors);
+      llvm::SmallVectorImpl<APINotesFunctionSelectorKey> &Selectors);
 
   /// Retrieve the selector ID for the given selector, or an empty
   /// optional if the string is unknown.
@@ -945,7 +945,8 @@ APINotesReader::Implementation::getIdentifierString(IdentifierID ID) {
 
 template <typename TableT>
 bool APINotesReader::Implementation::collectExactFunctionParameterSelectors(
-    TableT *Table, llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors) {
+    TableT *Table,
+    llvm::SmallVectorImpl<APINotesFunctionSelectorKey> &Selectors) {
   static_assert(std::is_same_v<TableT, SerializedGlobalFunctionTable> ||
                 std::is_same_v<TableT, SerializedCXXMethodTable>);
   constexpr bool IsCXXMethod = std::is_same_v<TableT, SerializedCXXMethodTable>;
@@ -957,16 +958,7 @@ bool APINotesReader::Implementation::collectExactFunctionParameterSelectors(
     if (!Key.parameterTypeIDs)
       continue;
 
-    APINotesFunctionSelector Selector;
-    Selector.Key = {Key, IsCXXMethod};
-    Selector.ParameterSpellings.reserve(Key.parameterTypeIDs->size());
-    for (IdentifierID TypeID : *Key.parameterTypeIDs) {
-      std::optional<llvm::StringRef> TypeName = getIdentifierString(TypeID);
-      if (!TypeName)
-        return false;
-      Selector.ParameterSpellings.push_back(TypeName->str());
-    }
-    Selectors.push_back(std::move(Selector));
+    Selectors.push_back(APINotesFunctionSelectorKey{Key, IsCXXMethod});
   }
 
   return true;
@@ -2539,13 +2531,31 @@ APINotesReader::getGlobalFunctionSelectorKey(
 }
 
 bool APINotesReader::collectExactFunctionParameterSelectors(
-    llvm::SmallVectorImpl<APINotesFunctionSelector> &Selectors) {
+    llvm::SmallVectorImpl<APINotesFunctionSelectorKey> &Selectors) {
   return Implementation->collectExactFunctionParameterSelectors(
              Implementation->GlobalFunctionTable.get(), Selectors) &&
          Implementation->collectExactFunctionParameterSelectors(
              Implementation->CXXMethodTable.get(), Selectors);
 }
 
+std::optional<llvm::SmallVector<std::string, 4>>
+APINotesReader::getParameterSelectorSpellingsForDiagnostics(
+    const APINotesFunctionSelectorKey &Key) {
+  if (!Key.Key.parameterTypeIDs)
+    return std::nullopt;
+
+  llvm::SmallVector<std::string, 4> ParameterSpellings;
+  ParameterSpellings.reserve(Key.Key.parameterTypeIDs->size());
+  for (IdentifierID TypeID : *Key.Key.parameterTypeIDs) {
+    std::optional<llvm::StringRef> TypeName =
+        Implementation->getIdentifierString(TypeID);
+    if (!TypeName)
+      return std::nullopt;
+    ParameterSpellings.push_back(TypeName->str());
+  }
+  return ParameterSpellings;
+}
+
 auto APINotesReader::lookupGlobalFunctionImpl(llvm::StringRef Name,
                                               std::optional<Context> Ctx)
     -> VersionedInfo<GlobalFunctionInfo> {
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 6bd88dccf9f65..2d6d39c3b1fbf 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -1083,7 +1083,7 @@ APINotesSelectorDiagnosticState::getOrCreateReaderState(
   if (!Inserted)
     return State;
 
-  SmallVector<api_notes::APINotesFunctionSelector, 4> Selectors;
+  SmallVector<api_notes::APINotesFunctionSelectorKey, 4> Selectors;
   if (!Reader.collectExactFunctionParameterSelectors(Selectors))
     return State;
 
@@ -1451,27 +1451,33 @@ void Sema::ProcessAPINotes(Decl *D) {
   }
 }
 
-void APINotesSelectorDiagnosticReaderState::diagnoseUnused(Sema &S) const {
-  for (const auto &Selector : Selectors) {
-    if (Selector.Used)
+void APINotesSelectorDiagnosticReaderState::diagnoseUnused(
+    Sema &S, api_notes::APINotesReader &Reader) const {
+  for (const auto &Selector : SelectorUsed) {
+    if (Selector.second)
       continue;
 
-    auto SeenName = SeenNames.find(Selector.BroadKey);
+    auto SeenName =
+        SeenNames.find(Selector.first.getWithoutParameterSelector());
     if (SeenName == SeenNames.end())
       continue;
 
+    std::optional<SmallVector<std::string, 4>> ParameterSpellings =
+        Reader.getParameterSelectorSpellingsForDiagnostics(Selector.first);
+    if (!ParameterSpellings)
+      continue;
+
     S.Diag(SeenName->second.Loc, diag::warn_apinotes_message)
         << (llvm::Twine("API notes entry for '") + SeenName->second.Name +
             "' has unmatched Where.Parameters " +
-            api_notes::formatAPINotesParameterSelector(
-                Selector.ParameterSpellings))
+            api_notes::formatAPINotesParameterSelector(*ParameterSpellings))
                .str();
   }
 }
 
 void APINotesSelectorDiagnosticState::diagnoseUnused(Sema &S) const {
   for (const auto &ReaderSelectors : Readers)
-    ReaderSelectors.second.diagnoseUnused(S);
+    ReaderSelectors.second.diagnoseUnused(S, *ReaderSelectors.first);
 }
 
 void Sema::DiagnoseUnusedAPINotesSelectors() {
diff --git a/clang/lib/Sema/SemaAPINotesInternal.h b/clang/lib/Sema/SemaAPINotesInternal.h
index 0ada6e4f74a30..25d2696f64ff3 100644
--- a/clang/lib/Sema/SemaAPINotesInternal.h
+++ b/clang/lib/Sema/SemaAPINotesInternal.h
@@ -26,13 +26,6 @@ namespace api_notes {
 class APINotesReader;
 } // namespace api_notes
 
-/// One stored exact Where.Parameters selector tracked for diagnostics.
-struct APINotesSelectorDiagnosticEntry {
-  api_notes::APINotesFunctionSelectorKey BroadKey;
-  llvm::SmallVector<std::string, 4> ParameterSpellings;
-  bool Used = false;
-};
-
 /// Source name and location for a declaration seen by Sema.
 struct APINotesSelectorDiagnosticName {
   SourceLocation Loc;
@@ -46,9 +39,9 @@ struct APINotesSelectorDiagnosticName {
 /// end-of-TU diagnostics can warn about exact selectors for known names that
 /// were never matched.
 struct APINotesSelectorDiagnosticReaderState {
-  /// Maps exact selector keys to entries in Selectors.
-  llvm::DenseMap<api_notes::APINotesFunctionSelectorKey, unsigned>
-      SelectorIndices;
+  /// Exact Where.Parameters selector keys stored by API notes. The bool is
+  /// true once Sema sees a declaration matching the exact selector.
+  llvm::DenseMap<api_notes::APINotesFunctionSelectorKey, bool> SelectorUsed;
 
   /// Maps broad/name-only keys to a declaration location/name used for
   /// diagnostics.
@@ -56,24 +49,17 @@ struct APINotesSelectorDiagnosticReaderState {
                  APINotesSelectorDiagnosticName>
       SeenNames;
 
-  llvm::SmallVector<APINotesSelectorDiagnosticEntry, 4> Selectors;
-
-  void addSelector(api_notes::APINotesFunctionSelector Selector) {
-    unsigned Index = Selectors.size();
-    APINotesSelectorDiagnosticEntry Entry;
-    Entry.BroadKey = Selector.Key.getWithoutParameterSelector();
-    Entry.ParameterSpellings = std::move(Selector.ParameterSpellings);
-    Selectors.push_back(std::move(Entry));
-    SelectorIndices.insert({Selector.Key, Index});
+  void addSelector(const api_notes::APINotesFunctionSelectorKey &Key) {
+    SelectorUsed.try_emplace(Key, false);
   }
 
-  void addSelectors(llvm::SmallVectorImpl<api_notes::APINotesFunctionSelector>
-                        &NewSelectors) {
-    Selectors.reserve(NewSelectors.size());
-    SelectorIndices.reserve(NewSelectors.size());
-    SeenNames.reserve(NewSelectors.size());
-    for (auto &Selector : NewSelectors)
-      addSelector(std::move(Selector));
+  void
+  addSelectors(llvm::SmallVectorImpl<api_notes::APINotesFunctionSelectorKey>
+                   &Selectors) {
+    SelectorUsed.reserve(Selectors.size());
+    SeenNames.reserve(Selectors.size());
+    for (const auto &Selector : Selectors)
+      addSelector(Selector);
   }
 
   void noteSeenDeclaration(const api_notes::APINotesFunctionSelectorKey &Key,
@@ -82,9 +68,9 @@ struct APINotesSelectorDiagnosticReaderState {
   }
 
   void markUsed(const api_notes::APINotesFunctionSelectorKey &Key) {
-    auto KnownSelector = SelectorIndices.find(Key);
-    if (KnownSelector != SelectorIndices.end())
-      Selectors[KnownSelector->second].Used = true;
+    auto KnownSelector = SelectorUsed.find(Key);
+    if (KnownSelector != SelectorUsed.end())
+      KnownSelector->second = true;
   }
 
   void markCandidatesUsed(
@@ -93,7 +79,7 @@ struct APINotesSelectorDiagnosticReaderState {
           GetSelectorKey,
       const APINotesParameterSelectorCandidates &Candidates);
 
-  void diagnoseUnused(Sema &S) const;
+  void diagnoseUnused(Sema &S, api_notes::APINotesReader &Reader) const;
 };
 
 /// Selector diagnostic state for all API notes readers used by one Sema.

>From 183772b396e9f1e527562b6512f13914cc8d0a0d Mon Sep 17 00:00:00 2001
From: stoeckoverflow <dominic-st at gmx.de>
Date: Tue, 28 Jul 2026 15:35:30 +0200
Subject: [PATCH 12/12] [APINotes] Refine unmatched selector diagnostic
 tracking

---
 clang/include/clang/APINotes/APINotesReader.h |  2 +-
 clang/include/clang/APINotes/Types.h          | 38 ++++++-------------
 clang/lib/APINotes/APINotesReader.cpp         | 27 ++++++-------
 clang/lib/APINotes/APINotesTypes.cpp          | 24 ------------
 clang/lib/APINotes/APINotesYAMLCompiler.cpp   |  3 +-
 clang/lib/Sema/SemaAPINotes.cpp               | 15 +++-----
 clang/lib/Sema/SemaAPINotesInternal.h         |  5 +--
 .../APINotes/where-parameters-diagnostics.cpp | 33 ++++++++++++++++
 8 files changed, 66 insertions(+), 81 deletions(-)

diff --git a/clang/include/clang/APINotes/APINotesReader.h b/clang/include/clang/APINotes/APINotesReader.h
index 4f60a25b55472..d74232bc334c6 100644
--- a/clang/include/clang/APINotes/APINotesReader.h
+++ b/clang/include/clang/APINotes/APINotesReader.h
@@ -219,7 +219,7 @@ class APINotesReader {
                                std::optional<Context> Ctx = std::nullopt);
 
   /// Collect exact parameter selector keys stored by this reader.
-  bool collectExactFunctionParameterSelectors(
+  void collectExactFunctionParameterSelectors(
       llvm::SmallVectorImpl<APINotesFunctionSelectorKey> &Selectors);
 
   /// Reconstruct parameter selector strings for a stored exact selector key.
diff --git a/clang/include/clang/APINotes/Types.h b/clang/include/clang/APINotes/Types.h
index 0852d7d6c3487..af989d3a1b7f0 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -15,7 +15,9 @@
 #include "llvm/ADT/Hashing.h"
 #include "llvm/ADT/PointerEmbeddedInt.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
 #include <climits>
 #include <optional>
 #include <string>
@@ -28,10 +30,15 @@ class raw_ostream;
 namespace clang {
 namespace api_notes {
 
-std::string
-formatAPINotesParameterSelector(llvm::ArrayRef<llvm::StringRef> Parameters);
-std::string
-formatAPINotesParameterSelector(llvm::ArrayRef<std::string> Parameters);
+template <typename RangeT>
+std::string formatAPINotesParameterSelector(RangeT &&Parameters) {
+  std::string Result;
+  llvm::raw_string_ostream OS(Result);
+  OS << "[";
+  llvm::interleaveComma(Parameters, OS);
+  OS << "]";
+  return Result;
+}
 
 enum class RetainCountConventionKind {
   None,
@@ -1091,14 +1098,6 @@ struct ObjCSelectorRef {
 
 namespace llvm {
 template <> struct DenseMapInfo<clang::api_notes::FunctionTableKey> {
-  static clang::api_notes::FunctionTableKey getEmptyKey() {
-    return {0, ~uint32_t(0)};
-  }
-
-  static clang::api_notes::FunctionTableKey getTombstoneKey() {
-    return {0, ~uint32_t(0) - 1};
-  }
-
   static unsigned getHashValue(const clang::api_notes::FunctionTableKey &Key) {
     return Key.hashValue();
   }
@@ -1110,21 +1109,6 @@ template <> struct DenseMapInfo<clang::api_notes::FunctionTableKey> {
 };
 
 template <> struct DenseMapInfo<clang::api_notes::APINotesFunctionSelectorKey> {
-  // The local Key values below are DenseMap sentinels for the wrapper type.
-  // Key.Key stores the wrapped FunctionTableKey sentinel.
-  static clang::api_notes::APINotesFunctionSelectorKey getEmptyKey() {
-    clang::api_notes::APINotesFunctionSelectorKey Key;
-    Key.Key = DenseMapInfo<clang::api_notes::FunctionTableKey>::getEmptyKey();
-    return Key;
-  }
-
-  static clang::api_notes::APINotesFunctionSelectorKey getTombstoneKey() {
-    clang::api_notes::APINotesFunctionSelectorKey Key;
-    Key.Key =
-        DenseMapInfo<clang::api_notes::FunctionTableKey>::getTombstoneKey();
-    return Key;
-  }
-
   static unsigned
   getHashValue(const clang::api_notes::APINotesFunctionSelectorKey &Key) {
     return Key.hashValue();
diff --git a/clang/lib/APINotes/APINotesReader.cpp b/clang/lib/APINotes/APINotesReader.cpp
index 2d5edaea4cdf5..aad597d93d9ee 100644
--- a/clang/lib/APINotes/APINotesReader.cpp
+++ b/clang/lib/APINotes/APINotesReader.cpp
@@ -847,8 +847,8 @@ class APINotesReader::Implementation {
   /// Collect exact parameter selector keys stored in the given function-like
   /// table.
   template <typename TableT>
-  bool collectExactFunctionParameterSelectors(
-      TableT *Table,
+  void collectExactFunctionParameterSelectors(
+      TableT &Table,
       llvm::SmallVectorImpl<APINotesFunctionSelectorKey> &Selectors);
 
   /// Retrieve the selector ID for the given selector, or an empty
@@ -944,24 +944,19 @@ APINotesReader::Implementation::getIdentifierString(IdentifierID ID) {
 }
 
 template <typename TableT>
-bool APINotesReader::Implementation::collectExactFunctionParameterSelectors(
-    TableT *Table,
+void APINotesReader::Implementation::collectExactFunctionParameterSelectors(
+    TableT &Table,
     llvm::SmallVectorImpl<APINotesFunctionSelectorKey> &Selectors) {
   static_assert(std::is_same_v<TableT, SerializedGlobalFunctionTable> ||
                 std::is_same_v<TableT, SerializedCXXMethodTable>);
   constexpr bool IsCXXMethod = std::is_same_v<TableT, SerializedCXXMethodTable>;
 
-  if (!Table)
-    return true;
-
-  for (const FunctionTableKey &Key : Table->keys()) {
+  for (const FunctionTableKey &Key : Table.keys()) {
     if (!Key.parameterTypeIDs)
       continue;
 
     Selectors.push_back(APINotesFunctionSelectorKey{Key, IsCXXMethod});
   }
-
-  return true;
 }
 
 std::optional<FunctionTableKey>
@@ -2530,12 +2525,14 @@ APINotesReader::getGlobalFunctionSelectorKey(
   return APINotesFunctionSelectorKey{*Key, /*IsCXXMethod=*/false};
 }
 
-bool APINotesReader::collectExactFunctionParameterSelectors(
+void APINotesReader::collectExactFunctionParameterSelectors(
     llvm::SmallVectorImpl<APINotesFunctionSelectorKey> &Selectors) {
-  return Implementation->collectExactFunctionParameterSelectors(
-             Implementation->GlobalFunctionTable.get(), Selectors) &&
-         Implementation->collectExactFunctionParameterSelectors(
-             Implementation->CXXMethodTable.get(), Selectors);
+  if (Implementation->GlobalFunctionTable)
+    Implementation->collectExactFunctionParameterSelectors(
+        *Implementation->GlobalFunctionTable, Selectors);
+  if (Implementation->CXXMethodTable)
+    Implementation->collectExactFunctionParameterSelectors(
+        *Implementation->CXXMethodTable, Selectors);
 }
 
 std::optional<llvm::SmallVector<std::string, 4>>
diff --git a/clang/lib/APINotes/APINotesTypes.cpp b/clang/lib/APINotes/APINotesTypes.cpp
index 7e3f71f719763..c8b9272aa0ab7 100644
--- a/clang/lib/APINotes/APINotesTypes.cpp
+++ b/clang/lib/APINotes/APINotesTypes.cpp
@@ -7,35 +7,11 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/APINotes/Types.h"
-#include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/raw_ostream.h"
 
-namespace {
-template <typename ParameterT>
-std::string
-formatAPINotesParameterSelectorImpl(llvm::ArrayRef<ParameterT> Parameters) {
-  std::string Result;
-  llvm::raw_string_ostream OS(Result);
-  OS << "[";
-  llvm::interleaveComma(Parameters, OS);
-  OS << "]";
-  return Result;
-}
-} // namespace
-
 namespace clang {
 namespace api_notes {
 
-std::string
-formatAPINotesParameterSelector(llvm::ArrayRef<llvm::StringRef> Parameters) {
-  return formatAPINotesParameterSelectorImpl(Parameters);
-}
-
-std::string
-formatAPINotesParameterSelector(llvm::ArrayRef<std::string> Parameters) {
-  return formatAPINotesParameterSelectorImpl(Parameters);
-}
-
 LLVM_DUMP_METHOD void CommonEntityInfo::dump(llvm::raw_ostream &OS) const {
   if (Unavailable)
     OS << "[Unavailable] (" << UnavailableMsg << ")" << ' ';
diff --git a/clang/lib/APINotes/APINotesYAMLCompiler.cpp b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
index 4d75bea792e3b..4079675228a21 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -804,7 +804,8 @@ getFunctionSelectorKey(llvm::StringRef Name,
 
   AppendKeyPart(Name);
   OS << ';';
-  llvm::interleave(Parameters, OS, AppendKeyPart, "");
+  for (llvm::StringRef Parameter : Parameters)
+    AppendKeyPart(Parameter);
   return Key.str().str();
 }
 
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index 2d6d39c3b1fbf..c5560605124c8 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -1084,9 +1084,7 @@ APINotesSelectorDiagnosticState::getOrCreateReaderState(
     return State;
 
   SmallVector<api_notes::APINotesFunctionSelectorKey, 4> Selectors;
-  if (!Reader.collectExactFunctionParameterSelectors(Selectors))
-    return State;
-
+  Reader.collectExactFunctionParameterSelectors(Selectors);
   State.addSelectors(Selectors);
   return State;
 }
@@ -1186,9 +1184,7 @@ void Sema::ProcessAPINotes(Decl *D) {
                                                       APINotesContext);
                 });
 
-          if (ParameterSelectorCandidates &&
-              !Diags.isIgnored(diag::warn_apinotes_message,
-                               FD->getLocation())) {
+          if (ParameterSelectorCandidates) {
             auto &DiagnosticState =
                 getAPINotesSelectorDiagnosticState(*this, Reader);
             if (auto BroadKey = Reader->getGlobalFunctionSelectorKey(
@@ -1408,9 +1404,7 @@ void Sema::ProcessAPINotes(Decl *D) {
                                                    Parameters);
                   });
 
-            if (ParameterSelectorCandidates &&
-                !Diags.isIgnored(diag::warn_apinotes_message,
-                                 CXXMethod->getLocation())) {
+            if (ParameterSelectorCandidates) {
               auto &DiagnosticState =
                   getAPINotesSelectorDiagnosticState(*this, Reader);
               if (auto BroadKey =
@@ -1484,6 +1478,7 @@ void Sema::DiagnoseUnusedAPINotesSelectors() {
   if (!APINotesSelectorDiagnostics)
     return;
 
-  APINotesSelectorDiagnostics->diagnoseUnused(*this);
+  if (!Diags.isIgnored(diag::warn_apinotes_message, SourceLocation()))
+    APINotesSelectorDiagnostics->diagnoseUnused(*this);
   APINotesSelectorDiagnostics.reset();
 }
diff --git a/clang/lib/Sema/SemaAPINotesInternal.h b/clang/lib/Sema/SemaAPINotesInternal.h
index 25d2696f64ff3..5766343956ce5 100644
--- a/clang/lib/Sema/SemaAPINotesInternal.h
+++ b/clang/lib/Sema/SemaAPINotesInternal.h
@@ -53,9 +53,8 @@ struct APINotesSelectorDiagnosticReaderState {
     SelectorUsed.try_emplace(Key, false);
   }
 
-  void
-  addSelectors(llvm::SmallVectorImpl<api_notes::APINotesFunctionSelectorKey>
-                   &Selectors) {
+  void addSelectors(
+      llvm::ArrayRef<api_notes::APINotesFunctionSelectorKey> Selectors) {
     SelectorUsed.reserve(Selectors.size());
     SeenNames.reserve(Selectors.size());
     for (const auto &Selector : Selectors)
diff --git a/clang/test/APINotes/where-parameters-diagnostics.cpp b/clang/test/APINotes/where-parameters-diagnostics.cpp
index 58bcaa7e114d8..c83646d80d464 100644
--- a/clang/test/APINotes/where-parameters-diagnostics.cpp
+++ b/clang/test/APINotes/where-parameters-diagnostics.cpp
@@ -1,6 +1,8 @@
 // RUN: rm -rf %t && split-file %s %t
 // RUN: not %clang_cc1 -fsyntax-only -fapinotes %t/diagnostics.cpp -I %t/WhereParametersDuplicateSelectorDiag 2>&1 | FileCheck %t/WhereParametersDuplicateSelectorDiag/APINotes.apinotes --check-prefix=DUPLICATE
 // RUN: rm -rf %t/ModulesCache && mkdir -p %t/ModulesCache
+// RUN: rm -rf %t/PragmaModulesCache && mkdir -p %t/PragmaModulesCache
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/PragmaModulesCache -fdisable-module-hash -fapinotes-modules -Wapinotes -fsyntax-only -I %t/WhereParametersPragmaDiag %t/pragma-diagnostics.cpp -x c++ 2>&1 | count 0
 // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wapinotes -fsyntax-only -I %S/Inputs/Headers %t/diagnostics.cpp -x c++ 2>&1 | FileCheck %s --check-prefix=UNMATCHED --implicit-check-not=diagnosticMatchedGlobal --implicit-check-not=diagnosticAliasMatchedGlobal --implicit-check-not=diagnosticMatchedMethod --implicit-check-not=diagnosticAliasMatchedMethod
 // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %t/diagnostics.cpp -ast-dump -ast-dump-filter diagnosticBroadGlobal -x c++ | FileCheck %s --check-prefix=BROAD-GLOBAL
 // RUN: %clang_cc1 -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/WhereParametersDiagnostics -fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %t/diagnostics.cpp -ast-dump -ast-dump-filter DiagnosticWidget::diagnosticBroadMethod -x c++ | FileCheck %s --check-prefix=BROAD-METHOD
@@ -127,3 +129,34 @@ Name: WhereParametersDiagnostics
       Parameters:
       - double
     SwiftName: allowedDouble(_:)
+
+
+//--- pragma-diagnostics.cpp
+#include "WhereParametersPragma.h"
+
+//--- WhereParametersPragmaDiag/module.modulemap
+module WhereParametersPragma { header "WhereParametersPragma.h" }
+
+//--- WhereParametersPragmaDiag/WhereParametersPragma.h
+#ifndef WHERE_PARAMETERS_PRAGMA_H
+#define WHERE_PARAMETERS_PRAGMA_H
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wapinotes"
+void pragmaMatched(int);
+#pragma clang diagnostic pop
+
+void pragmaMatched(float);
+
+#endif // WHERE_PARAMETERS_PRAGMA_H
+
+//--- WhereParametersPragmaDiag/WhereParametersPragma.apinotes
+---
+Name: WhereParametersPragma
+Functions:
+- Name: pragmaMatched
+  Where:
+    Parameters:
+    - int
+  SwiftName: pragmaMatched(_:)
+...



More information about the cfe-commits mailing list