https://github.com/StoeckOverflow created 
https://github.com/llvm/llvm-project/pull/213043

This PR normalizes exact `Where.Parameters` selector spellings and adds focused 
unit coverage for declaration-side selector extraction.

It teaches API notes conversion to normalize selector strings before storing, 
comparing, and diagnosing them, so supported equivalent spellings such as `int 
*` and `int*`, `Box<int, double>` and `Box<int,double>`, or top-level by-value 
/ pointer `const` variants compare consistently with Sema-created declaration 
selectors.

The normalizer is intentionally lexical. It does not parse selector strings as 
Clang types and does not broaden `Where.Parameters` into canonical type 
matching.

It moves the declaration-side selector code from `SemaAPINotes.cpp` into 
`clang/Sema/APINotesSelector.h` and `clang/lib/Sema/APINotesSelector.cpp`. The 
new helper, `getAPINotesParameterSelectorCandidates`, builds the 
source-spelling selector and optional desugared fallback selector for a 
`FunctionDecl`.

`SemaAPINotes.cpp` now calls that helper for exact `Where.Parameters` matching 
and unmatched-selector diagnostics, while keeping API notes lookup, note 
application, and diagnostic emission there.

The new `APINotesSelectorTest` unit test builds small Clang ASTs from 
declaration snippets and checks the selector strings returned by 
`getAPINotesParameterSelectorCandidates`.

Tests cover whitespace normalization, pointer/reference punctuation spacing, 
template punctuation spacing, supported top-level `const` normalization, 
normalized duplicate-selector diagnostics, zero-parameter selectors, 
multi-parameter/defaulted declarations, alias and deep-alias fallback, and 
Objective-C++ nullability stripping. Existing Sema/API-notes coverage continues 
to exercise global-function and C++-method matching paths.

Reviewers: @Xazax-hun @j-hui @egorzhdan

>From a8d94426ecc21e5b8cb7c8f5edcbddbb240fcb71 Mon Sep 17 00:00:00 2001
From: stoeckoverflow <[email protected]>
Date: Fri, 19 Jun 2026 13:42:06 +0200
Subject: [PATCH] [APINotes] Normalize Where.Parameters selector spellings

---
 clang/include/clang/APINotes/Types.h          |   3 +
 clang/include/clang/Sema/APINotesSelector.h   |  44 +++++
 clang/lib/APINotes/APINotesTypes.cpp          | 117 ++++++++++++
 clang/lib/APINotes/APINotesYAMLCompiler.cpp   |  73 ++++++--
 clang/lib/Sema/APINotesSelector.cpp           |  85 +++++++++
 clang/lib/Sema/CMakeLists.txt                 |   1 +
 clang/lib/Sema/SemaAPINotes.cpp               |  86 +--------
 .../WhereParametersNormalization.apinotes     | 131 ++++++++++++++
 .../Headers/WhereParametersNormalization.h    |  49 ++++++
 .../APINotes/Inputs/Headers/module.modulemap  |   5 +
 .../APINotes/where-parameters-diagnostics.cpp |  24 +++
 .../where-parameters-normalization.cpp        | 112 ++++++++++++
 clang/unittests/Sema/APINotesSelectorTest.cpp | 166 ++++++++++++++++++
 clang/unittests/Sema/CMakeLists.txt           |   1 +
 14 files changed, 798 insertions(+), 99 deletions(-)
 create mode 100644 clang/include/clang/Sema/APINotesSelector.h
 create mode 100644 clang/lib/Sema/APINotesSelector.cpp
 create mode 100644 
clang/test/APINotes/Inputs/Headers/WhereParametersNormalization.apinotes
 create mode 100644 
clang/test/APINotes/Inputs/Headers/WhereParametersNormalization.h
 create mode 100644 clang/test/APINotes/where-parameters-normalization.cpp
 create mode 100644 clang/unittests/Sema/APINotesSelectorTest.cpp

diff --git a/clang/include/clang/APINotes/Types.h 
b/clang/include/clang/APINotes/Types.h
index af989d3a1b7f0..a839c08300b40 100644
--- a/clang/include/clang/APINotes/Types.h
+++ b/clang/include/clang/APINotes/Types.h
@@ -66,6 +66,9 @@ enum class SwiftNewTypeKind {
 
 enum class SwiftSafetyKind { Unspecified, Safe, Unsafe, None };
 
+/// Normalize an API notes parameter selector spelling for matching.
+std::string normalizeAPINotesParameterSelector(llvm::StringRef Spelling);
+
 /// Describes API notes data for any entity.
 ///
 /// This is used as the base of all API notes.
diff --git a/clang/include/clang/Sema/APINotesSelector.h 
b/clang/include/clang/Sema/APINotesSelector.h
new file mode 100644
index 0000000000000..8a95437354a4e
--- /dev/null
+++ b/clang/include/clang/Sema/APINotesSelector.h
@@ -0,0 +1,44 @@
+//===--- APINotesSelector.h - API Notes selector helpers --------*- 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_SEMA_APINOTESSELECTOR_H
+#define LLVM_CLANG_SEMA_APINOTESSELECTOR_H
+
+#include "llvm/ADT/SmallVector.h"
+#include <optional>
+#include <string>
+
+namespace clang {
+
+class ASTContext;
+class FunctionDecl;
+
+struct APINotesParameterSelector {
+  llvm::SmallVector<std::string, 4> Parameters;
+
+  bool operator==(const APINotesParameterSelector &Other) const {
+    return Parameters == Other.Parameters;
+  }
+
+  bool operator!=(const APINotesParameterSelector &Other) const {
+    return !(*this == Other);
+  }
+};
+
+struct APINotesParameterSelectorCandidates {
+  APINotesParameterSelector Source;
+  std::optional<APINotesParameterSelector> Desugared;
+};
+
+std::optional<APINotesParameterSelectorCandidates>
+getAPINotesParameterSelectorCandidates(const ASTContext &Context,
+                                       const FunctionDecl *FD);
+
+} // namespace clang
+
+#endif // LLVM_CLANG_SEMA_APINOTESSELECTOR_H
diff --git a/clang/lib/APINotes/APINotesTypes.cpp 
b/clang/lib/APINotes/APINotesTypes.cpp
index c8b9272aa0ab7..35ea18a0ebfda 100644
--- a/clang/lib/APINotes/APINotesTypes.cpp
+++ b/clang/lib/APINotes/APINotesTypes.cpp
@@ -7,11 +7,128 @@
 
//===----------------------------------------------------------------------===//
 
 #include "clang/APINotes/Types.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/raw_ostream.h"
+#include <tuple>
 
 namespace clang {
 namespace api_notes {
 
+// Conservatively detect spellings where cv-qualification belongs to an
+// indirect/declarator layer rather than the by-value parameter itself.
+static bool
+hasTopLevelIndirectParameterSelectorSpelling(llvm::StringRef Spelling) {
+  unsigned Depth = 0;
+
+  for (char C : Spelling) {
+    switch (C) {
+    case '*':
+    case '&':
+      if (Depth == 0)
+        return true;
+      break;
+
+    case '[':
+    case '(':
+      if (Depth == 0)
+        return true;
+      ++Depth;
+      break;
+
+    case '<':
+      ++Depth;
+      break;
+
+    case '>':
+    case ']':
+    case ')':
+      if (Depth != 0)
+        --Depth;
+      break;
+
+    default:
+      break;
+    }
+  }
+
+  return false;
+}
+
+static bool shouldDropParameterSelectorSpace(char Previous, char Next) {
+  if (Previous == '<' || Previous == ',' || Next == '>' || Next == ',' ||
+      Next == '<')
+    return true;
+
+  if (Next == '*' || Next == '&')
+    return true;
+
+  if (Previous == '&' && Next == '&')
+    return true;
+
+  return false;
+}
+
+static void
+collapseParameterSelectorWhitespace(llvm::StringRef Spelling,
+                                    llvm::SmallVectorImpl<char> &Collapsed) {
+  Collapsed.clear();
+  while (!Spelling.empty()) {
+    llvm::StringRef Token;
+    std::tie(Token, Spelling) = llvm::getToken(Spelling);
+    if (Token.empty())
+      break;
+
+    if (!Collapsed.empty())
+      Collapsed.push_back(' ');
+    Collapsed.append(Token.begin(), Token.end());
+  }
+}
+
+static llvm::StringRef stripTopLevelValueConst(llvm::StringRef Spelling) {
+  if (!hasTopLevelIndirectParameterSelectorSpelling(Spelling))
+    Spelling.consume_front("const ");
+  Spelling.consume_back(" const");
+  return Spelling;
+}
+
+// Remove spaces around selector punctuation while preserving token-separating
+// spaces such as the one in "unsigned int".
+static void removeParameterSelectorPunctuationSpaces(
+    llvm::StringRef Spelling, llvm::SmallVectorImpl<char> &Normalized) {
+  Normalized.clear();
+  for (unsigned I = 0, E = Spelling.size(); I != E; ++I) {
+    char C = Spelling[I];
+    if (C == ' ' && I != 0 && I + 1 != E &&
+        shouldDropParameterSelectorSpace(Spelling[I - 1], Spelling[I + 1]))
+      continue;
+
+    Normalized.push_back(C);
+  }
+}
+
+static std::string stripTopLevelPointerConst(llvm::StringRef Spelling) {
+  if (!Spelling.consume_back("*const"))
+    return Spelling.str();
+
+  std::string WithoutTopLevelConst = Spelling.str();
+  WithoutTopLevelConst += '*';
+  return WithoutTopLevelConst;
+}
+
+std::string normalizeAPINotesParameterSelector(llvm::StringRef Spelling) {
+  llvm::SmallString<32> Collapsed;
+  collapseParameterSelectorWhitespace(Spelling, Collapsed);
+
+  llvm::StringRef WithoutTopLevelValueConst =
+      stripTopLevelValueConst(Collapsed);
+
+  llvm::SmallString<32> WithoutPunctuationSpaces;
+  removeParameterSelectorPunctuationSpaces(WithoutTopLevelValueConst,
+                                           WithoutPunctuationSpaces);
+  return stripTopLevelPointerConst(WithoutPunctuationSpaces);
+}
+
 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 4079675228a21..e9b6cc847952c 100644
--- a/clang/lib/APINotes/APINotesYAMLCompiler.cpp
+++ b/clang/lib/APINotes/APINotesYAMLCompiler.cpp
@@ -21,6 +21,7 @@
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/VersionTuple.h"
@@ -809,6 +810,31 @@ getFunctionSelectorKey(llvm::StringRef Name,
   return Key.str().str();
 }
 
+// YAML conversion has parameter spellings but no AST context. Keep this as a
+// narrow lexical normalization step. Declaration spellings are normalized with
+// QualType in Sema before using the same lexical selector normalization.
+static void normalizeWhereParameterList(
+    llvm::ArrayRef<llvm::StringRef> Parameters,
+    llvm::SmallVectorImpl<std::string> &NormalizedParameters) {
+  NormalizedParameters.clear();
+  NormalizedParameters.reserve(Parameters.size());
+
+  for (llvm::StringRef Parameter : Parameters)
+    NormalizedParameters.push_back(
+        normalizeAPINotesParameterSelector(Parameter));
+}
+
+static llvm::SmallVector<llvm::StringRef, 4>
+getParameterSelectorRefs(llvm::ArrayRef<std::string> Parameters) {
+  llvm::SmallVector<llvm::StringRef, 4> ParameterRefs;
+  ParameterRefs.reserve(Parameters.size());
+
+  for (const std::string &Parameter : Parameters)
+    ParameterRefs.push_back(Parameter);
+
+  return ParameterRefs;
+}
+
 class YAMLConverter {
   const Module &M;
   APINotesWriter Writer;
@@ -1190,24 +1216,32 @@ class YAMLConverter {
         continue;
 
       if (WhereParameters.second) {
+        llvm::SmallVector<std::string, 4> NormalizedWhereParameters;
+        normalizeWhereParameterList(*WhereParameters.second,
+                                    NormalizedWhereParameters);
+        auto NormalizedWhereParameterRefs =
+            getParameterSelectorRefs(NormalizedWhereParameters);
         if (!KnownMethodSelectors
                  .insert(getFunctionSelectorKey(CXXMethod.Name,
-                                                *WhereParameters.second))
+                                                NormalizedWhereParameterRefs))
                  .second) {
           emitError(llvm::Twine("multiple API notes entries for C++ method '") 
+
                     CXXMethod.Name + "' with Where.Parameters " +
-                    formatAPINotesParameterSelector(*WhereParameters.second));
+                    api_notes::formatAPINotesParameterSelector(
+                        NormalizedWhereParameters));
           continue;
         }
+
+        CXXMethodInfo MI;
+        convertFunction(CXXMethod, MI);
+        Writer.addCXXMethod(TagCtxID, CXXMethod.Name,
+                            NormalizedWhereParameterRefs, MI, SwiftVersion);
+        continue;
       }
 
       CXXMethodInfo MI;
       convertFunction(CXXMethod, MI);
-      if (WhereParameters.second)
-        Writer.addCXXMethod(TagCtxID, CXXMethod.Name, *WhereParameters.second,
-                            MI, SwiftVersion);
-      else
-        Writer.addCXXMethod(TagCtxID, CXXMethod.Name, MI, SwiftVersion);
+      Writer.addCXXMethod(TagCtxID, CXXMethod.Name, MI, SwiftVersion);
     }
 
     // Convert nested tags.
@@ -1284,21 +1318,32 @@ class YAMLConverter {
         continue;
 
       if (WhereParameters.second) {
+        llvm::SmallVector<std::string, 4> NormalizedWhereParameters;
+        normalizeWhereParameterList(*WhereParameters.second,
+                                    NormalizedWhereParameters);
+        auto NormalizedWhereParameterRefs =
+            getParameterSelectorRefs(NormalizedWhereParameters);
         if (!KnownFunctionSelectors
                  .insert(getFunctionSelectorKey(Function.Name,
-                                                *WhereParameters.second))
+                                                NormalizedWhereParameterRefs))
                  .second) {
           emitError(
               llvm::Twine("multiple API notes entries for global function '") +
               Function.Name + "' with Where.Parameters " +
-              formatAPINotesParameterSelector(*WhereParameters.second));
+              formatAPINotesParameterSelector(NormalizedWhereParameters));
           continue;
         }
+
+        GlobalFunctionInfo GFI;
+        convertFunction(Function, GFI);
+        Writer.addGlobalFunction(Ctx, Function.Name,
+                                 NormalizedWhereParameterRefs, GFI,
+                                 SwiftVersion);
+        continue;
       }
 
       // Check for duplicate name-only global functions.
-      if (!WhereParameters.second &&
-          !KnownNameOnlyFunctions.insert(Function.Name).second) {
+      if (!KnownNameOnlyFunctions.insert(Function.Name).second) {
         emitError(llvm::Twine("multiple definitions of global function '") +
                   Function.Name + "'");
         continue;
@@ -1306,11 +1351,7 @@ class YAMLConverter {
 
       GlobalFunctionInfo GFI;
       convertFunction(Function, GFI);
-      if (WhereParameters.second)
-        Writer.addGlobalFunction(Ctx, Function.Name, *WhereParameters.second,
-                                 GFI, SwiftVersion);
-      else
-        Writer.addGlobalFunction(Ctx, Function.Name, GFI, SwiftVersion);
+      Writer.addGlobalFunction(Ctx, Function.Name, GFI, SwiftVersion);
     }
 
     // Write all enumerators.
diff --git a/clang/lib/Sema/APINotesSelector.cpp 
b/clang/lib/Sema/APINotesSelector.cpp
new file mode 100644
index 0000000000000..c45c0f7d28927
--- /dev/null
+++ b/clang/lib/Sema/APINotesSelector.cpp
@@ -0,0 +1,85 @@
+//===--- APINotesSelector.cpp - API Notes selector helpers 
----------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Sema/APINotesSelector.h"
+#include "clang/APINotes/Types.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/PrettyPrinter.h"
+#include "clang/AST/Type.h"
+
+using namespace clang;
+
+namespace {
+
+void stripAPINotesParameterNullability(QualType &ParamType) {
+  while (true) {
+    if (!AttributedType::stripOuterNullability(ParamType))
+      return;
+  }
+}
+
+PrintingPolicy
+getAPINotesParameterSelectorPrintingPolicy(const ASTContext &Context) {
+  PrintingPolicy Policy(Context.getLangOpts());
+  Policy.PrintAsCanonical = false;
+  Policy.FullyQualifiedName = false;
+  Policy.SuppressScope = false;
+  Policy.UsePreferredNames = false;
+  Policy.MSVCFormatting = false;
+  Policy.SplitTemplateClosers = false;
+  Policy.IncludeNewlines = false;
+  return Policy;
+}
+
+// Print the APINotes selector spelling for one parameter. The source-spelled
+// selector is tried first. The desugared spelling is only a permissive
+// fallback.
+std::string getAPINotesParameterSelectorSpelling(QualType ParamType,
+                                                 const ASTContext &Context,
+                                                 const PrintingPolicy &Policy,
+                                                 bool Desugar) {
+  if (Desugar)
+    ParamType = ParamType.getDesugaredType(Context);
+
+  ParamType.removeLocalConst();
+  stripAPINotesParameterNullability(ParamType);
+
+  return api_notes::normalizeAPINotesParameterSelector(
+      ParamType.getAsString(Policy));
+}
+
+} // namespace
+
+std::optional<APINotesParameterSelectorCandidates>
+clang::getAPINotesParameterSelectorCandidates(const ASTContext &Context,
+                                              const FunctionDecl *FD) {
+  const auto *FPT = FD->getType()->getAs<FunctionProtoType>();
+  if (!FPT)
+    return std::nullopt;
+
+  APINotesParameterSelectorCandidates Candidates;
+  APINotesParameterSelector Desugared;
+  Candidates.Source.Parameters.reserve(FPT->getNumParams());
+  Desugared.Parameters.reserve(FPT->getNumParams());
+
+  const PrintingPolicy Policy =
+      getAPINotesParameterSelectorPrintingPolicy(Context);
+  for (QualType ParamType : FPT->param_types()) {
+    Candidates.Source.Parameters.push_back(
+        getAPINotesParameterSelectorSpelling(ParamType, Context, Policy,
+                                             /*Desugar=*/false));
+    Desugared.Parameters.push_back(getAPINotesParameterSelectorSpelling(
+        ParamType, Context, Policy, /*Desugar=*/true));
+  }
+
+  if (Candidates.Source != Desugared)
+    Candidates.Desugared = std::move(Desugared);
+
+  return Candidates;
+}
diff --git a/clang/lib/Sema/CMakeLists.txt b/clang/lib/Sema/CMakeLists.txt
index 88f0c993888d9..7ab1916eb568b 100644
--- a/clang/lib/Sema/CMakeLists.txt
+++ b/clang/lib/Sema/CMakeLists.txt
@@ -14,6 +14,7 @@ clang_tablegen(OpenCLBuiltins.inc -gen-clang-opencl-builtins
   )
 
 add_clang_library(clangSema
+  APINotesSelector.cpp
   AnalysisBasedWarnings.cpp
   CheckExprLifetime.cpp
   CodeCompleteConsumer.cpp
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index c5560605124c8..0813e5a6603b2 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -21,6 +21,7 @@
 #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
 #include "clang/Basic/SourceLocation.h"
 #include "clang/Lex/Lexer.h"
+#include "clang/Sema/APINotesSelector.h"
 #include "clang/Sema/SemaObjC.h"
 #include "clang/Sema/SemaSwift.h"
 #include <stack>
@@ -994,87 +995,6 @@ UnwindTagContext(TagDecl *DC, api_notes::APINotesManager 
&APINotes) {
   return std::nullopt;
 }
 
-static void stripAPINotesParameterNullability(QualType &ParamType) {
-  while (true) {
-    if (!AttributedType::stripOuterNullability(ParamType))
-      return;
-  }
-}
-
-namespace clang {
-struct APINotesParameterSelector {
-  SmallVector<std::string, 4> Parameters;
-
-  bool operator==(const APINotesParameterSelector &Other) const {
-    return Parameters == Other.Parameters;
-  }
-
-  bool operator!=(const APINotesParameterSelector &Other) const {
-    return !(*this == Other);
-  }
-};
-
-struct APINotesParameterSelectorCandidates {
-  APINotesParameterSelector Source;
-  std::optional<APINotesParameterSelector> Desugared;
-};
-} // namespace clang
-
-static PrintingPolicy
-getAPINotesParameterSelectorPrintingPolicy(const ASTContext &Context) {
-  PrintingPolicy Policy(Context.getLangOpts());
-  Policy.PrintAsCanonical = false;
-  Policy.FullyQualifiedName = false;
-  Policy.SuppressScope = false;
-  Policy.UsePreferredNames = false;
-  Policy.MSVCFormatting = false;
-  Policy.SplitTemplateClosers = false;
-  Policy.IncludeNewlines = false;
-  return Policy;
-}
-
-// Print the APINotes selector spelling for one parameter. The source-spelled
-// selector is tried first. The desugared spelling is only a permissive
-// fallback.
-static std::string getAPINotesParameterSelectorSpelling(
-    QualType ParamType, const ASTContext &Context, const PrintingPolicy 
&Policy,
-    bool Desugar) {
-  if (Desugar)
-    ParamType = ParamType.getDesugaredType(Context);
-
-  ParamType.removeLocalConst();
-  stripAPINotesParameterNullability(ParamType);
-
-  return ParamType.getAsString(Policy);
-}
-
-static std::optional<APINotesParameterSelectorCandidates>
-getAPINotesParameterSelectorCandidates(const Sema &S, const FunctionDecl *FD) {
-  const auto *FPT = FD->getType()->getAs<FunctionProtoType>();
-  if (!FPT)
-    return std::nullopt;
-
-  APINotesParameterSelectorCandidates Candidates;
-  APINotesParameterSelector Desugared;
-  Candidates.Source.Parameters.reserve(FPT->getNumParams());
-  Desugared.Parameters.reserve(FPT->getNumParams());
-
-  const PrintingPolicy Policy =
-      getAPINotesParameterSelectorPrintingPolicy(S.Context);
-  for (QualType ParamType : FPT->param_types()) {
-    Candidates.Source.Parameters.push_back(
-        getAPINotesParameterSelectorSpelling(ParamType, S.Context, Policy,
-                                             /*Desugar=*/false));
-    Desugared.Parameters.push_back(getAPINotesParameterSelectorSpelling(
-        ParamType, S.Context, Policy, /*Desugar=*/true));
-  }
-
-  if (Candidates.Source != Desugared)
-    Candidates.Desugared = std::move(Desugared);
-
-  return Candidates;
-}
-
 APINotesSelectorDiagnosticReaderState &
 APINotesSelectorDiagnosticState::getOrCreateReaderState(
     api_notes::APINotesReader &Reader) {
@@ -1169,7 +1089,7 @@ void Sema::ProcessAPINotes(Decl *D) {
     if (auto FD = dyn_cast<FunctionDecl>(D)) {
       if (FD->getDeclName().isIdentifier()) {
         auto ParameterSelectorCandidates =
-            getAPINotesParameterSelectorCandidates(*this, FD);
+            getAPINotesParameterSelectorCandidates(Context, FD);
 
         for (auto Reader : Readers) {
           auto Info =
@@ -1382,7 +1302,7 @@ void Sema::ProcessAPINotes(Decl *D) {
           !isa<CXXDestructorDecl>(CXXMethod) &&
           !isa<CXXConversionDecl>(CXXMethod)) {
         auto ParameterSelectorCandidates =
-            getAPINotesParameterSelectorCandidates(*this, CXXMethod);
+            getAPINotesParameterSelectorCandidates(getASTContext(), CXXMethod);
         for (auto Reader : Readers) {
           if (auto Context = UnwindTagContext(TagContext, APINotes)) {
             std::string MethodName;
diff --git 
a/clang/test/APINotes/Inputs/Headers/WhereParametersNormalization.apinotes 
b/clang/test/APINotes/Inputs/Headers/WhereParametersNormalization.apinotes
new file mode 100644
index 0000000000000..6c9ed80e4a431
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/WhereParametersNormalization.apinotes
@@ -0,0 +1,131 @@
+---
+Name: WhereParametersNormalization
+Functions:
+- Name: normalizedEmpty
+  Where:
+    Parameters: []
+  SwiftName: normalizedEmpty()
+- Name: normalizedDefaults
+  Where:
+    Parameters:
+    - int
+    - double
+  SwiftName: normalizedDefaults(_:_:)
+- Name: normalizedWhitespace
+  Where:
+    Parameters:
+    - '  unsigned   int  '
+  SwiftName: normalizedWhitespace(_:)
+- Name: normalizedTemplateSpacing
+  Where:
+    Parameters:
+    - 'NormalizationBox<int,double>'
+  SwiftName: normalizedTemplateSpacing(_:)
+- Name: normalizedPointerSpacing
+  Where:
+    Parameters:
+    - 'int*'
+  SwiftName: normalizedPointerSpacing(_:)
+- Name: normalizedRValueReferenceSpacing
+  Where:
+    Parameters:
+    - 'int&&'
+  SwiftName: normalizedRValueReferenceSpacing(_:)
+- Name: normalizedConstValue
+  Where:
+    Parameters:
+    - int
+  SwiftName: normalizedConstValue(_:)
+- Name: normalizedConstSpelling
+  Where:
+    Parameters:
+    - const int
+  SwiftName: normalizedConstSpelling(_:)
+- Name: normalizedConstSuffixSpelling
+  Where:
+    Parameters:
+    - int const
+  SwiftName: normalizedConstSuffixSpelling(_:)
+- Name: normalizedPointerConst
+  Where:
+    Parameters:
+    - 'int * const'
+  SwiftName: normalizedPointerConst(_:)
+- Name: normalizedPointeeConst
+  Where:
+    Parameters:
+    - 'const int *'
+  SwiftName: normalizedPointeeConst(_:)
+- Name: normalizedPointeeConstMismatch
+  Where:
+    Parameters:
+    - 'int *'
+  SwiftName: shouldNotApplyNormalizedPointeeConst(_:)
+- Name: normalizedAlias
+  Where:
+    Parameters:
+    - int
+  SwiftName: normalizedAlias(_:)
+- Name: normalizedDeepAlias
+  Where:
+    Parameters:
+    - int
+  SwiftName: normalizedDeepAlias(_:)
+- Name: normalizedDeepAliasSource
+  Where:
+    Parameters:
+    - NormalizationDeepAliasInt
+  SwiftName: normalizedDeepAliasSource(_:)
+- Name: normalizedIntermediateAliasMismatch
+  Where:
+    Parameters:
+    - NormalizationAliasAliasInt
+  SwiftName: shouldNotApplyNormalizedIntermediateAlias(_:)
+- Name: normalizedConstAlias
+  Where:
+    Parameters:
+    - int
+  SwiftName: normalizedConstAlias(_:)
+- Name: normalizedNullable
+  Where:
+    Parameters:
+    - 'char *'
+  SwiftName: normalizedNullable(_:)
+- Name: normalizedRawInt
+  Where:
+    Parameters:
+    - int
+  SwiftName: normalizedRawInt(_:)
+Tags:
+- Name: NormalizationWidget
+  Methods:
+  - Name: empty
+    Where:
+      Parameters: []
+    SwiftName: empty()
+  - Name: defaults
+    Where:
+      Parameters:
+      - int
+      - double
+    SwiftName: defaults(_:_:)
+  - Name: configure
+    Where:
+      Parameters:
+      - int
+    SwiftName: configure(_:)
+  - Name: pointerSpacing
+    Where:
+      Parameters:
+      - 'int*'
+    SwiftName: pointerSpacing(_:)
+  - Name: pointeeConstMismatch
+    Where:
+      Parameters:
+      - 'int *'
+    SwiftName: shouldNotApplyNormalizedPointeeConst(_:)
+  - Name: deepAlias
+    Where:
+      Parameters:
+      - int
+    SwiftName: deepAlias(_:)
diff --git a/clang/test/APINotes/Inputs/Headers/WhereParametersNormalization.h 
b/clang/test/APINotes/Inputs/Headers/WhereParametersNormalization.h
new file mode 100644
index 0000000000000..8abb575c72985
--- /dev/null
+++ b/clang/test/APINotes/Inputs/Headers/WhereParametersNormalization.h
@@ -0,0 +1,49 @@
+#ifndef WHERE_PARAMETERS_NORMALIZATION_H
+#define WHERE_PARAMETERS_NORMALIZATION_H
+
+using NormalizationAliasInt = int;
+using NormalizationAliasAliasInt = NormalizationAliasInt;
+using NormalizationDeepAliasInt = NormalizationAliasAliasInt;
+using NormalizationConstAliasInt = const int;
+
+template <typename T, typename U> struct NormalizationBox {};
+
+void normalizedEmpty();
+void normalizedEmpty(int);
+
+void normalizedDefaults(int, double = 0);
+void normalizedDefaults(int);
+
+void normalizedWhitespace(unsigned int);
+void normalizedTemplateSpacing(NormalizationBox<int, double>);
+void normalizedPointerSpacing(int *);
+void normalizedRValueReferenceSpacing(int &&);
+void normalizedConstValue(const int);
+void normalizedConstSpelling(int);
+void normalizedConstSuffixSpelling(int);
+void normalizedPointerConst(int *const);
+void normalizedPointeeConst(const int *);
+void normalizedPointeeConstMismatch(const int *);
+void normalizedAlias(NormalizationAliasInt);
+void normalizedDeepAlias(NormalizationDeepAliasInt);
+void normalizedDeepAliasSource(NormalizationDeepAliasInt);
+void normalizedIntermediateAliasMismatch(NormalizationDeepAliasInt);
+void normalizedConstAlias(NormalizationConstAliasInt);
+void normalizedNullable(char * _Nullable);
+void normalizedRawInt(int);
+
+struct NormalizationWidget {
+  void empty();
+  void empty(int);
+
+  void defaults(int, double = 0);
+  void defaults(int);
+
+  static void configure(int);
+
+  void pointerSpacing(int *);
+  void pointeeConstMismatch(const int *);
+  void deepAlias(NormalizationDeepAliasInt);
+};
+
+#endif // WHERE_PARAMETERS_NORMALIZATION_H
diff --git a/clang/test/APINotes/Inputs/Headers/module.modulemap 
b/clang/test/APINotes/Inputs/Headers/module.modulemap
index 592d482ea7a57..644828ad0cfb6 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 WhereParametersNormalization {
+  header "WhereParametersNormalization.h"
+  export *
+}
diff --git a/clang/test/APINotes/where-parameters-diagnostics.cpp 
b/clang/test/APINotes/where-parameters-diagnostics.cpp
index 68ff7a1b113df..b553503834983 100644
--- a/clang/test/APINotes/where-parameters-diagnostics.cpp
+++ b/clang/test/APINotes/where-parameters-diagnostics.cpp
@@ -123,12 +123,14 @@ Name: WhereParametersDiagnostics
 
 void duplicateGlobal(int);
 void duplicateEmpty();
+void duplicateNormalizedGlobal(int *);
 void allowedGlobal(int);
 void allowedGlobal(double);
 
 struct DiagnosticWidget {
   void duplicateMethod(int);
   void duplicateEmpty();
+  void duplicateNormalizedMethod(int *);
   void allowed(int);
   void allowed(double);
 };
@@ -159,6 +161,17 @@ Name: WhereParametersDiagnostics
     Parameters: []
   SwiftName: duplicateEmptyB()
 # DUPLICATE: error: multiple API notes entries for global function 
'duplicateEmpty' with Where.Parameters []
+- Name: duplicateNormalizedGlobal
+  Where:
+    Parameters:
+    - int *
+  SwiftName: duplicateNormalizedGlobalA(_:)
+- Name: duplicateNormalizedGlobal
+  Where:
+    Parameters:
+    - int*
+  SwiftName: duplicateNormalizedGlobalB(_:)
+# DUPLICATE: error: multiple API notes entries for global function 
'duplicateNormalizedGlobal' with Where.Parameters [int*]
 - Name: allowedGlobal
   SwiftPrivate: true
 - Name: allowedGlobal
@@ -194,6 +207,17 @@ Name: WhereParametersDiagnostics
       Parameters: []
     SwiftName: duplicateEmptyB()
 # DUPLICATE: error: multiple API notes entries for C++ method 'duplicateEmpty' 
with Where.Parameters []
+  - Name: duplicateNormalizedMethod
+    Where:
+      Parameters:
+      - int *
+    SwiftName: duplicateNormalizedMethodA(_:)
+  - Name: duplicateNormalizedMethod
+    Where:
+      Parameters:
+      - int*
+    SwiftName: duplicateNormalizedMethodB(_:)
+# DUPLICATE: error: multiple API notes entries for C++ method 
'duplicateNormalizedMethod' with Where.Parameters [int*]
   - Name: allowed
     SwiftPrivate: true
   - Name: allowed
diff --git a/clang/test/APINotes/where-parameters-normalization.cpp 
b/clang/test/APINotes/where-parameters-normalization.cpp
new file mode 100644
index 0000000000000..e3fb5f15a6e29
--- /dev/null
+++ b/clang/test/APINotes/where-parameters-normalization.cpp
@@ -0,0 +1,112 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -fsyntax-only -I 
%S/Inputs/Headers %s -x c++
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedEmpty -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-EMPTY %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedDefaults -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-DEFAULTS %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedWhitespace -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-WHITESPACE %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedTemplateSpacing -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-TEMPLATE-SPACING %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedPointerSpacing -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-POINTER-SPACING %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedRValueReferenceSpacing -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-RVALUE-REFERENCE-SPACING %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedConstValue -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-CONST-VALUE %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedConstSpelling -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-CONST-SPELLING %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedConstSuffixSpelling -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-CONST-SUFFIX-SPELLING %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedPointerConst -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-POINTER-CONST %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedPointeeConst -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-POINTEE-CONST %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedPointeeConstMismatch -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-POINTEE-CONST-MISMATCH %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedAlias -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-ALIAS %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedDeepAlias -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-DEEP-ALIAS %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedDeepAliasSource -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-DEEP-ALIAS-SOURCE %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedIntermediateAliasMismatch -x c++ | 
FileCheck --check-prefix=CHECK-GLOBAL-INTERMEDIATE-ALIAS-MISMATCH %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedConstAlias -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-CONST-ALIAS %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedNullable -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-NULLABLE %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter normalizedRawInt -x c++ | FileCheck 
--check-prefix=CHECK-GLOBAL-RAW-INT %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter NormalizationWidget::empty -x c++ | FileCheck 
--check-prefix=CHECK-METHOD-EMPTY %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter NormalizationWidget::defaults -x c++ | FileCheck 
--check-prefix=CHECK-METHOD-DEFAULTS %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter NormalizationWidget::configure -x c++ | FileCheck 
--check-prefix=CHECK-METHOD-STATIC %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter NormalizationWidget::pointerSpacing -x c++ | 
FileCheck --check-prefix=CHECK-METHOD-POINTER-SPACING %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter NormalizationWidget::pointeeConstMismatch -x c++ | 
FileCheck --check-prefix=CHECK-METHOD-POINTEE-CONST-MISMATCH %s
+// RUN: %clang_cc1 -fmodules -fimplicit-module-maps 
-fmodules-cache-path=%t/ModulesCache/WhereParametersNormalization 
-fdisable-module-hash -fapinotes-modules -Wno-apinotes -I %S/Inputs/Headers %s 
-ast-dump -ast-dump-filter NormalizationWidget::deepAlias -x c++ | FileCheck 
--check-prefix=CHECK-METHOD-DEEP-ALIAS %s
+
+#include "WhereParametersNormalization.h"
+
+// CHECK-GLOBAL-EMPTY: FunctionDecl {{.+}} normalizedEmpty 'void ()'
+// CHECK-GLOBAL-EMPTY-NEXT: SwiftNameAttr {{.+}} "normalizedEmpty()"
+// CHECK-GLOBAL-EMPTY: FunctionDecl {{.+}} normalizedEmpty 'void (int)'
+// CHECK-GLOBAL-EMPTY-NOT: SwiftNameAttr
+
+// CHECK-GLOBAL-DEFAULTS: FunctionDecl {{.+}} normalizedDefaults 'void (int, 
double)'
+// CHECK-GLOBAL-DEFAULTS: SwiftNameAttr {{.+}} "normalizedDefaults(_:_:)"
+// CHECK-GLOBAL-DEFAULTS: FunctionDecl {{.+}} normalizedDefaults 'void (int)'
+// CHECK-GLOBAL-DEFAULTS-NOT: SwiftNameAttr
+
+// CHECK-GLOBAL-WHITESPACE: FunctionDecl {{.+}} normalizedWhitespace 'void 
(unsigned int)'
+// CHECK-GLOBAL-WHITESPACE: SwiftNameAttr {{.+}} "normalizedWhitespace(_:)"
+
+// CHECK-GLOBAL-TEMPLATE-SPACING: FunctionDecl {{.+}} 
normalizedTemplateSpacing 'void (NormalizationBox<int, double>)'
+// CHECK-GLOBAL-TEMPLATE-SPACING: SwiftNameAttr {{.+}} 
"normalizedTemplateSpacing(_:)"
+
+// CHECK-GLOBAL-POINTER-SPACING: FunctionDecl {{.+}} normalizedPointerSpacing 
'void (int *)'
+// CHECK-GLOBAL-POINTER-SPACING: SwiftNameAttr {{.+}} 
"normalizedPointerSpacing(_:)"
+
+// CHECK-GLOBAL-RVALUE-REFERENCE-SPACING: FunctionDecl {{.+}} 
normalizedRValueReferenceSpacing 'void (int &&)'
+// CHECK-GLOBAL-RVALUE-REFERENCE-SPACING: SwiftNameAttr {{.+}} 
"normalizedRValueReferenceSpacing(_:)"
+
+// CHECK-GLOBAL-CONST-VALUE: FunctionDecl {{.+}} normalizedConstValue 'void 
(const int)'
+// CHECK-GLOBAL-CONST-VALUE: SwiftNameAttr {{.+}} "normalizedConstValue(_:)"
+
+// CHECK-GLOBAL-CONST-SPELLING: FunctionDecl {{.+}} normalizedConstSpelling 
'void (int)'
+// CHECK-GLOBAL-CONST-SPELLING: SwiftNameAttr {{.+}} 
"normalizedConstSpelling(_:)"
+
+// CHECK-GLOBAL-CONST-SUFFIX-SPELLING: FunctionDecl {{.+}} 
normalizedConstSuffixSpelling 'void (int)'
+// CHECK-GLOBAL-CONST-SUFFIX-SPELLING: SwiftNameAttr {{.+}} 
"normalizedConstSuffixSpelling(_:)"
+
+// CHECK-GLOBAL-POINTER-CONST: FunctionDecl {{.+}} normalizedPointerConst 
'void (int *const)'
+// CHECK-GLOBAL-POINTER-CONST: SwiftNameAttr {{.+}} 
"normalizedPointerConst(_:)"
+
+// CHECK-GLOBAL-POINTEE-CONST: FunctionDecl {{.+}} normalizedPointeeConst 
'void (const int *)'
+// CHECK-GLOBAL-POINTEE-CONST: SwiftNameAttr {{.+}} 
"normalizedPointeeConst(_:)"
+
+// CHECK-GLOBAL-POINTEE-CONST-MISMATCH: FunctionDecl {{.+}} 
normalizedPointeeConstMismatch 'void (const int *)'
+// CHECK-GLOBAL-POINTEE-CONST-MISMATCH-NOT: SwiftNameAttr
+
+// CHECK-GLOBAL-ALIAS: FunctionDecl {{.+}} normalizedAlias 'void 
(NormalizationAliasInt)'
+// CHECK-GLOBAL-ALIAS: SwiftNameAttr {{.+}} "normalizedAlias(_:)"
+
+// CHECK-GLOBAL-DEEP-ALIAS: FunctionDecl {{.+}} normalizedDeepAlias 'void 
(NormalizationDeepAliasInt)'
+// CHECK-GLOBAL-DEEP-ALIAS: SwiftNameAttr {{.+}} "normalizedDeepAlias(_:)"
+
+// CHECK-GLOBAL-DEEP-ALIAS-SOURCE: FunctionDecl {{.+}} 
normalizedDeepAliasSource 'void (NormalizationDeepAliasInt)'
+// CHECK-GLOBAL-DEEP-ALIAS-SOURCE: SwiftNameAttr {{.+}} 
"normalizedDeepAliasSource(_:)"
+
+// CHECK-GLOBAL-INTERMEDIATE-ALIAS-MISMATCH: FunctionDecl {{.+}} 
normalizedIntermediateAliasMismatch 'void (NormalizationDeepAliasInt)'
+// CHECK-GLOBAL-INTERMEDIATE-ALIAS-MISMATCH-NOT: SwiftNameAttr
+
+// CHECK-GLOBAL-CONST-ALIAS: FunctionDecl {{.+}} normalizedConstAlias 'void 
(NormalizationConstAliasInt)'
+// CHECK-GLOBAL-CONST-ALIAS: SwiftNameAttr {{.+}} "normalizedConstAlias(_:)"
+
+// CHECK-GLOBAL-NULLABLE: FunctionDecl {{.+}} normalizedNullable 'void (char * 
_Nullable)'
+// CHECK-GLOBAL-NULLABLE: SwiftNameAttr {{.+}} "normalizedNullable(_:)"
+
+// CHECK-GLOBAL-RAW-INT: FunctionDecl {{.+}} normalizedRawInt 'void (int)'
+// CHECK-GLOBAL-RAW-INT: SwiftNameAttr {{.+}} "normalizedRawInt(_:)"
+
+// CHECK-METHOD-EMPTY: CXXMethodDecl {{.+}} empty 'void ()'
+// CHECK-METHOD-EMPTY-NEXT: SwiftNameAttr {{.+}} "empty()"
+// CHECK-METHOD-EMPTY: CXXMethodDecl {{.+}} empty 'void (int)'
+// CHECK-METHOD-EMPTY-NOT: SwiftNameAttr
+
+// CHECK-METHOD-DEFAULTS: CXXMethodDecl {{.+}} defaults 'void (int, double)'
+// CHECK-METHOD-DEFAULTS: SwiftNameAttr {{.+}} "defaults(_:_:)"
+// CHECK-METHOD-DEFAULTS: CXXMethodDecl {{.+}} defaults 'void (int)'
+// CHECK-METHOD-DEFAULTS-NOT: SwiftNameAttr
+
+// CHECK-METHOD-STATIC: CXXMethodDecl {{.+}} configure 'void (int)' static
+// CHECK-METHOD-STATIC: SwiftNameAttr {{.+}} "configure(_:)"
+
+// CHECK-METHOD-POINTER-SPACING: CXXMethodDecl {{.+}} pointerSpacing 'void 
(int *)'
+// CHECK-METHOD-POINTER-SPACING: SwiftNameAttr {{.+}} "pointerSpacing(_:)"
+
+// CHECK-METHOD-POINTEE-CONST-MISMATCH: CXXMethodDecl {{.+}} 
pointeeConstMismatch 'void (const int *)'
+// CHECK-METHOD-POINTEE-CONST-MISMATCH-NOT: SwiftNameAttr
+
+// CHECK-METHOD-DEEP-ALIAS: CXXMethodDecl {{.+}} deepAlias 'void 
(NormalizationDeepAliasInt)'
+// CHECK-METHOD-DEEP-ALIAS: SwiftNameAttr {{.+}} "deepAlias(_:)"
diff --git a/clang/unittests/Sema/APINotesSelectorTest.cpp 
b/clang/unittests/Sema/APINotesSelectorTest.cpp
new file mode 100644
index 0000000000000..af08ad2754a8a
--- /dev/null
+++ b/clang/unittests/Sema/APINotesSelectorTest.cpp
@@ -0,0 +1,166 @@
+//===- unittests/Sema/APINotesSelectorTest.cpp 
----------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Sema/APINotesSelector.h"
+#include "clang/APINotes/Types.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Tooling/Tooling.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringRef.h"
+#include "gtest/gtest.h"
+#include <initializer_list>
+#include <string>
+#include <vector>
+
+using namespace clang;
+
+namespace {
+
+using clang::ast_matchers::functionDecl;
+using clang::ast_matchers::hasName;
+using clang::ast_matchers::match;
+using clang::ast_matchers::unless;
+using clang::tooling::buildASTFromCodeWithArgs;
+
+llvm::SmallVector<std::string, 4>
+makeParameterList(std::initializer_list<llvm::StringRef> Parameters) {
+  llvm::SmallVector<std::string, 4> Result;
+  for (llvm::StringRef Parameter : Parameters)
+    Result.push_back(Parameter.str());
+  return Result;
+}
+
+std::string formatSelector(llvm::ArrayRef<std::string> Parameters) {
+  return api_notes::formatAPINotesParameterSelector(Parameters);
+}
+
+void expectParameterList(llvm::ArrayRef<std::string> Actual,
+                         std::initializer_list<llvm::StringRef> ExpectedRefs,
+                         llvm::StringRef Label) {
+  llvm::SmallVector<std::string, 4> Expected = makeParameterList(ExpectedRefs);
+
+  EXPECT_EQ(Actual.size(), Expected.size())
+      << Label << " selector: expected " << formatSelector(Expected) << ", got 
"
+      << formatSelector(Actual);
+  if (Actual.size() != Expected.size())
+    return;
+
+  for (unsigned I = 0, E = Expected.size(); I != E; ++I) {
+    EXPECT_EQ(Actual[I], Expected[I])
+        << Label << " selector: expected " << formatSelector(Expected)
+        << ", got " << formatSelector(Actual);
+  }
+}
+
+const FunctionDecl *findTarget(ASTUnit &AST) {
+  auto Results =
+      match(functionDecl(hasName("target"), unless(ast_matchers::isImplicit()))
+                .bind("fn"),
+            AST.getASTContext());
+  EXPECT_EQ(Results.size(), 1u);
+  if (Results.size() != 1u)
+    return nullptr;
+  return Results[0].getNodeAs<FunctionDecl>("fn");
+}
+
+void expectSelectors(llvm::StringRef Code,
+                     std::initializer_list<llvm::StringRef> Source,
+                     std::initializer_list<llvm::StringRef> Desugared = {},
+                     bool ExpectDesugared = false,
+                     bool IsObjectiveCXX = false) {
+  std::vector<std::string> Args;
+  std::string FileName;
+  if (IsObjectiveCXX) {
+    Args = {"-x", "objective-c++", "-std=c++20"};
+    FileName = "input.mm";
+  } else {
+    Args = {"-std=c++20"};
+    FileName = "input.cpp";
+  }
+
+  std::unique_ptr<ASTUnit> AST = buildASTFromCodeWithArgs(Code, Args, 
FileName);
+  ASSERT_TRUE(AST);
+
+  const FunctionDecl *Target = findTarget(*AST);
+  ASSERT_NE(Target, nullptr);
+
+  std::optional<APINotesParameterSelectorCandidates> Candidates =
+      getAPINotesParameterSelectorCandidates(AST->getASTContext(), Target);
+  ASSERT_TRUE(Candidates);
+
+  expectParameterList(Candidates->Source.Parameters, Source, "source");
+
+  EXPECT_EQ(Candidates->Desugared.has_value(), ExpectDesugared);
+  if (ExpectDesugared)
+    expectParameterList(Candidates->Desugared->Parameters, Desugared,
+                        "desugared");
+}
+
+TEST(APINotesSelectorTest, ExtractsZeroParameterSelector) {
+  expectSelectors("void target();", {});
+}
+
+TEST(APINotesSelectorTest, ExtractsMultipleParametersAndIgnoresDefaults) {
+  expectSelectors("void target(int, double = 0);", {"int", "double"});
+}
+
+TEST(APINotesSelectorTest, DropsTopLevelConstFromValueParameter) {
+  expectSelectors("void target(const int);", {"int"});
+}
+
+TEST(APINotesSelectorTest, NormalizesPointerAndReferenceSpacing) {
+  expectSelectors("void target(int *, int &, int &&);",
+                  {"int*", "int&", "int&&"});
+}
+
+TEST(APINotesSelectorTest, DropsTopLevelConstFromPointerValueParameter) {
+  expectSelectors("void target(int *const);", {"int*"});
+}
+
+TEST(APINotesSelectorTest, PreservesPointeeConstOnPointerParameter) {
+  expectSelectors("void target(const int *);", {"const int*"});
+}
+
+TEST(APINotesSelectorTest, NormalizesTemplateSpacing) {
+  expectSelectors(R"cpp(
+    template <typename T, typename U> struct Box {};
+    void target(Box<int, double>);
+  )cpp",
+                  {"Box<int,double>"});
+}
+
+TEST(APINotesSelectorTest,
+     PreservesAliasAsSourceSelectorWithDesugaredFallback) {
+  expectSelectors(R"cpp(
+    using AliasInt = int;
+    void target(AliasInt);
+  )cpp",
+                  {"AliasInt"}, {"int"}, /*ExpectDesugared=*/true);
+}
+
+TEST(APINotesSelectorTest,
+     PreservesDeepAliasAsSourceSelectorWithDesugaredFallback) {
+  expectSelectors(R"cpp(
+    using AliasInt = int;
+    using DeepAliasInt = AliasInt;
+    void target(DeepAliasInt);
+  )cpp",
+                  {"DeepAliasInt"}, {"int"}, /*ExpectDesugared=*/true);
+}
+
+TEST(APINotesSelectorTest, StripsParameterNullability) {
+  expectSelectors("void target(char * _Nonnull);", {"char*"},
+                  /*Desugared=*/{}, /*ExpectDesugared=*/false,
+                  /*IsObjectiveCXX=*/true);
+}
+
+} // namespace
diff --git a/clang/unittests/Sema/CMakeLists.txt 
b/clang/unittests/Sema/CMakeLists.txt
index 188f6135a60ac..e11f04e35f296 100644
--- a/clang/unittests/Sema/CMakeLists.txt
+++ b/clang/unittests/Sema/CMakeLists.txt
@@ -3,6 +3,7 @@
 # large statically linked binary, but separating it out is
 # the right tradeoff today.
 add_distinct_clang_unittest(SemaTests
+  APINotesSelectorTest.cpp
   ExternalSemaSourceTest.cpp
   CodeCompleteTest.cpp
   HeuristicResolverTest.cpp

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to