[clang] [clang][ssaf] Add Entity Linker and associated data structures (PR #181765)

Aviral Goel via cfe-commits cfe-commits at lists.llvm.org
Thu Feb 19 18:50:59 PST 2026


https://github.com/aviralg updated https://github.com/llvm/llvm-project/pull/181765

>From a513fdb439c2245b22440d0658db936f79e1ca81 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Feb 2026 17:29:25 -0800
Subject: [PATCH 01/14] Add Entity Linker algorithm and associated data
 structures

---
 .../Scalable/EntityLinker/EntityLinker.h      |  65 ++++++
 .../EntityLinker/EntitySummaryEncoding.h      |  41 ++++
 .../Scalable/EntityLinker/LUSummary.h         |  56 +++++
 .../Scalable/EntityLinker/LUSummaryEncoding.h |  57 ++++++
 .../Scalable/EntityLinker/TUSummaryEncoding.h |  59 ++++++
 .../clang/Analysis/Scalable/Model/EntityId.h  |   7 +-
 .../Analysis/Scalable/Model/EntityIdTable.h   |   5 +-
 .../Analysis/Scalable/Model/EntityLinkage.h   |   4 +-
 .../Analysis/Scalable/Model/EntityName.h      |   1 +
 .../Analysis/Scalable/Support/ErrorBuilder.h  |  89 ++++++++
 clang/lib/Analysis/Scalable/CMakeLists.txt    |   2 +
 .../Scalable/EntityLinker/EntityLinker.cpp    | 192 ++++++++++++++++++
 .../Scalable/Serialization/JSONFormat.cpp     |  97 +--------
 .../Scalable/Support/ErrorBuilder.cpp         |  59 ++++++
 14 files changed, 631 insertions(+), 103 deletions(-)
 create mode 100644 clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
 create mode 100644 clang/include/clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h
 create mode 100644 clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
 create mode 100644 clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
 create mode 100644 clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
 create mode 100644 clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
 create mode 100644 clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
 create mode 100644 clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp

diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
new file mode 100644
index 0000000000000..b628af9d25843
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
@@ -0,0 +1,65 @@
+//===- EntityLinker.h - Class for linking entities --------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines the EntityLinker class that combines multiple TU summaries
+//  into a unified LU summary by deduplicating entities and patching summaries.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_ENTITYLINKER_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_ENTITYLINKER_H
+
+#include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityLinkage.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include "llvm/Support/Error.h"
+#include <map>
+#include <memory>
+#include <vector>
+
+namespace clang::ssaf {
+
+class EntitySummaryEncoding;
+class TUSummaryEncoding;
+
+class EntityLinker {
+  LUSummaryEncoding Output;
+
+public:
+  EntityLinker(NestedBuildNamespace LUNamespace)
+      : Output(std::move(LUNamespace)) {}
+
+  llvm::Error link(std::unique_ptr<TUSummaryEncoding> Summary);
+
+  const LUSummaryEncoding &getOutput() const { return Output; }
+
+private:
+  llvm::Expected<EntityId> resolve(const EntityName &OldName,
+                                   const EntityId OldId,
+                                   const EntityLinkage &EL);
+
+  llvm::Error
+  merge(std::map<SummaryName,
+                 std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+            &InputData,
+        std::map<SummaryName,
+                 std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+            &OutputData,
+        const EntityId OldId, const EntityId NewId, const EntityLinkage &EL,
+        std::vector<EntitySummaryEncoding *> &PatchTargets);
+
+  void patch(std::vector<EntitySummaryEncoding *> &PatchTargets,
+             const std::map<EntityId, EntityId> &EntityResolutionTable);
+};
+
+} // end namespace clang::ssaf
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_ENTITYLINKER_H
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h
new file mode 100644
index 0000000000000..a38dd0c895452
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h
@@ -0,0 +1,41 @@
+//===- EntitySummaryEncoding.h ----------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the EntitySummaryEncoding class, which represents
+// EntitySummary data in an encoded, format-specific form.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_ENTITYSUMMARYENCODING_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_ENTITYSUMMARYENCODING_H
+
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include <map>
+
+namespace clang::ssaf {
+
+/// Represents EntitySummary data in its serialized, format-specific encoding.
+///
+/// This abstract base class allows the entity linker to manipulate serialized
+/// entity summary data without knowing the exact schema of the EntitySummary
+/// subclass. The primary operation is patching EntityId references when
+/// entities are merged during linking.
+class EntitySummaryEncoding {
+public:
+  virtual ~EntitySummaryEncoding() = default;
+
+  /// Updates EntityId references in the encoded data.
+  ///
+  /// \param EntityResolutionTable Mapping from old EntityIds to new EntityIds.
+  virtual void
+  patch(const std::map<EntityId, EntityId> &EntityResolutionTable) = 0;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_ENTITYSUMMARYENCODING_H
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
new file mode 100644
index 0000000000000..54d18d78d53bf
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
@@ -0,0 +1,56 @@
+//===- LUSummary.h ----------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the LUSummary class, which represents a link unit summary
+// containing merged and deduplicated entity summaries from multiple TUs.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_LUSUMMARY_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_LUSUMMARY_H
+
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
+#include "clang/Analysis/Scalable/Model/EntityLinkage.h"
+#include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include <map>
+#include <memory>
+
+namespace clang::ssaf {
+
+class EntitySummary;
+class SerializationFormat;
+class SummaryViewBuilder;
+
+/// Represents a link unit (LU) summary containing merged entity summaries.
+///
+/// LUSummary is the result of linking multiple translation unit summaries
+/// together. It contains deduplicated entities with their linkage information
+/// and the merged entity summaries.
+class LUSummary {
+  NestedBuildNamespace LUNamespace;
+
+  EntityIdTable IdTable;
+
+  std::map<EntityId, EntityLinkage> LinkageTable;
+
+  std::map<SummaryName, std::map<EntityId, std::unique_ptr<EntitySummary>>>
+      Data;
+
+public:
+  LUSummary(NestedBuildNamespace LUNamespace)
+      : LUNamespace(std::move(LUNamespace)) {}
+
+  friend class SerializationFormat;
+  friend class SummaryViewBuilder;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_LUSUMMARY_H
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
new file mode 100644
index 0000000000000..39185990a9ea6
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
@@ -0,0 +1,57 @@
+//===- LUSummaryEncoding.h --------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the LUSummaryEncoding class, which represents a link unit
+// summary in its serialized, format-specific encoding.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_LUSUMMARYENCODING_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_LUSUMMARYENCODING_H
+
+#include "clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
+#include "clang/Analysis/Scalable/Model/EntityLinkage.h"
+#include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include <map>
+#include <memory>
+
+namespace clang::ssaf {
+
+class EntityLinker;
+class SerializationFormat;
+
+/// Represents a link unit summary in its serialized encoding.
+///
+/// LUSummaryEncoding holds the combined entity summary data from multiple
+/// translation units in a format-specific encoding. It is produced by the
+/// entity linker and contains deduplicated and patched entity summaries.
+class LUSummaryEncoding {
+  NestedBuildNamespace LUNamespace;
+
+  EntityIdTable IdTable;
+
+  std::map<EntityId, EntityLinkage> LinkageTable;
+
+  std::map<SummaryName,
+           std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+      Data;
+
+public:
+  LUSummaryEncoding(NestedBuildNamespace LUNamespace)
+      : LUNamespace(std::move(LUNamespace)) {}
+
+  friend class EntityLinker;
+  friend class SerializationFormat;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_LUSUMMARYENCODING_H
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
new file mode 100644
index 0000000000000..1b42eb70d09d0
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
@@ -0,0 +1,59 @@
+//===- TUSummaryEncoding.h --------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This file defines the TUSummaryEncoding class, which represents a
+// translation unit summary in its serialized, format-specific encoding.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_TUSUMMARYENCODING_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_TUSUMMARYENCODING_H
+
+#include "clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
+#include "clang/Analysis/Scalable/Model/EntityLinkage.h"
+#include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include <map>
+#include <memory>
+
+namespace clang::ssaf {
+
+class EntityLinker;
+class SerializationFormat;
+
+/// Represents a translation unit summary in its serialized encoding.
+///
+/// TUSummaryEncoding holds entity summary data in a format-specific encoding
+/// that can be manipulated by the entity linker without deserializing the
+/// full EntitySummary objects. This enables efficient entity ID patching
+/// during the linking process.
+class TUSummaryEncoding {
+  /// Identifies the translation unit.
+  BuildNamespace TUNamespace;
+
+  EntityIdTable IdTable;
+
+  std::map<EntityId, EntityLinkage> LinkageTable;
+
+  std::map<SummaryName,
+           std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+      Data;
+
+public:
+  TUSummaryEncoding(BuildNamespace TUNamespace)
+      : TUNamespace(std::move(TUNamespace)) {}
+
+  friend class EntityLinker;
+  friend class SerializationFormat;
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_TUSUMMARYENCODING_H
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityId.h b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
index e348486386cb6..231525b445ca0 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityId.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
@@ -28,9 +28,6 @@ class EntityIdTable;
 ///
 /// \see EntityIdTable
 class EntityId {
-  friend class EntityIdTable;
-  friend class SerializationFormat;
-
   size_t Index;
 
   explicit EntityId(size_t Index) : Index(Index) {}
@@ -41,6 +38,10 @@ class EntityId {
   bool operator==(const EntityId &Other) const { return Index == Other.Index; }
   bool operator<(const EntityId &Other) const { return Index < Other.Index; }
   bool operator!=(const EntityId &Other) const { return !(*this == Other); }
+
+  friend class EntityIdTable;
+  friend class EntityLinker;
+  friend class SerializationFormat;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
index a1099c4e4d0f8..b12d3e0c0faec 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
@@ -21,8 +21,6 @@ namespace clang::ssaf {
 /// The table maps each unique EntityName to exactly one EntityId.
 /// Entities are never removed.
 class EntityIdTable {
-  friend class SerializationFormat;
-
   std::map<EntityName, EntityId> Entities;
 
 public:
@@ -45,6 +43,9 @@ class EntityIdTable {
 
   /// Returns the number of unique entities in the table.
   size_t count() const { return Entities.size(); }
+
+  friend class EntityLinker;
+  friend class SerializationFormat;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
index ba5f7d3073a30..af775769dc1e4 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
@@ -17,8 +17,6 @@ namespace clang::ssaf {
 /// or external linkage, which determines its visibility and accessibility
 /// across translation units.
 class EntityLinkage {
-  friend class SerializationFormat;
-
 public:
   enum class LinkageType {
     None,     ///< local variables, function parameters
@@ -32,6 +30,8 @@ class EntityLinkage {
 
 private:
   LinkageType Linkage;
+
+  friend class SerializationFormat;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityName.h b/clang/include/clang/Analysis/Scalable/Model/EntityName.h
index 23890ab7bea43..6bf51844f2f5b 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityName.h
@@ -47,6 +47,7 @@ class EntityName {
   /// \param Namespace The namespace steps to append to this entity's namespace.
   EntityName makeQualified(NestedBuildNamespace Namespace) const;
 
+  friend class EntityLinker;
   friend class LinkUnitResolution;
   friend class SerializationFormat;
 };
diff --git a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
new file mode 100644
index 0000000000000..4ccda48e2391a
--- /dev/null
+++ b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
@@ -0,0 +1,89 @@
+//===- ErrorBuilder.h - Fluent API for contextual errors --------*- 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
+//
+//===----------------------------------------------------------------------===//
+//
+//  This file defines the ErrorBuilder class, which provides a fluent API for
+//  constructing contextual error messages with layered context information.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_ANALYSIS_SCALABLE_SUPPORT_ERRORBUILDER_H
+#define LLVM_CLANG_ANALYSIS_SCALABLE_SUPPORT_ERRORBUILDER_H
+
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatVariadic.h"
+#include <string>
+#include <system_error>
+#include <vector>
+
+namespace clang::ssaf {
+
+/// Fluent API for constructing contextual errors.
+///
+/// ErrorBuilder allows building error messages with layered context
+/// information. Context is added from innermost to outermost, and the final
+/// error message presents the context in reverse order (outermost first).
+///
+/// Example usage:
+///   return ErrorBuilder::create(std::errc::invalid_argument,
+///                               "invalid value {0}", value)
+///       .context("processing field '{0}'", fieldName)
+///       .context("reading configuration")
+///       .build();
+class ErrorBuilder {
+private:
+  std::error_code Code;
+  std::vector<std::string> ContextStack;
+
+  // Private constructor - only accessible via static factories.
+  explicit ErrorBuilder(std::error_code EC) : Code(EC) {}
+
+  // Helper: Format message and add to context stack.
+  template <typename... Args>
+  void addFormattedContext(const char *Fmt, Args &&...ArgVals) {
+    std::string Message =
+        llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str();
+    ContextStack.push_back(std::move(Message));
+  }
+
+public:
+  // Static factory: Create new error from error code and formatted message.
+  template <typename... Args>
+  static ErrorBuilder create(std::error_code EC, const char *Fmt,
+                             Args &&...ArgVals) {
+    ErrorBuilder Builder(EC);
+    Builder.addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
+    return Builder;
+  }
+
+  // Convenience overload for std::errc.
+  template <typename... Args>
+  static ErrorBuilder create(std::errc EC, const char *Fmt, Args &&...ArgVals) {
+    return create(std::make_error_code(EC), Fmt,
+                  std::forward<Args>(ArgVals)...);
+  }
+
+  // Static factory: Wrap existing error and optionally add context.
+  static ErrorBuilder wrap(llvm::Error E);
+
+  // Add context (plain string).
+  ErrorBuilder &context(const char *Msg);
+
+  // Add context (formatted string).
+  template <typename... Args>
+  ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
+    addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
+    return *this;
+  }
+
+  // Build the final error.
+  llvm::Error build();
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_ANALYSIS_SCALABLE_SUPPORT_ERRORBUILDER_H
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 522fc9dcf078d..8ef1625cc704b 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -4,11 +4,13 @@ set(LLVM_LINK_COMPONENTS
 
 add_clang_library(clangAnalysisScalable
   ASTEntityMapping.cpp
+  EntityLinker/EntityLinker.cpp
   Model/BuildNamespace.cpp
   Model/EntityIdTable.cpp
   Model/EntityName.cpp
   Serialization/JSONFormat.cpp
   Serialization/SerializationFormatRegistry.cpp
+  Support/ErrorBuilder.cpp
   TUSummary/ExtractorRegistry.cpp
 
   LINK_LIBS
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
new file mode 100644
index 0000000000000..2c7344b19de55
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -0,0 +1,192 @@
+//===- EntityLinker.cpp ----------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/EntityLinker/EntityLinker.h"
+#include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/FormatVariadic.h"
+
+using namespace clang::ssaf;
+
+//----------------------------------------------------------------------------
+// Error Message Constants
+//----------------------------------------------------------------------------
+
+namespace {
+
+namespace ErrorMessages {
+
+constexpr const char *EntityIdAlreadyExistsInLinkageTable =
+    "EntityId({0}) already exists in LU linkage table";
+
+constexpr const char *FailedToMergeSummaryData =
+    "failed to merge summary data for TU EntityId({0}) resolved to LU "
+    "EntityId({1}) with linkage '{2}'";
+
+constexpr const char *MissingLinkageInformation =
+    "missing linkage information for TU EntityId({0})";
+
+constexpr const char *DuplicateEntityIdInLinking =
+    "duplicate TU EntityId({0}) encountered during linking";
+
+constexpr const char *ResolvingEntity =
+    "resolving entity '{0}' (TU EntityId({1}))";
+constexpr const char *MergingSummaryData = "merging summary data";
+constexpr const char *LinkingTUSummary = "linking TU summary";
+
+} // namespace ErrorMessages
+
+} // namespace
+
+static NestedBuildNamespace
+resolveNamespace(const NestedBuildNamespace &LUNamespace,
+                 const NestedBuildNamespace &EntityNamespace,
+                 const EntityLinkage::LinkageType Linkage) {
+  switch (Linkage) {
+  case EntityLinkage::LinkageType::None:
+  case EntityLinkage::LinkageType::Internal:
+    return EntityNamespace.makeQualified(LUNamespace);
+  case EntityLinkage::LinkageType::External:
+    return NestedBuildNamespace(LUNamespace);
+  }
+
+  llvm_unreachable("Unhandled EntityLinkage::LinkageType variant");
+}
+
+llvm::Expected<EntityId> EntityLinker::resolve(const EntityName &OldName,
+                                               const EntityId OldId,
+                                               const EntityLinkage &EL) {
+  NestedBuildNamespace NewNamespace =
+      resolveNamespace(Output.LUNamespace, OldName.Namespace, EL.getLinkage());
+
+  EntityName NewName(OldName.USR, OldName.Suffix, NewNamespace);
+
+  // NewId construction will always return a fresh id for `None` and `Internal`
+  // linkage entities since their namespaces will be different even if their
+  // names clash. For `External` linkage entities with clashing names this
+  // function will return the id assigned at the first insertion.
+  EntityId NewId = Output.IdTable.getId(NewName);
+
+  auto [It, Inserted] = Output.LinkageTable.try_emplace(NewId, EL);
+  if (!Inserted) {
+    return ErrorBuilder::create(
+               llvm::inconvertibleErrorCode(),
+               ErrorMessages::EntityIdAlreadyExistsInLinkageTable, NewId.Index)
+        .build();
+  }
+
+  return NewId;
+}
+
+llvm::Error EntityLinker::merge(
+    std::map<SummaryName,
+             std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+        &InputData,
+    std::map<SummaryName,
+             std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
+        &OutputData,
+    const EntityId OldId, const EntityId NewId, const EntityLinkage &EL,
+    std::vector<EntitySummaryEncoding *> &PatchTargets) {
+  for (auto &[Name, DataMap] : InputData) {
+    auto Iter = DataMap.find(OldId);
+    if (Iter == DataMap.end())
+      continue;
+
+    auto &OutputMap = OutputData[Name];
+    auto Result = OutputMap.insert({NewId, std::move(Iter->second)});
+
+    // If insertion is successful, we will have to replace OldId with NewId in
+    // this EntitySummaryEncoding.
+    if (Result.second) {
+      PatchTargets.push_back(Result.first->second.get());
+    } else {
+      switch (EL.getLinkage()) {
+        // Insertion should never fail for `None` and `Internal` linkage
+        // entities because these entities have different namespaces even if
+        // their names clash.
+      case EntityLinkage::LinkageType::None:
+        return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
+                                    ErrorMessages::FailedToMergeSummaryData,
+                                    OldId.Index, NewId.Index, "None")
+            .build();
+      case EntityLinkage::LinkageType::Internal:
+        return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
+                                    ErrorMessages::FailedToMergeSummaryData,
+                                    OldId.Index, NewId.Index, "Internal")
+            .build();
+      case EntityLinkage::LinkageType::External:
+        // Insertion is expected to fail for duplicate occurrences of `External`
+        // linkage entities. We will report these cases to help users debug
+        // potential ODR violations.
+        // TODO - issue diagnostic log for dropping data using instrumentation
+        // framework.
+        break;
+      }
+    }
+  }
+
+  return llvm::Error::success();
+}
+
+void EntityLinker::patch(
+    std::vector<EntitySummaryEncoding *> &PatchTargets,
+    const std::map<EntityId, EntityId> &EntityResolutionTable) {
+  for (auto *PatchTarget : PatchTargets) {
+    PatchTarget->patch(EntityResolutionTable);
+  }
+}
+
+llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
+  std::map<EntityId, EntityId> EntityResolutionTable;
+  std::vector<EntitySummaryEncoding *> PatchTargets;
+
+  for (const auto &[OldName, OldId] : Summary->IdTable.Entities) {
+
+    auto Iter = Summary->LinkageTable.find(OldId);
+    if (Iter == Summary->LinkageTable.end()) {
+      return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
+                                  ErrorMessages::MissingLinkageInformation,
+                                  OldId.Index)
+          .context(ErrorMessages::LinkingTUSummary)
+          .build();
+    }
+
+    EntityLinkage &Linkage = Iter->second;
+
+    llvm::Expected<EntityId> NewIdOrErr = resolve(OldName, OldId, Linkage);
+    if (!NewIdOrErr)
+      return ErrorBuilder::wrap(NewIdOrErr.takeError())
+          .context(ErrorMessages::LinkingTUSummary)
+          .build();
+
+    EntityId NewId = *NewIdOrErr;
+
+    auto Res = EntityResolutionTable.insert({OldId, NewId});
+    if (!Res.second) {
+      return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
+                                  ErrorMessages::DuplicateEntityIdInLinking,
+                                  OldId.Index)
+          .context(ErrorMessages::LinkingTUSummary)
+          .build();
+    }
+
+    if (llvm::Error Err = merge(Summary->Data, Output.Data, OldId, NewId,
+                                Linkage, PatchTargets))
+      return ErrorBuilder::wrap(std::move(Err))
+          .context(ErrorMessages::MergingSummaryData)
+          .context(ErrorMessages::LinkingTUSummary)
+          .build();
+  }
+
+  patch(PatchTargets, EntityResolutionTable);
+
+  return llvm::Error::success();
+}
diff --git a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
index 6f7de45e863d1..0007ddf6a275f 100644
--- a/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
+++ b/clang/lib/Analysis/Scalable/Serialization/JSONFormat.cpp
@@ -1,4 +1,5 @@
 #include "clang/Analysis/Scalable/Serialization/JSONFormat.h"
+#include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
 #include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
 
 #include "llvm/ADT/StringExtras.h"
@@ -15,102 +16,6 @@ using Array = llvm::json::Array;
 using Object = llvm::json::Object;
 using Value = llvm::json::Value;
 
-//----------------------------------------------------------------------------
-// ErrorBuilder - Fluent API for constructing contextual errors.
-//----------------------------------------------------------------------------
-
-namespace {
-
-class ErrorBuilder {
-private:
-  std::error_code Code;
-  std::vector<std::string> ContextStack;
-
-  // Private constructor - only accessible via static factories.
-  explicit ErrorBuilder(std::error_code EC) : Code(EC) {}
-
-  // Helper: Format message and add to context stack.
-  template <typename... Args>
-  void addFormattedContext(const char *Fmt, Args &&...ArgVals) {
-    std::string Message =
-        llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str();
-    ContextStack.push_back(std::move(Message));
-  }
-
-public:
-  // Static factory: Create new error from error code and formatted message.
-  template <typename... Args>
-  static ErrorBuilder create(std::error_code EC, const char *Fmt,
-                             Args &&...ArgVals) {
-    ErrorBuilder Builder(EC);
-    Builder.addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
-    return Builder;
-  }
-
-  // Convenience overload for std::errc.
-  template <typename... Args>
-  static ErrorBuilder create(std::errc EC, const char *Fmt, Args &&...ArgVals) {
-    return create(std::make_error_code(EC), Fmt,
-                  std::forward<Args>(ArgVals)...);
-  }
-
-  // Static factory: Wrap existing error and optionally add context.
-  static ErrorBuilder wrap(llvm::Error E) {
-    if (!E) {
-      llvm::consumeError(std::move(E));
-      // Return builder with generic error code for success case.
-      return ErrorBuilder(std::make_error_code(std::errc::invalid_argument));
-    }
-
-    std::error_code EC;
-    bool FirstError = true;
-    ErrorBuilder Builder(std::make_error_code(std::errc::invalid_argument));
-
-    llvm::handleAllErrors(std::move(E), [&](const llvm::ErrorInfoBase &EI) {
-      // Capture error code from the first error only.
-      if (FirstError) {
-        EC = EI.convertToErrorCode();
-        Builder.Code = EC;
-        FirstError = false;
-      }
-
-      // Collect messages from all errors.
-      std::string ErrorMsg = EI.message();
-      if (!ErrorMsg.empty()) {
-        Builder.ContextStack.push_back(std::move(ErrorMsg));
-      }
-    });
-
-    return Builder;
-  }
-
-  // Add context (plain string).
-  ErrorBuilder &context(const char *Msg) {
-    ContextStack.push_back(Msg);
-    return *this;
-  }
-
-  // Add context (formatted string).
-  template <typename... Args>
-  ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
-    addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
-    return *this;
-  }
-
-  // Build the final error.
-  llvm::Error build() {
-    if (ContextStack.empty())
-      return llvm::Error::success();
-
-    // Reverse the context stack so that the most recent context appears first
-    // and the wrapped error (if any) appears last.
-    return llvm::createStringError(
-        llvm::join(llvm::reverse(ContextStack), "\n"), Code);
-  }
-};
-
-} // namespace
-
 //----------------------------------------------------------------------------
 // File Format Constant
 //----------------------------------------------------------------------------
diff --git a/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
new file mode 100644
index 0000000000000..ad0a013bcf2f2
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
@@ -0,0 +1,59 @@
+//===- ErrorBuilder.cpp ----------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringExtras.h"
+
+namespace clang::ssaf {
+
+ErrorBuilder ErrorBuilder::wrap(llvm::Error E) {
+  if (!E) {
+    llvm::consumeError(std::move(E));
+    // Return builder with generic error code for success case.
+    return ErrorBuilder(std::make_error_code(std::errc::invalid_argument));
+  }
+
+  std::error_code EC;
+  bool FirstError = true;
+  ErrorBuilder Builder(std::make_error_code(std::errc::invalid_argument));
+
+  llvm::handleAllErrors(std::move(E), [&](const llvm::ErrorInfoBase &EI) {
+    // Capture error code from the first error only.
+    if (FirstError) {
+      EC = EI.convertToErrorCode();
+      Builder.Code = EC;
+      FirstError = false;
+    }
+
+    // Collect messages from all errors.
+    std::string ErrorMsg = EI.message();
+    if (!ErrorMsg.empty()) {
+      Builder.ContextStack.push_back(std::move(ErrorMsg));
+    }
+  });
+
+  return Builder;
+}
+
+ErrorBuilder &ErrorBuilder::context(const char *Msg) {
+  ContextStack.push_back(Msg);
+  return *this;
+}
+
+llvm::Error ErrorBuilder::build() {
+  if (ContextStack.empty())
+    return llvm::Error::success();
+
+  // Reverse the context stack so that the most recent context appears first
+  // and the wrapped error (if any) appears last.
+  return llvm::createStringError(llvm::join(llvm::reverse(ContextStack), "\n"),
+                                 Code);
+}
+
+} // namespace clang::ssaf

>From 848d72a7612a881d518f0b49a391398aeb78cf68 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Mon, 16 Feb 2026 17:44:17 -0800
Subject: [PATCH 02/14] Quality Fixes

---
 .../Scalable/EntityLinker/EntityLinker.h      |  5 ++--
 .../Analysis/Scalable/Model/EntityLinkage.h   |  5 ++++
 clang/lib/Analysis/Scalable/CMakeLists.txt    |  1 +
 .../Scalable/EntityLinker/EntityLinker.cpp    | 29 +++++++------------
 .../Analysis/Scalable/Model/EntityLinkage.cpp | 27 +++++++++++++++++
 5 files changed, 47 insertions(+), 20 deletions(-)
 create mode 100644 clang/lib/Analysis/Scalable/Model/EntityLinkage.cpp

diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
index b628af9d25843..4788f27ce24a0 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
@@ -44,7 +44,7 @@ class EntityLinker {
 private:
   llvm::Expected<EntityId> resolve(const EntityName &OldName,
                                    const EntityId OldId,
-                                   const EntityLinkage &EL);
+                                   const EntityLinkage &Linkage);
 
   llvm::Error
   merge(std::map<SummaryName,
@@ -53,7 +53,8 @@ class EntityLinker {
         std::map<SummaryName,
                  std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
             &OutputData,
-        const EntityId OldId, const EntityId NewId, const EntityLinkage &EL,
+        const EntityId OldId, const EntityId NewId,
+        const EntityLinkage &Linkage,
         std::vector<EntitySummaryEncoding *> &PatchTargets);
 
   void patch(std::vector<EntitySummaryEncoding *> &PatchTargets,
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
index af775769dc1e4..a0014f0039f61 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
@@ -9,6 +9,8 @@
 #ifndef LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_ENTITYLINKAGE_H
 #define LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_ENTITYLINKAGE_H
 
+#include "llvm/ADT/StringRef.h"
+
 namespace clang::ssaf {
 
 /// Represents the linkage properties of an entity in the program model.
@@ -34,6 +36,9 @@ class EntityLinkage {
   friend class SerializationFormat;
 };
 
+/// Returns a string representation of the linkage type.
+llvm::StringRef toString(EntityLinkage::LinkageType Linkage);
+
 } // namespace clang::ssaf
 
 #endif // LLVM_CLANG_ANALYSIS_SCALABLE_MODEL_ENTITYLINKAGE_H
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 8ef1625cc704b..7e4404d96bbd4 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -7,6 +7,7 @@ add_clang_library(clangAnalysisScalable
   EntityLinker/EntityLinker.cpp
   Model/BuildNamespace.cpp
   Model/EntityIdTable.cpp
+  Model/EntityLinkage.cpp
   Model/EntityName.cpp
   Serialization/JSONFormat.cpp
   Serialization/SerializationFormatRegistry.cpp
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index 2c7344b19de55..9b15181721566 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -8,11 +8,9 @@
 
 #include "clang/Analysis/Scalable/EntityLinker/EntityLinker.h"
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
-#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
 #include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorHandling.h"
-#include "llvm/Support/FormatVariadic.h"
 
 using namespace clang::ssaf;
 
@@ -37,8 +35,6 @@ constexpr const char *MissingLinkageInformation =
 constexpr const char *DuplicateEntityIdInLinking =
     "duplicate TU EntityId({0}) encountered during linking";
 
-constexpr const char *ResolvingEntity =
-    "resolving entity '{0}' (TU EntityId({1}))";
 constexpr const char *MergingSummaryData = "merging summary data";
 constexpr const char *LinkingTUSummary = "linking TU summary";
 
@@ -63,9 +59,9 @@ resolveNamespace(const NestedBuildNamespace &LUNamespace,
 
 llvm::Expected<EntityId> EntityLinker::resolve(const EntityName &OldName,
                                                const EntityId OldId,
-                                               const EntityLinkage &EL) {
-  NestedBuildNamespace NewNamespace =
-      resolveNamespace(Output.LUNamespace, OldName.Namespace, EL.getLinkage());
+                                               const EntityLinkage &Linkage) {
+  NestedBuildNamespace NewNamespace = resolveNamespace(
+      Output.LUNamespace, OldName.Namespace, Linkage.getLinkage());
 
   EntityName NewName(OldName.USR, OldName.Suffix, NewNamespace);
 
@@ -75,7 +71,7 @@ llvm::Expected<EntityId> EntityLinker::resolve(const EntityName &OldName,
   // function will return the id assigned at the first insertion.
   EntityId NewId = Output.IdTable.getId(NewName);
 
-  auto [It, Inserted] = Output.LinkageTable.try_emplace(NewId, EL);
+  auto [It, Inserted] = Output.LinkageTable.try_emplace(NewId, Linkage);
   if (!Inserted) {
     return ErrorBuilder::create(
                llvm::inconvertibleErrorCode(),
@@ -93,7 +89,7 @@ llvm::Error EntityLinker::merge(
     std::map<SummaryName,
              std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
         &OutputData,
-    const EntityId OldId, const EntityId NewId, const EntityLinkage &EL,
+    const EntityId OldId, const EntityId NewId, const EntityLinkage &Linkage,
     std::vector<EntitySummaryEncoding *> &PatchTargets) {
   for (auto &[Name, DataMap] : InputData) {
     auto Iter = DataMap.find(OldId);
@@ -101,26 +97,23 @@ llvm::Error EntityLinker::merge(
       continue;
 
     auto &OutputMap = OutputData[Name];
-    auto Result = OutputMap.insert({NewId, std::move(Iter->second)});
+    auto InsertResult = OutputMap.insert({NewId, std::move(Iter->second)});
 
     // If insertion is successful, we will have to replace OldId with NewId in
     // this EntitySummaryEncoding.
-    if (Result.second) {
-      PatchTargets.push_back(Result.first->second.get());
+    if (InsertResult.second) {
+      PatchTargets.push_back(InsertResult.first->second.get());
     } else {
-      switch (EL.getLinkage()) {
+      switch (Linkage.getLinkage()) {
         // Insertion should never fail for `None` and `Internal` linkage
         // entities because these entities have different namespaces even if
         // their names clash.
       case EntityLinkage::LinkageType::None:
-        return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
-                                    ErrorMessages::FailedToMergeSummaryData,
-                                    OldId.Index, NewId.Index, "None")
-            .build();
       case EntityLinkage::LinkageType::Internal:
         return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
                                     ErrorMessages::FailedToMergeSummaryData,
-                                    OldId.Index, NewId.Index, "Internal")
+                                    OldId.Index, NewId.Index,
+                                    toString(Linkage.getLinkage()))
             .build();
       case EntityLinkage::LinkageType::External:
         // Insertion is expected to fail for duplicate occurrences of `External`
diff --git a/clang/lib/Analysis/Scalable/Model/EntityLinkage.cpp b/clang/lib/Analysis/Scalable/Model/EntityLinkage.cpp
new file mode 100644
index 0000000000000..b1f50964969c3
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/Model/EntityLinkage.cpp
@@ -0,0 +1,27 @@
+//===- EntityLinkage.cpp ----------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Analysis/Scalable/Model/EntityLinkage.h"
+#include "llvm/Support/ErrorHandling.h"
+
+namespace clang::ssaf {
+
+llvm::StringRef toString(EntityLinkage::LinkageType Linkage) {
+  switch (Linkage) {
+  case EntityLinkage::LinkageType::None:
+    return "None";
+  case EntityLinkage::LinkageType::Internal:
+    return "Internal";
+  case EntityLinkage::LinkageType::External:
+    return "External";
+  }
+
+  llvm_unreachable("Unhandled EntityLinkage::LinkageType variant");
+}
+
+} // namespace clang::ssaf

>From f8ac91011576e959f3a45e1dfb1e88084dc49a4b Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Tue, 17 Feb 2026 11:38:44 -0800
Subject: [PATCH 03/14] More quality fixes

---
 .../Scalable/EntityLinker/EntityLinker.h      | 21 ++++++++++++---
 .../Scalable/EntityLinker/LUSummary.h         |  6 ++---
 .../Scalable/EntityLinker/LUSummaryEncoding.h | 10 ++++---
 .../Scalable/EntityLinker/TUSummaryEncoding.h | 11 +++++---
 .../clang/Analysis/Scalable/Model/EntityId.h  |  8 +++---
 .../Analysis/Scalable/Model/EntityIdTable.h   |  6 ++---
 .../Analysis/Scalable/Model/EntityLinkage.h   |  4 +--
 .../Analysis/Scalable/Model/EntityName.h      |  8 +++---
 .../Scalable/EntityLinker/EntityLinker.cpp    | 26 ++++++++++++-------
 9 files changed, 64 insertions(+), 36 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
index 4788f27ce24a0..c362a2f87d0f7 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
@@ -17,8 +17,6 @@
 #include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
 #include "clang/Analysis/Scalable/Model/BuildNamespace.h"
 #include "clang/Analysis/Scalable/Model/EntityId.h"
-#include "clang/Analysis/Scalable/Model/EntityLinkage.h"
-#include "clang/Analysis/Scalable/Model/EntityName.h"
 #include "clang/Analysis/Scalable/Model/SummaryName.h"
 #include "llvm/Support/Error.h"
 #include <map>
@@ -27,6 +25,8 @@
 
 namespace clang::ssaf {
 
+class EntityLinkage;
+class EntityName;
 class EntitySummaryEncoding;
 class TUSummaryEncoding;
 
@@ -34,16 +34,31 @@ class EntityLinker {
   LUSummaryEncoding Output;
 
 public:
+  /// Constructs an EntityLinker for a link unit.
+  ///
+  /// \param LUNamespace The namespace identifying this link unit.
   EntityLinker(NestedBuildNamespace LUNamespace)
       : Output(std::move(LUNamespace)) {}
 
+  /// Links a translation unit summary into the link unit summary.
+  ///
+  /// Processes entity names, resolves namespace conflicts based on linkage,
+  /// deduplicates entities, and patches entity ID references in the summary
+  /// data. The provided TU summary is consumed by this operation.
+  ///
+  /// \param Summary The TU summary to link. Ownership is transferred.
+  /// \returns Error if linking fails (e.g., duplicate internal entities,
+  ///          missing linkage information), success otherwise.
   llvm::Error link(std::unique_ptr<TUSummaryEncoding> Summary);
 
+  /// Returns the accumulated link unit summary.
+  ///
+  /// \returns A const reference to the linked output containing all
+  ///          deduplicated and patched entity summaries.
   const LUSummaryEncoding &getOutput() const { return Output; }
 
 private:
   llvm::Expected<EntityId> resolve(const EntityName &OldName,
-                                   const EntityId OldId,
                                    const EntityLinkage &Linkage);
 
   llvm::Error
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
index 54d18d78d53bf..735e569cbd4af 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
@@ -34,6 +34,9 @@ class SummaryViewBuilder;
 /// together. It contains deduplicated entities with their linkage information
 /// and the merged entity summaries.
 class LUSummary {
+  friend class SerializationFormat;
+  friend class SummaryViewBuilder;
+
   NestedBuildNamespace LUNamespace;
 
   EntityIdTable IdTable;
@@ -46,9 +49,6 @@ class LUSummary {
 public:
   LUSummary(NestedBuildNamespace LUNamespace)
       : LUNamespace(std::move(LUNamespace)) {}
-
-  friend class SerializationFormat;
-  friend class SummaryViewBuilder;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
index 39185990a9ea6..196e2109471cc 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
@@ -34,12 +34,19 @@ class SerializationFormat;
 /// translation units in a format-specific encoding. It is produced by the
 /// entity linker and contains deduplicated and patched entity summaries.
 class LUSummaryEncoding {
+  friend class EntityLinker;
+  friend class SerializationFormat;
+
+  /// The namespace identifying this link unit.
   NestedBuildNamespace LUNamespace;
 
+  /// Maps entity names to their unique identifiers within this link unit.
   EntityIdTable IdTable;
 
+  /// Maps entity IDs to their linkage properties (None, Internal, External).
   std::map<EntityId, EntityLinkage> LinkageTable;
 
+  /// Encoded summary data organized by summary type and entity ID.
   std::map<SummaryName,
            std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
       Data;
@@ -47,9 +54,6 @@ class LUSummaryEncoding {
 public:
   LUSummaryEncoding(NestedBuildNamespace LUNamespace)
       : LUNamespace(std::move(LUNamespace)) {}
-
-  friend class EntityLinker;
-  friend class SerializationFormat;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
index 1b42eb70d09d0..b73a58b9d2984 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
@@ -35,13 +35,19 @@ class SerializationFormat;
 /// full EntitySummary objects. This enables efficient entity ID patching
 /// during the linking process.
 class TUSummaryEncoding {
-  /// Identifies the translation unit.
+  friend class EntityLinker;
+  friend class SerializationFormat;
+
+  /// The namespace identifying this translation unit.
   BuildNamespace TUNamespace;
 
+  /// Maps entity names to their unique identifiers within this TU.
   EntityIdTable IdTable;
 
+  /// Maps entity IDs to their linkage properties (None, Internal, External).
   std::map<EntityId, EntityLinkage> LinkageTable;
 
+  /// Encoded summary data organized by summary type and entity ID.
   std::map<SummaryName,
            std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
       Data;
@@ -49,9 +55,6 @@ class TUSummaryEncoding {
 public:
   TUSummaryEncoding(BuildNamespace TUNamespace)
       : TUNamespace(std::move(TUNamespace)) {}
-
-  friend class EntityLinker;
-  friend class SerializationFormat;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityId.h b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
index 231525b445ca0..e6f57adf24690 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityId.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
@@ -28,6 +28,10 @@ class EntityIdTable;
 ///
 /// \see EntityIdTable
 class EntityId {
+  friend class EntityIdTable;
+  friend class EntityLinker;
+  friend class SerializationFormat;
+
   size_t Index;
 
   explicit EntityId(size_t Index) : Index(Index) {}
@@ -38,10 +42,6 @@ class EntityId {
   bool operator==(const EntityId &Other) const { return Index == Other.Index; }
   bool operator<(const EntityId &Other) const { return Index < Other.Index; }
   bool operator!=(const EntityId &Other) const { return !(*this == Other); }
-
-  friend class EntityIdTable;
-  friend class EntityLinker;
-  friend class SerializationFormat;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
index b12d3e0c0faec..e563cacb63ca4 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
@@ -21,6 +21,9 @@ namespace clang::ssaf {
 /// The table maps each unique EntityName to exactly one EntityId.
 /// Entities are never removed.
 class EntityIdTable {
+  friend class EntityLinker;
+  friend class SerializationFormat;
+
   std::map<EntityName, EntityId> Entities;
 
 public:
@@ -43,9 +46,6 @@ class EntityIdTable {
 
   /// Returns the number of unique entities in the table.
   size_t count() const { return Entities.size(); }
-
-  friend class EntityLinker;
-  friend class SerializationFormat;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
index a0014f0039f61..4e715fc12af4c 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
@@ -19,6 +19,8 @@ namespace clang::ssaf {
 /// or external linkage, which determines its visibility and accessibility
 /// across translation units.
 class EntityLinkage {
+  friend class SerializationFormat;
+
 public:
   enum class LinkageType {
     None,     ///< local variables, function parameters
@@ -32,8 +34,6 @@ class EntityLinkage {
 
 private:
   LinkageType Linkage;
-
-  friend class SerializationFormat;
 };
 
 /// Returns a string representation of the linkage type.
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityName.h b/clang/include/clang/Analysis/Scalable/Model/EntityName.h
index 6bf51844f2f5b..42021cfd6b610 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityName.h
@@ -25,6 +25,10 @@ namespace clang::ssaf {
 /// Client code should not make assumptions about the implementation details,
 /// such as USRs.
 class EntityName {
+  friend class EntityLinker;
+  friend class LinkUnitResolution;
+  friend class SerializationFormat;
+
   std::string USR;
   llvm::SmallString<16> Suffix;
   NestedBuildNamespace Namespace;
@@ -46,10 +50,6 @@ class EntityName {
   ///
   /// \param Namespace The namespace steps to append to this entity's namespace.
   EntityName makeQualified(NestedBuildNamespace Namespace) const;
-
-  friend class EntityLinker;
-  friend class LinkUnitResolution;
-  friend class SerializationFormat;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index 9b15181721566..b23c19f0936b2 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -8,9 +8,12 @@
 
 #include "clang/Analysis/Scalable/EntityLinker/EntityLinker.h"
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/Model/EntityLinkage.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
 #include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorHandling.h"
+#include <cassert>
 
 using namespace clang::ssaf;
 
@@ -58,7 +61,6 @@ resolveNamespace(const NestedBuildNamespace &LUNamespace,
 }
 
 llvm::Expected<EntityId> EntityLinker::resolve(const EntityName &OldName,
-                                               const EntityId OldId,
                                                const EntityLinkage &Linkage) {
   NestedBuildNamespace NewNamespace = resolveNamespace(
       Output.LUNamespace, OldName.Namespace, Linkage.getLinkage());
@@ -71,7 +73,8 @@ llvm::Expected<EntityId> EntityLinker::resolve(const EntityName &OldName,
   // function will return the id assigned at the first insertion.
   EntityId NewId = Output.IdTable.getId(NewName);
 
-  auto [It, Inserted] = Output.LinkageTable.try_emplace(NewId, Linkage);
+  [[maybe_unused]] auto [It, Inserted] =
+      Output.LinkageTable.try_emplace(NewId, Linkage);
   if (!Inserted) {
     return ErrorBuilder::create(
                llvm::inconvertibleErrorCode(),
@@ -93,8 +96,9 @@ llvm::Error EntityLinker::merge(
     std::vector<EntitySummaryEncoding *> &PatchTargets) {
   for (auto &[Name, DataMap] : InputData) {
     auto Iter = DataMap.find(OldId);
-    if (Iter == DataMap.end())
+    if (Iter == DataMap.end()) {
       continue;
+    }
 
     auto &OutputMap = OutputData[Name];
     auto InsertResult = OutputMap.insert({NewId, std::move(Iter->second)});
@@ -133,6 +137,7 @@ void EntityLinker::patch(
     std::vector<EntitySummaryEncoding *> &PatchTargets,
     const std::map<EntityId, EntityId> &EntityResolutionTable) {
   for (auto *PatchTarget : PatchTargets) {
+    assert(PatchTarget && "Patch target cannot be null");
     PatchTarget->patch(EntityResolutionTable);
   }
 }
@@ -142,7 +147,6 @@ llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
   std::vector<EntitySummaryEncoding *> PatchTargets;
 
   for (const auto &[OldName, OldId] : Summary->IdTable.Entities) {
-
     auto Iter = Summary->LinkageTable.find(OldId);
     if (Iter == Summary->LinkageTable.end()) {
       return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
@@ -152,18 +156,19 @@ llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
           .build();
     }
 
-    EntityLinkage &Linkage = Iter->second;
+    const EntityLinkage &Linkage = Iter->second;
 
-    llvm::Expected<EntityId> NewIdOrErr = resolve(OldName, OldId, Linkage);
-    if (!NewIdOrErr)
+    auto NewIdOrErr = resolve(OldName, Linkage);
+    if (!NewIdOrErr) {
       return ErrorBuilder::wrap(NewIdOrErr.takeError())
           .context(ErrorMessages::LinkingTUSummary)
           .build();
+    }
 
     EntityId NewId = *NewIdOrErr;
 
-    auto Res = EntityResolutionTable.insert({OldId, NewId});
-    if (!Res.second) {
+    auto InsertResult = EntityResolutionTable.insert({OldId, NewId});
+    if (!InsertResult.second) {
       return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
                                   ErrorMessages::DuplicateEntityIdInLinking,
                                   OldId.Index)
@@ -172,11 +177,12 @@ llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
     }
 
     if (llvm::Error Err = merge(Summary->Data, Output.Data, OldId, NewId,
-                                Linkage, PatchTargets))
+                                Linkage, PatchTargets)) {
       return ErrorBuilder::wrap(std::move(Err))
           .context(ErrorMessages::MergingSummaryData)
           .context(ErrorMessages::LinkingTUSummary)
           .build();
+    }
   }
 
   patch(PatchTargets, EntityResolutionTable);

>From 5d929d62deac051f72a6e093763547423a2d527f Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Tue, 17 Feb 2026 16:36:14 -0800
Subject: [PATCH 04/14] Tests

---
 .../Scalable/EntityLinker/EntityLinker.h      |   5 +
 .../Scalable/EntityLinker/LUSummary.h         |   1 +
 .../Scalable/EntityLinker/LUSummaryEncoding.h |   1 +
 .../Scalable/EntityLinker/TUSummaryEncoding.h |   1 +
 .../Scalable/Model/PrivateFieldNames.def      |  12 +
 .../Serialization/SerializationFormat.h       |   8 +
 .../Scalable/EntityLinker/EntityLinker.cpp    |  13 +-
 .../Analysis/Scalable/CMakeLists.txt          |   1 +
 .../Analysis/Scalable/EntityLinkerTest.cpp    | 713 ++++++++++++++++++
 .../unittests/Analysis/Scalable/TestFixture.h |   4 +
 10 files changed, 753 insertions(+), 6 deletions(-)
 create mode 100644 clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp

diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
index c362a2f87d0f7..2e203ee5f13d7 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
@@ -57,6 +57,11 @@ class EntityLinker {
   ///          deduplicated and patched entity summaries.
   const LUSummaryEncoding &getOutput() const { return Output; }
 
+  /// Returns the accumulated link unit summary.
+  ///
+  /// \returns A mutable reference to the linked output.
+  LUSummaryEncoding &getOutput() { return Output; }
+
 private:
   llvm::Expected<EntityId> resolve(const EntityName &OldName,
                                    const EntityLinkage &Linkage);
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
index 735e569cbd4af..4741272fb0f36 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
@@ -36,6 +36,7 @@ class SummaryViewBuilder;
 class LUSummary {
   friend class SerializationFormat;
   friend class SummaryViewBuilder;
+  friend class TestFixture;
 
   NestedBuildNamespace LUNamespace;
 
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
index 196e2109471cc..18762a7ebc67d 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
@@ -36,6 +36,7 @@ class SerializationFormat;
 class LUSummaryEncoding {
   friend class EntityLinker;
   friend class SerializationFormat;
+  friend class TestFixture;
 
   /// The namespace identifying this link unit.
   NestedBuildNamespace LUNamespace;
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
index b73a58b9d2984..5df8680867bf5 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
@@ -37,6 +37,7 @@ class SerializationFormat;
 class TUSummaryEncoding {
   friend class EntityLinker;
   friend class SerializationFormat;
+  friend class TestFixture;
 
   /// The namespace identifying this translation unit.
   BuildNamespace TUNamespace;
diff --git a/clang/include/clang/Analysis/Scalable/Model/PrivateFieldNames.def b/clang/include/clang/Analysis/Scalable/Model/PrivateFieldNames.def
index f4e952c04ed2f..8ef57b9a1aa19 100644
--- a/clang/include/clang/Analysis/Scalable/Model/PrivateFieldNames.def
+++ b/clang/include/clang/Analysis/Scalable/Model/PrivateFieldNames.def
@@ -25,10 +25,22 @@ FIELD(EntityLinkage, Linkage)
 FIELD(EntityName, Namespace)
 FIELD(EntityName, Suffix)
 FIELD(EntityName, USR)
+FIELD(LUSummary, Data)
+FIELD(LUSummary, IdTable)
+FIELD(LUSummary, LinkageTable)
+FIELD(LUSummary, LUNamespace)
+FIELD(LUSummaryEncoding, Data)
+FIELD(LUSummaryEncoding, IdTable)
+FIELD(LUSummaryEncoding, LinkageTable)
+FIELD(LUSummaryEncoding, LUNamespace)
 FIELD(NestedBuildNamespace, Namespaces)
 FIELD(TUSummary, Data)
 FIELD(TUSummary, IdTable)
 FIELD(TUSummary, LinkageTable)
 FIELD(TUSummary, TUNamespace)
+FIELD(TUSummaryEncoding, Data)
+FIELD(TUSummaryEncoding, IdTable)
+FIELD(TUSummaryEncoding, LinkageTable)
+FIELD(TUSummaryEncoding, TUNamespace)
 
 #undef FIELD
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index 5686a088c72d4..d136e588e0404 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -14,6 +14,10 @@
 #ifndef CLANG_ANALYSIS_SCALABLE_SERIALIZATION_SERIALIZATION_FORMAT_H
 #define CLANG_ANALYSIS_SCALABLE_SERIALIZATION_SERIALIZATION_FORMAT_H
 
+#include "clang/Analysis/Scalable/EntityLinker/EntityLinker.h"
+#include "clang/Analysis/Scalable/EntityLinker/LUSummary.h"
+#include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
 #include "clang/Analysis/Scalable/Model/BuildNamespace.h"
 #include "clang/Analysis/Scalable/Model/SummaryName.h"
 #include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
@@ -24,10 +28,14 @@ namespace clang::ssaf {
 
 class EntityId;
 class EntityIdTable;
+class EntityLinker;
 class EntityName;
 class EntitySummary;
+class LUSummary;
+class LUSummaryEncoding;
 class SummaryName;
 class TUSummary;
+class TUSummaryEncoding;
 
 /// Abstract base class for serialization formats.
 class SerializationFormat {
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index b23c19f0936b2..65b8adf7642ae 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -75,12 +75,13 @@ llvm::Expected<EntityId> EntityLinker::resolve(const EntityName &OldName,
 
   [[maybe_unused]] auto [It, Inserted] =
       Output.LinkageTable.try_emplace(NewId, Linkage);
-  if (!Inserted) {
-    return ErrorBuilder::create(
-               llvm::inconvertibleErrorCode(),
-               ErrorMessages::EntityIdAlreadyExistsInLinkageTable, NewId.Index)
-        .build();
-  }
+  // if (!Inserted) {
+  //   return ErrorBuilder::create(
+  //              llvm::inconvertibleErrorCode(),
+  //              ErrorMessages::EntityIdAlreadyExistsInLinkageTable,
+  //              NewId.Index)
+  //       .build();
+  // }
 
   return NewId;
 }
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index 5529ca06de170..c191abfc56eac 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -4,6 +4,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   EntityIdTest.cpp
   EntityIdTableTest.cpp
   EntityLinkageTest.cpp
+  EntityLinkerTest.cpp
   EntityNameTest.cpp
   Registries/FancyAnalysisData.cpp
   Registries/MockSerializationFormat.cpp
diff --git a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
new file mode 100644
index 0000000000000..5ef5d5fd4d1ca
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
@@ -0,0 +1,713 @@
+//===- unittests/Analysis/Scalable/EntityLinkerTest.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/Analysis/Scalable/EntityLinker/EntityLinker.h"
+#include "TestFixture.h"
+#include "clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h"
+#include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityIdTable.h"
+#include "clang/Analysis/Scalable/Model/EntityLinkage.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+#include <memory>
+
+namespace clang::ssaf {
+
+namespace {
+
+// Mock EntitySummaryEncoding for testing
+class MockEntitySummaryEncoding : public EntitySummaryEncoding {
+public:
+  MockEntitySummaryEncoding() : Id(++Index) {}
+
+  size_t getId() const { return Id; }
+
+  void
+  patch(const std::map<EntityId, EntityId> &EntityResolutionTable) override {
+    PatchedIds = EntityResolutionTable;
+  }
+
+  const std::map<EntityId, EntityId> &getPatchedIds() const {
+    return PatchedIds;
+  }
+
+  static size_t Index;
+
+private:
+  size_t Id;
+  std::map<EntityId, EntityId> PatchedIds;
+};
+
+size_t MockEntitySummaryEncoding::Index = 0;
+
+class EntityLinkerTest : public TestFixture {
+protected:
+  // Helper to create a TUSummaryEncoding with entities
+  std::unique_ptr<TUSummaryEncoding>
+  createTUSummaryEncoding(BuildNamespaceKind Kind, llvm::StringRef Name) {
+    return std::make_unique<TUSummaryEncoding>(BuildNamespace(Kind, Name));
+  }
+
+  // Helper to add an entity to a TUSummaryEncoding
+  EntityId addEntity(TUSummaryEncoding &TU, llvm::StringRef USR,
+                     EntityLinkage::LinkageType Linkage) {
+    EntityName Name(USR, "", NestedBuildNamespace(getTUNamespace(TU)));
+    EntityId Id = getIdTable(TU).getId(Name);
+    getLinkageTable(TU).insert({Id, EntityLinkage(Linkage)});
+    return Id;
+  }
+
+  // Helper to add summary data to a TUSummaryEncoding
+  size_t addSummaryData(TUSummaryEncoding &TU, EntityId EId,
+                        llvm::StringRef SummaryNameStr) {
+    SummaryName SN(SummaryNameStr.str());
+    auto Summary = std::make_unique<MockEntitySummaryEncoding>();
+    const size_t ESId = Summary->getId();
+    getData(TU)[SN][EId] = std::move(Summary);
+    return ESId;
+  }
+};
+
+TEST_F(EntityLinkerTest, NoLink) {
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+
+  EntityLinker Linker(LUNamespace);
+
+  const auto &Output = Linker.getOutput();
+  EXPECT_EQ(getIdTable(Output).count(), 0u);
+  EXPECT_EQ(getLinkageTable(Output).size(), 0u);
+  EXPECT_EQ(getData(Output).size(), 0u);
+}
+
+TEST_F(EntityLinkerTest, EmptyLink) {
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+
+  EntityLinker Linker(LUNamespace);
+
+  auto TUEmpty =
+      createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TUEmpty");
+
+  EXPECT_THAT_ERROR(Linker.link(std::move(TUEmpty)), llvm::Succeeded());
+
+  const auto &Output = Linker.getOutput();
+  EXPECT_EQ(getIdTable(Output).count(), 0u);
+  EXPECT_EQ(getLinkageTable(Output).size(), 0u);
+  EXPECT_EQ(getData(Output).size(), 0u);
+}
+
+TEST_F(EntityLinkerTest, NonEmptyLink) {
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+
+  EntityLinker Linker(LUNamespace);
+
+  auto TU = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
+
+  const auto EIdA = addEntity(*TU, "A", EntityLinkage::LinkageType::None);
+  const auto ESIdAS1 = addSummaryData(*TU, EIdA, "S1");
+  const auto ESIdAS2 = addSummaryData(*TU, EIdA, "S2");
+
+  const auto EIdB = addEntity(*TU, "B", EntityLinkage::LinkageType::Internal);
+  const auto ESIdBS1 = addSummaryData(*TU, EIdB, "S1");
+  const auto ESIdBS2 = addSummaryData(*TU, EIdB, "S2");
+
+  const auto EIdC = addEntity(*TU, "C", EntityLinkage::LinkageType::External);
+  const auto ESIdCS1 = addSummaryData(*TU, EIdC, "S1");
+
+  const auto EIdD = addEntity(*TU, "D", EntityLinkage::LinkageType::External);
+  const auto ESIdDS2 = addSummaryData(*TU, EIdD, "S2");
+
+  const BuildNamespace TUNamespace = getTUNamespace(*TU);
+
+  EXPECT_THAT_ERROR(Linker.link(std::move(TU)), llvm::Succeeded());
+
+  const auto &Output = Linker.getOutput();
+  const auto &IdTable = getIdTable(Output);
+  const auto &LinkageTable = getLinkageTable(Output);
+  const auto &Data = getData(Output);
+
+  // Construct the nested namespace with TU inside LU
+  std::vector<BuildNamespace> NamespaceVec;
+  NamespaceVec.push_back(TUNamespace);
+  for (const auto &NS : getNamespaces(LUNamespace)) {
+    NamespaceVec.push_back(NS);
+  }
+  NestedBuildNamespace LocalNamespace(NamespaceVec);
+
+  EntityName NameA("A", "", LocalNamespace);
+  EntityName NameB("B", "", LocalNamespace);
+  EntityName NameC("C", "", LUNamespace);
+  EntityName NameD("D", "", LUNamespace);
+
+  // EntityIDTable Tests.
+  {
+    const auto &Entities = getEntities(IdTable);
+
+    EXPECT_EQ(IdTable.count(), 4u);
+
+    EXPECT_TRUE(IdTable.contains(NameA));
+    EXPECT_EQ(Entities.at(NameA), EIdA);
+
+    EXPECT_TRUE(IdTable.contains(NameB));
+    EXPECT_EQ(Entities.at(NameB), EIdB);
+
+    EXPECT_TRUE(IdTable.contains(NameC));
+    EXPECT_EQ(Entities.at(NameC), EIdC);
+
+    EXPECT_TRUE(IdTable.contains(NameD));
+    EXPECT_EQ(Entities.at(NameD), EIdD);
+  }
+
+  // LinkageTable Tests.
+  {
+    EXPECT_EQ(LinkageTable.size(), 4u);
+
+    ASSERT_NE(LinkageTable.find(EIdA), LinkageTable.end());
+    EXPECT_EQ(getLinkage(LinkageTable.at(EIdA)),
+              EntityLinkage::LinkageType::None);
+
+    ASSERT_NE(LinkageTable.find(EIdB), LinkageTable.end());
+    EXPECT_EQ(LinkageTable.at(EIdB).getLinkage(),
+              EntityLinkage::LinkageType::Internal);
+
+    ASSERT_NE(LinkageTable.find(EIdC), LinkageTable.end());
+    EXPECT_EQ(LinkageTable.at(EIdC).getLinkage(),
+              EntityLinkage::LinkageType::External);
+
+    ASSERT_NE(LinkageTable.find(EIdD), LinkageTable.end());
+    EXPECT_EQ(LinkageTable.at(EIdD).getLinkage(),
+              EntityLinkage::LinkageType::External);
+  }
+
+  // Data Tests.
+  {
+    EXPECT_EQ(Data.size(), 2u);
+
+    std::map<EntityId, EntityId> ExpectedEntityResolutionMapping = {
+        {EIdA, EIdA}, {EIdB, EIdB}, {EIdC, EIdC}, {EIdD, EIdD}};
+
+    // S1 Tests.
+    {
+      SummaryName S1("S1");
+      ASSERT_NE(Data.find(S1), Data.end());
+
+      const auto &S1Data = Data.at(S1);
+      EXPECT_EQ(S1Data.size(), 3u);
+
+      EXPECT_NE(S1Data.find(EIdA), S1Data.end());
+      auto *MockA =
+          static_cast<MockEntitySummaryEncoding *>(S1Data.at(EIdA).get());
+      EXPECT_EQ(MockA->getId(), ESIdAS1);
+      EXPECT_EQ(MockA->getPatchedIds(), ExpectedEntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdB), S1Data.end());
+      auto *MockB =
+          static_cast<MockEntitySummaryEncoding *>(S1Data.at(EIdB).get());
+      EXPECT_EQ(MockB->getId(), ESIdBS1);
+      EXPECT_EQ(MockB->getPatchedIds(), ExpectedEntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdC), S1Data.end());
+      auto *MockC =
+          static_cast<MockEntitySummaryEncoding *>(S1Data.at(EIdC).get());
+      EXPECT_EQ(MockC->getId(), ESIdCS1);
+      EXPECT_EQ(MockC->getPatchedIds(), ExpectedEntityResolutionMapping);
+    }
+
+    // S2 Tests.
+    {
+      SummaryName S2("S2");
+      ASSERT_NE(Data.find(S2), Data.end());
+
+      const auto &S2Data = Data.at(S2);
+      EXPECT_EQ(S2Data.size(), 3u);
+
+      EXPECT_NE(S2Data.find(EIdA), S2Data.end());
+      auto *MockA =
+          static_cast<MockEntitySummaryEncoding *>(S2Data.at(EIdA).get());
+      EXPECT_EQ(MockA->getId(), ESIdAS2);
+      EXPECT_EQ(MockA->getPatchedIds(), ExpectedEntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdB), S2Data.end());
+      auto *MockB =
+          static_cast<MockEntitySummaryEncoding *>(S2Data.at(EIdB).get());
+      EXPECT_EQ(MockB->getId(), ESIdBS2);
+      EXPECT_EQ(MockB->getPatchedIds(), ExpectedEntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdD), S2Data.end());
+      auto *MockD =
+          static_cast<MockEntitySummaryEncoding *>(S2Data.at(EIdD).get());
+      EXPECT_EQ(MockD->getId(), ESIdDS2);
+      EXPECT_EQ(MockD->getPatchedIds(), ExpectedEntityResolutionMapping);
+    }
+  }
+}
+
+TEST_F(EntityLinkerTest, TwoTULinkWithAllCombinations) {
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+
+  EntityLinker Linker(LUNamespace);
+
+  // Create TU1 with entities covering all linkage types and summary
+  // distributions
+  auto TU1 =
+      createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU1");
+
+  // None linkage entities in TU1
+  const auto EIdTU1_X_None =
+      addEntity(*TU1, "X", EntityLinkage::LinkageType::None);
+  const auto ESIdTU1_X_S1 = addSummaryData(*TU1, EIdTU1_X_None, "S1");
+
+  const auto EIdTU1_Y_None =
+      addEntity(*TU1, "Y", EntityLinkage::LinkageType::None);
+  const auto ESIdTU1_Y_S2 = addSummaryData(*TU1, EIdTU1_Y_None, "S2");
+
+  const auto EIdTU1_Z_None =
+      addEntity(*TU1, "Z", EntityLinkage::LinkageType::None);
+  const auto ESIdTU1_Z_S1 = addSummaryData(*TU1, EIdTU1_Z_None, "S1");
+  const auto ESIdTU1_Z_S2 = addSummaryData(*TU1, EIdTU1_Z_None, "S2");
+
+  // Internal linkage entities in TU1
+  const auto EIdTU1_A_Internal =
+      addEntity(*TU1, "A", EntityLinkage::LinkageType::Internal);
+  const auto ESIdTU1_A_S1 = addSummaryData(*TU1, EIdTU1_A_Internal, "S1");
+
+  const auto EIdTU1_B_Internal =
+      addEntity(*TU1, "B", EntityLinkage::LinkageType::Internal);
+  const auto ESIdTU1_B_S2 = addSummaryData(*TU1, EIdTU1_B_Internal, "S2");
+
+  const auto EIdTU1_C_Internal =
+      addEntity(*TU1, "C", EntityLinkage::LinkageType::Internal);
+  const auto ESIdTU1_C_S1 = addSummaryData(*TU1, EIdTU1_C_Internal, "S1");
+  const auto ESIdTU1_C_S2 = addSummaryData(*TU1, EIdTU1_C_Internal, "S2");
+
+  // External linkage entities in TU1
+  const auto EIdTU1_P_External =
+      addEntity(*TU1, "P", EntityLinkage::LinkageType::External);
+  const auto ESIdTU1_P_S1 = addSummaryData(*TU1, EIdTU1_P_External, "S1");
+
+  const auto EIdTU1_Q_External =
+      addEntity(*TU1, "Q", EntityLinkage::LinkageType::External);
+  const auto ESIdTU1_Q_S2 = addSummaryData(*TU1, EIdTU1_Q_External, "S2");
+
+  const auto EIdTU1_R_External =
+      addEntity(*TU1, "R", EntityLinkage::LinkageType::External);
+  const auto ESIdTU1_R_S1 = addSummaryData(*TU1, EIdTU1_R_External, "S1");
+  const auto ESIdTU1_R_S2 = addSummaryData(*TU1, EIdTU1_R_External, "S2");
+
+  const BuildNamespace TU1Namespace = getTUNamespace(*TU1);
+
+  // Link TU1
+  EXPECT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+  // Create TU2 with entities covering all combinations including duplicates
+  auto TU2 =
+      createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU2");
+
+  // None linkage entities in TU2 - includes duplicates and unique
+  const auto EIdTU2_X_None =
+      addEntity(*TU2, "X", EntityLinkage::LinkageType::None);
+  const auto ESIdTU2_X_S2 = addSummaryData(*TU2, EIdTU2_X_None, "S2");
+
+  const auto EIdTU2_Y_None =
+      addEntity(*TU2, "Y", EntityLinkage::LinkageType::None);
+  const auto ESIdTU2_Y_S1 = addSummaryData(*TU2, EIdTU2_Y_None, "S1");
+
+  const auto EIdTU2_W_None =
+      addEntity(*TU2, "W", EntityLinkage::LinkageType::None);
+  const auto ESIdTU2_W_S1 = addSummaryData(*TU2, EIdTU2_W_None, "S1");
+  const auto ESIdTU2_W_S2 = addSummaryData(*TU2, EIdTU2_W_None, "S2");
+
+  // Internal linkage entities in TU2 - includes duplicates and unique
+  const auto EIdTU2_A_Internal =
+      addEntity(*TU2, "A", EntityLinkage::LinkageType::Internal);
+  const auto ESIdTU2_A_S2 = addSummaryData(*TU2, EIdTU2_A_Internal, "S2");
+
+  const auto EIdTU2_B_Internal =
+      addEntity(*TU2, "B", EntityLinkage::LinkageType::Internal);
+  const auto ESIdTU2_B_S1 = addSummaryData(*TU2, EIdTU2_B_Internal, "S1");
+
+  const auto EIdTU2_D_Internal =
+      addEntity(*TU2, "D", EntityLinkage::LinkageType::Internal);
+  const auto ESIdTU2_D_S1 = addSummaryData(*TU2, EIdTU2_D_Internal, "S1");
+  const auto ESIdTU2_D_S2 = addSummaryData(*TU2, EIdTU2_D_Internal, "S2");
+
+  // External linkage entities in TU2 - includes duplicates (will be dropped)
+  // and unique
+  const auto EIdTU2_P_External =
+      addEntity(*TU2, "P", EntityLinkage::LinkageType::External);
+  const auto ESIdTU2_P_S2 = addSummaryData(*TU2, EIdTU2_P_External, "S2");
+
+  const auto EIdTU2_Q_External =
+      addEntity(*TU2, "Q", EntityLinkage::LinkageType::External);
+  const auto ESIdTU2_Q_S1 = addSummaryData(*TU2, EIdTU2_Q_External, "S1");
+
+  const auto EIdTU2_S_External =
+      addEntity(*TU2, "S", EntityLinkage::LinkageType::External);
+  const auto ESIdTU2_S_S1 = addSummaryData(*TU2, EIdTU2_S_External, "S1");
+  const auto ESIdTU2_S_S2 = addSummaryData(*TU2, EIdTU2_S_External, "S2");
+
+  const BuildNamespace TU2Namespace = getTUNamespace(*TU2);
+
+  // Link TU2
+  EXPECT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
+
+  // Verify the output
+  const auto &Output = Linker.getOutput();
+  const auto &IdTable = getIdTable(Output);
+  const auto &LinkageTable = getLinkageTable(Output);
+  const auto &Data = getData(Output);
+
+  // Construct the nested namespaces
+  std::vector<BuildNamespace> TU1NamespaceVec;
+  TU1NamespaceVec.push_back(TU1Namespace);
+  for (const auto &NS : getNamespaces(LUNamespace)) {
+    TU1NamespaceVec.push_back(NS);
+  }
+  NestedBuildNamespace TU1LocalNamespace(TU1NamespaceVec);
+
+  std::vector<BuildNamespace> TU2NamespaceVec;
+  TU2NamespaceVec.push_back(TU2Namespace);
+  for (const auto &NS : getNamespaces(LUNamespace)) {
+    TU2NamespaceVec.push_back(NS);
+  }
+  NestedBuildNamespace TU2LocalNamespace(TU2NamespaceVec);
+
+  // Create expected entity names
+  // None linkage entities use local namespace (TU scoped)
+  EntityName NameTU1_X_None("X", "", TU1LocalNamespace);
+  EntityName NameTU1_Y_None("Y", "", TU1LocalNamespace);
+  EntityName NameTU1_Z_None("Z", "", TU1LocalNamespace);
+  EntityName NameTU2_X_None("X", "", TU2LocalNamespace);
+  EntityName NameTU2_Y_None("Y", "", TU2LocalNamespace);
+  EntityName NameTU2_W_None("W", "", TU2LocalNamespace);
+
+  // Internal linkage entities use local namespace (TU scoped)
+  EntityName NameTU1_A_Internal("A", "", TU1LocalNamespace);
+  EntityName NameTU1_B_Internal("B", "", TU1LocalNamespace);
+  EntityName NameTU1_C_Internal("C", "", TU1LocalNamespace);
+  EntityName NameTU2_A_Internal("A", "", TU2LocalNamespace);
+  EntityName NameTU2_B_Internal("B", "", TU2LocalNamespace);
+  EntityName NameTU2_D_Internal("D", "", TU2LocalNamespace);
+
+  // External linkage entities use LU namespace (shared across TUs)
+  EntityName NameP_External("P", "", LUNamespace);
+  EntityName NameQ_External("Q", "", LUNamespace);
+  EntityName NameR_External("R", "", LUNamespace);
+  EntityName NameS_External("S", "", LUNamespace);
+
+  // EntityIdTable Tests
+  {
+    const auto &Entities = getEntities(IdTable);
+
+    // Should have 6 None + 6 Internal + 4 External = 16 entities total
+    EXPECT_EQ(IdTable.count(), 16u);
+
+    // TU1 None linkage entities
+    EXPECT_TRUE(IdTable.contains(NameTU1_X_None));
+    ASSERT_EQ(Entities.at(NameTU1_X_None), EIdTU1_X_None);
+
+    EXPECT_TRUE(IdTable.contains(NameTU1_Y_None));
+    EXPECT_EQ(Entities.at(NameTU1_Y_None), EIdTU1_Y_None);
+
+    EXPECT_TRUE(IdTable.contains(NameTU1_Z_None));
+    EXPECT_EQ(Entities.at(NameTU1_Z_None), EIdTU1_Z_None);
+
+    // TU2 None linkage entities (different from TU1 due to namespace)
+    EXPECT_TRUE(IdTable.contains(NameTU2_X_None));
+    EXPECT_EQ(Entities.at(NameTU2_X_None), EIdTU2_X_None);
+
+    EXPECT_TRUE(IdTable.contains(NameTU2_Y_None));
+    EXPECT_EQ(Entities.at(NameTU2_Y_None), EIdTU2_Y_None);
+
+    EXPECT_TRUE(IdTable.contains(NameTU2_W_None));
+    EXPECT_EQ(Entities.at(NameTU2_W_None), EIdTU2_W_None);
+
+    // TU1 Internal linkage entities
+    EXPECT_TRUE(IdTable.contains(NameTU1_A_Internal));
+    EXPECT_EQ(Entities.at(NameTU1_A_Internal), EIdTU1_A_Internal);
+
+    EXPECT_TRUE(IdTable.contains(NameTU1_B_Internal));
+    EXPECT_EQ(Entities.at(NameTU1_B_Internal), EIdTU1_B_Internal);
+
+    EXPECT_TRUE(IdTable.contains(NameTU1_C_Internal));
+    EXPECT_EQ(Entities.at(NameTU1_C_Internal), EIdTU1_C_Internal);
+
+    // TU2 Internal linkage entities (different from TU1 due to namespace)
+    EXPECT_TRUE(IdTable.contains(NameTU2_A_Internal));
+    EXPECT_EQ(Entities.at(NameTU2_A_Internal), EIdTU2_A_Internal);
+
+    EXPECT_TRUE(IdTable.contains(NameTU2_B_Internal));
+    EXPECT_EQ(Entities.at(NameTU2_B_Internal), EIdTU2_B_Internal);
+
+    EXPECT_TRUE(IdTable.contains(NameTU2_D_Internal));
+    EXPECT_EQ(Entities.at(NameTU2_D_Internal), EIdTU2_D_Internal);
+
+    // External linkage entities (shared across TUs)
+    EXPECT_TRUE(IdTable.contains(NameP_External));
+    EXPECT_EQ(Entities.at(NameP_External), EIdTU1_P_External);
+
+    EXPECT_TRUE(IdTable.contains(NameQ_External));
+    EXPECT_EQ(Entities.at(NameQ_External), EIdTU1_Q_External);
+
+    EXPECT_TRUE(IdTable.contains(NameR_External));
+    EXPECT_EQ(Entities.at(NameR_External), EIdTU1_R_External);
+
+    EXPECT_TRUE(IdTable.contains(NameS_External));
+    EXPECT_EQ(Entities.at(NameS_External), EIdTU2_S_External);
+  }
+
+  // LinkageTable Tests
+  {
+    EXPECT_EQ(LinkageTable.size(), 16u);
+
+    // Verify None linkage entities
+    EXPECT_EQ(LinkageTable.at(EIdTU1_X_None).getLinkage(),
+              EntityLinkage::LinkageType::None);
+    EXPECT_EQ(LinkageTable.at(EIdTU1_Y_None).getLinkage(),
+              EntityLinkage::LinkageType::None);
+    EXPECT_EQ(LinkageTable.at(EIdTU1_Z_None).getLinkage(),
+              EntityLinkage::LinkageType::None);
+    EXPECT_EQ(LinkageTable.at(EIdTU2_X_None).getLinkage(),
+              EntityLinkage::LinkageType::None);
+    EXPECT_EQ(LinkageTable.at(EIdTU2_Y_None).getLinkage(),
+              EntityLinkage::LinkageType::None);
+    EXPECT_EQ(LinkageTable.at(EIdTU2_W_None).getLinkage(),
+              EntityLinkage::LinkageType::None);
+
+    // Verify Internal linkage entities
+    EXPECT_EQ(LinkageTable.at(EIdTU1_A_Internal).getLinkage(),
+              EntityLinkage::LinkageType::Internal);
+    EXPECT_EQ(LinkageTable.at(EIdTU1_B_Internal).getLinkage(),
+              EntityLinkage::LinkageType::Internal);
+    EXPECT_EQ(LinkageTable.at(EIdTU1_C_Internal).getLinkage(),
+              EntityLinkage::LinkageType::Internal);
+    EXPECT_EQ(LinkageTable.at(EIdTU2_A_Internal).getLinkage(),
+              EntityLinkage::LinkageType::Internal);
+    EXPECT_EQ(LinkageTable.at(EIdTU2_B_Internal).getLinkage(),
+              EntityLinkage::LinkageType::Internal);
+    EXPECT_EQ(LinkageTable.at(EIdTU2_D_Internal).getLinkage(),
+              EntityLinkage::LinkageType::Internal);
+
+    // Verify External linkage entities
+    EXPECT_EQ(LinkageTable.at(EIdTU1_P_External).getLinkage(),
+              EntityLinkage::LinkageType::External);
+    EXPECT_EQ(LinkageTable.at(EIdTU1_Q_External).getLinkage(),
+              EntityLinkage::LinkageType::External);
+    EXPECT_EQ(LinkageTable.at(EIdTU1_R_External).getLinkage(),
+              EntityLinkage::LinkageType::External);
+    EXPECT_EQ(LinkageTable.at(EIdTU2_S_External).getLinkage(),
+              EntityLinkage::LinkageType::External);
+  }
+
+  // Data Tests
+  {
+    EXPECT_EQ(Data.size(), 2u);
+
+    // Build entity resolution mappings for each TU
+    std::map<EntityId, EntityId> TU1EntityResolutionMapping = {
+        {EIdTU1_X_None, EIdTU1_X_None},
+        {EIdTU1_Y_None, EIdTU1_Y_None},
+        {EIdTU1_Z_None, EIdTU1_Z_None},
+        {EIdTU1_A_Internal, EIdTU1_A_Internal},
+        {EIdTU1_B_Internal, EIdTU1_B_Internal},
+        {EIdTU1_C_Internal, EIdTU1_C_Internal},
+        {EIdTU1_P_External, EIdTU1_P_External},
+        {EIdTU1_Q_External, EIdTU1_Q_External},
+        {EIdTU1_R_External, EIdTU1_R_External}};
+
+    std::map<EntityId, EntityId> TU2EntityResolutionMapping = {
+        {EIdTU2_X_None, EIdTU2_X_None},
+        {EIdTU2_Y_None, EIdTU2_Y_None},
+        {EIdTU2_W_None, EIdTU2_W_None},
+        {EIdTU2_A_Internal, EIdTU2_A_Internal},
+        {EIdTU2_B_Internal, EIdTU2_B_Internal},
+        {EIdTU2_D_Internal, EIdTU2_D_Internal},
+        // External linkage entities from TU2 resolve to TU1's IDs if duplicate
+        {EIdTU2_P_External, EIdTU1_P_External},
+        {EIdTU2_Q_External, EIdTU1_Q_External},
+        {EIdTU2_S_External, EIdTU2_S_External}};
+
+    // S1 Tests
+    {
+      SummaryName S1("S1");
+      ASSERT_NE(Data.find(S1), Data.end());
+
+      const auto &S1Data = Data.at(S1);
+      // S1 should contain: TU1(X,Z,A,C,P,R) + TU2(Y,W,B,D,S) = 11 entities
+      // Note: TU2's P and Q external entities are dropped because TU1 already
+      // has them
+      EXPECT_EQ(S1Data.size(), 11u);
+
+      // Verify TU1 entities in S1
+      EXPECT_NE(S1Data.find(EIdTU1_X_None), S1Data.end());
+      auto *MockTU1_X = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU1_X_None).get());
+      EXPECT_EQ(MockTU1_X->getId(), ESIdTU1_X_S1);
+      EXPECT_EQ(MockTU1_X->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdTU1_Z_None), S1Data.end());
+      auto *MockTU1_Z = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU1_Z_None).get());
+      EXPECT_EQ(MockTU1_Z->getId(), ESIdTU1_Z_S1);
+      EXPECT_EQ(MockTU1_Z->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdTU1_A_Internal), S1Data.end());
+      auto *MockTU1_A = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU1_A_Internal).get());
+      EXPECT_EQ(MockTU1_A->getId(), ESIdTU1_A_S1);
+      EXPECT_EQ(MockTU1_A->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdTU1_C_Internal), S1Data.end());
+      auto *MockTU1_C = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU1_C_Internal).get());
+      EXPECT_EQ(MockTU1_C->getId(), ESIdTU1_C_S1);
+      EXPECT_EQ(MockTU1_C->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdTU1_P_External), S1Data.end());
+      auto *MockTU1_P = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU1_P_External).get());
+      EXPECT_EQ(MockTU1_P->getId(), ESIdTU1_P_S1);
+      EXPECT_EQ(MockTU1_P->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdTU1_R_External), S1Data.end());
+      auto *MockTU1_R = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU1_R_External).get());
+      EXPECT_EQ(MockTU1_R->getId(), ESIdTU1_R_S1);
+      EXPECT_EQ(MockTU1_R->getPatchedIds(), TU1EntityResolutionMapping);
+
+      // Verify TU2 entities in S1
+      EXPECT_NE(S1Data.find(EIdTU2_Y_None), S1Data.end());
+      auto *MockTU2_Y = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU2_Y_None).get());
+      EXPECT_EQ(MockTU2_Y->getId(), ESIdTU2_Y_S1);
+      EXPECT_EQ(MockTU2_Y->getPatchedIds(), TU2EntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdTU2_W_None), S1Data.end());
+      auto *MockTU2_W = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU2_W_None).get());
+      EXPECT_EQ(MockTU2_W->getId(), ESIdTU2_W_S1);
+      EXPECT_EQ(MockTU2_W->getPatchedIds(), TU2EntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdTU2_B_Internal), S1Data.end());
+      auto *MockTU2_B = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU2_B_Internal).get());
+      EXPECT_EQ(MockTU2_B->getId(), ESIdTU2_B_S1);
+      EXPECT_EQ(MockTU2_B->getPatchedIds(), TU2EntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdTU2_D_Internal), S1Data.end());
+      auto *MockTU2_D = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU2_D_Internal).get());
+      EXPECT_EQ(MockTU2_D->getId(), ESIdTU2_D_S1);
+      EXPECT_EQ(MockTU2_D->getPatchedIds(), TU2EntityResolutionMapping);
+
+      EXPECT_NE(S1Data.find(EIdTU2_S_External), S1Data.end());
+      auto *MockTU2_S = static_cast<MockEntitySummaryEncoding *>(
+          S1Data.at(EIdTU2_S_External).get());
+      EXPECT_EQ(MockTU2_S->getId(), ESIdTU2_S_S1);
+      EXPECT_EQ(MockTU2_S->getPatchedIds(), TU2EntityResolutionMapping);
+
+      // Verify TU2's duplicate external entities are NOT in S1
+      EXPECT_EQ(S1Data.find(EIdTU2_Q_External), S1Data.end());
+    }
+
+    // S2 Tests
+    {
+      SummaryName S2("S2");
+      ASSERT_NE(Data.find(S2), Data.end());
+
+      const auto &S2Data = Data.at(S2);
+      // S2 should contain: TU1(Y,Z,B,C,Q,R) + TU2(X,W,A,D,S) = 11 entities
+      // Note: TU2's P and Q external entities are dropped because TU1 already
+      // has them
+      EXPECT_EQ(S2Data.size(), 11u);
+
+      // Verify TU1 entities in S2
+      EXPECT_NE(S2Data.find(EIdTU1_Y_None), S2Data.end());
+      auto *MockTU1_Y = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU1_Y_None).get());
+      EXPECT_EQ(MockTU1_Y->getId(), ESIdTU1_Y_S2);
+      EXPECT_EQ(MockTU1_Y->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdTU1_Z_None), S2Data.end());
+      auto *MockTU1_Z = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU1_Z_None).get());
+      EXPECT_EQ(MockTU1_Z->getId(), ESIdTU1_Z_S2);
+      EXPECT_EQ(MockTU1_Z->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdTU1_B_Internal), S2Data.end());
+      auto *MockTU1_B = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU1_B_Internal).get());
+      EXPECT_EQ(MockTU1_B->getId(), ESIdTU1_B_S2);
+      EXPECT_EQ(MockTU1_B->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdTU1_C_Internal), S2Data.end());
+      auto *MockTU1_C = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU1_C_Internal).get());
+      EXPECT_EQ(MockTU1_C->getId(), ESIdTU1_C_S2);
+      EXPECT_EQ(MockTU1_C->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdTU1_Q_External), S2Data.end());
+      auto *MockTU1_Q = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU1_Q_External).get());
+      EXPECT_EQ(MockTU1_Q->getId(), ESIdTU1_Q_S2);
+      EXPECT_EQ(MockTU1_Q->getPatchedIds(), TU1EntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdTU1_R_External), S2Data.end());
+      auto *MockTU1_R = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU1_R_External).get());
+      EXPECT_EQ(MockTU1_R->getId(), ESIdTU1_R_S2);
+      EXPECT_EQ(MockTU1_R->getPatchedIds(), TU1EntityResolutionMapping);
+
+      // Verify TU2 entities in S2
+      EXPECT_NE(S2Data.find(EIdTU2_X_None), S2Data.end());
+      auto *MockTU2_X = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU2_X_None).get());
+      EXPECT_EQ(MockTU2_X->getId(), ESIdTU2_X_S2);
+      EXPECT_EQ(MockTU2_X->getPatchedIds(), TU2EntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdTU2_W_None), S2Data.end());
+      auto *MockTU2_W = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU2_W_None).get());
+      EXPECT_EQ(MockTU2_W->getId(), ESIdTU2_W_S2);
+      EXPECT_EQ(MockTU2_W->getPatchedIds(), TU2EntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdTU2_A_Internal), S2Data.end());
+      auto *MockTU2_A = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU2_A_Internal).get());
+      EXPECT_EQ(MockTU2_A->getId(), ESIdTU2_A_S2);
+      EXPECT_EQ(MockTU2_A->getPatchedIds(), TU2EntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdTU2_D_Internal), S2Data.end());
+      auto *MockTU2_D = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU2_D_Internal).get());
+      EXPECT_EQ(MockTU2_D->getId(), ESIdTU2_D_S2);
+      EXPECT_EQ(MockTU2_D->getPatchedIds(), TU2EntityResolutionMapping);
+
+      EXPECT_NE(S2Data.find(EIdTU2_S_External), S2Data.end());
+      auto *MockTU2_S = static_cast<MockEntitySummaryEncoding *>(
+          S2Data.at(EIdTU2_S_External).get());
+      EXPECT_EQ(MockTU2_S->getId(), ESIdTU2_S_S2);
+      EXPECT_EQ(MockTU2_S->getPatchedIds(), TU2EntityResolutionMapping);
+
+      // Verify TU2's duplicate external entities are NOT in S2
+      EXPECT_EQ(S2Data.find(EIdTU2_P_External), S2Data.end());
+    }
+  }
+}
+
+} // namespace
+
+} // namespace clang::ssaf
diff --git a/clang/unittests/Analysis/Scalable/TestFixture.h b/clang/unittests/Analysis/Scalable/TestFixture.h
index 427769819a703..c8936fd2cfb53 100644
--- a/clang/unittests/Analysis/Scalable/TestFixture.h
+++ b/clang/unittests/Analysis/Scalable/TestFixture.h
@@ -9,6 +9,10 @@
 #ifndef LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H
 #define LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H
 
+#include "clang/Analysis/Scalable/EntityLinker/EntityLinker.h"
+#include "clang/Analysis/Scalable/EntityLinker/LUSummary.h"
+#include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
+#include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
 #include "clang/Analysis/Scalable/Model/BuildNamespace.h"
 #include "clang/Analysis/Scalable/Model/EntityId.h"
 #include "clang/Analysis/Scalable/Model/EntityIdTable.h"

>From 40a3657716c371b88604f68ce149d93dc45a9f69 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Tue, 17 Feb 2026 20:54:03 -0800
Subject: [PATCH 05/14] Add tests for ErrorBuilder

---
 .../Analysis/Scalable/Support/ErrorBuilder.h  | 108 +++++++++-
 .../Scalable/Support/ErrorBuilder.cpp         |  23 ++-
 .../Analysis/Scalable/CMakeLists.txt          |   1 +
 .../Analysis/Scalable/ErrorBuilderTest.cpp    | 187 ++++++++++++++++++
 4 files changed, 299 insertions(+), 20 deletions(-)
 create mode 100644 clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp

diff --git a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
index 4ccda48e2391a..b0849269edd41 100644
--- a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
@@ -25,7 +25,7 @@ namespace clang::ssaf {
 /// Fluent API for constructing contextual errors.
 ///
 /// ErrorBuilder allows building error messages with layered context
-/// information. Context is added from innermost to outermost, and the final
+/// information. Context is added innermost to outermost, and the final
 /// error message presents the context in reverse order (outermost first).
 ///
 /// Example usage:
@@ -39,19 +39,35 @@ class ErrorBuilder {
   std::error_code Code;
   std::vector<std::string> ContextStack;
 
-  // Private constructor - only accessible via static factories.
   explicit ErrorBuilder(std::error_code EC) : Code(EC) {}
 
-  // Helper: Format message and add to context stack.
+  void pushContext(std::string Msg) {
+    if (!Msg.empty()) {
+      ContextStack.push_back(std::move(Msg));
+    }
+  }
+
   template <typename... Args>
   void addFormattedContext(const char *Fmt, Args &&...ArgVals) {
     std::string Message =
         llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str();
-    ContextStack.push_back(std::move(Message));
+    pushContext(std::move(Message));
   }
 
 public:
-  // Static factory: Create new error from error code and formatted message.
+  /// Create an ErrorBuilder with an error code and formatted message.
+  ///
+  /// \param EC The error code for this error.
+  /// \param Fmt Format string for the error message (using llvm::formatv).
+  /// \param ArgVals Arguments for the format string.
+  /// \returns A new ErrorBuilder with the initial error message.
+  ///
+  /// Example:
+  /// \code
+  ///   return ErrorBuilder::create(std::errc::invalid_argument,
+  ///                               "invalid value: {0}", 42)
+  ///       .build();
+  /// \endcode
   template <typename... Args>
   static ErrorBuilder create(std::error_code EC, const char *Fmt,
                              Args &&...ArgVals) {
@@ -60,27 +76,99 @@ class ErrorBuilder {
     return Builder;
   }
 
-  // Convenience overload for std::errc.
+  /// Convenience overload that accepts std::errc instead of std::error_code.
+  ///
+  /// \param EC The error condition for this error.
+  /// \param Fmt Format string for the error message.
+  /// \param ArgVals Arguments for the format string.
+  /// \returns A new ErrorBuilder with the initial error message.
   template <typename... Args>
   static ErrorBuilder create(std::errc EC, const char *Fmt, Args &&...ArgVals) {
     return create(std::make_error_code(EC), Fmt,
                   std::forward<Args>(ArgVals)...);
   }
 
-  // Static factory: Wrap existing error and optionally add context.
+  /// Wrap an existing error and optionally add context.
+  ///
+  /// Extracts the error code and message(s) from the given error. If multiple
+  /// errors are joined (via llvm::joinErrors), their messages are combined
+  /// using " + " separator.
+  ///
+  /// \param E The error to wrap. Must be a failure (cannot be success).
+  /// \returns A new ErrorBuilder containing the wrapped error information.
+  ///
+  /// \pre E must evaluate to true (i.e., must be a failure). Wrapping
+  ///      Error::success() is a programming error and will trigger an
+  ///      assertion failure in debug builds.
+  ///
+  /// Example:
+  /// \code
+  ///   if (auto Err = foo())
+  ///     return ErrorBuilder::wrap(std::move(Err))
+  ///         .context("while processing file")
+  ///         .build();
+  /// \endcode
   static ErrorBuilder wrap(llvm::Error E);
 
-  // Add context (plain string).
+  /// Add context information as a plain string.
+  ///
+  /// Empty strings are ignored and not added to the context stack.
+  ///
+  /// \param Msg Context message to add. Must be a null-terminated string.
+  /// \returns Reference to this ErrorBuilder for method chaining.
+  ///
+  /// Example:
+  /// \code
+  ///   return ErrorBuilder::create(...)
+  ///       .context("reading configuration file")
+  ///       .build();
+  /// \endcode
   ErrorBuilder &context(const char *Msg);
 
-  // Add context (formatted string).
+  /// Add context information with formatted string.
+  ///
+  /// Uses llvm::formatv for formatting. Empty messages (after formatting)
+  /// are ignored and not added to the context stack.
+  ///
+  /// \param Fmt Format string (using llvm::formatv syntax).
+  /// \param ArgVals Arguments for the format string.
+  /// \returns Reference to this ErrorBuilder for method chaining.
+  ///
+  /// Example:
+  /// \code
+  ///   return ErrorBuilder::create(...)
+  ///       .context("processing field '{0}'", fieldName)
+  ///       .context("at line {0}, column {1}", line, col)
+  ///       .build();
+  /// \endcode
   template <typename... Args>
   ErrorBuilder &context(const char *Fmt, Args &&...ArgVals) {
     addFormattedContext(Fmt, std::forward<Args>(ArgVals)...);
     return *this;
   }
 
-  // Build the final error.
+  /// Build and return the final error.
+  ///
+  /// Constructs an llvm::Error with all accumulated context. The context
+  /// is presented in reverse order: most recent context first, original
+  /// error message last. Each context layer is separated by a newline.
+  ///
+  /// \returns An llvm::Error containing the error code and formatted message.
+  ///          Even if no context was added (empty context stack), an error
+  ///          with the stored error code is returned.
+  ///
+  /// Example output:
+  /// \code
+  ///   // ErrorBuilder::create(errc::invalid_argument, "value is 42")
+  ///   //     .context("processing field 'age'")
+  ///   //     .context("reading config")
+  ///   //     .build();
+  ///   //
+  ///   // Produces:
+  ///   // "reading config
+  ///   //  processing field 'age'
+  ///   //  value is 42"
+  /// \endcode
   llvm::Error build();
 };
 
diff --git a/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
index ad0a013bcf2f2..5876edb03997e 100644
--- a/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
+++ b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
@@ -9,18 +9,18 @@
 #include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringExtras.h"
+#include <cassert>
 
 namespace clang::ssaf {
 
 ErrorBuilder ErrorBuilder::wrap(llvm::Error E) {
-  if (!E) {
-    llvm::consumeError(std::move(E));
-    // Return builder with generic error code for success case.
-    return ErrorBuilder(std::make_error_code(std::errc::invalid_argument));
-  }
+  assert(
+      E &&
+      "Cannot wrap a success error - check for success before calling wrap()");
 
   std::error_code EC;
   bool FirstError = true;
+  std::vector<std::string> Messages;
   ErrorBuilder Builder(std::make_error_code(std::errc::invalid_argument));
 
   llvm::handleAllErrors(std::move(E), [&](const llvm::ErrorInfoBase &EI) {
@@ -34,24 +34,27 @@ ErrorBuilder ErrorBuilder::wrap(llvm::Error E) {
     // Collect messages from all errors.
     std::string ErrorMsg = EI.message();
     if (!ErrorMsg.empty()) {
-      Builder.ContextStack.push_back(std::move(ErrorMsg));
+      Messages.push_back(std::move(ErrorMsg));
     }
   });
 
+  // Combine all messages with " + " and push as a single context entry
+  std::string CombinedMsg = llvm::join(Messages, " + ");
+  Builder.pushContext(std::move(CombinedMsg));
+
   return Builder;
 }
 
 ErrorBuilder &ErrorBuilder::context(const char *Msg) {
-  ContextStack.push_back(Msg);
+  pushContext(std::string(Msg));
   return *this;
 }
 
 llvm::Error ErrorBuilder::build() {
-  if (ContextStack.empty())
-    return llvm::Error::success();
-
   // Reverse the context stack so that the most recent context appears first
   // and the wrapped error (if any) appears last.
+  // Note: Even if ContextStack is empty, we create an error with the stored
+  // error code and an empty message (this is valid in LLVM).
   return llvm::createStringError(llvm::join(llvm::reverse(ContextStack), "\n"),
                                  Code);
 }
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index c191abfc56eac..ce5219e348c11 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -6,6 +6,7 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   EntityLinkageTest.cpp
   EntityLinkerTest.cpp
   EntityNameTest.cpp
+  ErrorBuilderTest.cpp
   Registries/FancyAnalysisData.cpp
   Registries/MockSerializationFormat.cpp
   Registries/MockSummaryExtractor1.cpp
diff --git a/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp b/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
new file mode 100644
index 0000000000000..ad0c3bf34038a
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
@@ -0,0 +1,187 @@
+//===- unittests/Analysis/Scalable/ErrorBuilderTest.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/Analysis/Scalable/Support/ErrorBuilder.h"
+#include "gtest/gtest.h"
+#include <system_error>
+
+using namespace llvm;
+
+namespace clang::ssaf {
+
+namespace {
+
+class ErrorBuilderTest : public ::testing::Test {
+protected:
+  struct ErrorInfo {
+    std::error_code Code;
+    std::string Message;
+  };
+
+  ErrorInfo extractErrorInfo(Error Err) {
+    ErrorInfo Info;
+
+    handleAllErrors(std::move(Err), [&](const StringError &SE) {
+      Info.Code = SE.convertToErrorCode();
+      Info.Message = SE.getMessage();
+    });
+
+    return Info;
+  }
+};
+
+TEST_F(ErrorBuilderTest, CreateSimpleError) {
+  auto Err =
+      ErrorBuilder::create(std::errc::invalid_argument, "test error").build();
+
+  auto Info = extractErrorInfo(std::move(Err));
+
+  EXPECT_EQ(Info.Code, std::errc::invalid_argument);
+  EXPECT_EQ(Info.Message, "test error");
+}
+
+TEST_F(ErrorBuilderTest, CreateWithErrorCode) {
+  auto EC = std::make_error_code(std::errc::no_such_file_or_directory);
+  auto Err = ErrorBuilder::create(EC, "file not found").build();
+
+  auto Info = extractErrorInfo(std::move(Err));
+
+  EXPECT_EQ(Info.Code, std::errc::no_such_file_or_directory);
+  EXPECT_EQ(Info.Message, "file not found");
+}
+
+TEST_F(ErrorBuilderTest, CreateWithFormattedMessage) {
+  auto Err = ErrorBuilder::create(std::errc::invalid_argument,
+                                  "field '{0}' has value {1}", "age", 150)
+                 .build();
+
+  auto Info = extractErrorInfo(std::move(Err));
+
+  EXPECT_EQ(Info.Code, std::errc::invalid_argument);
+  EXPECT_EQ(Info.Message, "field 'age' has value 150");
+}
+
+TEST_F(ErrorBuilderTest, AddPlainContext) {
+  auto Err = ErrorBuilder::create(std::errc::invalid_argument, "inner error")
+                 .context("outer context")
+                 .build();
+
+  auto Info = extractErrorInfo(std::move(Err));
+
+  EXPECT_EQ(Info.Code, std::errc::invalid_argument);
+  EXPECT_EQ(Info.Message, "outer context\ninner error");
+}
+
+TEST_F(ErrorBuilderTest, AddMultipleArgumentContext) {
+  auto Err = ErrorBuilder::create(std::errc::invalid_argument,
+                                  "expected {0} but got {1} in field '{2}'",
+                                  "string", "number", "value")
+                 .context("parsing line {0}", 42)
+                 .context("reading file '{0}'", "config.json")
+                 .build();
+
+  auto Info = extractErrorInfo(std::move(Err));
+
+  EXPECT_EQ(Info.Code, std::errc::invalid_argument);
+  EXPECT_EQ(Info.Message, "reading file 'config.json'\n"
+                          "parsing line 42\n"
+                          "expected string but got number in field 'value'");
+}
+
+TEST_F(ErrorBuilderTest, AddSpecialCharacterContext) {
+  auto Err = ErrorBuilder::create(std::errc::invalid_argument,
+                                  "special chars: {0}", "test\nwith\nnewlines")
+                 .context("tab\tseparated\tvalues")
+                 .build();
+
+  auto Info = extractErrorInfo(std::move(Err));
+
+  EXPECT_EQ(Info.Code, std::errc::invalid_argument);
+  EXPECT_EQ(Info.Message, "tab\tseparated\tvalues\n"
+                          "special chars: test\n"
+                          "with\n"
+                          "newlines");
+}
+
+TEST_F(ErrorBuilderTest, WrapExistingError) {
+  auto OriginalErr =
+      createStringError(std::errc::invalid_argument, "original error message");
+
+  auto WrappedErr = ErrorBuilder::wrap(std::move(OriginalErr))
+                        .context("additional context")
+                        .build();
+
+  auto Info = extractErrorInfo(std::move(WrappedErr));
+
+  EXPECT_EQ(Info.Code, std::errc::invalid_argument);
+  EXPECT_EQ(Info.Message, "additional context\noriginal error message");
+}
+
+TEST_F(ErrorBuilderTest, WrapMultipleErrors) {
+  auto Err1 = createStringError(std::errc::invalid_argument, "first");
+  auto Err2 = createStringError(std::errc::argument_list_too_long, "second");
+  auto Err3 = createStringError(std::errc::filename_too_long, "third");
+
+  auto JoinedErr =
+      joinErrors(std::move(Err1), joinErrors(std::move(Err2), std::move(Err3)));
+
+  auto WrappedErr = ErrorBuilder::wrap(std::move(JoinedErr))
+                        .context("wrapping three joined errors")
+                        .build();
+
+  auto Info = extractErrorInfo(std::move(WrappedErr));
+
+  EXPECT_EQ(Info.Code, std::errc::invalid_argument);
+  // All three messages combined with " + " in the order they were handled
+  EXPECT_EQ(Info.Message,
+            "wrapping three joined errors\nfirst + second + third");
+}
+
+TEST_F(ErrorBuilderTest, WrapErrorWithEmptyMessage) {
+  auto EmptyErr = createStringError(std::errc::invalid_argument, "");
+
+  auto WrappedErr = ErrorBuilder::wrap(std::move(EmptyErr))
+                        .context("")
+                        .context("wrapping error with empty message")
+                        .context("")
+                        .build();
+
+  auto Info = extractErrorInfo(std::move(WrappedErr));
+
+  EXPECT_EQ(Info.Code, std::errc::invalid_argument);
+  EXPECT_EQ(Info.Message, "wrapping error with empty message");
+}
+
+TEST_F(ErrorBuilderTest, CreateErrorWithEmptyMessage) {
+  auto Err = ErrorBuilder::create(std::errc::invalid_argument, "")
+                 .context("")
+                 .context("creating error with empty message")
+                 .context("")
+                 .build();
+
+  auto Info = extractErrorInfo(std::move(Err));
+
+  EXPECT_EQ(Info.Code, std::errc::invalid_argument);
+  EXPECT_EQ(Info.Message, "creating error with empty message");
+}
+
+#ifndef NDEBUG
+// Death test only works in debug builds where assertions are enabled
+TEST_F(ErrorBuilderTest, WrapSuccessErrorTriggersAssertion) {
+  EXPECT_DEATH(
+      {
+        auto SuccessErr = Error::success();
+        ErrorBuilder::wrap(std::move(SuccessErr));
+      },
+      "Cannot wrap a success error - check for success before calling wrap()");
+}
+#endif // !NDEBUG
+
+} // namespace
+
+} // namespace clang::ssaf

>From 7fb6e6e75892bdad74b40a28e33855122979a58d Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 18 Feb 2026 13:59:16 -0800
Subject: [PATCH 06/14] Fix tests

---
 .../Analysis/Scalable/EntityLinkerTest.cpp    | 911 +++++++++---------
 .../Analysis/Scalable/TestFixture.cpp         |  14 +
 .../unittests/Analysis/Scalable/TestFixture.h |   1 +
 3 files changed, 445 insertions(+), 481 deletions(-)

diff --git a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
index 5ef5d5fd4d1ca..5c77dbc367cc6 100644
--- a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
+++ b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
@@ -21,11 +21,11 @@
 #include "gtest/gtest.h"
 #include <memory>
 
-namespace clang::ssaf {
+using namespace clang::ssaf;
+using namespace llvm;
 
 namespace {
 
-// Mock EntitySummaryEncoding for testing
 class MockEntitySummaryEncoding : public EntitySummaryEncoding {
 public:
   MockEntitySummaryEncoding() : Id(++Index) {}
@@ -52,13 +52,20 @@ size_t MockEntitySummaryEncoding::Index = 0;
 
 class EntityLinkerTest : public TestFixture {
 protected:
-  // Helper to create a TUSummaryEncoding with entities
   std::unique_ptr<TUSummaryEncoding>
   createTUSummaryEncoding(BuildNamespaceKind Kind, llvm::StringRef Name) {
     return std::make_unique<TUSummaryEncoding>(BuildNamespace(Kind, Name));
   }
 
-  // Helper to add an entity to a TUSummaryEncoding
+  size_t addSummaryData(TUSummaryEncoding &TU, EntityId EId,
+                        llvm::StringRef SummaryNameStr) {
+    SummaryName SN(SummaryNameStr.str());
+    auto Summary = std::make_unique<MockEntitySummaryEncoding>();
+    const size_t ESId = Summary->getId();
+    getData(TU)[SN][EId] = std::move(Summary);
+    return ESId;
+  }
+
   EntityId addEntity(TUSummaryEncoding &TU, llvm::StringRef USR,
                      EntityLinkage::LinkageType Linkage) {
     EntityName Name(USR, "", NestedBuildNamespace(getTUNamespace(TU)));
@@ -67,17 +74,122 @@ class EntityLinkerTest : public TestFixture {
     return Id;
   }
 
-  // Helper to add summary data to a TUSummaryEncoding
-  size_t addSummaryData(TUSummaryEncoding &TU, EntityId EId,
-                        llvm::StringRef SummaryNameStr) {
-    SummaryName SN(SummaryNameStr.str());
-    auto Summary = std::make_unique<MockEntitySummaryEncoding>();
-    const size_t ESId = Summary->getId();
-    getData(TU)[SN][EId] = std::move(Summary);
-    return ESId;
+  NestedBuildNamespace
+  makeLocalNamespace(const BuildNamespace &TUNamespace,
+                     const NestedBuildNamespace &LUNamespace) {
+    return NestedBuildNamespace(TUNamespace).makeQualified(LUNamespace);
   }
 };
 
+// ============================================================================
+// Entity ID Table Matchers
+// ============================================================================
+
+MATCHER_P(ContainsEntity, entityName,
+          std::string(negation ? "does not contain" : "contains") +
+              " entity with name '" + ::testing::PrintToString(entityName) +
+              "'") {
+  return arg.contains(entityName);
+}
+
+MATCHER_P(IdTableHasSize, expectedCount,
+          std::string("has ") + ::testing::PrintToString(expectedCount) +
+              " entities") {
+  if (arg.count() != expectedCount) {
+    *result_listener << "has " << arg.count() << " entities";
+    return false;
+  }
+  return true;
+}
+
+// ============================================================================
+// Linkage Table Matchers
+// ============================================================================
+
+MATCHER_P2(EntityHasLinkage, entityId, expectedLinkage,
+           std::string("entity has ") +
+               ::testing::PrintToString(expectedLinkage) + " linkage") {
+  auto it = arg.find(entityId);
+  if (it == arg.end()) {
+    *result_listener << "entity " << ::testing::PrintToString(entityId)
+                     << " not found in linkage table";
+    return false;
+  }
+
+  auto actualLinkage = it->second.getLinkage();
+  if (actualLinkage != expectedLinkage) {
+    *result_listener << "entity " << ::testing::PrintToString(entityId)
+                     << " has linkage "
+                     << ::testing::PrintToString(actualLinkage);
+    return false;
+  }
+
+  return true;
+}
+
+MATCHER_P(LinkageTableHasSize, expectedSize,
+          std::string("linkage table has size ") +
+              ::testing::PrintToString(expectedSize)) {
+  if (arg.size() != expectedSize) {
+    *result_listener << "has size " << arg.size();
+    return false;
+  }
+  return true;
+}
+
+// ============================================================================
+// Summary Data Matchers
+// ============================================================================
+
+MATCHER_P3(HasSummaryData, entityId, expectedMockId, expectedResolutionMapping,
+           std::string("has summary data for entity with expected mock ID ") +
+               ::testing::PrintToString(expectedMockId)) {
+
+  auto it = arg.find(entityId);
+  if (it == arg.end()) {
+    *result_listener << "entity " << ::testing::PrintToString(entityId)
+                     << " not found in summary data";
+    return false;
+  }
+
+  auto *mock = static_cast<const MockEntitySummaryEncoding *>(it->second.get());
+
+  if (mock->getId() != expectedMockId) {
+    *result_listener << "entity " << ::testing::PrintToString(entityId)
+                     << " has mock ID " << mock->getId() << " (expected "
+                     << expectedMockId << ")";
+    return false;
+  }
+
+  if (mock->getPatchedIds() != expectedResolutionMapping) {
+    *result_listener << "entity " << ::testing::PrintToString(entityId)
+                     << " has different resolution mapping";
+    return false;
+  }
+
+  return true;
+}
+
+MATCHER_P(DoesNotContainSummaryFor, entityId,
+          std::string("does not contain summary data for entity")) {
+  if (arg.find(entityId) != arg.end()) {
+    *result_listener << "unexpectedly contains entity "
+                     << ::testing::PrintToString(entityId);
+    return false;
+  }
+  return true;
+}
+
+MATCHER_P(SummaryDataHasSize, expectedSize,
+          std::string("summary data has size ") +
+              ::testing::PrintToString(expectedSize)) {
+  if (arg.size() != expectedSize) {
+    *result_listener << "has size " << arg.size();
+    return false;
+  }
+  return true;
+}
+
 TEST_F(EntityLinkerTest, NoLink) {
   NestedBuildNamespace LUNamespace(
       {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
@@ -115,141 +227,104 @@ TEST_F(EntityLinkerTest, NonEmptyLink) {
 
   auto TU = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
 
-  const auto EIdA = addEntity(*TU, "A", EntityLinkage::LinkageType::None);
-  const auto ESIdAS1 = addSummaryData(*TU, EIdA, "S1");
-  const auto ESIdAS2 = addSummaryData(*TU, EIdA, "S2");
+  const auto TU_A_Id = addEntity(*TU, "A", EntityLinkage::LinkageType::None);
+  const auto TU_A_S1_Data = addSummaryData(*TU, TU_A_Id, "S1");
+  const auto TU_A_S2_Data = addSummaryData(*TU, TU_A_Id, "S2");
 
-  const auto EIdB = addEntity(*TU, "B", EntityLinkage::LinkageType::Internal);
-  const auto ESIdBS1 = addSummaryData(*TU, EIdB, "S1");
-  const auto ESIdBS2 = addSummaryData(*TU, EIdB, "S2");
+  const auto TU_B_Id =
+      addEntity(*TU, "B", EntityLinkage::LinkageType::Internal);
+  const auto TU_B_S1_Data = addSummaryData(*TU, TU_B_Id, "S1");
+  const auto TU_B_S2_Data = addSummaryData(*TU, TU_B_Id, "S2");
 
-  const auto EIdC = addEntity(*TU, "C", EntityLinkage::LinkageType::External);
-  const auto ESIdCS1 = addSummaryData(*TU, EIdC, "S1");
+  const auto TU_C_Id =
+      addEntity(*TU, "C", EntityLinkage::LinkageType::External);
+  const auto TU_C_S1_Data = addSummaryData(*TU, TU_C_Id, "S1");
 
-  const auto EIdD = addEntity(*TU, "D", EntityLinkage::LinkageType::External);
-  const auto ESIdDS2 = addSummaryData(*TU, EIdD, "S2");
+  const auto TU_D_Id =
+      addEntity(*TU, "D", EntityLinkage::LinkageType::External);
+  const auto TU_D_S2_Data = addSummaryData(*TU, TU_D_Id, "S2");
 
   const BuildNamespace TUNamespace = getTUNamespace(*TU);
 
-  EXPECT_THAT_ERROR(Linker.link(std::move(TU)), llvm::Succeeded());
+  ASSERT_THAT_ERROR(Linker.link(std::move(TU)), llvm::Succeeded());
 
   const auto &Output = Linker.getOutput();
   const auto &IdTable = getIdTable(Output);
+  const auto &Entities = getEntities(IdTable);
   const auto &LinkageTable = getLinkageTable(Output);
   const auto &Data = getData(Output);
 
-  // Construct the nested namespace with TU inside LU
-  std::vector<BuildNamespace> NamespaceVec;
-  NamespaceVec.push_back(TUNamespace);
-  for (const auto &NS : getNamespaces(LUNamespace)) {
-    NamespaceVec.push_back(NS);
-  }
-  NestedBuildNamespace LocalNamespace(NamespaceVec);
+  NestedBuildNamespace LocalNamespace =
+      NestedBuildNamespace(TUNamespace).makeQualified(LUNamespace);
 
-  EntityName NameA("A", "", LocalNamespace);
-  EntityName NameB("B", "", LocalNamespace);
-  EntityName NameC("C", "", LUNamespace);
-  EntityName NameD("D", "", LUNamespace);
+  EntityName LU_A_Name("A", "", LocalNamespace);
+  EntityName LU_B_Name("B", "", LocalNamespace);
+  EntityName LU_C_Name("C", "", LUNamespace);
+  EntityName LU_D_Name("D", "", LUNamespace);
 
-  // EntityIDTable Tests.
+  // EntityIdTable Tests.
   {
-    const auto &Entities = getEntities(IdTable);
-
-    EXPECT_EQ(IdTable.count(), 4u);
-
-    EXPECT_TRUE(IdTable.contains(NameA));
-    EXPECT_EQ(Entities.at(NameA), EIdA);
-
-    EXPECT_TRUE(IdTable.contains(NameB));
-    EXPECT_EQ(Entities.at(NameB), EIdB);
-
-    EXPECT_TRUE(IdTable.contains(NameC));
-    EXPECT_EQ(Entities.at(NameC), EIdC);
-
-    EXPECT_TRUE(IdTable.contains(NameD));
-    EXPECT_EQ(Entities.at(NameD), EIdD);
+    ASSERT_THAT(IdTable, IdTableHasSize(4u));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_A_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_B_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_C_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_D_Name));
   }
 
+  // This is safe since we confirmed that these entities are present in the
+  // block above.
+  const auto LU_A_Id = Entities.at(LU_A_Name);
+  const auto LU_B_Id = Entities.at(LU_B_Name);
+  const auto LU_C_Id = Entities.at(LU_C_Name);
+  const auto LU_D_Id = Entities.at(LU_D_Name);
+
   // LinkageTable Tests.
   {
-    EXPECT_EQ(LinkageTable.size(), 4u);
-
-    ASSERT_NE(LinkageTable.find(EIdA), LinkageTable.end());
-    EXPECT_EQ(getLinkage(LinkageTable.at(EIdA)),
-              EntityLinkage::LinkageType::None);
-
-    ASSERT_NE(LinkageTable.find(EIdB), LinkageTable.end());
-    EXPECT_EQ(LinkageTable.at(EIdB).getLinkage(),
-              EntityLinkage::LinkageType::Internal);
-
-    ASSERT_NE(LinkageTable.find(EIdC), LinkageTable.end());
-    EXPECT_EQ(LinkageTable.at(EIdC).getLinkage(),
-              EntityLinkage::LinkageType::External);
-
-    ASSERT_NE(LinkageTable.find(EIdD), LinkageTable.end());
-    EXPECT_EQ(LinkageTable.at(EIdD).getLinkage(),
-              EntityLinkage::LinkageType::External);
+    ASSERT_THAT(LinkageTable, LinkageTableHasSize(4u));
+    ASSERT_THAT(LinkageTable,
+                EntityHasLinkage(LU_A_Id, EntityLinkage::LinkageType::None));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_B_Id, EntityLinkage::LinkageType::Internal));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_C_Id, EntityLinkage::LinkageType::External));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_D_Id, EntityLinkage::LinkageType::External));
   }
 
+  std::map<EntityId, EntityId> Resolution = {{TU_A_Id, LU_A_Id},
+                                             {TU_B_Id, LU_B_Id},
+                                             {TU_C_Id, LU_C_Id},
+                                             {TU_D_Id, LU_D_Id}};
+
   // Data Tests.
   {
-    EXPECT_EQ(Data.size(), 2u);
-
-    std::map<EntityId, EntityId> ExpectedEntityResolutionMapping = {
-        {EIdA, EIdA}, {EIdB, EIdB}, {EIdC, EIdC}, {EIdD, EIdD}};
+    ASSERT_EQ(Data.size(), 2u);
 
-    // S1 Tests.
+    // S1 Data Tests.
     {
       SummaryName S1("S1");
       ASSERT_NE(Data.find(S1), Data.end());
-
       const auto &S1Data = Data.at(S1);
-      EXPECT_EQ(S1Data.size(), 3u);
-
-      EXPECT_NE(S1Data.find(EIdA), S1Data.end());
-      auto *MockA =
-          static_cast<MockEntitySummaryEncoding *>(S1Data.at(EIdA).get());
-      EXPECT_EQ(MockA->getId(), ESIdAS1);
-      EXPECT_EQ(MockA->getPatchedIds(), ExpectedEntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdB), S1Data.end());
-      auto *MockB =
-          static_cast<MockEntitySummaryEncoding *>(S1Data.at(EIdB).get());
-      EXPECT_EQ(MockB->getId(), ESIdBS1);
-      EXPECT_EQ(MockB->getPatchedIds(), ExpectedEntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdC), S1Data.end());
-      auto *MockC =
-          static_cast<MockEntitySummaryEncoding *>(S1Data.at(EIdC).get());
-      EXPECT_EQ(MockC->getId(), ESIdCS1);
-      EXPECT_EQ(MockC->getPatchedIds(), ExpectedEntityResolutionMapping);
+
+      ASSERT_THAT(S1Data, SummaryDataHasSize(3u));
+      ASSERT_THAT(S1Data, HasSummaryData(LU_A_Id, TU_A_S1_Data, Resolution));
+      ASSERT_THAT(S1Data, HasSummaryData(LU_B_Id, TU_B_S1_Data, Resolution));
+      ASSERT_THAT(S1Data, HasSummaryData(TU_C_Id, TU_C_S1_Data, Resolution));
     }
 
-    // S2 Tests.
+    // S2 Data Tests.
     {
       SummaryName S2("S2");
       ASSERT_NE(Data.find(S2), Data.end());
-
       const auto &S2Data = Data.at(S2);
-      EXPECT_EQ(S2Data.size(), 3u);
-
-      EXPECT_NE(S2Data.find(EIdA), S2Data.end());
-      auto *MockA =
-          static_cast<MockEntitySummaryEncoding *>(S2Data.at(EIdA).get());
-      EXPECT_EQ(MockA->getId(), ESIdAS2);
-      EXPECT_EQ(MockA->getPatchedIds(), ExpectedEntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdB), S2Data.end());
-      auto *MockB =
-          static_cast<MockEntitySummaryEncoding *>(S2Data.at(EIdB).get());
-      EXPECT_EQ(MockB->getId(), ESIdBS2);
-      EXPECT_EQ(MockB->getPatchedIds(), ExpectedEntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdD), S2Data.end());
-      auto *MockD =
-          static_cast<MockEntitySummaryEncoding *>(S2Data.at(EIdD).get());
-      EXPECT_EQ(MockD->getId(), ESIdDS2);
-      EXPECT_EQ(MockD->getPatchedIds(), ExpectedEntityResolutionMapping);
+
+      ASSERT_THAT(S2Data, SummaryDataHasSize(3u));
+      ASSERT_THAT(S2Data, HasSummaryData(LU_A_Id, TU_A_S2_Data, Resolution));
+      ASSERT_THAT(S2Data, HasSummaryData(LU_B_Id, TU_B_S2_Data, Resolution));
+      ASSERT_THAT(S2Data, HasSummaryData(TU_D_Id, TU_D_S2_Data, Resolution));
     }
   }
 }
@@ -260,454 +335,328 @@ TEST_F(EntityLinkerTest, TwoTULinkWithAllCombinations) {
 
   EntityLinker Linker(LUNamespace);
 
-  // Create TU1 with entities covering all linkage types and summary
-  // distributions
   auto TU1 =
       createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU1");
 
   // None linkage entities in TU1
-  const auto EIdTU1_X_None =
-      addEntity(*TU1, "X", EntityLinkage::LinkageType::None);
-  const auto ESIdTU1_X_S1 = addSummaryData(*TU1, EIdTU1_X_None, "S1");
+  const auto TU1_X_Id = addEntity(*TU1, "X", EntityLinkage::LinkageType::None);
+  const auto TU1_X_S1_Data = addSummaryData(*TU1, TU1_X_Id, "S1");
 
-  const auto EIdTU1_Y_None =
-      addEntity(*TU1, "Y", EntityLinkage::LinkageType::None);
-  const auto ESIdTU1_Y_S2 = addSummaryData(*TU1, EIdTU1_Y_None, "S2");
+  const auto TU1_Y_Id = addEntity(*TU1, "Y", EntityLinkage::LinkageType::None);
+  const auto TU1_Y_S2_Data = addSummaryData(*TU1, TU1_Y_Id, "S2");
 
-  const auto EIdTU1_Z_None =
-      addEntity(*TU1, "Z", EntityLinkage::LinkageType::None);
-  const auto ESIdTU1_Z_S1 = addSummaryData(*TU1, EIdTU1_Z_None, "S1");
-  const auto ESIdTU1_Z_S2 = addSummaryData(*TU1, EIdTU1_Z_None, "S2");
+  const auto TU1_Z_Id = addEntity(*TU1, "Z", EntityLinkage::LinkageType::None);
+  const auto TU1_Z_S1_Data = addSummaryData(*TU1, TU1_Z_Id, "S1");
+  const auto TU1_Z_S2_Data = addSummaryData(*TU1, TU1_Z_Id, "S2");
 
   // Internal linkage entities in TU1
-  const auto EIdTU1_A_Internal =
+  const auto TU1_A_Id =
       addEntity(*TU1, "A", EntityLinkage::LinkageType::Internal);
-  const auto ESIdTU1_A_S1 = addSummaryData(*TU1, EIdTU1_A_Internal, "S1");
+  const auto TU1_A_S1_Data = addSummaryData(*TU1, TU1_A_Id, "S1");
 
-  const auto EIdTU1_B_Internal =
+  const auto TU1_B_Id =
       addEntity(*TU1, "B", EntityLinkage::LinkageType::Internal);
-  const auto ESIdTU1_B_S2 = addSummaryData(*TU1, EIdTU1_B_Internal, "S2");
+  const auto TU1_B_S2_Data = addSummaryData(*TU1, TU1_B_Id, "S2");
 
-  const auto EIdTU1_C_Internal =
+  const auto TU1_C_Id =
       addEntity(*TU1, "C", EntityLinkage::LinkageType::Internal);
-  const auto ESIdTU1_C_S1 = addSummaryData(*TU1, EIdTU1_C_Internal, "S1");
-  const auto ESIdTU1_C_S2 = addSummaryData(*TU1, EIdTU1_C_Internal, "S2");
+  const auto TU1_C_S1_Data = addSummaryData(*TU1, TU1_C_Id, "S1");
+  const auto TU1_C_S2_Data = addSummaryData(*TU1, TU1_C_Id, "S2");
 
   // External linkage entities in TU1
-  const auto EIdTU1_P_External =
+  const auto TU1_P_Id =
       addEntity(*TU1, "P", EntityLinkage::LinkageType::External);
-  const auto ESIdTU1_P_S1 = addSummaryData(*TU1, EIdTU1_P_External, "S1");
+  const auto TU1_P_S1_Data = addSummaryData(*TU1, TU1_P_Id, "S1");
 
-  const auto EIdTU1_Q_External =
+  const auto TU1_Q_Id =
       addEntity(*TU1, "Q", EntityLinkage::LinkageType::External);
-  const auto ESIdTU1_Q_S2 = addSummaryData(*TU1, EIdTU1_Q_External, "S2");
+  const auto TU1_Q_S2_Data = addSummaryData(*TU1, TU1_Q_Id, "S2");
 
-  const auto EIdTU1_R_External =
+  const auto TU1_R_Id =
       addEntity(*TU1, "R", EntityLinkage::LinkageType::External);
-  const auto ESIdTU1_R_S1 = addSummaryData(*TU1, EIdTU1_R_External, "S1");
-  const auto ESIdTU1_R_S2 = addSummaryData(*TU1, EIdTU1_R_External, "S2");
+  const auto TU1_R_S1_Data = addSummaryData(*TU1, TU1_R_Id, "S1");
+  const auto TU1_R_S2_Data = addSummaryData(*TU1, TU1_R_Id, "S2");
 
   const BuildNamespace TU1Namespace = getTUNamespace(*TU1);
 
-  // Link TU1
-  EXPECT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+  ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
 
-  // Create TU2 with entities covering all combinations including duplicates
   auto TU2 =
       createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU2");
 
-  // None linkage entities in TU2 - includes duplicates and unique
-  const auto EIdTU2_X_None =
-      addEntity(*TU2, "X", EntityLinkage::LinkageType::None);
-  const auto ESIdTU2_X_S2 = addSummaryData(*TU2, EIdTU2_X_None, "S2");
+  // None linkage entities in TU2 - includes duplicates and uniques
+  const auto TU2_X_Id = addEntity(*TU2, "X", EntityLinkage::LinkageType::None);
+  const auto TU2_X_S2_Data = addSummaryData(*TU2, TU2_X_Id, "S2");
 
-  const auto EIdTU2_Y_None =
-      addEntity(*TU2, "Y", EntityLinkage::LinkageType::None);
-  const auto ESIdTU2_Y_S1 = addSummaryData(*TU2, EIdTU2_Y_None, "S1");
+  const auto TU2_Y_Id = addEntity(*TU2, "Y", EntityLinkage::LinkageType::None);
+  const auto TU2_Y_S1_Data = addSummaryData(*TU2, TU2_Y_Id, "S1");
 
-  const auto EIdTU2_W_None =
-      addEntity(*TU2, "W", EntityLinkage::LinkageType::None);
-  const auto ESIdTU2_W_S1 = addSummaryData(*TU2, EIdTU2_W_None, "S1");
-  const auto ESIdTU2_W_S2 = addSummaryData(*TU2, EIdTU2_W_None, "S2");
+  const auto TU2_W_Id = addEntity(*TU2, "W", EntityLinkage::LinkageType::None);
+  const auto TU2_W_S1_Data = addSummaryData(*TU2, TU2_W_Id, "S1");
+  const auto TU2_W_S2_Data = addSummaryData(*TU2, TU2_W_Id, "S2");
 
   // Internal linkage entities in TU2 - includes duplicates and unique
-  const auto EIdTU2_A_Internal =
+  const auto TU2_A_Id =
       addEntity(*TU2, "A", EntityLinkage::LinkageType::Internal);
-  const auto ESIdTU2_A_S2 = addSummaryData(*TU2, EIdTU2_A_Internal, "S2");
+  const auto TU2_A_S2_Data = addSummaryData(*TU2, TU2_A_Id, "S2");
 
-  const auto EIdTU2_B_Internal =
+  const auto TU2_B_Id =
       addEntity(*TU2, "B", EntityLinkage::LinkageType::Internal);
-  const auto ESIdTU2_B_S1 = addSummaryData(*TU2, EIdTU2_B_Internal, "S1");
+  const auto TU2_B_S1_Data = addSummaryData(*TU2, TU2_B_Id, "S1");
 
-  const auto EIdTU2_D_Internal =
+  const auto TU2_D_Id =
       addEntity(*TU2, "D", EntityLinkage::LinkageType::Internal);
-  const auto ESIdTU2_D_S1 = addSummaryData(*TU2, EIdTU2_D_Internal, "S1");
-  const auto ESIdTU2_D_S2 = addSummaryData(*TU2, EIdTU2_D_Internal, "S2");
+  const auto TU2_D_S1_Data = addSummaryData(*TU2, TU2_D_Id, "S1");
+  const auto TU2_D_S2_Data = addSummaryData(*TU2, TU2_D_Id, "S2");
 
   // External linkage entities in TU2 - includes duplicates (will be dropped)
-  // and unique
-  const auto EIdTU2_P_External =
+  // and uniques
+  const auto TU2_P_Id =
       addEntity(*TU2, "P", EntityLinkage::LinkageType::External);
-  const auto ESIdTU2_P_S2 = addSummaryData(*TU2, EIdTU2_P_External, "S2");
+  const auto TU2_P_S2_Data = addSummaryData(*TU2, TU2_P_Id, "S2");
 
-  const auto EIdTU2_Q_External =
+  const auto TU2_Q_Id =
       addEntity(*TU2, "Q", EntityLinkage::LinkageType::External);
-  const auto ESIdTU2_Q_S1 = addSummaryData(*TU2, EIdTU2_Q_External, "S1");
+  const auto TU2_Q_S1_Data = addSummaryData(*TU2, TU2_Q_Id, "S1");
 
-  const auto EIdTU2_S_External =
+  const auto TU2_S_Id =
       addEntity(*TU2, "S", EntityLinkage::LinkageType::External);
-  const auto ESIdTU2_S_S1 = addSummaryData(*TU2, EIdTU2_S_External, "S1");
-  const auto ESIdTU2_S_S2 = addSummaryData(*TU2, EIdTU2_S_External, "S2");
+  const auto TU2_S_S1_Data = addSummaryData(*TU2, TU2_S_Id, "S1");
+  const auto TU2_S_S2_Data = addSummaryData(*TU2, TU2_S_Id, "S2");
 
   const BuildNamespace TU2Namespace = getTUNamespace(*TU2);
 
-  // Link TU2
-  EXPECT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
+  ASSERT_THAT_ERROR(Linker.link(std::move(TU2)), llvm::Succeeded());
 
-  // Verify the output
   const auto &Output = Linker.getOutput();
   const auto &IdTable = getIdTable(Output);
+  const auto &Entities = getEntities(IdTable);
   const auto &LinkageTable = getLinkageTable(Output);
   const auto &Data = getData(Output);
 
-  // Construct the nested namespaces
-  std::vector<BuildNamespace> TU1NamespaceVec;
-  TU1NamespaceVec.push_back(TU1Namespace);
-  for (const auto &NS : getNamespaces(LUNamespace)) {
-    TU1NamespaceVec.push_back(NS);
-  }
-  NestedBuildNamespace TU1LocalNamespace(TU1NamespaceVec);
+  NestedBuildNamespace TU1LocalNamespace =
+      NestedBuildNamespace(TU1Namespace).makeQualified(LUNamespace);
 
-  std::vector<BuildNamespace> TU2NamespaceVec;
-  TU2NamespaceVec.push_back(TU2Namespace);
-  for (const auto &NS : getNamespaces(LUNamespace)) {
-    TU2NamespaceVec.push_back(NS);
-  }
-  NestedBuildNamespace TU2LocalNamespace(TU2NamespaceVec);
+  NestedBuildNamespace TU2LocalNamespace =
+      NestedBuildNamespace(TU2Namespace).makeQualified(LUNamespace);
 
-  // Create expected entity names
   // None linkage entities use local namespace (TU scoped)
-  EntityName NameTU1_X_None("X", "", TU1LocalNamespace);
-  EntityName NameTU1_Y_None("Y", "", TU1LocalNamespace);
-  EntityName NameTU1_Z_None("Z", "", TU1LocalNamespace);
-  EntityName NameTU2_X_None("X", "", TU2LocalNamespace);
-  EntityName NameTU2_Y_None("Y", "", TU2LocalNamespace);
-  EntityName NameTU2_W_None("W", "", TU2LocalNamespace);
+  EntityName LU_TU1_X_Name("X", "", TU1LocalNamespace);
+  EntityName LU_TU1_Y_Name("Y", "", TU1LocalNamespace);
+  EntityName LU_TU1_Z_Name("Z", "", TU1LocalNamespace);
+  EntityName LU_TU2_X_Name("X", "", TU2LocalNamespace);
+  EntityName LU_TU2_Y_Name("Y", "", TU2LocalNamespace);
+  EntityName LU_TU2_W_Name("W", "", TU2LocalNamespace);
 
   // Internal linkage entities use local namespace (TU scoped)
-  EntityName NameTU1_A_Internal("A", "", TU1LocalNamespace);
-  EntityName NameTU1_B_Internal("B", "", TU1LocalNamespace);
-  EntityName NameTU1_C_Internal("C", "", TU1LocalNamespace);
-  EntityName NameTU2_A_Internal("A", "", TU2LocalNamespace);
-  EntityName NameTU2_B_Internal("B", "", TU2LocalNamespace);
-  EntityName NameTU2_D_Internal("D", "", TU2LocalNamespace);
+  EntityName LU_TU1_A_Name("A", "", TU1LocalNamespace);
+  EntityName LU_TU1_B_Name("B", "", TU1LocalNamespace);
+  EntityName LU_TU1_C_Name("C", "", TU1LocalNamespace);
+  EntityName LU_TU2_A_Name("A", "", TU2LocalNamespace);
+  EntityName LU_TU2_B_Name("B", "", TU2LocalNamespace);
+  EntityName LU_TU2_D_Name("D", "", TU2LocalNamespace);
 
   // External linkage entities use LU namespace (shared across TUs)
-  EntityName NameP_External("P", "", LUNamespace);
-  EntityName NameQ_External("Q", "", LUNamespace);
-  EntityName NameR_External("R", "", LUNamespace);
-  EntityName NameS_External("S", "", LUNamespace);
+  EntityName LU_P_Name("P", "", LUNamespace);
+  EntityName LU_Q_Name("Q", "", LUNamespace);
+  EntityName LU_R_Name("R", "", LUNamespace);
+  EntityName LU_S_Name("S", "", LUNamespace);
 
-  // EntityIdTable Tests
+  // EntityIdTable Tests.
   {
-    const auto &Entities = getEntities(IdTable);
-
     // Should have 6 None + 6 Internal + 4 External = 16 entities total
-    EXPECT_EQ(IdTable.count(), 16u);
-
-    // TU1 None linkage entities
-    EXPECT_TRUE(IdTable.contains(NameTU1_X_None));
-    ASSERT_EQ(Entities.at(NameTU1_X_None), EIdTU1_X_None);
-
-    EXPECT_TRUE(IdTable.contains(NameTU1_Y_None));
-    EXPECT_EQ(Entities.at(NameTU1_Y_None), EIdTU1_Y_None);
-
-    EXPECT_TRUE(IdTable.contains(NameTU1_Z_None));
-    EXPECT_EQ(Entities.at(NameTU1_Z_None), EIdTU1_Z_None);
-
-    // TU2 None linkage entities (different from TU1 due to namespace)
-    EXPECT_TRUE(IdTable.contains(NameTU2_X_None));
-    EXPECT_EQ(Entities.at(NameTU2_X_None), EIdTU2_X_None);
-
-    EXPECT_TRUE(IdTable.contains(NameTU2_Y_None));
-    EXPECT_EQ(Entities.at(NameTU2_Y_None), EIdTU2_Y_None);
-
-    EXPECT_TRUE(IdTable.contains(NameTU2_W_None));
-    EXPECT_EQ(Entities.at(NameTU2_W_None), EIdTU2_W_None);
-
-    // TU1 Internal linkage entities
-    EXPECT_TRUE(IdTable.contains(NameTU1_A_Internal));
-    EXPECT_EQ(Entities.at(NameTU1_A_Internal), EIdTU1_A_Internal);
-
-    EXPECT_TRUE(IdTable.contains(NameTU1_B_Internal));
-    EXPECT_EQ(Entities.at(NameTU1_B_Internal), EIdTU1_B_Internal);
-
-    EXPECT_TRUE(IdTable.contains(NameTU1_C_Internal));
-    EXPECT_EQ(Entities.at(NameTU1_C_Internal), EIdTU1_C_Internal);
-
-    // TU2 Internal linkage entities (different from TU1 due to namespace)
-    EXPECT_TRUE(IdTable.contains(NameTU2_A_Internal));
-    EXPECT_EQ(Entities.at(NameTU2_A_Internal), EIdTU2_A_Internal);
-
-    EXPECT_TRUE(IdTable.contains(NameTU2_B_Internal));
-    EXPECT_EQ(Entities.at(NameTU2_B_Internal), EIdTU2_B_Internal);
-
-    EXPECT_TRUE(IdTable.contains(NameTU2_D_Internal));
-    EXPECT_EQ(Entities.at(NameTU2_D_Internal), EIdTU2_D_Internal);
-
-    // External linkage entities (shared across TUs)
-    EXPECT_TRUE(IdTable.contains(NameP_External));
-    EXPECT_EQ(Entities.at(NameP_External), EIdTU1_P_External);
-
-    EXPECT_TRUE(IdTable.contains(NameQ_External));
-    EXPECT_EQ(Entities.at(NameQ_External), EIdTU1_Q_External);
-
-    EXPECT_TRUE(IdTable.contains(NameR_External));
-    EXPECT_EQ(Entities.at(NameR_External), EIdTU1_R_External);
-
-    EXPECT_TRUE(IdTable.contains(NameS_External));
-    EXPECT_EQ(Entities.at(NameS_External), EIdTU2_S_External);
+    ASSERT_THAT(IdTable, IdTableHasSize(16u));
+
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU1_X_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU1_Y_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU1_Z_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU2_X_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU2_Y_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU2_W_Name));
+
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU1_A_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU1_B_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU1_C_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU2_A_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU2_B_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_TU2_D_Name));
+
+    ASSERT_THAT(IdTable, ContainsEntity(LU_P_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_Q_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_R_Name));
+    ASSERT_THAT(IdTable, ContainsEntity(LU_S_Name));
   }
 
-  // LinkageTable Tests
+  // This is safe since we confirmed that these entities are present in the
+  // block above.
+  const auto LU_TU1_X_Id = Entities.at(LU_TU1_X_Name);
+  const auto LU_TU1_Y_Id = Entities.at(LU_TU1_Y_Name);
+  const auto LU_TU1_Z_Id = Entities.at(LU_TU1_Z_Name);
+  const auto LU_TU2_X_Id = Entities.at(LU_TU2_X_Name);
+  const auto LU_TU2_Y_Id = Entities.at(LU_TU2_Y_Name);
+  const auto LU_TU2_W_Id = Entities.at(LU_TU2_W_Name);
+  const auto LU_TU1_A_Id = Entities.at(LU_TU1_A_Name);
+  const auto LU_TU1_B_Id = Entities.at(LU_TU1_B_Name);
+  const auto LU_TU1_C_Id = Entities.at(LU_TU1_C_Name);
+  const auto LU_TU2_A_Id = Entities.at(LU_TU2_A_Name);
+  const auto LU_TU2_B_Id = Entities.at(LU_TU2_B_Name);
+  const auto LU_TU2_D_Id = Entities.at(LU_TU2_D_Name);
+  const auto LU_P_Id = Entities.at(LU_P_Name);
+  const auto LU_Q_Id = Entities.at(LU_Q_Name);
+  const auto LU_R_Id = Entities.at(LU_R_Name);
+  const auto LU_S_Id = Entities.at(LU_S_Name);
+
+  // LinkageTable Tests.
   {
-    EXPECT_EQ(LinkageTable.size(), 16u);
-
-    // Verify None linkage entities
-    EXPECT_EQ(LinkageTable.at(EIdTU1_X_None).getLinkage(),
-              EntityLinkage::LinkageType::None);
-    EXPECT_EQ(LinkageTable.at(EIdTU1_Y_None).getLinkage(),
-              EntityLinkage::LinkageType::None);
-    EXPECT_EQ(LinkageTable.at(EIdTU1_Z_None).getLinkage(),
-              EntityLinkage::LinkageType::None);
-    EXPECT_EQ(LinkageTable.at(EIdTU2_X_None).getLinkage(),
-              EntityLinkage::LinkageType::None);
-    EXPECT_EQ(LinkageTable.at(EIdTU2_Y_None).getLinkage(),
-              EntityLinkage::LinkageType::None);
-    EXPECT_EQ(LinkageTable.at(EIdTU2_W_None).getLinkage(),
-              EntityLinkage::LinkageType::None);
-
-    // Verify Internal linkage entities
-    EXPECT_EQ(LinkageTable.at(EIdTU1_A_Internal).getLinkage(),
-              EntityLinkage::LinkageType::Internal);
-    EXPECT_EQ(LinkageTable.at(EIdTU1_B_Internal).getLinkage(),
-              EntityLinkage::LinkageType::Internal);
-    EXPECT_EQ(LinkageTable.at(EIdTU1_C_Internal).getLinkage(),
-              EntityLinkage::LinkageType::Internal);
-    EXPECT_EQ(LinkageTable.at(EIdTU2_A_Internal).getLinkage(),
-              EntityLinkage::LinkageType::Internal);
-    EXPECT_EQ(LinkageTable.at(EIdTU2_B_Internal).getLinkage(),
-              EntityLinkage::LinkageType::Internal);
-    EXPECT_EQ(LinkageTable.at(EIdTU2_D_Internal).getLinkage(),
-              EntityLinkage::LinkageType::Internal);
-
-    // Verify External linkage entities
-    EXPECT_EQ(LinkageTable.at(EIdTU1_P_External).getLinkage(),
-              EntityLinkage::LinkageType::External);
-    EXPECT_EQ(LinkageTable.at(EIdTU1_Q_External).getLinkage(),
-              EntityLinkage::LinkageType::External);
-    EXPECT_EQ(LinkageTable.at(EIdTU1_R_External).getLinkage(),
-              EntityLinkage::LinkageType::External);
-    EXPECT_EQ(LinkageTable.at(EIdTU2_S_External).getLinkage(),
-              EntityLinkage::LinkageType::External);
+    ASSERT_THAT(LinkageTable, LinkageTableHasSize(16u));
+
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU1_X_Id, EntityLinkage::LinkageType::None));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU1_Y_Id, EntityLinkage::LinkageType::None));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU1_Z_Id, EntityLinkage::LinkageType::None));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU2_X_Id, EntityLinkage::LinkageType::None));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU2_Y_Id, EntityLinkage::LinkageType::None));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU2_W_Id, EntityLinkage::LinkageType::None));
+
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU1_A_Id, EntityLinkage::LinkageType::Internal));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU1_B_Id, EntityLinkage::LinkageType::Internal));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU1_C_Id, EntityLinkage::LinkageType::Internal));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU2_A_Id, EntityLinkage::LinkageType::Internal));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU2_B_Id, EntityLinkage::LinkageType::Internal));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_TU2_D_Id, EntityLinkage::LinkageType::Internal));
+
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_P_Id, EntityLinkage::LinkageType::External));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_Q_Id, EntityLinkage::LinkageType::External));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_R_Id, EntityLinkage::LinkageType::External));
+    ASSERT_THAT(
+        LinkageTable,
+        EntityHasLinkage(LU_S_Id, EntityLinkage::LinkageType::External));
   }
 
-  // Data Tests
+  // Data Tests.
   {
-    EXPECT_EQ(Data.size(), 2u);
-
-    // Build entity resolution mappings for each TU
-    std::map<EntityId, EntityId> TU1EntityResolutionMapping = {
-        {EIdTU1_X_None, EIdTU1_X_None},
-        {EIdTU1_Y_None, EIdTU1_Y_None},
-        {EIdTU1_Z_None, EIdTU1_Z_None},
-        {EIdTU1_A_Internal, EIdTU1_A_Internal},
-        {EIdTU1_B_Internal, EIdTU1_B_Internal},
-        {EIdTU1_C_Internal, EIdTU1_C_Internal},
-        {EIdTU1_P_External, EIdTU1_P_External},
-        {EIdTU1_Q_External, EIdTU1_Q_External},
-        {EIdTU1_R_External, EIdTU1_R_External}};
-
-    std::map<EntityId, EntityId> TU2EntityResolutionMapping = {
-        {EIdTU2_X_None, EIdTU2_X_None},
-        {EIdTU2_Y_None, EIdTU2_Y_None},
-        {EIdTU2_W_None, EIdTU2_W_None},
-        {EIdTU2_A_Internal, EIdTU2_A_Internal},
-        {EIdTU2_B_Internal, EIdTU2_B_Internal},
-        {EIdTU2_D_Internal, EIdTU2_D_Internal},
-        // External linkage entities from TU2 resolve to TU1's IDs if duplicate
-        {EIdTU2_P_External, EIdTU1_P_External},
-        {EIdTU2_Q_External, EIdTU1_Q_External},
-        {EIdTU2_S_External, EIdTU2_S_External}};
-
-    // S1 Tests
+    ASSERT_EQ(Data.size(), 2u);
+
+    // Build entity resolution mappings for each TU.
+    std::map<EntityId, EntityId> TU1Resolution = {
+        {TU1_X_Id, LU_TU1_X_Id}, {TU1_Y_Id, LU_TU1_Y_Id},
+        {TU1_Z_Id, LU_TU1_Z_Id}, {TU1_A_Id, LU_TU1_A_Id},
+        {TU1_B_Id, LU_TU1_B_Id}, {TU1_C_Id, LU_TU1_C_Id},
+        {TU1_P_Id, LU_P_Id},     {TU1_Q_Id, LU_Q_Id},
+        {TU1_R_Id, LU_R_Id}};
+
+    std::map<EntityId, EntityId> TU2Resolution = {
+        {TU2_X_Id, LU_TU2_X_Id}, {TU2_Y_Id, LU_TU2_Y_Id},
+        {TU2_W_Id, LU_TU2_W_Id}, {TU2_A_Id, LU_TU2_A_Id},
+        {TU2_B_Id, LU_TU2_B_Id}, {TU2_D_Id, LU_TU2_D_Id},
+        {TU2_P_Id, LU_P_Id},     {TU2_Q_Id, LU_Q_Id},
+        {TU2_S_Id, LU_S_Id}};
+
+    // S1 Data Tests.
     {
       SummaryName S1("S1");
       ASSERT_NE(Data.find(S1), Data.end());
-
       const auto &S1Data = Data.at(S1);
-      // S1 should contain: TU1(X,Z,A,C,P,R) + TU2(Y,W,B,D,S) = 11 entities
-      // Note: TU2's P and Q external entities are dropped because TU1 already
-      // has them
-      EXPECT_EQ(S1Data.size(), 11u);
-
-      // Verify TU1 entities in S1
-      EXPECT_NE(S1Data.find(EIdTU1_X_None), S1Data.end());
-      auto *MockTU1_X = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU1_X_None).get());
-      EXPECT_EQ(MockTU1_X->getId(), ESIdTU1_X_S1);
-      EXPECT_EQ(MockTU1_X->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdTU1_Z_None), S1Data.end());
-      auto *MockTU1_Z = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU1_Z_None).get());
-      EXPECT_EQ(MockTU1_Z->getId(), ESIdTU1_Z_S1);
-      EXPECT_EQ(MockTU1_Z->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdTU1_A_Internal), S1Data.end());
-      auto *MockTU1_A = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU1_A_Internal).get());
-      EXPECT_EQ(MockTU1_A->getId(), ESIdTU1_A_S1);
-      EXPECT_EQ(MockTU1_A->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdTU1_C_Internal), S1Data.end());
-      auto *MockTU1_C = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU1_C_Internal).get());
-      EXPECT_EQ(MockTU1_C->getId(), ESIdTU1_C_S1);
-      EXPECT_EQ(MockTU1_C->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdTU1_P_External), S1Data.end());
-      auto *MockTU1_P = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU1_P_External).get());
-      EXPECT_EQ(MockTU1_P->getId(), ESIdTU1_P_S1);
-      EXPECT_EQ(MockTU1_P->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdTU1_R_External), S1Data.end());
-      auto *MockTU1_R = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU1_R_External).get());
-      EXPECT_EQ(MockTU1_R->getId(), ESIdTU1_R_S1);
-      EXPECT_EQ(MockTU1_R->getPatchedIds(), TU1EntityResolutionMapping);
-
-      // Verify TU2 entities in S1
-      EXPECT_NE(S1Data.find(EIdTU2_Y_None), S1Data.end());
-      auto *MockTU2_Y = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU2_Y_None).get());
-      EXPECT_EQ(MockTU2_Y->getId(), ESIdTU2_Y_S1);
-      EXPECT_EQ(MockTU2_Y->getPatchedIds(), TU2EntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdTU2_W_None), S1Data.end());
-      auto *MockTU2_W = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU2_W_None).get());
-      EXPECT_EQ(MockTU2_W->getId(), ESIdTU2_W_S1);
-      EXPECT_EQ(MockTU2_W->getPatchedIds(), TU2EntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdTU2_B_Internal), S1Data.end());
-      auto *MockTU2_B = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU2_B_Internal).get());
-      EXPECT_EQ(MockTU2_B->getId(), ESIdTU2_B_S1);
-      EXPECT_EQ(MockTU2_B->getPatchedIds(), TU2EntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdTU2_D_Internal), S1Data.end());
-      auto *MockTU2_D = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU2_D_Internal).get());
-      EXPECT_EQ(MockTU2_D->getId(), ESIdTU2_D_S1);
-      EXPECT_EQ(MockTU2_D->getPatchedIds(), TU2EntityResolutionMapping);
-
-      EXPECT_NE(S1Data.find(EIdTU2_S_External), S1Data.end());
-      auto *MockTU2_S = static_cast<MockEntitySummaryEncoding *>(
-          S1Data.at(EIdTU2_S_External).get());
-      EXPECT_EQ(MockTU2_S->getId(), ESIdTU2_S_S1);
-      EXPECT_EQ(MockTU2_S->getPatchedIds(), TU2EntityResolutionMapping);
-
-      // Verify TU2's duplicate external entities are NOT in S1
-      EXPECT_EQ(S1Data.find(EIdTU2_Q_External), S1Data.end());
+
+      // S1 should contain: TU1(X,Z,A,C,P,R) + TU2(Y,W,B,D,Q,S) = 12 entities.
+      ASSERT_THAT(S1Data, SummaryDataHasSize(12u));
+
+      // TU1 entities in S1.
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_TU1_X_Id, TU1_X_S1_Data, TU1Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_TU1_Z_Id, TU1_Z_S1_Data, TU1Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_TU1_A_Id, TU1_A_S1_Data, TU1Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_TU1_C_Id, TU1_C_S1_Data, TU1Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_P_Id, TU1_P_S1_Data, TU1Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_R_Id, TU1_R_S1_Data, TU1Resolution));
+
+      // TU2 entities in S1.
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_TU2_Y_Id, TU2_Y_S1_Data, TU2Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_TU2_W_Id, TU2_W_S1_Data, TU2Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_TU2_B_Id, TU2_B_S1_Data, TU2Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_TU2_D_Id, TU2_D_S1_Data, TU2Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_Q_Id, TU2_Q_S1_Data, TU2Resolution));
+      ASSERT_THAT(S1Data,
+                  HasSummaryData(LU_S_Id, TU2_S_S1_Data, TU2Resolution));
     }
 
-    // S2 Tests
+    // S2 Data Tests.
     {
       SummaryName S2("S2");
       ASSERT_NE(Data.find(S2), Data.end());
-
       const auto &S2Data = Data.at(S2);
-      // S2 should contain: TU1(Y,Z,B,C,Q,R) + TU2(X,W,A,D,S) = 11 entities
-      // Note: TU2's P and Q external entities are dropped because TU1 already
-      // has them
-      EXPECT_EQ(S2Data.size(), 11u);
-
-      // Verify TU1 entities in S2
-      EXPECT_NE(S2Data.find(EIdTU1_Y_None), S2Data.end());
-      auto *MockTU1_Y = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU1_Y_None).get());
-      EXPECT_EQ(MockTU1_Y->getId(), ESIdTU1_Y_S2);
-      EXPECT_EQ(MockTU1_Y->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdTU1_Z_None), S2Data.end());
-      auto *MockTU1_Z = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU1_Z_None).get());
-      EXPECT_EQ(MockTU1_Z->getId(), ESIdTU1_Z_S2);
-      EXPECT_EQ(MockTU1_Z->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdTU1_B_Internal), S2Data.end());
-      auto *MockTU1_B = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU1_B_Internal).get());
-      EXPECT_EQ(MockTU1_B->getId(), ESIdTU1_B_S2);
-      EXPECT_EQ(MockTU1_B->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdTU1_C_Internal), S2Data.end());
-      auto *MockTU1_C = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU1_C_Internal).get());
-      EXPECT_EQ(MockTU1_C->getId(), ESIdTU1_C_S2);
-      EXPECT_EQ(MockTU1_C->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdTU1_Q_External), S2Data.end());
-      auto *MockTU1_Q = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU1_Q_External).get());
-      EXPECT_EQ(MockTU1_Q->getId(), ESIdTU1_Q_S2);
-      EXPECT_EQ(MockTU1_Q->getPatchedIds(), TU1EntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdTU1_R_External), S2Data.end());
-      auto *MockTU1_R = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU1_R_External).get());
-      EXPECT_EQ(MockTU1_R->getId(), ESIdTU1_R_S2);
-      EXPECT_EQ(MockTU1_R->getPatchedIds(), TU1EntityResolutionMapping);
-
-      // Verify TU2 entities in S2
-      EXPECT_NE(S2Data.find(EIdTU2_X_None), S2Data.end());
-      auto *MockTU2_X = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU2_X_None).get());
-      EXPECT_EQ(MockTU2_X->getId(), ESIdTU2_X_S2);
-      EXPECT_EQ(MockTU2_X->getPatchedIds(), TU2EntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdTU2_W_None), S2Data.end());
-      auto *MockTU2_W = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU2_W_None).get());
-      EXPECT_EQ(MockTU2_W->getId(), ESIdTU2_W_S2);
-      EXPECT_EQ(MockTU2_W->getPatchedIds(), TU2EntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdTU2_A_Internal), S2Data.end());
-      auto *MockTU2_A = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU2_A_Internal).get());
-      EXPECT_EQ(MockTU2_A->getId(), ESIdTU2_A_S2);
-      EXPECT_EQ(MockTU2_A->getPatchedIds(), TU2EntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdTU2_D_Internal), S2Data.end());
-      auto *MockTU2_D = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU2_D_Internal).get());
-      EXPECT_EQ(MockTU2_D->getId(), ESIdTU2_D_S2);
-      EXPECT_EQ(MockTU2_D->getPatchedIds(), TU2EntityResolutionMapping);
-
-      EXPECT_NE(S2Data.find(EIdTU2_S_External), S2Data.end());
-      auto *MockTU2_S = static_cast<MockEntitySummaryEncoding *>(
-          S2Data.at(EIdTU2_S_External).get());
-      EXPECT_EQ(MockTU2_S->getId(), ESIdTU2_S_S2);
-      EXPECT_EQ(MockTU2_S->getPatchedIds(), TU2EntityResolutionMapping);
-
-      // Verify TU2's duplicate external entities are NOT in S2
-      EXPECT_EQ(S2Data.find(EIdTU2_P_External), S2Data.end());
+
+      // S2 should contain: TU1(Y,Z,B,C,Q,R) + TU2(X,W,A,D,P,S) = 12 entities.
+      ASSERT_THAT(S2Data, SummaryDataHasSize(12u));
+
+      // TU1 entities in S2.
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_TU1_Y_Id, TU1_Y_S2_Data, TU1Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_TU1_Z_Id, TU1_Z_S2_Data, TU1Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_TU1_B_Id, TU1_B_S2_Data, TU1Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_TU1_C_Id, TU1_C_S2_Data, TU1Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_Q_Id, TU1_Q_S2_Data, TU1Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_R_Id, TU1_R_S2_Data, TU1Resolution));
+
+      // TU2 entities in S2.
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_TU2_X_Id, TU2_X_S2_Data, TU2Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_TU2_W_Id, TU2_W_S2_Data, TU2Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_TU2_A_Id, TU2_A_S2_Data, TU2Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_TU2_D_Id, TU2_D_S2_Data, TU2Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_P_Id, TU2_P_S2_Data, TU2Resolution));
+      ASSERT_THAT(S2Data,
+                  HasSummaryData(LU_S_Id, TU2_S_S2_Data, TU2Resolution));
     }
   }
 }
 
 } // namespace
-
-} // namespace clang::ssaf
diff --git a/clang/unittests/Analysis/Scalable/TestFixture.cpp b/clang/unittests/Analysis/Scalable/TestFixture.cpp
index c3c7027c1dc3f..56bacb811472d 100644
--- a/clang/unittests/Analysis/Scalable/TestFixture.cpp
+++ b/clang/unittests/Analysis/Scalable/TestFixture.cpp
@@ -24,6 +24,20 @@ template <class T> static std::string asString(const T &Obj) {
 void TestFixture::PrintTo(const EntityId &E, std::ostream *OS) {
   *OS << "EntityId(" << E.Index << ")";
 }
+
 void TestFixture::PrintTo(const SummaryName &N, std::ostream *OS) {
   *OS << "SummaryName(" << N.Name << ")";
 }
+
+// Free functions for Google Test matchers
+namespace clang::ssaf {
+
+void PrintTo(const EntityId &E, std::ostream *OS) {
+  TestFixture::PrintTo(E, OS);
+}
+
+void PrintTo(const SummaryName &N, std::ostream *OS) {
+  TestFixture::PrintTo(N, OS);
+}
+
+} // namespace clang::ssaf
diff --git a/clang/unittests/Analysis/Scalable/TestFixture.h b/clang/unittests/Analysis/Scalable/TestFixture.h
index c8936fd2cfb53..7f2622f195cb7 100644
--- a/clang/unittests/Analysis/Scalable/TestFixture.h
+++ b/clang/unittests/Analysis/Scalable/TestFixture.h
@@ -31,6 +31,7 @@ class TestFixture : public ::testing::Test {
   static auto &get##FIELD_NAME(CLASS &X) { return X.FIELD_NAME; }
 #include "clang/Analysis/Scalable/Model/PrivateFieldNames.def"
 
+public:
   static void PrintTo(const EntityId &, std::ostream *);
   static void PrintTo(const SummaryName &, std::ostream *);
 };

>From 24d6ad715eead60db6a17a56af18ccba02b881c3 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 18 Feb 2026 15:25:02 -0800
Subject: [PATCH 07/14] Improve ErrorBuilder

---
 .../Analysis/Scalable/Support/ErrorBuilder.h  |  2 +-
 .../Scalable/Support/ErrorBuilder.cpp         | 11 ++++++----
 .../Analysis/Scalable/ErrorBuilderTest.cpp    | 22 +++++++++----------
 3 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
index b0849269edd41..5f7556b63fad0 100644
--- a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
@@ -169,7 +169,7 @@ class ErrorBuilder {
   ///   //  processing field 'age'
   ///   //  value is 42"
   /// \endcode
-  llvm::Error build();
+  llvm::Error build() const;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
index 5876edb03997e..2280a4b674595 100644
--- a/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
+++ b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
@@ -13,6 +13,9 @@
 
 namespace clang::ssaf {
 
+constexpr llvm::StringLiteral ErrorSeparator = " + ";
+constexpr llvm::StringLiteral ContextSeparator = "\n";
+
 ErrorBuilder ErrorBuilder::wrap(llvm::Error E) {
   assert(
       E &&
@@ -39,7 +42,7 @@ ErrorBuilder ErrorBuilder::wrap(llvm::Error E) {
   });
 
   // Combine all messages with " + " and push as a single context entry
-  std::string CombinedMsg = llvm::join(Messages, " + ");
+  std::string CombinedMsg = llvm::join(Messages, ErrorSeparator);
   Builder.pushContext(std::move(CombinedMsg));
 
   return Builder;
@@ -50,13 +53,13 @@ ErrorBuilder &ErrorBuilder::context(const char *Msg) {
   return *this;
 }
 
-llvm::Error ErrorBuilder::build() {
+llvm::Error ErrorBuilder::build() const {
   // Reverse the context stack so that the most recent context appears first
   // and the wrapped error (if any) appears last.
   // Note: Even if ContextStack is empty, we create an error with the stored
   // error code and an empty message (this is valid in LLVM).
-  return llvm::createStringError(llvm::join(llvm::reverse(ContextStack), "\n"),
-                                 Code);
+  return llvm::createStringError(
+      llvm::join(llvm::reverse(ContextStack), ContextSeparator), Code);
 }
 
 } // namespace clang::ssaf
diff --git a/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp b/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
index ad0c3bf34038a..3a867a2b8f0ce 100644
--- a/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
+++ b/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
@@ -35,7 +35,7 @@ class ErrorBuilderTest : public ::testing::Test {
   }
 };
 
-TEST_F(ErrorBuilderTest, CreateSimpleError) {
+TEST_F(ErrorBuilderTest, CreatesSimpleError) {
   auto Err =
       ErrorBuilder::create(std::errc::invalid_argument, "test error").build();
 
@@ -45,7 +45,7 @@ TEST_F(ErrorBuilderTest, CreateSimpleError) {
   EXPECT_EQ(Info.Message, "test error");
 }
 
-TEST_F(ErrorBuilderTest, CreateWithErrorCode) {
+TEST_F(ErrorBuilderTest, CreatesErrorWithErrorCode) {
   auto EC = std::make_error_code(std::errc::no_such_file_or_directory);
   auto Err = ErrorBuilder::create(EC, "file not found").build();
 
@@ -55,7 +55,7 @@ TEST_F(ErrorBuilderTest, CreateWithErrorCode) {
   EXPECT_EQ(Info.Message, "file not found");
 }
 
-TEST_F(ErrorBuilderTest, CreateWithFormattedMessage) {
+TEST_F(ErrorBuilderTest, CreatesErrorWithFormattedMessage) {
   auto Err = ErrorBuilder::create(std::errc::invalid_argument,
                                   "field '{0}' has value {1}", "age", 150)
                  .build();
@@ -66,7 +66,7 @@ TEST_F(ErrorBuilderTest, CreateWithFormattedMessage) {
   EXPECT_EQ(Info.Message, "field 'age' has value 150");
 }
 
-TEST_F(ErrorBuilderTest, AddPlainContext) {
+TEST_F(ErrorBuilderTest, AddsPlainContext) {
   auto Err = ErrorBuilder::create(std::errc::invalid_argument, "inner error")
                  .context("outer context")
                  .build();
@@ -77,7 +77,7 @@ TEST_F(ErrorBuilderTest, AddPlainContext) {
   EXPECT_EQ(Info.Message, "outer context\ninner error");
 }
 
-TEST_F(ErrorBuilderTest, AddMultipleArgumentContext) {
+TEST_F(ErrorBuilderTest, AddsMultipleContextLayers) {
   auto Err = ErrorBuilder::create(std::errc::invalid_argument,
                                   "expected {0} but got {1} in field '{2}'",
                                   "string", "number", "value")
@@ -93,7 +93,7 @@ TEST_F(ErrorBuilderTest, AddMultipleArgumentContext) {
                           "expected string but got number in field 'value'");
 }
 
-TEST_F(ErrorBuilderTest, AddSpecialCharacterContext) {
+TEST_F(ErrorBuilderTest, HandlesSpecialCharactersInContext) {
   auto Err = ErrorBuilder::create(std::errc::invalid_argument,
                                   "special chars: {0}", "test\nwith\nnewlines")
                  .context("tab\tseparated\tvalues")
@@ -108,7 +108,7 @@ TEST_F(ErrorBuilderTest, AddSpecialCharacterContext) {
                           "newlines");
 }
 
-TEST_F(ErrorBuilderTest, WrapExistingError) {
+TEST_F(ErrorBuilderTest, WrapsExistingError) {
   auto OriginalErr =
       createStringError(std::errc::invalid_argument, "original error message");
 
@@ -122,7 +122,7 @@ TEST_F(ErrorBuilderTest, WrapExistingError) {
   EXPECT_EQ(Info.Message, "additional context\noriginal error message");
 }
 
-TEST_F(ErrorBuilderTest, WrapMultipleErrors) {
+TEST_F(ErrorBuilderTest, WrapsMultipleJoinedErrors) {
   auto Err1 = createStringError(std::errc::invalid_argument, "first");
   auto Err2 = createStringError(std::errc::argument_list_too_long, "second");
   auto Err3 = createStringError(std::errc::filename_too_long, "third");
@@ -142,7 +142,7 @@ TEST_F(ErrorBuilderTest, WrapMultipleErrors) {
             "wrapping three joined errors\nfirst + second + third");
 }
 
-TEST_F(ErrorBuilderTest, WrapErrorWithEmptyMessage) {
+TEST_F(ErrorBuilderTest, WrapsErrorWithEmptyMessage) {
   auto EmptyErr = createStringError(std::errc::invalid_argument, "");
 
   auto WrappedErr = ErrorBuilder::wrap(std::move(EmptyErr))
@@ -157,7 +157,7 @@ TEST_F(ErrorBuilderTest, WrapErrorWithEmptyMessage) {
   EXPECT_EQ(Info.Message, "wrapping error with empty message");
 }
 
-TEST_F(ErrorBuilderTest, CreateErrorWithEmptyMessage) {
+TEST_F(ErrorBuilderTest, CreatesErrorWithEmptyMessage) {
   auto Err = ErrorBuilder::create(std::errc::invalid_argument, "")
                  .context("")
                  .context("creating error with empty message")
@@ -172,7 +172,7 @@ TEST_F(ErrorBuilderTest, CreateErrorWithEmptyMessage) {
 
 #ifndef NDEBUG
 // Death test only works in debug builds where assertions are enabled
-TEST_F(ErrorBuilderTest, WrapSuccessErrorTriggersAssertion) {
+TEST_F(ErrorBuilderTest, TriggersAssertionOnWrappingSuccessError) {
   EXPECT_DEATH(
       {
         auto SuccessErr = Error::success();

>From a3263a13a4a3caad744bc0063106ae82936f87ea Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 18 Feb 2026 15:35:33 -0800
Subject: [PATCH 08/14] Update EntityLinkerTest

---
 .../Analysis/Scalable/EntityLinkerTest.cpp         | 14 ++++----------
 1 file changed, 4 insertions(+), 10 deletions(-)

diff --git a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
index 5c77dbc367cc6..56a2c52b89981 100644
--- a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
+++ b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
@@ -73,12 +73,6 @@ class EntityLinkerTest : public TestFixture {
     getLinkageTable(TU).insert({Id, EntityLinkage(Linkage)});
     return Id;
   }
-
-  NestedBuildNamespace
-  makeLocalNamespace(const BuildNamespace &TUNamespace,
-                     const NestedBuildNamespace &LUNamespace) {
-    return NestedBuildNamespace(TUNamespace).makeQualified(LUNamespace);
-  }
 };
 
 // ============================================================================
@@ -190,7 +184,7 @@ MATCHER_P(SummaryDataHasSize, expectedSize,
   return true;
 }
 
-TEST_F(EntityLinkerTest, NoLink) {
+TEST_F(EntityLinkerTest, CreatesEmptyLinker) {
   NestedBuildNamespace LUNamespace(
       {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
 
@@ -202,7 +196,7 @@ TEST_F(EntityLinkerTest, NoLink) {
   EXPECT_EQ(getData(Output).size(), 0u);
 }
 
-TEST_F(EntityLinkerTest, EmptyLink) {
+TEST_F(EntityLinkerTest, LinksEmptyTranslationUnit) {
   NestedBuildNamespace LUNamespace(
       {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
 
@@ -219,7 +213,7 @@ TEST_F(EntityLinkerTest, EmptyLink) {
   EXPECT_EQ(getData(Output).size(), 0u);
 }
 
-TEST_F(EntityLinkerTest, NonEmptyLink) {
+TEST_F(EntityLinkerTest, LinksOneTranslationUnit) {
   NestedBuildNamespace LUNamespace(
       {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
 
@@ -329,7 +323,7 @@ TEST_F(EntityLinkerTest, NonEmptyLink) {
   }
 }
 
-TEST_F(EntityLinkerTest, TwoTULinkWithAllCombinations) {
+TEST_F(EntityLinkerTest, LinksTwoTranslationUnits) {
   NestedBuildNamespace LUNamespace(
       {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
 

>From f9bbac05f65786e51753c3bbf6f765112b2c0214 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Wed, 18 Feb 2026 19:06:48 -0800
Subject: [PATCH 09/14] More fixes

---
 .../Scalable/EntityLinker/EntityLinker.h      |  78 +++++--
 .../Analysis/Scalable/Model/BuildNamespace.h  |   1 +
 .../Analysis/Scalable/Support/ErrorBuilder.h  |  29 ++-
 .../Scalable/EntityLinker/EntityLinker.cpp    | 205 ++++++++---------
 .../Analysis/Scalable/EntityLinkerTest.cpp    | 207 ++++++++++++++++++
 .../Analysis/Scalable/ErrorBuilderTest.cpp    |   9 +
 6 files changed, 412 insertions(+), 117 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
index 2e203ee5f13d7..8592985a13cee 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
@@ -21,6 +21,7 @@
 #include "llvm/Support/Error.h"
 #include <map>
 #include <memory>
+#include <set>
 #include <vector>
 
 namespace clang::ssaf {
@@ -32,6 +33,7 @@ class TUSummaryEncoding;
 
 class EntityLinker {
   LUSummaryEncoding Output;
+  std::set<BuildNamespace> ProcessedTUNamespaces;
 
 public:
   /// Constructs an EntityLinker for a link unit.
@@ -47,8 +49,9 @@ class EntityLinker {
   /// data. The provided TU summary is consumed by this operation.
   ///
   /// \param Summary The TU summary to link. Ownership is transferred.
-  /// \returns Error if linking fails (e.g., duplicate internal entities,
-  ///          missing linkage information), success otherwise.
+  /// \returns Error if the TU namespace has already been linked, success
+  ///          otherwise. Corrupted summary data (missing linkage information,
+  ///          duplicate entity IDs) triggers a fatal error.
   llvm::Error link(std::unique_ptr<TUSummaryEncoding> Summary);
 
   /// Returns the accumulated link unit summary.
@@ -63,20 +66,67 @@ class EntityLinker {
   LUSummaryEncoding &getOutput() { return Output; }
 
 private:
-  llvm::Expected<EntityId> resolve(const EntityName &OldName,
-                                   const EntityLinkage &Linkage);
+  /// Resolves a TU entity name to an LU entity name and ID.
+  ///
+  /// Determines the appropriate namespace for the entity based on its linkage
+  /// type. Entities with None or Internal linkage are scoped to their TU,
+  /// while External linkage entities are scoped to the LU. Creates or retrieves
+  /// the corresponding EntityId in the output LinkageTable.
+  ///
+  /// For None and Internal linkage entities, duplicate insertion in the
+  /// LinkageTable triggers a fatal error (indicates corrupted data).
+  /// For External linkage entities, duplicate insertion is allowed (expected
+  /// for multiple definitions of the same entity).
+  ///
+  /// \param OldName The entity name in the TU namespace.
+  /// \param Linkage The linkage type determining namespace resolution strategy.
+  /// \returns The resolved LU EntityId.
+  EntityId resolveEntity(const EntityName &OldName,
+                         const EntityLinkage &Linkage);
+
+  /// Builds a map from each TU EntityId to its corresponding LU EntityId.
+  ///
+  /// Iterates over all entities in Summary's IdTable, looks up their linkage,
+  /// and calls resolveEntity() to obtain the LU-scoped EntityId. The resulting
+  /// map is used by merge() and patch() to translate TU IDs into LU IDs.
+  ///
+  /// Corrupted input triggers a fatal error: missing linkage for an entity in
+  /// IdTable, or a duplicate EntityId appearing under two different names.
+  ///
+  /// \param Summary The TU summary whose entities are being resolved.
+  /// \returns A map from TU EntityIds to their corresponding LU EntityIds.
+  std::map<EntityId, EntityId> resolve(TUSummaryEncoding &Summary);
 
-  llvm::Error
-  merge(std::map<SummaryName,
-                 std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
-            &InputData,
-        std::map<SummaryName,
-                 std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
-            &OutputData,
-        const EntityId OldId, const EntityId NewId,
-        const EntityLinkage &Linkage,
-        std::vector<EntitySummaryEncoding *> &PatchTargets);
+  /// Merges all summary data from a TU into the LU output.
+  ///
+  /// Iterates over every (SummaryName, EntityId, data) entry in Summary.
+  /// Each TU EntityId is translated to its LU EntityId via
+  /// EntityResolutionTable, then the data is moved into the corresponding
+  /// output map entry.
+  ///
+  /// For External linkage entities, a duplicate entry (same LU EntityId already
+  /// present for a given SummaryName) is silently dropped — first occurrence
+  /// wins. For None and Internal linkage entities, a duplicate entry indicates
+  /// corrupted data and triggers a fatal error.
+  ///
+  /// \param Summary The TU summary whose data is being merged (data is moved
+  ///        out).
+  /// \param EntityResolutionTable Map from TU EntityIds to LU EntityIds,
+  ///        as produced by resolve().
+  /// \returns Pointers to each EntitySummaryEncoding successfully inserted into
+  ///          the output, which must subsequently be patched via patch().
+  std::vector<EntitySummaryEncoding *>
+  merge(TUSummaryEncoding &Summary,
+        std::map<EntityId, EntityId> EntityResolutionTable);
 
+  /// Patches EntityId references in merged summary data.
+  ///
+  /// Calls the patch() method on each EntitySummaryEncoding that was
+  /// successfully merged into the LU output, updating all embedded EntityId
+  /// references from TU IDs to LU IDs using the provided resolution table.
+  ///
+  /// \param PatchTargets Vector of summary encodings that need patching.
+  /// \param EntityResolutionTable Map from TU EntityIds to LU EntityIds.
   void patch(std::vector<EntitySummaryEncoding *> &PatchTargets,
              const std::map<EntityId, EntityId> &EntityResolutionTable);
 };
diff --git a/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h b/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h
index 2cd8990708b8d..b94afb5f1bc0c 100644
--- a/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h
+++ b/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h
@@ -62,6 +62,7 @@ class BuildNamespace {
   bool operator!=(const BuildNamespace &Other) const;
   bool operator<(const BuildNamespace &Other) const;
 
+  friend class EntityLinker;
   friend class SerializationFormat;
   friend class TestFixture;
 };
diff --git a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
index 5f7556b63fad0..7302f53705b11 100644
--- a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
@@ -47,11 +47,14 @@ class ErrorBuilder {
     }
   }
 
+  template <typename... Args>
+  static std::string formatErrorMessage(const char *Fmt, Args &&...ArgVals) {
+    return llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str();
+  }
+
   template <typename... Args>
   void addFormattedContext(const char *Fmt, Args &&...ArgVals) {
-    std::string Message =
-        llvm::formatv(Fmt, std::forward<Args>(ArgVals)...).str();
-    pushContext(std::move(Message));
+    pushContext(formatErrorMessage(Fmt, std::forward<Args>(ArgVals)...));
   }
 
 public:
@@ -170,6 +173,26 @@ class ErrorBuilder {
   ///   //  value is 42"
   /// \endcode
   llvm::Error build() const;
+
+  /// Report a fatal error with formatted message and terminate execution.
+  ///
+  /// Combines llvm::formatv and llvm::report_fatal_error. This is a static
+  /// utility method for reporting unrecoverable errors that indicate bugs
+  /// or corrupted data.
+  ///
+  /// \param Fmt Format string for the error message (using llvm::formatv).
+  /// \param ArgVals Arguments for the format string.
+  ///
+  /// Example:
+  /// \code
+  ///   ErrorBuilder::fatal("Entity {0} with {1} linkage already exists",
+  ///                       entityId, linkageType);
+  /// \endcode
+  template <typename... Args>
+  [[noreturn]] static void fatal(const char *Fmt, Args &&...ArgVals) {
+    llvm::report_fatal_error(llvm::StringRef(
+        formatErrorMessage(Fmt, std::forward<Args>(ArgVals)...)));
+  }
 };
 
 } // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index 65b8adf7642ae..068279c99eafa 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -12,8 +12,8 @@
 #include "clang/Analysis/Scalable/Model/EntityName.h"
 #include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
 #include "llvm/Support/Error.h"
-#include "llvm/Support/ErrorHandling.h"
 #include <cassert>
+#include <system_error>
 
 using namespace clang::ssaf;
 
@@ -25,21 +25,29 @@ namespace {
 
 namespace ErrorMessages {
 
-constexpr const char *EntityIdAlreadyExistsInLinkageTable =
-    "EntityId({0}) already exists in LU linkage table";
-
-constexpr const char *FailedToMergeSummaryData =
-    "failed to merge summary data for TU EntityId({0}) resolved to LU "
-    "EntityId({1}) with linkage '{2}'";
+constexpr const char *EntityAlreadyExistsInLinkageTable =
+    "EntityLinker: Entity {0} with {1} linkage already exists in "
+    "LinkageTable - indicates corrupted data or logic bug";
 
 constexpr const char *MissingLinkageInformation =
-    "missing linkage information for TU EntityId({0})";
+    "EntityLinker: Entity {0} is missing linkage "
+    "information in TU summary - indicates corrupted TUSummary";
+
+constexpr const char *DuplicateEntityIdInTUSummary =
+    "EntityLinker: Duplicate entity ID {0} in TU summary - indicates "
+    "corrupted TUSummary with duplicate entities";
+
+constexpr const char *EntityNotFoundInResolutionTable =
+    "EntityLinker: Entity {0} not found in EntityResolutionTable - "
+    "indicates corrupted TUSummary or bug in resolve logic";
 
-constexpr const char *DuplicateEntityIdInLinking =
-    "duplicate TU EntityId({0}) encountered during linking";
+constexpr const char *FailedToInsertEntityIntoOutputSummary =
+    "EntityLinker: Failed to insert data against SummaryName({0}) for "
+    "EntityId({0}) with linkage '{1}' into - indicates corrupted data or logic "
+    "bug";
 
-constexpr const char *MergingSummaryData = "merging summary data";
-constexpr const char *LinkingTUSummary = "linking TU summary";
+constexpr const char *DuplicateTUNamespace =
+    "failed to link TU summary: duplicate namespace '{0}'";
 
 } // namespace ErrorMessages
 
@@ -60,8 +68,8 @@ resolveNamespace(const NestedBuildNamespace &LUNamespace,
   llvm_unreachable("Unhandled EntityLinkage::LinkageType variant");
 }
 
-llvm::Expected<EntityId> EntityLinker::resolve(const EntityName &OldName,
-                                               const EntityLinkage &Linkage) {
+EntityId EntityLinker::resolveEntity(const EntityName &OldName,
+                                     const EntityLinkage &Linkage) {
   NestedBuildNamespace NewNamespace = resolveNamespace(
       Output.LUNamespace, OldName.Namespace, Linkage.getLinkage());
 
@@ -73,119 +81,116 @@ llvm::Expected<EntityId> EntityLinker::resolve(const EntityName &OldName,
   // function will return the id assigned at the first insertion.
   EntityId NewId = Output.IdTable.getId(NewName);
 
-  [[maybe_unused]] auto [It, Inserted] =
-      Output.LinkageTable.try_emplace(NewId, Linkage);
-  // if (!Inserted) {
-  //   return ErrorBuilder::create(
-  //              llvm::inconvertibleErrorCode(),
-  //              ErrorMessages::EntityIdAlreadyExistsInLinkageTable,
-  //              NewId.Index)
-  //       .build();
-  // }
+  auto InsertResult = Output.LinkageTable.try_emplace(NewId, Linkage);
+  if (!InsertResult.second) {
+    // Insertion failure for None/Internal linkage is a fatal error because
+    // these entities have unique namespaces and should never collide.
+    // External linkage entities may collide (expected for duplicate
+    // definitions).
+    if (Linkage.getLinkage() == EntityLinkage::LinkageType::None ||
+        Linkage.getLinkage() == EntityLinkage::LinkageType::Internal) {
+      ErrorBuilder::fatal(ErrorMessages::EntityAlreadyExistsInLinkageTable,
+                          NewId.Index, toString(Linkage.getLinkage()));
+    }
+  }
 
   return NewId;
 }
 
-llvm::Error EntityLinker::merge(
-    std::map<SummaryName,
-             std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
-        &InputData,
-    std::map<SummaryName,
-             std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
-        &OutputData,
-    const EntityId OldId, const EntityId NewId, const EntityLinkage &Linkage,
-    std::vector<EntitySummaryEncoding *> &PatchTargets) {
-  for (auto &[Name, DataMap] : InputData) {
-    auto Iter = DataMap.find(OldId);
-    if (Iter == DataMap.end()) {
-      continue;
+std::map<EntityId, EntityId> EntityLinker::resolve(TUSummaryEncoding &Summary) {
+  std::map<EntityId, EntityId> EntityResolutionTable;
+
+  for (const auto &[OldName, OldId] : Summary.IdTable.Entities) {
+    auto Iter = Summary.LinkageTable.find(OldId);
+    if (Iter == Summary.LinkageTable.end()) {
+      ErrorBuilder::fatal(ErrorMessages::MissingLinkageInformation,
+                          OldId.Index);
+    }
+
+    const EntityLinkage &Linkage = Iter->second;
+
+    EntityId NewId = resolveEntity(OldName, Linkage);
+
+    auto InsertResult = EntityResolutionTable.insert({OldId, NewId});
+    if (!InsertResult.second) {
+      ErrorBuilder::fatal(ErrorMessages::DuplicateEntityIdInTUSummary,
+                          OldId.Index);
     }
+  }
+
+  return EntityResolutionTable;
+}
+
+std::vector<EntitySummaryEncoding *>
+EntityLinker::merge(TUSummaryEncoding &Summary,
+                    std::map<EntityId, EntityId> EntityResolutionTable) {
+  std::vector<EntitySummaryEncoding *> PatchTargets;
+
+  for (auto &[SN, DataMap] : Summary.Data) {
+    auto &OutputSummaryData = Output.Data[SN];
 
-    auto &OutputMap = OutputData[Name];
-    auto InsertResult = OutputMap.insert({NewId, std::move(Iter->second)});
+    for (auto &[OldId, ES] : DataMap) {
+
+      auto Iter = EntityResolutionTable.find(OldId);
+      if (Iter == EntityResolutionTable.end()) {
+        ErrorBuilder::fatal(ErrorMessages::EntityNotFoundInResolutionTable,
+                            OldId.Index);
+      }
+      const auto NewId = Iter->second;
+      auto InsertionResult =
+          OutputSummaryData.try_emplace(NewId, std::move(ES));
+
+      if (InsertionResult.second) {
+        PatchTargets.push_back(InsertionResult.first->second.get());
+      } else {
+        // Safe to retrieve linkage using .at since the resolve step ensures
+        // linkage information is always present for every Id.
+        auto LinkageType = Summary.LinkageTable.at(OldId).getLinkage();
 
-    // If insertion is successful, we will have to replace OldId with NewId in
-    // this EntitySummaryEncoding.
-    if (InsertResult.second) {
-      PatchTargets.push_back(InsertResult.first->second.get());
-    } else {
-      switch (Linkage.getLinkage()) {
         // Insertion should never fail for `None` and `Internal` linkage
         // entities because these entities have different namespaces even if
         // their names clash.
-      case EntityLinkage::LinkageType::None:
-      case EntityLinkage::LinkageType::Internal:
-        return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
-                                    ErrorMessages::FailedToMergeSummaryData,
-                                    OldId.Index, NewId.Index,
-                                    toString(Linkage.getLinkage()))
-            .build();
-      case EntityLinkage::LinkageType::External:
-        // Insertion is expected to fail for duplicate occurrences of `External`
-        // linkage entities. We will report these cases to help users debug
-        // potential ODR violations.
-        // TODO - issue diagnostic log for dropping data using instrumentation
-        // framework.
-        break;
+        if (LinkageType == EntityLinkage::LinkageType::None ||
+            LinkageType == EntityLinkage::LinkageType::Internal) {
+          ErrorBuilder::fatal(
+              ErrorMessages::FailedToInsertEntityIntoOutputSummary, NewId.Index,
+              toString(LinkageType));
+        }
+
+        // Insertion is expected to fail for duplicate occurrences of
+        // `External` linkage entities.
+        // TODO - report these cases in a "debug" mode to help
+        // debug potential ODR violations.
       }
     }
   }
 
-  return llvm::Error::success();
+  return PatchTargets;
 }
 
 void EntityLinker::patch(
     std::vector<EntitySummaryEncoding *> &PatchTargets,
     const std::map<EntityId, EntityId> &EntityResolutionTable) {
   for (auto *PatchTarget : PatchTargets) {
-    assert(PatchTarget && "Patch target cannot be null");
+    assert(PatchTarget && "EntityLinker::patch: Patch target cannot be null - "
+                          "indicates bug in merge logic");
     PatchTarget->patch(EntityResolutionTable);
   }
 }
 
 llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
-  std::map<EntityId, EntityId> EntityResolutionTable;
-  std::vector<EntitySummaryEncoding *> PatchTargets;
-
-  for (const auto &[OldName, OldId] : Summary->IdTable.Entities) {
-    auto Iter = Summary->LinkageTable.find(OldId);
-    if (Iter == Summary->LinkageTable.end()) {
-      return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
-                                  ErrorMessages::MissingLinkageInformation,
-                                  OldId.Index)
-          .context(ErrorMessages::LinkingTUSummary)
-          .build();
-    }
-
-    const EntityLinkage &Linkage = Iter->second;
-
-    auto NewIdOrErr = resolve(OldName, Linkage);
-    if (!NewIdOrErr) {
-      return ErrorBuilder::wrap(NewIdOrErr.takeError())
-          .context(ErrorMessages::LinkingTUSummary)
-          .build();
-    }
-
-    EntityId NewId = *NewIdOrErr;
-
-    auto InsertResult = EntityResolutionTable.insert({OldId, NewId});
-    if (!InsertResult.second) {
-      return ErrorBuilder::create(llvm::inconvertibleErrorCode(),
-                                  ErrorMessages::DuplicateEntityIdInLinking,
-                                  OldId.Index)
-          .context(ErrorMessages::LinkingTUSummary)
-          .build();
-    }
-
-    if (llvm::Error Err = merge(Summary->Data, Output.Data, OldId, NewId,
-                                Linkage, PatchTargets)) {
-      return ErrorBuilder::wrap(std::move(Err))
-          .context(ErrorMessages::MergingSummaryData)
-          .context(ErrorMessages::LinkingTUSummary)
-          .build();
-    }
+  // Check for duplicate TU namespace
+  auto [It, Inserted] = ProcessedTUNamespaces.insert(Summary->TUNamespace);
+  if (!Inserted) {
+    return ErrorBuilder::create(std::errc::invalid_argument,
+                                ErrorMessages::DuplicateTUNamespace,
+                                Summary->TUNamespace.Name)
+        .build();
   }
 
+  TUSummaryEncoding &SummaryRef = *Summary.get();
+  auto EntityResolutionTable = resolve(SummaryRef);
+  auto PatchTargets = merge(SummaryRef, EntityResolutionTable);
   patch(PatchTargets, EntityResolutionTable);
 
   return llvm::Error::success();
diff --git a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
index 56a2c52b89981..0bd2e63069580 100644
--- a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
+++ b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
@@ -653,4 +653,211 @@ TEST_F(EntityLinkerTest, LinksTwoTranslationUnits) {
   }
 }
 
+// ============================================================================
+// Fatal Error Tests
+//
+// These tests verify that corrupted TU summary data triggers fatal errors.
+// Each test constructs a TUSummaryEncoding that violates an invariant by
+// directly manipulating internal state via TestFixture accessors, then
+// asserts that link() terminates the process.
+// ============================================================================
+
+TEST_F(EntityLinkerTest, FatalOnEntityMissingLinkageInformation) {
+  // An entity that is in IdTable but has no entry in LinkageTable indicates
+  // a corrupted TUSummary and triggers a fatal error.
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+  EntityLinker Linker(LUNamespace);
+
+  auto TU = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
+
+  // Use addEntity to get a valid EntityId, then remove its linkage entry to
+  // simulate a TUSummary where the IdTable and LinkageTable are out of sync.
+  const auto Id = addEntity(*TU, "A", EntityLinkage::LinkageType::External);
+  getLinkageTable(*TU).erase(Id);
+
+  EXPECT_DEATH(
+      { (void)Linker.link(std::move(TU)); },
+      "EntityLinker: Entity .* is missing linkage information in TU summary");
+}
+
+TEST_F(EntityLinkerTest, FatalOnDuplicateEntityIdInTUSummary) {
+  // Two different EntityNames mapping to the same EntityId indicates corrupted
+  // TUSummary data and triggers a fatal error.
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+  EntityLinker Linker(LUNamespace);
+
+  auto TU = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
+
+  // Insert first entity normally.
+  const auto Id = addEntity(*TU, "A", EntityLinkage::LinkageType::External);
+
+  // Directly insert a second EntityName that maps to the same EntityId,
+  // bypassing the normal getId() uniqueness guarantee.
+  NestedBuildNamespace TUNested(getTUNamespace(*TU));
+  EntityName SecondName("B", "", TUNested);
+  getEntities(getIdTable(*TU)).insert({SecondName, Id});
+
+  EXPECT_DEATH(
+      { (void)Linker.link(std::move(TU)); },
+      "EntityLinker: Duplicate entity ID .* in TU summary");
+}
+
+TEST_F(EntityLinkerTest, FatalOnEntityNotFoundInResolutionTable) {
+  // Summary data that references an EntityId not present in
+  // IdTable/LinkageTable will not appear in the resolution table, triggering a
+  // fatal error in merge.
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+  EntityLinker Linker(LUNamespace);
+
+  auto TU = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
+
+  // Register one entity normally so resolution succeeds for it.
+  addEntity(*TU, "A", EntityLinkage::LinkageType::External);
+
+  // Obtain a second EntityId from a separate table — it will never appear in
+  // the TU's resolution table because it is not in TU's IdTable or
+  // LinkageTable.
+  EntityIdTable AuxTable;
+  NestedBuildNamespace TUNested(getTUNamespace(*TU));
+  EntityName AuxName("B", "", TUNested);
+  const auto OrphanId = AuxTable.getId(AuxName);
+
+  // Insert summary data keyed on the orphan ID.
+  SummaryName SN("S1");
+  getData(*TU)[SN][OrphanId] = std::make_unique<MockEntitySummaryEncoding>();
+
+  EXPECT_DEATH(
+      { (void)Linker.link(std::move(TU)); },
+      "EntityLinker: Entity .* not found in EntityResolutionTable");
+}
+
+TEST_F(EntityLinkerTest, FatalOnEntityAlreadyExistsInLinkageTableForNone) {
+  // If the output LinkageTable already contains the LU EntityId that a None
+  // linkage entity resolves to, a fatal error is triggered. This can only
+  // happen due to data corruption or a bug in resolve logic, so we simulate
+  // it by pre-populating the output.
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+  EntityLinker Linker(LUNamespace);
+
+  // Link a first TU so the output already has an entry for entity "A"/None.
+  auto TU1 = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
+  addEntity(*TU1, "A", EntityLinkage::LinkageType::None);
+  const BuildNamespace TUNamespace = getTUNamespace(*TU1);
+  ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+  // Determine the LU EntityId that was assigned to A.
+  NestedBuildNamespace LocalNS =
+      NestedBuildNamespace(TUNamespace).makeQualified(LUNamespace);
+  EntityName LU_A_Name("A", "", LocalNS);
+  const auto LU_A_Id =
+      getEntities(getIdTable(Linker.getOutput())).at(LU_A_Name);
+
+  // Inject the resolved LU name -> LU_A_Id mapping into the output IdTable so
+  // that getId() returns LU_A_Id when TU2's "A"/None entity is resolved. Since
+  // LU_A_Id is already in OutLinkage (from TU1), the try_emplace in
+  // resolveEntity() will fail, triggering the fatal.
+  auto &OutEntities = getEntities(getIdTable(Linker.getOutput()));
+  auto &OutLinkage = getLinkageTable(Linker.getOutput());
+
+  // Inject a second name -> same id mapping in the output IdTable so the next
+  // getId() call returns LU_A_Id for a new name that a fresh TU entity resolves
+  // to — and the LinkageTable already has that id, triggering the fatal.
+  NestedBuildNamespace FakeTUNested(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "TU2"));
+  NestedBuildNamespace FakeLocalNS = FakeTUNested.makeQualified(LUNamespace);
+  EntityName FakeA("A", "", FakeLocalNS);
+  OutEntities.insert({FakeA, LU_A_Id});
+  // LU_A_Id is already in OutLinkage from linking TU1 with None linkage.
+  ASSERT_NE(OutLinkage.find(LU_A_Id), OutLinkage.end());
+
+  // Now link a TU2 whose "A" entity resolves to FakeLocalNS, which maps to
+  // LU_A_Id (already in LinkageTable) — triggering the fatal.
+  auto TU2 =
+      createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU2");
+  addEntity(*TU2, "A", EntityLinkage::LinkageType::None);
+
+  EXPECT_DEATH(
+      { (void)Linker.link(std::move(TU2)); },
+      "EntityLinker: Entity .* with .* linkage already exists in LinkageTable");
+}
+
+TEST_F(EntityLinkerTest, FatalOnInternalEntityAlreadyExistsInLinkageTable) {
+  // Same as above but for Internal linkage.
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+  EntityLinker Linker(LUNamespace);
+
+  auto TU1 = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
+  addEntity(*TU1, "A", EntityLinkage::LinkageType::Internal);
+  const BuildNamespace TUNamespace = getTUNamespace(*TU1);
+  ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
+
+  NestedBuildNamespace LocalNS =
+      NestedBuildNamespace(TUNamespace).makeQualified(LUNamespace);
+  EntityName LU_A_Name("A", "", LocalNS);
+  const auto LU_A_Id =
+      getEntities(getIdTable(Linker.getOutput())).at(LU_A_Name);
+
+  auto &OutEntities = getEntities(getIdTable(Linker.getOutput()));
+  auto &OutLinkage = getLinkageTable(Linker.getOutput());
+
+  NestedBuildNamespace FakeTUNested(
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "TU2"));
+  NestedBuildNamespace FakeLocalNS = FakeTUNested.makeQualified(LUNamespace);
+  EntityName FakeA("A", "", FakeLocalNS);
+  OutEntities.insert({FakeA, LU_A_Id});
+  ASSERT_NE(OutLinkage.find(LU_A_Id), OutLinkage.end());
+
+  auto TU2 =
+      createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU2");
+  addEntity(*TU2, "A", EntityLinkage::LinkageType::Internal);
+
+  EXPECT_DEATH(
+      { (void)Linker.link(std::move(TU2)); },
+      "EntityLinker: Entity .* with .* linkage already exists in LinkageTable");
+}
+
+TEST_F(EntityLinkerTest,
+       FatalOnFailedInsertNoneLinkageEntityIntoOutputSummary) {
+  // If a None linkage entity's summary data cannot be inserted into the output
+  // (because its LU EntityId is already present for the same SummaryName), a
+  // fatal error is triggered. We simulate this by pre-populating the output's
+  // data map with the target LU EntityId while keeping that ID absent from
+  // OutLinkage, so resolveEntity() succeeds (first insertion into LinkageTable)
+  // but merge() then finds the data slot already occupied.
+  NestedBuildNamespace LUNamespace(
+      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
+  EntityLinker Linker(LUNamespace);
+
+  // Compute the LU namespace that TU2's None entity "A" will resolve to.
+  BuildNamespace TU2NS(BuildNamespaceKind::CompilationUnit, "TU2");
+  NestedBuildNamespace TU2LocalNS =
+      NestedBuildNamespace(TU2NS).makeQualified(LUNamespace);
+  EntityName LU_A_Name("A", "", TU2LocalNS);
+
+  // Allocate the LU EntityId for A by registering the name in the output
+  // IdTable, then pre-insert summary data for it — but do NOT insert into
+  // LinkageTable, so resolveEntity's try_emplace will succeed.
+  const auto LU_A_Id = getIdTable(Linker.getOutput()).getId(LU_A_Name);
+  SummaryName SN("S1");
+  getData(Linker.getOutput())[SN].try_emplace(
+      LU_A_Id, std::make_unique<MockEntitySummaryEncoding>());
+
+  // Link TU2: its "A"/None entity resolves to LU_A_Id (already in IdTable),
+  // resolveEntity inserts it into LinkageTable (succeeds), then merge fails
+  // because Data[S1][LU_A_Id] is already occupied.
+  auto TU2 =
+      createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU2");
+  const auto TU2_A_Id = addEntity(*TU2, "A", EntityLinkage::LinkageType::None);
+  addSummaryData(*TU2, TU2_A_Id, "S1");
+
+  EXPECT_DEATH(
+      { (void)Linker.link(std::move(TU2)); },
+      "EntityLinker: Failed to insert data against SummaryName");
+}
+
 } // namespace
diff --git a/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp b/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
index 3a867a2b8f0ce..79e197a0b0f51 100644
--- a/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
+++ b/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
@@ -182,6 +182,15 @@ TEST_F(ErrorBuilderTest, TriggersAssertionOnWrappingSuccessError) {
 }
 #endif // !NDEBUG
 
+TEST_F(ErrorBuilderTest, FatalTerminatesExecution) {
+  EXPECT_DEATH(
+      {
+        ErrorBuilder::fatal("Entity {0} with {1} linkage already exists", 42,
+                            "Internal");
+      },
+      "Entity 42 with Internal linkage already exists");
+}
+
 } // namespace
 
 } // namespace clang::ssaf

>From dade9a1c010c13ec32c0f4db6925f55b7fe2f211 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 19 Feb 2026 14:30:05 -0800
Subject: [PATCH 10/14] More fixes

---
 .../Scalable/EntityLinker/EntityLinker.h      |  80 ++---
 .../Analysis/Scalable/Model/EntityIdTable.h   |   1 -
 .../Analysis/Scalable/Model/EntityName.h      |   5 +-
 .../Analysis/Scalable/Support/ErrorBuilder.h  |   1 +
 .../Scalable/EntityLinker/EntityLinker.cpp    |  87 ++---
 .../Scalable/Support/ErrorBuilder.cpp         |  22 +-
 .../Analysis/Scalable/EntityLinkerTest.cpp    | 301 ++++--------------
 7 files changed, 132 insertions(+), 365 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
index 8592985a13cee..d75cde073d600 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
@@ -15,9 +15,6 @@
 #define LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_ENTITYLINKER_H
 
 #include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
-#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
-#include "clang/Analysis/Scalable/Model/EntityId.h"
-#include "clang/Analysis/Scalable/Model/SummaryName.h"
 #include "llvm/Support/Error.h"
 #include <map>
 #include <memory>
@@ -26,6 +23,8 @@
 
 namespace clang::ssaf {
 
+class BuildNamespace;
+class EntityId;
 class EntityLinkage;
 class EntityName;
 class EntitySummaryEncoding;
@@ -36,101 +35,62 @@ class EntityLinker {
   std::set<BuildNamespace> ProcessedTUNamespaces;
 
 public:
-  /// Constructs an EntityLinker for a link unit.
+  /// Constructs an EntityLinker to link TU summaries into a LU summary.
   ///
   /// \param LUNamespace The namespace identifying this link unit.
   EntityLinker(NestedBuildNamespace LUNamespace)
       : Output(std::move(LUNamespace)) {}
 
-  /// Links a translation unit summary into the link unit summary.
+  /// Links a TU summary into a LU summary.
   ///
-  /// Processes entity names, resolves namespace conflicts based on linkage,
-  /// deduplicates entities, and patches entity ID references in the summary
-  /// data. The provided TU summary is consumed by this operation.
+  /// Deduplicates entities, patches entity ID references in the entity summary,
+  /// and merges them into a single data store. The provided TU summary is
+  /// consumed by this operation.
   ///
   /// \param Summary The TU summary to link. Ownership is transferred.
   /// \returns Error if the TU namespace has already been linked, success
   ///          otherwise. Corrupted summary data (missing linkage information,
-  ///          duplicate entity IDs) triggers a fatal error.
+  ///          duplicate entity IDs, etc.) triggers a fatal error.
   llvm::Error link(std::unique_ptr<TUSummaryEncoding> Summary);
 
-  /// Returns the accumulated link unit summary.
+  /// Returns the accumulated LU summary.
   ///
-  /// \returns A const reference to the linked output containing all
-  ///          deduplicated and patched entity summaries.
+  /// \returns LU summary containing all the deduplicated and patched entity
+  /// summaries.
   const LUSummaryEncoding &getOutput() const { return Output; }
 
-  /// Returns the accumulated link unit summary.
-  ///
-  /// \returns A mutable reference to the linked output.
-  LUSummaryEncoding &getOutput() { return Output; }
-
 private:
   /// Resolves a TU entity name to an LU entity name and ID.
   ///
-  /// Determines the appropriate namespace for the entity based on its linkage
-  /// type. Entities with None or Internal linkage are scoped to their TU,
-  /// while External linkage entities are scoped to the LU. Creates or retrieves
-  /// the corresponding EntityId in the output LinkageTable.
-  ///
-  /// For None and Internal linkage entities, duplicate insertion in the
-  /// LinkageTable triggers a fatal error (indicates corrupted data).
-  /// For External linkage entities, duplicate insertion is allowed (expected
-  /// for multiple definitions of the same entity).
-  ///
   /// \param OldName The entity name in the TU namespace.
-  /// \param Linkage The linkage type determining namespace resolution strategy.
+  /// \param Linkage The linkage determining namespace resolution strategy.
   /// \returns The resolved LU EntityId.
   EntityId resolveEntity(const EntityName &OldName,
                          const EntityLinkage &Linkage);
 
-  /// Builds a map from each TU EntityId to its corresponding LU EntityId.
-  ///
-  /// Iterates over all entities in Summary's IdTable, looks up their linkage,
-  /// and calls resolveEntity() to obtain the LU-scoped EntityId. The resulting
-  /// map is used by merge() and patch() to translate TU IDs into LU IDs.
-  ///
-  /// Corrupted input triggers a fatal error: missing linkage for an entity in
-  /// IdTable, or a duplicate EntityId appearing under two different names.
+  /// Resolves each TU EntityId to its corresponding LU EntityId.
   ///
   /// \param Summary The TU summary whose entities are being resolved.
   /// \returns A map from TU EntityIds to their corresponding LU EntityIds.
   std::map<EntityId, EntityId> resolve(TUSummaryEncoding &Summary);
 
-  /// Merges all summary data from a TU into the LU output.
-  ///
-  /// Iterates over every (SummaryName, EntityId, data) entry in Summary.
-  /// Each TU EntityId is translated to its LU EntityId via
-  /// EntityResolutionTable, then the data is moved into the corresponding
-  /// output map entry.
-  ///
-  /// For External linkage entities, a duplicate entry (same LU EntityId already
-  /// present for a given SummaryName) is silently dropped — first occurrence
-  /// wins. For None and Internal linkage entities, a duplicate entry indicates
-  /// corrupted data and triggers a fatal error.
+  /// Merges all summary data from a TU summary into the LU Summary.
   ///
-  /// \param Summary The TU summary whose data is being merged (data is moved
-  ///        out).
-  /// \param EntityResolutionTable Map from TU EntityIds to LU EntityIds,
-  ///        as produced by resolve().
-  /// \returns Pointers to each EntitySummaryEncoding successfully inserted into
-  ///          the output, which must subsequently be patched via patch().
+  /// \param Summary The TU summary whose data is being merged.
+  /// \param EntityResolutionTable Map from TU EntityIds to LU EntityIds.
+  /// \returns Pointers to each EntitySummaryEncoding successfully merged.
   std::vector<EntitySummaryEncoding *>
   merge(TUSummaryEncoding &Summary,
-        std::map<EntityId, EntityId> EntityResolutionTable);
+        const std::map<EntityId, EntityId> &EntityResolutionTable);
 
   /// Patches EntityId references in merged summary data.
   ///
-  /// Calls the patch() method on each EntitySummaryEncoding that was
-  /// successfully merged into the LU output, updating all embedded EntityId
-  /// references from TU IDs to LU IDs using the provided resolution table.
-  ///
   /// \param PatchTargets Vector of summary encodings that need patching.
   /// \param EntityResolutionTable Map from TU EntityIds to LU EntityIds.
-  void patch(std::vector<EntitySummaryEncoding *> &PatchTargets,
+  void patch(const std::vector<EntitySummaryEncoding *> &PatchTargets,
              const std::map<EntityId, EntityId> &EntityResolutionTable);
 };
 
-} // end namespace clang::ssaf
+} // namespace clang::ssaf
 
 #endif // LLVM_CLANG_ANALYSIS_SCALABLE_ENTITYLINKER_ENTITYLINKER_H
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
index 631116cad408a..6c5f27907adb4 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
@@ -21,7 +21,6 @@ namespace clang::ssaf {
 /// The table maps each unique EntityName to exactly one EntityId.
 /// Entities are never removed.
 class EntityIdTable {
-  friend class EntityLinker;
   friend class SerializationFormat;
   friend class TestFixture;
 
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityName.h b/clang/include/clang/Analysis/Scalable/Model/EntityName.h
index 512dc03ff4ef9..0f653411fe244 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityName.h
@@ -26,8 +26,8 @@ namespace clang::ssaf {
 /// such as USRs.
 class EntityName {
   friend class EntityLinker;
-  friend class LinkUnitResolution;
   friend class SerializationFormat;
+  friend class TestFixture;
 
   std::string USR;
   llvm::SmallString<16> Suffix;
@@ -50,9 +50,6 @@ class EntityName {
   ///
   /// \param Namespace The namespace steps to append to this entity's namespace.
   EntityName makeQualified(NestedBuildNamespace Namespace) const;
-
-  friend class SerializationFormat;
-  friend class TestFixture;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
index 7302f53705b11..c7e2a62f7c064 100644
--- a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
@@ -16,6 +16,7 @@
 
 #include "llvm/Support/Error.h"
 #include "llvm/Support/FormatVariadic.h"
+#include <optional>
 #include <string>
 #include <system_error>
 #include <vector>
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index 068279c99eafa..442bfde98b937 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -11,40 +11,36 @@
 #include "clang/Analysis/Scalable/Model/EntityLinkage.h"
 #include "clang/Analysis/Scalable/Model/EntityName.h"
 #include "clang/Analysis/Scalable/Support/ErrorBuilder.h"
-#include "llvm/Support/Error.h"
 #include <cassert>
-#include <system_error>
 
 using namespace clang::ssaf;
 
-//----------------------------------------------------------------------------
+//===----------------------------------------------------------------------===//
 // Error Message Constants
-//----------------------------------------------------------------------------
+//===----------------------------------------------------------------------===//
 
 namespace {
 
 namespace ErrorMessages {
 
+constexpr const char *EntityLinkerFatalErrorPrefix =
+    "EntityLinker: Corrupted TUSummary or logic bug";
+
 constexpr const char *EntityAlreadyExistsInLinkageTable =
-    "EntityLinker: Entity {0} with {1} linkage already exists in "
-    "LinkageTable - indicates corrupted data or logic bug";
+    "{0} - EntityId '{1}' with LinkageType '{2}' already exists in LUSummary";
 
 constexpr const char *MissingLinkageInformation =
-    "EntityLinker: Entity {0} is missing linkage "
-    "information in TU summary - indicates corrupted TUSummary";
+    "{0} - EntityId '{1}' missing linkage information in TUSummary";
 
 constexpr const char *DuplicateEntityIdInTUSummary =
-    "EntityLinker: Duplicate entity ID {0} in TU summary - indicates "
-    "corrupted TUSummary with duplicate entities";
+    "{0} - Duplicate EntityID '{1}' in EntityResolutionTable";
 
 constexpr const char *EntityNotFoundInResolutionTable =
-    "EntityLinker: Entity {0} not found in EntityResolutionTable - "
-    "indicates corrupted TUSummary or bug in resolve logic";
+    "{0} - EntityId '{1}' not found in EntityResolutionTable";
 
 constexpr const char *FailedToInsertEntityIntoOutputSummary =
-    "EntityLinker: Failed to insert data against SummaryName({0}) for "
-    "EntityId({0}) with linkage '{1}' into - indicates corrupted data or logic "
-    "bug";
+    "{0} - Failed to insert data for EntityId '{1}' with LinkageType '{2}' "
+    "against SummaryName '{3}' to LUSummary";
 
 constexpr const char *DuplicateTUNamespace =
     "failed to link TU summary: duplicate namespace '{0}'";
@@ -53,7 +49,9 @@ constexpr const char *DuplicateTUNamespace =
 
 } // namespace
 
-static NestedBuildNamespace
+namespace {
+
+NestedBuildNamespace
 resolveNamespace(const NestedBuildNamespace &LUNamespace,
                  const NestedBuildNamespace &EntityNamespace,
                  const EntityLinkage::LinkageType Linkage) {
@@ -68,6 +66,8 @@ resolveNamespace(const NestedBuildNamespace &LUNamespace,
   llvm_unreachable("Unhandled EntityLinkage::LinkageType variant");
 }
 
+} // namespace
+
 EntityId EntityLinker::resolveEntity(const EntityName &OldName,
                                      const EntityLinkage &Linkage) {
   NestedBuildNamespace NewNamespace = resolveNamespace(
@@ -81,15 +81,15 @@ EntityId EntityLinker::resolveEntity(const EntityName &OldName,
   // function will return the id assigned at the first insertion.
   EntityId NewId = Output.IdTable.getId(NewName);
 
-  auto InsertResult = Output.LinkageTable.try_emplace(NewId, Linkage);
-  if (!InsertResult.second) {
-    // Insertion failure for None/Internal linkage is a fatal error because
-    // these entities have unique namespaces and should never collide.
-    // External linkage entities may collide (expected for duplicate
-    // definitions).
+  auto InsertionResult = Output.LinkageTable.try_emplace(NewId, Linkage);
+  if (!InsertionResult.second) {
+    // Insertion failure for `None` and `Internal` linkage is a fatal error
+    // because these entities have unique namespaces and should never collide.
+    // `External` linkage entities may collide.
     if (Linkage.getLinkage() == EntityLinkage::LinkageType::None ||
         Linkage.getLinkage() == EntityLinkage::LinkageType::Internal) {
       ErrorBuilder::fatal(ErrorMessages::EntityAlreadyExistsInLinkageTable,
+                          ErrorMessages::EntityLinkerFatalErrorPrefix,
                           NewId.Index, toString(Linkage.getLinkage()));
     }
   }
@@ -100,10 +100,11 @@ EntityId EntityLinker::resolveEntity(const EntityName &OldName,
 std::map<EntityId, EntityId> EntityLinker::resolve(TUSummaryEncoding &Summary) {
   std::map<EntityId, EntityId> EntityResolutionTable;
 
-  for (const auto &[OldName, OldId] : Summary.IdTable.Entities) {
+  Summary.IdTable.forEach([&](const EntityName &OldName, const EntityId OldId) {
     auto Iter = Summary.LinkageTable.find(OldId);
     if (Iter == Summary.LinkageTable.end()) {
       ErrorBuilder::fatal(ErrorMessages::MissingLinkageInformation,
+                          ErrorMessages::EntityLinkerFatalErrorPrefix,
                           OldId.Index);
     }
 
@@ -111,32 +112,35 @@ std::map<EntityId, EntityId> EntityLinker::resolve(TUSummaryEncoding &Summary) {
 
     EntityId NewId = resolveEntity(OldName, Linkage);
 
-    auto InsertResult = EntityResolutionTable.insert({OldId, NewId});
-    if (!InsertResult.second) {
+    auto InsertionResult = EntityResolutionTable.insert({OldId, NewId});
+    if (!InsertionResult.second) {
       ErrorBuilder::fatal(ErrorMessages::DuplicateEntityIdInTUSummary,
+                          ErrorMessages::EntityLinkerFatalErrorPrefix,
                           OldId.Index);
     }
-  }
+  });
 
   return EntityResolutionTable;
 }
 
 std::vector<EntitySummaryEncoding *>
 EntityLinker::merge(TUSummaryEncoding &Summary,
-                    std::map<EntityId, EntityId> EntityResolutionTable) {
+                    const std::map<EntityId, EntityId> &EntityResolutionTable) {
   std::vector<EntitySummaryEncoding *> PatchTargets;
 
   for (auto &[SN, DataMap] : Summary.Data) {
     auto &OutputSummaryData = Output.Data[SN];
 
     for (auto &[OldId, ES] : DataMap) {
-
       auto Iter = EntityResolutionTable.find(OldId);
       if (Iter == EntityResolutionTable.end()) {
         ErrorBuilder::fatal(ErrorMessages::EntityNotFoundInResolutionTable,
+                            ErrorMessages::EntityLinkerFatalErrorPrefix,
                             OldId.Index);
       }
+
       const auto NewId = Iter->second;
+
       auto InsertionResult =
           OutputSummaryData.try_emplace(NewId, std::move(ES));
 
@@ -144,22 +148,22 @@ EntityLinker::merge(TUSummaryEncoding &Summary,
         PatchTargets.push_back(InsertionResult.first->second.get());
       } else {
         // Safe to retrieve linkage using .at since the resolve step ensures
-        // linkage information is always present for every Id.
+        // linkage information is always present for every OldId.
         auto LinkageType = Summary.LinkageTable.at(OldId).getLinkage();
 
         // Insertion should never fail for `None` and `Internal` linkage
-        // entities because these entities have different namespaces even if
-        // their names clash.
+        // entities because these entities will have different namespaces across
+        // TUs even if their names match.
         if (LinkageType == EntityLinkage::LinkageType::None ||
             LinkageType == EntityLinkage::LinkageType::Internal) {
           ErrorBuilder::fatal(
-              ErrorMessages::FailedToInsertEntityIntoOutputSummary, NewId.Index,
-              toString(LinkageType));
+              ErrorMessages::FailedToInsertEntityIntoOutputSummary,
+              ErrorMessages::EntityLinkerFatalErrorPrefix, NewId.Index,
+              toString(LinkageType), SN.str());
         }
 
-        // Insertion is expected to fail for duplicate occurrences of
-        // `External` linkage entities.
-        // TODO - report these cases in a "debug" mode to help
+        // Insertion is expected to fail for duplicate occurrences of `External`
+        // linkage entities. TODO - report these cases in a "debug" mode to help
         // debug potential ODR violations.
       }
     }
@@ -169,19 +173,17 @@ EntityLinker::merge(TUSummaryEncoding &Summary,
 }
 
 void EntityLinker::patch(
-    std::vector<EntitySummaryEncoding *> &PatchTargets,
+    const std::vector<EntitySummaryEncoding *> &PatchTargets,
     const std::map<EntityId, EntityId> &EntityResolutionTable) {
   for (auto *PatchTarget : PatchTargets) {
-    assert(PatchTarget && "EntityLinker::patch: Patch target cannot be null - "
-                          "indicates bug in merge logic");
+    assert(PatchTarget && "EntityLinker::patch: Patch target cannot be null");
     PatchTarget->patch(EntityResolutionTable);
   }
 }
 
 llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
-  // Check for duplicate TU namespace
-  auto [It, Inserted] = ProcessedTUNamespaces.insert(Summary->TUNamespace);
-  if (!Inserted) {
+  auto InsertionResult = ProcessedTUNamespaces.insert(Summary->TUNamespace);
+  if (!InsertionResult.second) {
     return ErrorBuilder::create(std::errc::invalid_argument,
                                 ErrorMessages::DuplicateTUNamespace,
                                 Summary->TUNamespace.Name)
@@ -189,6 +191,7 @@ llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
   }
 
   TUSummaryEncoding &SummaryRef = *Summary.get();
+
   auto EntityResolutionTable = resolve(SummaryRef);
   auto PatchTargets = merge(SummaryRef, EntityResolutionTable);
   patch(PatchTargets, EntityResolutionTable);
diff --git a/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
index 2280a4b674595..5ee4955adedd7 100644
--- a/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
+++ b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
@@ -21,29 +21,27 @@ ErrorBuilder ErrorBuilder::wrap(llvm::Error E) {
       E &&
       "Cannot wrap a success error - check for success before calling wrap()");
 
-  std::error_code EC;
-  bool FirstError = true;
+  std::optional<std::error_code> EC;
   std::vector<std::string> Messages;
-  ErrorBuilder Builder(std::make_error_code(std::errc::invalid_argument));
 
   llvm::handleAllErrors(std::move(E), [&](const llvm::ErrorInfoBase &EI) {
     // Capture error code from the first error only.
-    if (FirstError) {
+    if (!EC)
       EC = EI.convertToErrorCode();
-      Builder.Code = EC;
-      FirstError = false;
-    }
 
     // Collect messages from all errors.
     std::string ErrorMsg = EI.message();
-    if (!ErrorMsg.empty()) {
+    if (!ErrorMsg.empty())
       Messages.push_back(std::move(ErrorMsg));
-    }
   });
 
-  // Combine all messages with " + " and push as a single context entry
-  std::string CombinedMsg = llvm::join(Messages, ErrorSeparator);
-  Builder.pushContext(std::move(CombinedMsg));
+  assert(EC && "wrap() called with a non-success error but no handler fired - "
+               "indicates a bug in handleAllErrors");
+
+  ErrorBuilder Builder(*EC);
+
+  // Combine all messages with " + " and push as a single context entry.
+  Builder.pushContext(llvm::join(Messages, ErrorSeparator));
 
   return Builder;
 }
diff --git a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
index 0bd2e63069580..9d4a62bfe6a04 100644
--- a/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
+++ b/clang/unittests/Analysis/Scalable/EntityLinkerTest.cpp
@@ -2,7 +2,7 @@
 //
 // 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.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //===----------------------------------------------------------------------===//
 
@@ -18,11 +18,14 @@
 #include "clang/Analysis/Scalable/Model/EntityName.h"
 #include "clang/Analysis/Scalable/Model/SummaryName.h"
 #include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <memory>
 
 using namespace clang::ssaf;
 using namespace llvm;
+using ::testing::HasSubstr;
+using ::testing::PrintToString;
 
 namespace {
 
@@ -52,6 +55,12 @@ size_t MockEntitySummaryEncoding::Index = 0;
 
 class EntityLinkerTest : public TestFixture {
 protected:
+  void SetUp() override {
+    // This ensures that the MockEntitySummary id assignment does not
+    // accidentally depend on test execution order.
+    MockEntitySummaryEncoding::Index = 0;
+  }
+
   std::unique_ptr<TUSummaryEncoding>
   createTUSummaryEncoding(BuildNamespaceKind Kind, llvm::StringRef Name) {
     return std::make_unique<TUSummaryEncoding>(BuildNamespace(Kind, Name));
@@ -79,17 +88,15 @@ class EntityLinkerTest : public TestFixture {
 // Entity ID Table Matchers
 // ============================================================================
 
-MATCHER_P(ContainsEntity, entityName,
+MATCHER_P(ContainsEntity, EntityName,
           std::string(negation ? "does not contain" : "contains") +
-              " entity with name '" + ::testing::PrintToString(entityName) +
-              "'") {
-  return arg.contains(entityName);
+              " entity '" + PrintToString(EntityName) + "'") {
+  return arg.contains(EntityName);
 }
 
-MATCHER_P(IdTableHasSize, expectedCount,
-          std::string("has ") + ::testing::PrintToString(expectedCount) +
-              " entities") {
-  if (arg.count() != expectedCount) {
+MATCHER_P(IdTableHasSize, ExpectedCount,
+          std::string("has ") + PrintToString(ExpectedCount) + " entities") {
+  if (arg.count() != ExpectedCount) {
     *result_listener << "has " << arg.count() << " entities";
     return false;
   }
@@ -100,31 +107,30 @@ MATCHER_P(IdTableHasSize, expectedCount,
 // Linkage Table Matchers
 // ============================================================================
 
-MATCHER_P2(EntityHasLinkage, entityId, expectedLinkage,
-           std::string("entity has ") +
-               ::testing::PrintToString(expectedLinkage) + " linkage") {
-  auto it = arg.find(entityId);
-  if (it == arg.end()) {
-    *result_listener << "entity " << ::testing::PrintToString(entityId)
+MATCHER_P2(EntityHasLinkage, EId, ExpectedLinkage,
+           std::string("entity has ") + PrintToString(ExpectedLinkage) +
+               " linkage") {
+  auto It = arg.find(EId);
+  if (It == arg.end()) {
+    *result_listener << "entity " << PrintToString(EId)
                      << " not found in linkage table";
     return false;
   }
 
-  auto actualLinkage = it->second.getLinkage();
-  if (actualLinkage != expectedLinkage) {
-    *result_listener << "entity " << ::testing::PrintToString(entityId)
-                     << " has linkage "
-                     << ::testing::PrintToString(actualLinkage);
+  auto ActualLinkage = It->second.getLinkage();
+  if (ActualLinkage != ExpectedLinkage) {
+    *result_listener << "entity " << PrintToString(EId) << " has linkage "
+                     << PrintToString(ActualLinkage);
     return false;
   }
 
   return true;
 }
 
-MATCHER_P(LinkageTableHasSize, expectedSize,
+MATCHER_P(LinkageTableHasSize, ExpectedSize,
           std::string("linkage table has size ") +
-              ::testing::PrintToString(expectedSize)) {
-  if (arg.size() != expectedSize) {
+              PrintToString(ExpectedSize)) {
+  if (arg.size() != ExpectedSize) {
     *result_listener << "has size " << arg.size();
     return false;
   }
@@ -135,28 +141,27 @@ MATCHER_P(LinkageTableHasSize, expectedSize,
 // Summary Data Matchers
 // ============================================================================
 
-MATCHER_P3(HasSummaryData, entityId, expectedMockId, expectedResolutionMapping,
+MATCHER_P3(HasSummaryData, EId, ExpectedMockId, ExpectedResolutionMapping,
            std::string("has summary data for entity with expected mock ID ") +
-               ::testing::PrintToString(expectedMockId)) {
+               PrintToString(ExpectedMockId)) {
 
-  auto it = arg.find(entityId);
-  if (it == arg.end()) {
-    *result_listener << "entity " << ::testing::PrintToString(entityId)
+  auto It = arg.find(EId);
+  if (It == arg.end()) {
+    *result_listener << "entity " << PrintToString(EId)
                      << " not found in summary data";
     return false;
   }
 
-  auto *mock = static_cast<const MockEntitySummaryEncoding *>(it->second.get());
+  auto *Mock = static_cast<const MockEntitySummaryEncoding *>(It->second.get());
 
-  if (mock->getId() != expectedMockId) {
-    *result_listener << "entity " << ::testing::PrintToString(entityId)
-                     << " has mock ID " << mock->getId() << " (expected "
-                     << expectedMockId << ")";
+  if (Mock->getId() != ExpectedMockId) {
+    *result_listener << "entity " << PrintToString(EId) << " has mock ID "
+                     << Mock->getId() << " (expected " << ExpectedMockId << ")";
     return false;
   }
 
-  if (mock->getPatchedIds() != expectedResolutionMapping) {
-    *result_listener << "entity " << ::testing::PrintToString(entityId)
+  if (Mock->getPatchedIds() != ExpectedResolutionMapping) {
+    *result_listener << "entity " << PrintToString(EId)
                      << " has different resolution mapping";
     return false;
   }
@@ -164,26 +169,19 @@ MATCHER_P3(HasSummaryData, entityId, expectedMockId, expectedResolutionMapping,
   return true;
 }
 
-MATCHER_P(DoesNotContainSummaryFor, entityId,
-          std::string("does not contain summary data for entity")) {
-  if (arg.find(entityId) != arg.end()) {
-    *result_listener << "unexpectedly contains entity "
-                     << ::testing::PrintToString(entityId);
-    return false;
-  }
-  return true;
-}
-
-MATCHER_P(SummaryDataHasSize, expectedSize,
-          std::string("summary data has size ") +
-              ::testing::PrintToString(expectedSize)) {
-  if (arg.size() != expectedSize) {
+MATCHER_P(SummaryDataHasSize, ExpectedSize,
+          std::string("summary data has size ") + PrintToString(ExpectedSize)) {
+  if (arg.size() != ExpectedSize) {
     *result_listener << "has size " << arg.size();
     return false;
   }
   return true;
 }
 
+// ============================================================================
+// ENTITY LINKER TESTS
+// ============================================================================
+
 TEST_F(EntityLinkerTest, CreatesEmptyLinker) {
   NestedBuildNamespace LUNamespace(
       {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
@@ -306,7 +304,7 @@ TEST_F(EntityLinkerTest, LinksOneTranslationUnit) {
       ASSERT_THAT(S1Data, SummaryDataHasSize(3u));
       ASSERT_THAT(S1Data, HasSummaryData(LU_A_Id, TU_A_S1_Data, Resolution));
       ASSERT_THAT(S1Data, HasSummaryData(LU_B_Id, TU_B_S1_Data, Resolution));
-      ASSERT_THAT(S1Data, HasSummaryData(TU_C_Id, TU_C_S1_Data, Resolution));
+      ASSERT_THAT(S1Data, HasSummaryData(LU_C_Id, TU_C_S1_Data, Resolution));
     }
 
     // S2 Data Tests.
@@ -318,7 +316,7 @@ TEST_F(EntityLinkerTest, LinksOneTranslationUnit) {
       ASSERT_THAT(S2Data, SummaryDataHasSize(3u));
       ASSERT_THAT(S2Data, HasSummaryData(LU_A_Id, TU_A_S2_Data, Resolution));
       ASSERT_THAT(S2Data, HasSummaryData(LU_B_Id, TU_B_S2_Data, Resolution));
-      ASSERT_THAT(S2Data, HasSummaryData(TU_D_Id, TU_D_S2_Data, Resolution));
+      ASSERT_THAT(S2Data, HasSummaryData(LU_D_Id, TU_D_S2_Data, Resolution));
     }
   }
 }
@@ -653,211 +651,22 @@ TEST_F(EntityLinkerTest, LinksTwoTranslationUnits) {
   }
 }
 
-// ============================================================================
-// Fatal Error Tests
-//
-// These tests verify that corrupted TU summary data triggers fatal errors.
-// Each test constructs a TUSummaryEncoding that violates an invariant by
-// directly manipulating internal state via TestFixture accessors, then
-// asserts that link() terminates the process.
-// ============================================================================
-
-TEST_F(EntityLinkerTest, FatalOnEntityMissingLinkageInformation) {
-  // An entity that is in IdTable but has no entry in LinkageTable indicates
-  // a corrupted TUSummary and triggers a fatal error.
+TEST_F(EntityLinkerTest, RejectsDuplicateTUSummary) {
   NestedBuildNamespace LUNamespace(
       {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
-  EntityLinker Linker(LUNamespace);
-
-  auto TU = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
-
-  // Use addEntity to get a valid EntityId, then remove its linkage entry to
-  // simulate a TUSummary where the IdTable and LinkageTable are out of sync.
-  const auto Id = addEntity(*TU, "A", EntityLinkage::LinkageType::External);
-  getLinkageTable(*TU).erase(Id);
-
-  EXPECT_DEATH(
-      { (void)Linker.link(std::move(TU)); },
-      "EntityLinker: Entity .* is missing linkage information in TU summary");
-}
-
-TEST_F(EntityLinkerTest, FatalOnDuplicateEntityIdInTUSummary) {
-  // Two different EntityNames mapping to the same EntityId indicates corrupted
-  // TUSummary data and triggers a fatal error.
-  NestedBuildNamespace LUNamespace(
-      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
-  EntityLinker Linker(LUNamespace);
-
-  auto TU = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
-
-  // Insert first entity normally.
-  const auto Id = addEntity(*TU, "A", EntityLinkage::LinkageType::External);
-
-  // Directly insert a second EntityName that maps to the same EntityId,
-  // bypassing the normal getId() uniqueness guarantee.
-  NestedBuildNamespace TUNested(getTUNamespace(*TU));
-  EntityName SecondName("B", "", TUNested);
-  getEntities(getIdTable(*TU)).insert({SecondName, Id});
-
-  EXPECT_DEATH(
-      { (void)Linker.link(std::move(TU)); },
-      "EntityLinker: Duplicate entity ID .* in TU summary");
-}
-
-TEST_F(EntityLinkerTest, FatalOnEntityNotFoundInResolutionTable) {
-  // Summary data that references an EntityId not present in
-  // IdTable/LinkageTable will not appear in the resolution table, triggering a
-  // fatal error in merge.
-  NestedBuildNamespace LUNamespace(
-      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
-  EntityLinker Linker(LUNamespace);
-
-  auto TU = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
-
-  // Register one entity normally so resolution succeeds for it.
-  addEntity(*TU, "A", EntityLinkage::LinkageType::External);
-
-  // Obtain a second EntityId from a separate table — it will never appear in
-  // the TU's resolution table because it is not in TU's IdTable or
-  // LinkageTable.
-  EntityIdTable AuxTable;
-  NestedBuildNamespace TUNested(getTUNamespace(*TU));
-  EntityName AuxName("B", "", TUNested);
-  const auto OrphanId = AuxTable.getId(AuxName);
 
-  // Insert summary data keyed on the orphan ID.
-  SummaryName SN("S1");
-  getData(*TU)[SN][OrphanId] = std::make_unique<MockEntitySummaryEncoding>();
-
-  EXPECT_DEATH(
-      { (void)Linker.link(std::move(TU)); },
-      "EntityLinker: Entity .* not found in EntityResolutionTable");
-}
-
-TEST_F(EntityLinkerTest, FatalOnEntityAlreadyExistsInLinkageTableForNone) {
-  // If the output LinkageTable already contains the LU EntityId that a None
-  // linkage entity resolves to, a fatal error is triggered. This can only
-  // happen due to data corruption or a bug in resolve logic, so we simulate
-  // it by pre-populating the output.
-  NestedBuildNamespace LUNamespace(
-      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
   EntityLinker Linker(LUNamespace);
 
-  // Link a first TU so the output already has an entry for entity "A"/None.
   auto TU1 = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
-  addEntity(*TU1, "A", EntityLinkage::LinkageType::None);
-  const BuildNamespace TUNamespace = getTUNamespace(*TU1);
-  ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
 
-  // Determine the LU EntityId that was assigned to A.
-  NestedBuildNamespace LocalNS =
-      NestedBuildNamespace(TUNamespace).makeQualified(LUNamespace);
-  EntityName LU_A_Name("A", "", LocalNS);
-  const auto LU_A_Id =
-      getEntities(getIdTable(Linker.getOutput())).at(LU_A_Name);
-
-  // Inject the resolved LU name -> LU_A_Id mapping into the output IdTable so
-  // that getId() returns LU_A_Id when TU2's "A"/None entity is resolved. Since
-  // LU_A_Id is already in OutLinkage (from TU1), the try_emplace in
-  // resolveEntity() will fail, triggering the fatal.
-  auto &OutEntities = getEntities(getIdTable(Linker.getOutput()));
-  auto &OutLinkage = getLinkageTable(Linker.getOutput());
-
-  // Inject a second name -> same id mapping in the output IdTable so the next
-  // getId() call returns LU_A_Id for a new name that a fresh TU entity resolves
-  // to — and the LinkageTable already has that id, triggering the fatal.
-  NestedBuildNamespace FakeTUNested(
-      BuildNamespace(BuildNamespaceKind::CompilationUnit, "TU2"));
-  NestedBuildNamespace FakeLocalNS = FakeTUNested.makeQualified(LUNamespace);
-  EntityName FakeA("A", "", FakeLocalNS);
-  OutEntities.insert({FakeA, LU_A_Id});
-  // LU_A_Id is already in OutLinkage from linking TU1 with None linkage.
-  ASSERT_NE(OutLinkage.find(LU_A_Id), OutLinkage.end());
-
-  // Now link a TU2 whose "A" entity resolves to FakeLocalNS, which maps to
-  // LU_A_Id (already in LinkageTable) — triggering the fatal.
-  auto TU2 =
-      createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU2");
-  addEntity(*TU2, "A", EntityLinkage::LinkageType::None);
-
-  EXPECT_DEATH(
-      { (void)Linker.link(std::move(TU2)); },
-      "EntityLinker: Entity .* with .* linkage already exists in LinkageTable");
-}
-
-TEST_F(EntityLinkerTest, FatalOnInternalEntityAlreadyExistsInLinkageTable) {
-  // Same as above but for Internal linkage.
-  NestedBuildNamespace LUNamespace(
-      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
-  EntityLinker Linker(LUNamespace);
-
-  auto TU1 = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
-  addEntity(*TU1, "A", EntityLinkage::LinkageType::Internal);
-  const BuildNamespace TUNamespace = getTUNamespace(*TU1);
   ASSERT_THAT_ERROR(Linker.link(std::move(TU1)), llvm::Succeeded());
 
-  NestedBuildNamespace LocalNS =
-      NestedBuildNamespace(TUNamespace).makeQualified(LUNamespace);
-  EntityName LU_A_Name("A", "", LocalNS);
-  const auto LU_A_Id =
-      getEntities(getIdTable(Linker.getOutput())).at(LU_A_Name);
-
-  auto &OutEntities = getEntities(getIdTable(Linker.getOutput()));
-  auto &OutLinkage = getLinkageTable(Linker.getOutput());
-
-  NestedBuildNamespace FakeTUNested(
-      BuildNamespace(BuildNamespaceKind::CompilationUnit, "TU2"));
-  NestedBuildNamespace FakeLocalNS = FakeTUNested.makeQualified(LUNamespace);
-  EntityName FakeA("A", "", FakeLocalNS);
-  OutEntities.insert({FakeA, LU_A_Id});
-  ASSERT_NE(OutLinkage.find(LU_A_Id), OutLinkage.end());
-
-  auto TU2 =
-      createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU2");
-  addEntity(*TU2, "A", EntityLinkage::LinkageType::Internal);
-
-  EXPECT_DEATH(
-      { (void)Linker.link(std::move(TU2)); },
-      "EntityLinker: Entity .* with .* linkage already exists in LinkageTable");
-}
-
-TEST_F(EntityLinkerTest,
-       FatalOnFailedInsertNoneLinkageEntityIntoOutputSummary) {
-  // If a None linkage entity's summary data cannot be inserted into the output
-  // (because its LU EntityId is already present for the same SummaryName), a
-  // fatal error is triggered. We simulate this by pre-populating the output's
-  // data map with the target LU EntityId while keeping that ID absent from
-  // OutLinkage, so resolveEntity() succeeds (first insertion into LinkageTable)
-  // but merge() then finds the data slot already occupied.
-  NestedBuildNamespace LUNamespace(
-      {BuildNamespace(BuildNamespaceKind::LinkUnit, "LU")});
-  EntityLinker Linker(LUNamespace);
-
-  // Compute the LU namespace that TU2's None entity "A" will resolve to.
-  BuildNamespace TU2NS(BuildNamespaceKind::CompilationUnit, "TU2");
-  NestedBuildNamespace TU2LocalNS =
-      NestedBuildNamespace(TU2NS).makeQualified(LUNamespace);
-  EntityName LU_A_Name("A", "", TU2LocalNS);
-
-  // Allocate the LU EntityId for A by registering the name in the output
-  // IdTable, then pre-insert summary data for it — but do NOT insert into
-  // LinkageTable, so resolveEntity's try_emplace will succeed.
-  const auto LU_A_Id = getIdTable(Linker.getOutput()).getId(LU_A_Name);
-  SummaryName SN("S1");
-  getData(Linker.getOutput())[SN].try_emplace(
-      LU_A_Id, std::make_unique<MockEntitySummaryEncoding>());
-
-  // Link TU2: its "A"/None entity resolves to LU_A_Id (already in IdTable),
-  // resolveEntity inserts it into LinkageTable (succeeds), then merge fails
-  // because Data[S1][LU_A_Id] is already occupied.
-  auto TU2 =
-      createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU2");
-  const auto TU2_A_Id = addEntity(*TU2, "A", EntityLinkage::LinkageType::None);
-  addSummaryData(*TU2, TU2_A_Id, "S1");
+  auto TU2 = createTUSummaryEncoding(BuildNamespaceKind::CompilationUnit, "TU");
 
-  EXPECT_DEATH(
-      { (void)Linker.link(std::move(TU2)); },
-      "EntityLinker: Failed to insert data against SummaryName");
+  ASSERT_THAT_ERROR(
+      Linker.link(std::move(TU2)),
+      llvm::FailedWithMessage(
+          HasSubstr("failed to link TU summary: duplicate namespace 'TU'")));
 }
 
 } // namespace

>From 43efdd48980f44cc51fc97e8d41f856cc4041f2b Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 19 Feb 2026 14:59:45 -0800
Subject: [PATCH 11/14] Fixes

---
 .../clang/Analysis/Scalable/EntityLinker/LUSummary.h |  3 ---
 .../Scalable/EntityLinker/LUSummaryEncoding.h        |  3 ---
 .../Scalable/EntityLinker/TUSummaryEncoding.h        |  3 ---
 .../Scalable/Serialization/SerializationFormat.h     | 12 ------------
 .../Analysis/Scalable/EntityLinker/EntityLinker.cpp  |  1 +
 5 files changed, 1 insertion(+), 21 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
index 4741272fb0f36..dc546d8ad80a1 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
@@ -25,8 +25,6 @@
 namespace clang::ssaf {
 
 class EntitySummary;
-class SerializationFormat;
-class SummaryViewBuilder;
 
 /// Represents a link unit (LU) summary containing merged entity summaries.
 ///
@@ -35,7 +33,6 @@ class SummaryViewBuilder;
 /// and the merged entity summaries.
 class LUSummary {
   friend class SerializationFormat;
-  friend class SummaryViewBuilder;
   friend class TestFixture;
 
   NestedBuildNamespace LUNamespace;
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
index 18762a7ebc67d..96f8ce7a39a02 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
@@ -25,9 +25,6 @@
 
 namespace clang::ssaf {
 
-class EntityLinker;
-class SerializationFormat;
-
 /// Represents a link unit summary in its serialized encoding.
 ///
 /// LUSummaryEncoding holds the combined entity summary data from multiple
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
index 5df8680867bf5..711bcbe28d86d 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
@@ -25,9 +25,6 @@
 
 namespace clang::ssaf {
 
-class EntityLinker;
-class SerializationFormat;
-
 /// Represents a translation unit summary in its serialized encoding.
 ///
 /// TUSummaryEncoding holds entity summary data in a format-specific encoding
diff --git a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
index d136e588e0404..622e3610bfc8e 100644
--- a/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
+++ b/clang/include/clang/Analysis/Scalable/Serialization/SerializationFormat.h
@@ -14,7 +14,6 @@
 #ifndef CLANG_ANALYSIS_SCALABLE_SERIALIZATION_SERIALIZATION_FORMAT_H
 #define CLANG_ANALYSIS_SCALABLE_SERIALIZATION_SERIALIZATION_FORMAT_H
 
-#include "clang/Analysis/Scalable/EntityLinker/EntityLinker.h"
 #include "clang/Analysis/Scalable/EntityLinker/LUSummary.h"
 #include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
@@ -26,17 +25,6 @@
 
 namespace clang::ssaf {
 
-class EntityId;
-class EntityIdTable;
-class EntityLinker;
-class EntityName;
-class EntitySummary;
-class LUSummary;
-class LUSummaryEncoding;
-class SummaryName;
-class TUSummary;
-class TUSummaryEncoding;
-
 /// Abstract base class for serialization formats.
 class SerializationFormat {
 public:
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index 442bfde98b937..a11dd852c06e8 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -7,6 +7,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "clang/Analysis/Scalable/EntityLinker/EntityLinker.h"
+#include "clang/Analysis/Scalable/EntityLinker/EntitySummaryEncoding.h"
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
 #include "clang/Analysis/Scalable/Model/EntityLinkage.h"
 #include "clang/Analysis/Scalable/Model/EntityName.h"

>From c92c0c8246a3c95a272750e21df4a1149f192a9f Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 19 Feb 2026 15:13:17 -0800
Subject: [PATCH 12/14] Fixes

---
 clang/unittests/Analysis/Scalable/TestFixture.h | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/clang/unittests/Analysis/Scalable/TestFixture.h b/clang/unittests/Analysis/Scalable/TestFixture.h
index 7f2622f195cb7..4b7771eb581b8 100644
--- a/clang/unittests/Analysis/Scalable/TestFixture.h
+++ b/clang/unittests/Analysis/Scalable/TestFixture.h
@@ -9,7 +9,6 @@
 #ifndef LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H
 #define LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H
 
-#include "clang/Analysis/Scalable/EntityLinker/EntityLinker.h"
 #include "clang/Analysis/Scalable/EntityLinker/LUSummary.h"
 #include "clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h"
 #include "clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h"
@@ -36,6 +35,9 @@ class TestFixture : public ::testing::Test {
   static void PrintTo(const SummaryName &, std::ostream *);
 };
 
+void PrintTo(const EntityId &E, std::ostream *OS);
+void PrintTo(const SummaryName &N, std::ostream *OS);
+
 } // namespace clang::ssaf
 
 #endif // LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H

>From 72250025dc9cee64447745971afb5215cda3ad33 Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 19 Feb 2026 15:33:24 -0800
Subject: [PATCH 13/14] Remove () to avoid regex matching.

---
 clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp b/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
index 79e197a0b0f51..f1ba580842b89 100644
--- a/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
+++ b/clang/unittests/Analysis/Scalable/ErrorBuilderTest.cpp
@@ -178,7 +178,7 @@ TEST_F(ErrorBuilderTest, TriggersAssertionOnWrappingSuccessError) {
         auto SuccessErr = Error::success();
         ErrorBuilder::wrap(std::move(SuccessErr));
       },
-      "Cannot wrap a success error - check for success before calling wrap()");
+      "Cannot wrap a success error - check for success before calling wrap");
 }
 #endif // !NDEBUG
 

>From 6a6f839cbab2d820b1f3adbaa2ca68263cc7867a Mon Sep 17 00:00:00 2001
From: Aviral Goel <agoel26 at apple.com>
Date: Thu, 19 Feb 2026 18:50:03 -0800
Subject: [PATCH 14/14] Minor refactorings

---
 .../Scalable/EntityLinker/EntityLinker.h      |  9 +--
 .../Scalable/EntityLinker/LUSummary.h         |  2 +-
 .../Scalable/EntityLinker/LUSummaryEncoding.h | 10 ++--
 .../Scalable/EntityLinker/TUSummaryEncoding.h | 10 ++--
 .../Analysis/Scalable/Support/ErrorBuilder.h  |  8 ++-
 .../Scalable/EntityLinker/EntityLinker.cpp    | 58 ++++++++-----------
 .../Scalable/Support/ErrorBuilder.cpp         |  4 +-
 7 files changed, 47 insertions(+), 54 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
index d75cde073d600..343f06686d90e 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/EntityLinker.h
@@ -23,11 +23,6 @@
 
 namespace clang::ssaf {
 
-class BuildNamespace;
-class EntityId;
-class EntityLinkage;
-class EntityName;
-class EntitySummaryEncoding;
 class TUSummaryEncoding;
 
 class EntityLinker {
@@ -38,7 +33,7 @@ class EntityLinker {
   /// Constructs an EntityLinker to link TU summaries into a LU summary.
   ///
   /// \param LUNamespace The namespace identifying this link unit.
-  EntityLinker(NestedBuildNamespace LUNamespace)
+  explicit EntityLinker(NestedBuildNamespace LUNamespace)
       : Output(std::move(LUNamespace)) {}
 
   /// Links a TU summary into a LU summary.
@@ -72,7 +67,7 @@ class EntityLinker {
   ///
   /// \param Summary The TU summary whose entities are being resolved.
   /// \returns A map from TU EntityIds to their corresponding LU EntityIds.
-  std::map<EntityId, EntityId> resolve(TUSummaryEncoding &Summary);
+  std::map<EntityId, EntityId> resolve(const TUSummaryEncoding &Summary);
 
   /// Merges all summary data from a TU summary into the LU Summary.
   ///
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
index dc546d8ad80a1..1be691fb485ff 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummary.h
@@ -45,7 +45,7 @@ class LUSummary {
       Data;
 
 public:
-  LUSummary(NestedBuildNamespace LUNamespace)
+  explicit LUSummary(NestedBuildNamespace LUNamespace)
       : LUNamespace(std::move(LUNamespace)) {}
 };
 
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
index 96f8ce7a39a02..63ac97d2cef32 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/LUSummaryEncoding.h
@@ -35,22 +35,22 @@ class LUSummaryEncoding {
   friend class SerializationFormat;
   friend class TestFixture;
 
-  /// The namespace identifying this link unit.
+  // The namespace identifying this link unit.
   NestedBuildNamespace LUNamespace;
 
-  /// Maps entity names to their unique identifiers within this link unit.
+  // Maps entity names to their unique identifiers within this link unit.
   EntityIdTable IdTable;
 
-  /// Maps entity IDs to their linkage properties (None, Internal, External).
+  // Maps entity IDs to their linkage properties.
   std::map<EntityId, EntityLinkage> LinkageTable;
 
-  /// Encoded summary data organized by summary type and entity ID.
+  // Encoded summary data organized by summary type and entity ID.
   std::map<SummaryName,
            std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
       Data;
 
 public:
-  LUSummaryEncoding(NestedBuildNamespace LUNamespace)
+  explicit LUSummaryEncoding(NestedBuildNamespace LUNamespace)
       : LUNamespace(std::move(LUNamespace)) {}
 };
 
diff --git a/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
index 711bcbe28d86d..a473e9fbf7b46 100644
--- a/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
+++ b/clang/include/clang/Analysis/Scalable/EntityLinker/TUSummaryEncoding.h
@@ -36,22 +36,22 @@ class TUSummaryEncoding {
   friend class SerializationFormat;
   friend class TestFixture;
 
-  /// The namespace identifying this translation unit.
+  // The namespace identifying this translation unit.
   BuildNamespace TUNamespace;
 
-  /// Maps entity names to their unique identifiers within this TU.
+  // Maps entity names to their unique identifiers within this TU.
   EntityIdTable IdTable;
 
-  /// Maps entity IDs to their linkage properties (None, Internal, External).
+  // Maps entity IDs to their linkage properties (None, Internal, External).
   std::map<EntityId, EntityLinkage> LinkageTable;
 
-  /// Encoded summary data organized by summary type and entity ID.
+  // Encoded summary data organized by summary type and entity ID.
   std::map<SummaryName,
            std::map<EntityId, std::unique_ptr<EntitySummaryEncoding>>>
       Data;
 
 public:
-  TUSummaryEncoding(BuildNamespace TUNamespace)
+  explicit TUSummaryEncoding(BuildNamespace TUNamespace)
       : TUNamespace(std::move(TUNamespace)) {}
 };
 
diff --git a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
index c7e2a62f7c064..a5fa0e9e5556c 100644
--- a/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/Support/ErrorBuilder.h
@@ -36,7 +36,6 @@ namespace clang::ssaf {
 ///       .context("reading configuration")
 ///       .build();
 class ErrorBuilder {
-private:
   std::error_code Code;
   std::vector<std::string> ContextStack;
 
@@ -161,6 +160,9 @@ class ErrorBuilder {
   ///          Even if no context was added (empty context stack), an error
   ///          with the stored error code is returned.
   ///
+  /// \pre The ErrorBuilder must have been created via \c create() or \c wrap().
+  ///      Constructing an ErrorBuilder directly is not supported.
+  ///
   /// Example output:
   /// \code
   ///   // ErrorBuilder::create(errc::invalid_argument, "value is 42")
@@ -184,6 +186,10 @@ class ErrorBuilder {
   /// \param Fmt Format string for the error message (using llvm::formatv).
   /// \param ArgVals Arguments for the format string.
   ///
+  /// \pre \p Fmt must be a valid llvm::formatv format string with the correct
+  ///      number and types of arguments. An invalid format string is undefined
+  ///      behaviour.
+  ///
   /// Example:
   /// \code
   ///   ErrorBuilder::fatal("Entity {0} with {1} linkage already exists",
diff --git a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
index a11dd852c06e8..ad7ae39402231 100644
--- a/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
+++ b/clang/lib/Analysis/Scalable/EntityLinker/EntityLinker.cpp
@@ -20,42 +20,36 @@ using namespace clang::ssaf;
 // Error Message Constants
 //===----------------------------------------------------------------------===//
 
-namespace {
-
 namespace ErrorMessages {
 
-constexpr const char *EntityLinkerFatalErrorPrefix =
+static constexpr const char *EntityLinkerFatalErrorPrefix =
     "EntityLinker: Corrupted TUSummary or logic bug";
 
-constexpr const char *EntityAlreadyExistsInLinkageTable =
+static constexpr const char *EntityAlreadyExistsInLinkageTable =
     "{0} - EntityId '{1}' with LinkageType '{2}' already exists in LUSummary";
 
-constexpr const char *MissingLinkageInformation =
+static constexpr const char *MissingLinkageInformation =
     "{0} - EntityId '{1}' missing linkage information in TUSummary";
 
-constexpr const char *DuplicateEntityIdInTUSummary =
+static constexpr const char *DuplicateEntityIdInTUSummary =
     "{0} - Duplicate EntityID '{1}' in EntityResolutionTable";
 
-constexpr const char *EntityNotFoundInResolutionTable =
+static constexpr const char *EntityNotFoundInResolutionTable =
     "{0} - EntityId '{1}' not found in EntityResolutionTable";
 
-constexpr const char *FailedToInsertEntityIntoOutputSummary =
+static constexpr const char *FailedToInsertEntityIntoOutputSummary =
     "{0} - Failed to insert data for EntityId '{1}' with LinkageType '{2}' "
     "against SummaryName '{3}' to LUSummary";
 
-constexpr const char *DuplicateTUNamespace =
+static constexpr const char *DuplicateTUNamespace =
     "failed to link TU summary: duplicate namespace '{0}'";
 
 } // namespace ErrorMessages
 
-} // namespace
-
-namespace {
-
-NestedBuildNamespace
+static NestedBuildNamespace
 resolveNamespace(const NestedBuildNamespace &LUNamespace,
                  const NestedBuildNamespace &EntityNamespace,
-                 const EntityLinkage::LinkageType Linkage) {
+                 EntityLinkage::LinkageType Linkage) {
   switch (Linkage) {
   case EntityLinkage::LinkageType::None:
   case EntityLinkage::LinkageType::Internal:
@@ -67,8 +61,6 @@ resolveNamespace(const NestedBuildNamespace &LUNamespace,
   llvm_unreachable("Unhandled EntityLinkage::LinkageType variant");
 }
 
-} // namespace
-
 EntityId EntityLinker::resolveEntity(const EntityName &OldName,
                                      const EntityLinkage &Linkage) {
   NestedBuildNamespace NewNamespace = resolveNamespace(
@@ -82,8 +74,8 @@ EntityId EntityLinker::resolveEntity(const EntityName &OldName,
   // function will return the id assigned at the first insertion.
   EntityId NewId = Output.IdTable.getId(NewName);
 
-  auto InsertionResult = Output.LinkageTable.try_emplace(NewId, Linkage);
-  if (!InsertionResult.second) {
+  auto [_, Inserted] = Output.LinkageTable.try_emplace(NewId, Linkage);
+  if (!Inserted) {
     // Insertion failure for `None` and `Internal` linkage is a fatal error
     // because these entities have unique namespaces and should never collide.
     // `External` linkage entities may collide.
@@ -98,7 +90,8 @@ EntityId EntityLinker::resolveEntity(const EntityName &OldName,
   return NewId;
 }
 
-std::map<EntityId, EntityId> EntityLinker::resolve(TUSummaryEncoding &Summary) {
+std::map<EntityId, EntityId>
+EntityLinker::resolve(const TUSummaryEncoding &Summary) {
   std::map<EntityId, EntityId> EntityResolutionTable;
 
   Summary.IdTable.forEach([&](const EntityName &OldName, const EntityId OldId) {
@@ -113,8 +106,8 @@ std::map<EntityId, EntityId> EntityLinker::resolve(TUSummaryEncoding &Summary) {
 
     EntityId NewId = resolveEntity(OldName, Linkage);
 
-    auto InsertionResult = EntityResolutionTable.insert({OldId, NewId});
-    if (!InsertionResult.second) {
+    auto [_, Inserted] = EntityResolutionTable.insert({OldId, NewId});
+    if (!Inserted) {
       ErrorBuilder::fatal(ErrorMessages::DuplicateEntityIdInTUSummary,
                           ErrorMessages::EntityLinkerFatalErrorPrefix,
                           OldId.Index);
@@ -142,25 +135,24 @@ EntityLinker::merge(TUSummaryEncoding &Summary,
 
       const auto NewId = Iter->second;
 
-      auto InsertionResult =
-          OutputSummaryData.try_emplace(NewId, std::move(ES));
+      auto [It, Inserted] = OutputSummaryData.try_emplace(NewId, std::move(ES));
 
-      if (InsertionResult.second) {
-        PatchTargets.push_back(InsertionResult.first->second.get());
+      if (Inserted) {
+        PatchTargets.push_back(It->second.get());
       } else {
         // Safe to retrieve linkage using .at since the resolve step ensures
         // linkage information is always present for every OldId.
-        auto LinkageType = Summary.LinkageTable.at(OldId).getLinkage();
+        auto Linkage = Summary.LinkageTable.at(OldId).getLinkage();
 
         // Insertion should never fail for `None` and `Internal` linkage
         // entities because these entities will have different namespaces across
         // TUs even if their names match.
-        if (LinkageType == EntityLinkage::LinkageType::None ||
-            LinkageType == EntityLinkage::LinkageType::Internal) {
+        if (Linkage == EntityLinkage::LinkageType::None ||
+            Linkage == EntityLinkage::LinkageType::Internal) {
           ErrorBuilder::fatal(
               ErrorMessages::FailedToInsertEntityIntoOutputSummary,
               ErrorMessages::EntityLinkerFatalErrorPrefix, NewId.Index,
-              toString(LinkageType), SN.str());
+              toString(Linkage), SN.str());
         }
 
         // Insertion is expected to fail for duplicate occurrences of `External`
@@ -183,15 +175,15 @@ void EntityLinker::patch(
 }
 
 llvm::Error EntityLinker::link(std::unique_ptr<TUSummaryEncoding> Summary) {
-  auto InsertionResult = ProcessedTUNamespaces.insert(Summary->TUNamespace);
-  if (!InsertionResult.second) {
+  auto [_, Inserted] = ProcessedTUNamespaces.insert(Summary->TUNamespace);
+  if (!Inserted) {
     return ErrorBuilder::create(std::errc::invalid_argument,
                                 ErrorMessages::DuplicateTUNamespace,
                                 Summary->TUNamespace.Name)
         .build();
   }
 
-  TUSummaryEncoding &SummaryRef = *Summary.get();
+  TUSummaryEncoding &SummaryRef = *Summary;
 
   auto EntityResolutionTable = resolve(SummaryRef);
   auto PatchTargets = merge(SummaryRef, EntityResolutionTable);
diff --git a/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
index 5ee4955adedd7..1d60491a98b76 100644
--- a/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
+++ b/clang/lib/Analysis/Scalable/Support/ErrorBuilder.cpp
@@ -13,8 +13,8 @@
 
 namespace clang::ssaf {
 
-constexpr llvm::StringLiteral ErrorSeparator = " + ";
-constexpr llvm::StringLiteral ContextSeparator = "\n";
+static constexpr llvm::StringLiteral ErrorSeparator = " + ";
+static constexpr llvm::StringLiteral ContextSeparator = "\n";
 
 ErrorBuilder ErrorBuilder::wrap(llvm::Error E) {
   assert(



More information about the cfe-commits mailing list