[clang] [APINotes] Diagnose invalid Where.Parameters selectors (PR #209408)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 27 02:54:02 PDT 2026
https://github.com/StoeckOverflow updated https://github.com/llvm/llvm-project/pull/209408
>From 91dbf3cd44e960b2568724e0c9bf4990d7684040 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 1/7] [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 a5b094c24c74322f598481359075d0469aa1c068 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 2/7] [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 1017bcf15acf65712f365a5c2ffdeef8a2c37765 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 3/7] [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 68fa0f539405de82ab23a0e9bf8a742990a55e63 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 4/7] [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 134a2e295f25447f883072a2d8e486bc967933af 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 5/7] [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 bb5697a16e090..2c5a257f41190 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -157,6 +157,7 @@ enum class OverloadCandidateParamOrder : char;
enum OverloadCandidateRewriteKind : unsigned;
class OverloadCandidateSet;
class Preprocessor;
+struct APINotesSelectorDiagnosticState;
class SemaAMDGPU;
class SemaARM;
class SemaAVR;
@@ -1313,6 +1314,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:
@@ -1665,6 +1668,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 e689c75eb566c..ae9dbe345f7bf 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"
@@ -1313,6 +1314,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 25eaaf2d8a27ab4a26927be147f442867debb10b 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 6/7] [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 dfcf9f62b73a2bc894b9389024a1516403ca4275 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 7/7] [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>;
More information about the cfe-commits
mailing list