https://github.com/steakhal updated 
https://github.com/llvm/llvm-project/pull/181220

From 0ee4ef9c39235db408b3abdd7757bed0cf0fa714 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Thu, 12 Feb 2026 20:42:39 +0100
Subject: [PATCH 1/8] [clang][ssaf] Implement TUSummaryBuilder with test
 infrastructure

Also adds a ssaf::TestFixture to provide access to the
private fields of the SSAF object for introspection.

Assisted-By: claude

rdar://168773578
---
 .../Analysis/Scalable/Model/BuildNamespace.h  |   7 +-
 .../clang/Analysis/Scalable/Model/EntityId.h  |   1 +
 .../Analysis/Scalable/Model/EntityIdTable.h   |   1 +
 .../Analysis/Scalable/Model/EntityLinkage.h   |   1 +
 .../Analysis/Scalable/Model/EntityName.h      |   2 +-
 .../Analysis/Scalable/Model/SummaryName.h     |   1 +
 .../Analysis/Scalable/TUSummary/TUSummary.h   |   2 +
 .../Scalable/TUSummary/TUSummaryBuilder.h     |  22 +-
 clang/lib/Analysis/Scalable/CMakeLists.txt    |   1 +
 .../Scalable/TUSummary/TUSummaryBuilder.cpp   |  17 +
 .../Analysis/Scalable/CMakeLists.txt          |   2 +
 .../Registries/MockTUSummaryBuilder.h         |   1 +
 .../SummaryExtractorRegistryTest.cpp          |  15 +-
 .../Scalable/TUSummaryBuilderTest.cpp         | 297 ++++++++++++++++++
 .../Analysis/Scalable/TestFixture.cpp         |  29 ++
 .../unittests/Analysis/Scalable/TestFixture.h |  36 +++
 16 files changed, 426 insertions(+), 9 deletions(-)
 create mode 100644 clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
 create mode 100644 clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
 create mode 100644 clang/unittests/Analysis/Scalable/TestFixture.cpp
 create mode 100644 clang/unittests/Analysis/Scalable/TestFixture.h

diff --git a/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h 
b/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h
index 5ca26df1e9252..2cd8990708b8d 100644
--- a/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h
+++ b/clang/include/clang/Analysis/Scalable/Model/BuildNamespace.h
@@ -63,6 +63,7 @@ class BuildNamespace {
   bool operator<(const BuildNamespace &Other) const;
 
   friend class SerializationFormat;
+  friend class TestFixture;
 };
 
 /// Represents a hierarchical sequence of build namespaces.
@@ -75,8 +76,6 @@ class BuildNamespace {
 /// For example, an entity might be qualified by a compilation unit namespace
 /// followed by a shared library namespace.
 class NestedBuildNamespace {
-  friend class SerializationFormat;
-
   std::vector<BuildNamespace> Namespaces;
 
 public:
@@ -114,8 +113,8 @@ class NestedBuildNamespace {
   bool operator!=(const NestedBuildNamespace &Other) const;
   bool operator<(const NestedBuildNamespace &Other) const;
 
-  friend class JSONWriter;
-  friend class LinkUnitResolution;
+  friend class SerializationFormat;
+  friend class TestFixture;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityId.h 
b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
index e348486386cb6..00a6f3447bbce 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityId.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityId.h
@@ -30,6 +30,7 @@ class EntityIdTable;
 class EntityId {
   friend class EntityIdTable;
   friend class SerializationFormat;
+  friend class TestFixture;
 
   size_t Index;
 
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h 
b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
index a1099c4e4d0f8..6c5f27907adb4 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityIdTable.h
@@ -22,6 +22,7 @@ namespace clang::ssaf {
 /// Entities are never removed.
 class EntityIdTable {
   friend class SerializationFormat;
+  friend class TestFixture;
 
   std::map<EntityName, EntityId> Entities;
 
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h 
b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
index ba5f7d3073a30..155ec69ea0b3a 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityLinkage.h
@@ -18,6 +18,7 @@ namespace clang::ssaf {
 /// across translation units.
 class EntityLinkage {
   friend class SerializationFormat;
+  friend class TestFixture;
 
 public:
   enum class LinkageType {
diff --git a/clang/include/clang/Analysis/Scalable/Model/EntityName.h 
b/clang/include/clang/Analysis/Scalable/Model/EntityName.h
index 23890ab7bea43..72dd9ac803052 100644
--- a/clang/include/clang/Analysis/Scalable/Model/EntityName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/EntityName.h
@@ -47,8 +47,8 @@ class EntityName {
   /// \param Namespace The namespace steps to append to this entity's 
namespace.
   EntityName makeQualified(NestedBuildNamespace Namespace) const;
 
-  friend class LinkUnitResolution;
   friend class SerializationFormat;
+  friend class TestFixture;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h 
b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
index 785fe0eb10372..36a8cf5da78b1 100644
--- a/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
+++ b/clang/include/clang/Analysis/Scalable/Model/SummaryName.h
@@ -31,6 +31,7 @@ class SummaryName {
 
 private:
   std::string Name;
+  friend class TestFixture;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummary.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummary.h
index 2520ce87f3959..40cb7d582cf6e 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummary.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummary.h
@@ -36,6 +36,8 @@ class TUSummary {
   TUSummary(BuildNamespace TUNamespace) : TUNamespace(std::move(TUNamespace)) 
{}
 
   friend class SerializationFormat;
+  friend class TestFixture;
+  friend class TUSummaryBuilder;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
index fa679c145faa5..ad41bfb9450bc 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
@@ -9,10 +9,30 @@
 #ifndef LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_TUSUMMARYBUILDER_H
 #define LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_TUSUMMARYBUILDER_H
 
+#include <memory>
+
 namespace clang::ssaf {
 
+class EntityId;
+class EntityName;
+class EntitySummary;
+class TUSummary;
+
 class TUSummaryBuilder {
-  // Empty for now.
+public:
+  explicit TUSummaryBuilder(TUSummary &Summary) : Summary(Summary) {}
+
+  /// Add an entity to the summary and return its EntityId.
+  /// If the entity already exists, returns the existing ID (idempotent).
+  EntityId addEntity(const EntityName &E);
+
+  /// Add analysis-specific fact data for an entity.
+  /// Precondition: The ContributingEntity must have been added via 
addEntity().
+  void addFact(EntityId ContributingEntity,
+               std::unique_ptr<EntitySummary> NewData);
+
+private:
+  TUSummary &Summary;
 };
 
 } // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/CMakeLists.txt 
b/clang/lib/Analysis/Scalable/CMakeLists.txt
index 522fc9dcf078d..a7311e4d69d67 100644
--- a/clang/lib/Analysis/Scalable/CMakeLists.txt
+++ b/clang/lib/Analysis/Scalable/CMakeLists.txt
@@ -10,6 +10,7 @@ add_clang_library(clangAnalysisScalable
   Serialization/JSONFormat.cpp
   Serialization/SerializationFormatRegistry.cpp
   TUSummary/ExtractorRegistry.cpp
+  TUSummary/TUSummaryBuilder.cpp
 
   LINK_LIBS
   clangAST
diff --git a/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp 
b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
new file mode 100644
index 0000000000000..f5ed26b144522
--- /dev/null
+++ b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
@@ -0,0 +1,17 @@
+#include "clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include <memory>
+
+using namespace clang;
+using namespace ssaf;
+
+EntityId TUSummaryBuilder::addEntity(const EntityName &E) {
+  return Summary.IdTable.getId(E);
+}
+
+void TUSummaryBuilder::addFact(EntityId ContributingEntity,
+                               std::unique_ptr<EntitySummary> NewData) {
+  Summary.Data[NewData->getSummaryName()][ContributingEntity] =
+      std::move(NewData);
+}
diff --git a/clang/unittests/Analysis/Scalable/CMakeLists.txt 
b/clang/unittests/Analysis/Scalable/CMakeLists.txt
index f1e1c874cc5cd..5529ca06de170 100644
--- a/clang/unittests/Analysis/Scalable/CMakeLists.txt
+++ b/clang/unittests/Analysis/Scalable/CMakeLists.txt
@@ -13,6 +13,8 @@ add_distinct_clang_unittest(ClangScalableAnalysisTests
   Registries/SummaryExtractorRegistryTest.cpp
   Serialization/JSONFormatTest.cpp
   SummaryNameTest.cpp
+  TestFixture.cpp
+  TUSummaryBuilderTest.cpp
 
   CLANG_LIBS
   clangAnalysisScalable
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/MockTUSummaryBuilder.h 
b/clang/unittests/Analysis/Scalable/Registries/MockTUSummaryBuilder.h
index ccb79ae042625..755a47471a9f8 100644
--- a/clang/unittests/Analysis/Scalable/Registries/MockTUSummaryBuilder.h
+++ b/clang/unittests/Analysis/Scalable/Registries/MockTUSummaryBuilder.h
@@ -14,6 +14,7 @@ namespace clang::ssaf {
 
 class MockTUSummaryBuilder : public TUSummaryBuilder {
 public:
+  using TUSummaryBuilder::TUSummaryBuilder;
   void sendMessage(llvm::Twine Message) { Stream << Message << '\n'; }
   std::string consumeMessages() { return std::move(OutputBuffer); }
 
diff --git 
a/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp 
b/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp
index 2076fae0b5ab0..a17a4f145b038 100644
--- 
a/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp
+++ 
b/clang/unittests/Analysis/Scalable/Registries/SummaryExtractorRegistryTest.cpp
@@ -8,6 +8,7 @@
 
 #include "MockTUSummaryBuilder.h"
 #include "clang/Analysis/Scalable/TUSummary/ExtractorRegistry.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
 #include "clang/Frontend/MultiplexConsumer.h"
 #include "clang/Tooling/Tooling.h"
 #include "llvm/ADT/StringRef.h"
@@ -17,6 +18,11 @@
 using namespace clang;
 using namespace ssaf;
 
+[[nodiscard]]
+static TUSummary makeFakeSummary() {
+  return BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
+}
+
 namespace {
 
 TEST(SummaryExtractorRegistryTest, isTUSummaryExtractorRegistered) {
@@ -39,7 +45,8 @@ TEST(SummaryExtractorRegistryTest, 
EnumeratingRegistryEntries) {
 }
 
 TEST(SummaryExtractorRegistryTest, InstantiatingExtractor1) {
-  MockTUSummaryBuilder FakeBuilder;
+  TUSummary Summary = makeFakeSummary();
+  MockTUSummaryBuilder FakeBuilder(Summary);
   {
     auto Consumer =
         makeTUSummaryExtractor("MockSummaryExtractor1", FakeBuilder);
@@ -52,7 +59,8 @@ TEST(SummaryExtractorRegistryTest, InstantiatingExtractor1) {
 }
 
 TEST(SummaryExtractorRegistryTest, InstantiatingExtractor2) {
-  MockTUSummaryBuilder FakeBuilder;
+  TUSummary Summary = makeFakeSummary();
+  MockTUSummaryBuilder FakeBuilder(Summary);
   {
     auto Consumer =
         makeTUSummaryExtractor("MockSummaryExtractor2", FakeBuilder);
@@ -65,7 +73,8 @@ TEST(SummaryExtractorRegistryTest, InstantiatingExtractor2) {
 }
 
 TEST(SummaryExtractorRegistryTest, InvokingExtractors) {
-  MockTUSummaryBuilder FakeBuilder;
+  TUSummary Summary = makeFakeSummary();
+  MockTUSummaryBuilder FakeBuilder(Summary);
   std::vector<std::unique_ptr<ASTConsumer>> Consumers;
   for (std::string Name : {"MockSummaryExtractor1", "MockSummaryExtractor2"}) {
     auto Consumer = makeTUSummaryExtractor(Name, FakeBuilder);
diff --git a/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp 
b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
new file mode 100644
index 0000000000000..2e40b5caf9ce5
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
@@ -0,0 +1,297 @@
+//===- unittests/Analysis/Scalable/TUSummaryBuilderTest.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/TUSummary/TUSummaryBuilder.h"
+#include "TestFixture.h"
+#include "clang/Analysis/Scalable/Model/BuildNamespace.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/Model/EntityName.h"
+#include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include "clang/Analysis/Scalable/Serialization/SerializationFormat.h"
+#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h"
+#include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include <type_traits>
+
+using namespace clang;
+using namespace ssaf;
+
+using llvm::SmallVector;
+using testing::Field;
+using testing::Optional;
+using testing::UnorderedElementsAre;
+
+[[nodiscard]]
+static TUSummary makeFakeSummary() {
+  return BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
+}
+
+[[nodiscard]]
+static EntityId addTestEntity(TUSummaryBuilder &Builder, llvm::StringRef USR) {
+  return Builder.addEntity(EntityName(USR, /*Suffix=*/"", /*Namespace=*/{}));
+}
+
+template <class ConcreteEntitySummary>
+[[nodiscard]]
+static SummaryName addFactTo(TUSummaryBuilder &Builder, EntityId ID,
+                             ConcreteEntitySummary Fact) {
+  static_assert(std::is_base_of_v<EntitySummary, ConcreteEntitySummary>);
+  auto NewFact = std::make_unique<ConcreteEntitySummary>(std::move(Fact));
+  SummaryName Name = NewFact->getSummaryName();
+  Builder.addFact(ID, std::move(NewFact));
+  return Name;
+}
+
+namespace {
+
+// Mock EntitySummary classes for testing
+struct MockSummaryData1 final : public EntitySummary {
+  explicit MockSummaryData1(int Value) : Value(Value) {}
+  SummaryName getSummaryName() const override {
+    return SummaryName("MockSummary1");
+  }
+  int Value;
+};
+
+struct MockSummaryData2 final : public EntitySummary {
+  explicit MockSummaryData2(std::string Text) : Text(std::move(Text)) {}
+  SummaryName getSummaryName() const override {
+    return SummaryName("MockSummary2");
+  }
+  std::string Text;
+};
+
+struct MockSummaryData3 final : public EntitySummary {
+  explicit MockSummaryData3(bool Flag) : Flag(Flag) {}
+  SummaryName getSummaryName() const override {
+    return SummaryName("MockSummary3");
+  }
+  bool Flag;
+};
+
+void PrintTo(const MockSummaryData1 &S, std::ostream *OS) {
+  *OS << "MockSummaryData1(" << S.getSummaryName().str().str() << ")";
+}
+void PrintTo(const MockSummaryData2 &S, std::ostream *OS) {
+  *OS << "MockSummaryData2(" << S.getSummaryName().str().str() << ")";
+}
+void PrintTo(const MockSummaryData3 &S, std::ostream *OS) {
+  *OS << "MockSummaryData3(" << S.getSummaryName().str().str() << ")";
+}
+
+struct TUSummaryBuilderTest : ssaf::TestFixture {
+  [[nodiscard]] static SmallVector<SummaryName>
+  summaryNames(const TUSummary &Summary) {
+    return llvm::to_vector(llvm::make_first_range(getData(Summary)));
+  }
+
+  [[nodiscard]] static SmallVector<EntityId>
+  entitiesOfSummary(const TUSummary &Summary, const SummaryName &Name) {
+    const auto &MappingIt = getData(Summary).find(Name);
+    if (MappingIt == getData(Summary).end())
+      return {};
+    return llvm::to_vector(llvm::make_first_range(MappingIt->second));
+  }
+
+  template <class ConcreteSummaryData>
+  [[nodiscard]] static std::optional<ConcreteSummaryData>
+  getAsEntitySummary(const TUSummary &Summary, const SummaryName &Name,
+                     EntityId E) {
+    static_assert(std::is_base_of_v<EntitySummary, ConcreteSummaryData>);
+    const auto &MappingIt = getData(Summary).find(Name);
+    if (MappingIt == getData(Summary).end())
+      return std::nullopt;
+    auto SummaryIt = MappingIt->second.find(E);
+    if (SummaryIt == MappingIt->second.end())
+      return std::nullopt;
+    assert(Name == SummaryIt->second->getSummaryName());
+    return static_cast<const ConcreteSummaryData &>(*SummaryIt->second);
+  }
+};
+
+TEST_F(TUSummaryBuilderTest, AddEntity) {
+  TUSummary Summary = makeFakeSummary();
+  TUSummaryBuilder Builder(Summary);
+
+  EntityName EN1("c:@F@foo", "", /*Namespace=*/{});
+  EntityName EN2("c:@F@bar", "", /*Namespace=*/{});
+
+  EntityId ID = Builder.addEntity(EN1);
+  EntityId IDAlias = Builder.addEntity(EN1);
+  EXPECT_EQ(ID, IDAlias); // Idenpotency
+
+  EntityId ID2 = Builder.addEntity(EN2);
+  EXPECT_NE(ID, ID2);
+  EXPECT_NE(IDAlias, ID2);
+
+  const EntityIdTable &IdTable = getIdTable(Summary);
+  EXPECT_EQ(IdTable.count(), 2U);
+  EXPECT_TRUE(IdTable.contains(EN1));
+  EXPECT_TRUE(IdTable.contains(EN2));
+}
+
+TEST_F(TUSummaryBuilderTest, TUSummaryBuilderAddSingleFact) {
+  TUSummary Summary = makeFakeSummary();
+  TUSummaryBuilder Builder(Summary);
+
+  EntityId ID = addTestEntity(Builder, "c:@F@foo");
+  SummaryName Name = addFactTo(Builder, ID, MockSummaryData1(10));
+
+  // Should have a summary type with an entity.
+  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID));
+
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID),
+              Optional(Field(&MockSummaryData1::Value, 10)));
+}
+
+TEST_F(TUSummaryBuilderTest, AddMultipleFactsToSameEntity) {
+  TUSummary Summary = makeFakeSummary();
+  TUSummaryBuilder Builder(Summary);
+  EntityId ID = addTestEntity(Builder, "c:@F@foo");
+
+  // Add different summary types to the same entity.
+  SummaryName Name1 = addFactTo(Builder, ID, MockSummaryData1(42));
+  SummaryName Name2 = addFactTo(Builder, ID, MockSummaryData2("test data"));
+  SummaryName Name3 = addFactTo(Builder, ID, MockSummaryData3(true));
+
+  // All Names must be unique
+  EXPECT_EQ((std::set<SummaryName>{Name1, Name2, Name3}.size()), 3U);
+
+  // Should have 3 summary type with the same entity.
+  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name1, Name2, 
Name3));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name1), UnorderedElementsAre(ID));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name2), UnorderedElementsAre(ID));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name3), UnorderedElementsAre(ID));
+
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID),
+              Optional(Field(&MockSummaryData1::Value, 42)));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData2>(Summary, Name2, ID),
+              Optional(Field(&MockSummaryData2::Text, "test data")));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData3>(Summary, Name3, ID),
+              Optional(Field(&MockSummaryData3::Flag, true)));
+}
+
+TEST_F(TUSummaryBuilderTest, AddSameFactTypeToMultipleEntities) {
+  TUSummary Summary = makeFakeSummary();
+  TUSummaryBuilder Builder(Summary);
+
+  EntityId ID1 = addTestEntity(Builder, "c:@F@foo");
+  EntityId ID2 = addTestEntity(Builder, "c:@F@bar");
+  EntityId ID3 = addTestEntity(Builder, "c:@F@baz");
+
+  // Add the same summary type to different entities.
+  SummaryName Name1 = addFactTo(Builder, ID1, MockSummaryData1(1));
+  SummaryName Name2 = addFactTo(Builder, ID2, MockSummaryData1(2));
+  SummaryName Name3 = addFactTo(Builder, ID3, MockSummaryData1(3));
+
+  // All 3 should be the same summary type.
+  EXPECT_THAT((llvm::ArrayRef{Name1, Name2, Name3}), testing::Each(Name1));
+
+  // Should have only 1 summary type with 3 entities.
+  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name1));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name1),
+              UnorderedElementsAre(ID1, ID2, ID3));
+
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID1),
+              Optional(Field(&MockSummaryData1::Value, 1)));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name2, ID2),
+              Optional(Field(&MockSummaryData1::Value, 2)));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name3, ID3),
+              Optional(Field(&MockSummaryData1::Value, 3)));
+}
+
+TEST_F(TUSummaryBuilderTest, AddFactReplacesExistingFact) {
+  TUSummary Summary = makeFakeSummary();
+  TUSummaryBuilder Builder(Summary);
+  EntityId ID = addTestEntity(Builder, "c:@F@foo");
+
+  SummaryName Name = addFactTo(Builder, ID, MockSummaryData1(10));
+
+  // Check the initial value.
+  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID),
+              Optional(Field(&MockSummaryData1::Value, 10)));
+
+  // Add another fact with the same SummaryName.
+  // This should replace the previous fact.
+  SummaryName ReplacementName = addFactTo(Builder, ID, MockSummaryData1(20));
+  ASSERT_EQ(ReplacementName, Name);
+
+  // Check that the value was replaced.
+  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID),
+              Optional(Field(&MockSummaryData1::Value, 20)));
+}
+
+TEST_F(TUSummaryBuilderTest, AddFactsComplexScenario) {
+  TUSummary Summary = makeFakeSummary();
+  TUSummaryBuilder Builder(Summary);
+
+  EntityId ID1 = addTestEntity(Builder, "c:@F@foo");
+  EntityId ID2 = addTestEntity(Builder, "c:@F@bar");
+
+  SummaryName Name1 = addFactTo(Builder, ID1, MockSummaryData1(10));
+  SummaryName Name2 = addFactTo(Builder, ID1, MockSummaryData2("twenty"));
+
+  SummaryName Name3 = addFactTo(Builder, ID2, MockSummaryData1(30));
+  SummaryName Name4 = addFactTo(Builder, ID2, MockSummaryData3(true));
+
+  // Check that we have only 3 distinct summary names.
+  EXPECT_EQ(Name1, Name3);
+  EXPECT_THAT((std::set{Name1, Name2, Name3, Name4}),
+              UnorderedElementsAre(Name1, Name2, Name4));
+
+  // Check that we have two facts for the two summaries each.
+  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name1, Name2, 
Name4));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name1),
+              UnorderedElementsAre(ID1, ID2));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name2), UnorderedElementsAre(ID1));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name4), UnorderedElementsAre(ID2));
+
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID1),
+              Optional(Field(&MockSummaryData1::Value, 10)));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID2),
+              Optional(Field(&MockSummaryData1::Value, 30)));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData2>(Summary, Name2, ID1),
+              Optional(Field(&MockSummaryData2::Text, "twenty")));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData3>(Summary, Name4, ID2),
+              Optional(Field(&MockSummaryData3::Flag, true)));
+
+  // Replace a fact of Name1 on entity 1 with the new value 50.
+  SummaryName Name5 = addFactTo(Builder, ID1, MockSummaryData1(50));
+  ASSERT_EQ(Name5, Name1);
+
+  // Check that the summary names and entity IDs didn't change.
+  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name1, Name2, 
Name4));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name1),
+              UnorderedElementsAre(ID1, ID2));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name2), UnorderedElementsAre(ID1));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name4), UnorderedElementsAre(ID2));
+
+  // Check the Name1 ID1 entity summary value was changed to 50.
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID1),
+              Optional(Field(&MockSummaryData1::Value, 50)));
+
+  // Check that the rest remained the same.
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID2),
+              Optional(Field(&MockSummaryData1::Value, 30)));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData2>(Summary, Name2, ID1),
+              Optional(Field(&MockSummaryData2::Text, "twenty")));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData3>(Summary, Name4, ID2),
+              Optional(Field(&MockSummaryData3::Flag, true)));
+}
+
+} // namespace
diff --git a/clang/unittests/Analysis/Scalable/TestFixture.cpp 
b/clang/unittests/Analysis/Scalable/TestFixture.cpp
new file mode 100644
index 0000000000000..c3c7027c1dc3f
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/TestFixture.cpp
@@ -0,0 +1,29 @@
+//===- unittests/Analysis/Scalable/TestFixture.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 "TestFixture.h"
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "llvm/Support/raw_ostream.h"
+#include <ostream>
+#include <string>
+
+using namespace clang;
+using namespace ssaf;
+
+template <class T> static std::string asString(const T &Obj) {
+  std::string Repr;
+  llvm::raw_string_ostream(Repr) << Obj;
+  return Repr;
+}
+
+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 << ")";
+}
diff --git a/clang/unittests/Analysis/Scalable/TestFixture.h 
b/clang/unittests/Analysis/Scalable/TestFixture.h
new file mode 100644
index 0000000000000..427769819a703
--- /dev/null
+++ b/clang/unittests/Analysis/Scalable/TestFixture.h
@@ -0,0 +1,36 @@
+//===- TestFixture.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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H
+#define LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_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 "clang/Analysis/Scalable/TUSummary/TUSummary.h"
+#include "gtest/gtest.h"
+#include <iosfwd>
+
+namespace clang::ssaf {
+
+class TestFixture : public ::testing::Test {
+protected:
+#define FIELD(CLASS, FIELD_NAME)                                               
\
+  static const auto &get##FIELD_NAME(const CLASS &X) { return X.FIELD_NAME; }  
\
+  static auto &get##FIELD_NAME(CLASS &X) { return X.FIELD_NAME; }
+#include "clang/Analysis/Scalable/Model/PrivateFieldNames.def"
+
+  static void PrintTo(const EntityId &, std::ostream *);
+  static void PrintTo(const SummaryName &, std::ostream *);
+};
+
+} // namespace clang::ssaf
+
+#endif // LLVM_CLANG_UNITTESTS_ANALYSIS_SCALABLE_TESTFIXTURE_H

From 334364fa74de931ab2ab9bbea584c5650f09c434 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Fri, 13 Feb 2026 20:51:58 +0100
Subject: [PATCH 2/8] Avoid overwriting the existing EntitySummary, let users
 decide

---
 .../Scalable/TUSummary/TUSummaryBuilder.h     |  14 +-
 .../Scalable/TUSummary/TUSummaryBuilder.cpp   |  12 +-
 .../Scalable/TUSummaryBuilderTest.cpp         | 155 ++++++++----------
 3 files changed, 82 insertions(+), 99 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
index ad41bfb9450bc..e6781a6348a9a 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_TUSUMMARYBUILDER_H
 
 #include <memory>
+#include <utility>
 
 namespace clang::ssaf {
 
@@ -26,10 +27,15 @@ class TUSummaryBuilder {
   /// If the entity already exists, returns the existing ID (idempotent).
   EntityId addEntity(const EntityName &E);
 
-  /// Add analysis-specific fact data for an entity.
-  /// Precondition: The ContributingEntity must have been added via 
addEntity().
-  void addFact(EntityId ContributingEntity,
-               std::unique_ptr<EntitySummary> NewData);
+  /// Associate a \p Data EntitySummary with the \p Entity.
+  /// This consumes the \p Data only if \p Entity wasn't associated yet with 
the
+  /// same kind of EntitySummary.
+  /// \returns a pointer to the EntitySummary and whether it inserted or not.
+  /// \note Be sure to pass exactly an expression of type
+  /// \sa std::unique_ptr<EntitySummary>, otherwise the conversion operator 
will
+  /// automatically consume the \p Data.
+  std::pair<EntitySummary *, bool>
+  addFact(EntityId Entity, std::unique_ptr<EntitySummary> &&Data);
 
 private:
   TUSummary &Summary;
diff --git a/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp 
b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
index f5ed26b144522..5df08c4ff069c 100644
--- a/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
+++ b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
@@ -1,7 +1,9 @@
 #include "clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h"
 #include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h"
 #include "clang/Analysis/Scalable/TUSummary/TUSummary.h"
 #include <memory>
+#include <utility>
 
 using namespace clang;
 using namespace ssaf;
@@ -10,8 +12,10 @@ EntityId TUSummaryBuilder::addEntity(const EntityName &E) {
   return Summary.IdTable.getId(E);
 }
 
-void TUSummaryBuilder::addFact(EntityId ContributingEntity,
-                               std::unique_ptr<EntitySummary> NewData) {
-  Summary.Data[NewData->getSummaryName()][ContributingEntity] =
-      std::move(NewData);
+std::pair<EntitySummary *, bool>
+TUSummaryBuilder::addFact(EntityId Entity,
+                          std::unique_ptr<EntitySummary> &&Data) {
+  auto &EntitySummaries = Summary.Data[Data->getSummaryName()];
+  auto [It, Inserted] = EntitySummaries.try_emplace(Entity, std::move(Data));
+  return {It->second.get(), Inserted};
 }
diff --git a/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp 
b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
index 2e40b5caf9ce5..9d6f3347e6d8e 100644
--- a/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
+++ b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
@@ -21,6 +21,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
+#include <memory>
 #include <type_traits>
 
 using namespace clang;
@@ -31,25 +32,25 @@ using testing::Field;
 using testing::Optional;
 using testing::UnorderedElementsAre;
 
-[[nodiscard]]
-static TUSummary makeFakeSummary() {
-  return BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
-}
-
 [[nodiscard]]
 static EntityId addTestEntity(TUSummaryBuilder &Builder, llvm::StringRef USR) {
   return Builder.addEntity(EntityName(USR, /*Suffix=*/"", /*Namespace=*/{}));
 }
 
+struct FactResult {
+  EntitySummary *Fact;
+  bool Inserted;
+};
+
 template <class ConcreteEntitySummary>
 [[nodiscard]]
-static SummaryName addFactTo(TUSummaryBuilder &Builder, EntityId ID,
-                             ConcreteEntitySummary Fact) {
+static auto addFactTo(TUSummaryBuilder &Builder, EntityId ID,
+                      ConcreteEntitySummary Fact) {
   static_assert(std::is_base_of_v<EntitySummary, ConcreteEntitySummary>);
   auto NewFact = std::make_unique<ConcreteEntitySummary>(std::move(Fact));
   SummaryName Name = NewFact->getSummaryName();
-  Builder.addFact(ID, std::move(NewFact));
-  return Name;
+  auto [Place, Inserted] = Builder.addFact(ID, std::move(NewFact));
+  return std::pair{Name, FactResult{Place, Inserted}};
 }
 
 namespace {
@@ -90,6 +91,10 @@ void PrintTo(const MockSummaryData3 &S, std::ostream *OS) {
 }
 
 struct TUSummaryBuilderTest : ssaf::TestFixture {
+  TUSummary Summary =
+      BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
+  TUSummaryBuilder Builder = TUSummaryBuilder(this->Summary);
+
   [[nodiscard]] static SmallVector<SummaryName>
   summaryNames(const TUSummary &Summary) {
     return llvm::to_vector(llvm::make_first_range(getData(Summary)));
@@ -120,9 +125,6 @@ struct TUSummaryBuilderTest : ssaf::TestFixture {
 };
 
 TEST_F(TUSummaryBuilderTest, AddEntity) {
-  TUSummary Summary = makeFakeSummary();
-  TUSummaryBuilder Builder(Summary);
-
   EntityName EN1("c:@F@foo", "", /*Namespace=*/{});
   EntityName EN2("c:@F@bar", "", /*Namespace=*/{});
 
@@ -141,11 +143,10 @@ TEST_F(TUSummaryBuilderTest, AddEntity) {
 }
 
 TEST_F(TUSummaryBuilderTest, TUSummaryBuilderAddSingleFact) {
-  TUSummary Summary = makeFakeSummary();
-  TUSummaryBuilder Builder(Summary);
-
   EntityId ID = addTestEntity(Builder, "c:@F@foo");
-  SummaryName Name = addFactTo(Builder, ID, MockSummaryData1(10));
+  auto [Name, Res] = addFactTo(Builder, ID, MockSummaryData1(10));
+  ASSERT_TRUE(Res.Inserted);
+  ASSERT_TRUE(Res.Fact);
 
   // Should have a summary type with an entity.
   EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
@@ -156,14 +157,18 @@ TEST_F(TUSummaryBuilderTest, 
TUSummaryBuilderAddSingleFact) {
 }
 
 TEST_F(TUSummaryBuilderTest, AddMultipleFactsToSameEntity) {
-  TUSummary Summary = makeFakeSummary();
-  TUSummaryBuilder Builder(Summary);
   EntityId ID = addTestEntity(Builder, "c:@F@foo");
 
   // Add different summary types to the same entity.
-  SummaryName Name1 = addFactTo(Builder, ID, MockSummaryData1(42));
-  SummaryName Name2 = addFactTo(Builder, ID, MockSummaryData2("test data"));
-  SummaryName Name3 = addFactTo(Builder, ID, MockSummaryData3(true));
+  auto [Name1, Res1] = addFactTo(Builder, ID, MockSummaryData1(42));
+  auto [Name2, Res2] = addFactTo(Builder, ID, MockSummaryData2("test data"));
+  auto [Name3, Res3] = addFactTo(Builder, ID, MockSummaryData3(true));
+  ASSERT_TRUE(Res1.Inserted);
+  ASSERT_TRUE(Res2.Inserted);
+  ASSERT_TRUE(Res3.Inserted);
+  ASSERT_TRUE(Res1.Fact);
+  ASSERT_TRUE(Res2.Fact);
+  ASSERT_TRUE(Res3.Fact);
 
   // All Names must be unique
   EXPECT_EQ((std::set<SummaryName>{Name1, Name2, Name3}.size()), 3U);
@@ -183,17 +188,20 @@ TEST_F(TUSummaryBuilderTest, 
AddMultipleFactsToSameEntity) {
 }
 
 TEST_F(TUSummaryBuilderTest, AddSameFactTypeToMultipleEntities) {
-  TUSummary Summary = makeFakeSummary();
-  TUSummaryBuilder Builder(Summary);
-
   EntityId ID1 = addTestEntity(Builder, "c:@F@foo");
   EntityId ID2 = addTestEntity(Builder, "c:@F@bar");
   EntityId ID3 = addTestEntity(Builder, "c:@F@baz");
 
   // Add the same summary type to different entities.
-  SummaryName Name1 = addFactTo(Builder, ID1, MockSummaryData1(1));
-  SummaryName Name2 = addFactTo(Builder, ID2, MockSummaryData1(2));
-  SummaryName Name3 = addFactTo(Builder, ID3, MockSummaryData1(3));
+  auto [Name1, Res1] = addFactTo(Builder, ID1, MockSummaryData1(1));
+  auto [Name2, Res2] = addFactTo(Builder, ID2, MockSummaryData1(2));
+  auto [Name3, Res3] = addFactTo(Builder, ID3, MockSummaryData1(3));
+  ASSERT_TRUE(Res1.Inserted);
+  ASSERT_TRUE(Res2.Inserted);
+  ASSERT_TRUE(Res3.Inserted);
+  ASSERT_TRUE(Res1.Fact);
+  ASSERT_TRUE(Res2.Fact);
+  ASSERT_TRUE(Res3.Fact);
 
   // All 3 should be the same summary type.
   EXPECT_THAT((llvm::ArrayRef{Name1, Name2, Name3}), testing::Each(Name1));
@@ -211,12 +219,12 @@ TEST_F(TUSummaryBuilderTest, 
AddSameFactTypeToMultipleEntities) {
               Optional(Field(&MockSummaryData1::Value, 3)));
 }
 
-TEST_F(TUSummaryBuilderTest, AddFactReplacesExistingFact) {
-  TUSummary Summary = makeFakeSummary();
-  TUSummaryBuilder Builder(Summary);
+TEST_F(TUSummaryBuilderTest, AddConflictingFactToSameEntity) {
   EntityId ID = addTestEntity(Builder, "c:@F@foo");
 
-  SummaryName Name = addFactTo(Builder, ID, MockSummaryData1(10));
+  auto [Name, Res] = addFactTo(Builder, ID, MockSummaryData1(10));
+  ASSERT_TRUE(Res.Inserted);
+  ASSERT_TRUE(Res.Fact);
 
   // Check the initial value.
   EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
@@ -224,74 +232,39 @@ TEST_F(TUSummaryBuilderTest, AddFactReplacesExistingFact) 
{
   EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID),
               Optional(Field(&MockSummaryData1::Value, 10)));
 
-  // Add another fact with the same SummaryName.
-  // This should replace the previous fact.
-  SummaryName ReplacementName = addFactTo(Builder, ID, MockSummaryData1(20));
-  ASSERT_EQ(ReplacementName, Name);
-
-  // Check that the value was replaced.
-  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
-  EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID));
-  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID),
-              Optional(Field(&MockSummaryData1::Value, 20)));
-}
-
-TEST_F(TUSummaryBuilderTest, AddFactsComplexScenario) {
-  TUSummary Summary = makeFakeSummary();
-  TUSummaryBuilder Builder(Summary);
-
-  EntityId ID1 = addTestEntity(Builder, "c:@F@foo");
-  EntityId ID2 = addTestEntity(Builder, "c:@F@bar");
-
-  SummaryName Name1 = addFactTo(Builder, ID1, MockSummaryData1(10));
-  SummaryName Name2 = addFactTo(Builder, ID1, MockSummaryData2("twenty"));
+  // This is a different fact of the same kind.
+  std::unique_ptr<EntitySummary> NewFact =
+      std::make_unique<MockSummaryData1>(20);
+  ASSERT_EQ(NewFact->getSummaryName(), Name);
 
-  SummaryName Name3 = addFactTo(Builder, ID2, MockSummaryData1(30));
-  SummaryName Name4 = addFactTo(Builder, ID2, MockSummaryData3(true));
+  // Let's add this different fact.
+  // This should keep the map intact and give us the existing entity summary.
+  auto [Slot, Inserted] = Builder.addFact(ID, std::move(NewFact));
+  ASSERT_FALSE(Inserted);
+  ASSERT_TRUE(Slot);
 
-  // Check that we have only 3 distinct summary names.
-  EXPECT_EQ(Name1, Name3);
-  EXPECT_THAT((std::set{Name1, Name2, Name3, Name4}),
-              UnorderedElementsAre(Name1, Name2, Name4));
+  // Check that the fact object is not consumed and remained the same.
+  ASSERT_TRUE(NewFact);
+  ASSERT_EQ(static_cast<MockSummaryData1 *>(NewFact.get())->Value, 20);
 
-  // Check that we have two facts for the two summaries each.
-  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name1, Name2, 
Name4));
-  EXPECT_THAT(entitiesOfSummary(Summary, Name1),
-              UnorderedElementsAre(ID1, ID2));
-  EXPECT_THAT(entitiesOfSummary(Summary, Name2), UnorderedElementsAre(ID1));
-  EXPECT_THAT(entitiesOfSummary(Summary, Name4), UnorderedElementsAre(ID2));
+  // Check that the Slot refers to the existing entity summary.
+  ASSERT_EQ(Slot->getSummaryName(), Name);
+  ASSERT_EQ(static_cast<MockSummaryData1 *>(Slot)->Value, 10);
 
-  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID1),
+  // Check that the values remained the same.
+  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID),
               Optional(Field(&MockSummaryData1::Value, 10)));
-  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID2),
-              Optional(Field(&MockSummaryData1::Value, 30)));
-  EXPECT_THAT(getAsEntitySummary<MockSummaryData2>(Summary, Name2, ID1),
-              Optional(Field(&MockSummaryData2::Text, "twenty")));
-  EXPECT_THAT(getAsEntitySummary<MockSummaryData3>(Summary, Name4, ID2),
-              Optional(Field(&MockSummaryData3::Flag, true)));
 
-  // Replace a fact of Name1 on entity 1 with the new value 50.
-  SummaryName Name5 = addFactTo(Builder, ID1, MockSummaryData1(50));
-  ASSERT_EQ(Name5, Name1);
+  // We can update the existing summary.
+  static_cast<MockSummaryData1 *>(Res.Fact)->Value = 30;
 
-  // Check that the summary names and entity IDs didn't change.
-  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name1, Name2, 
Name4));
-  EXPECT_THAT(entitiesOfSummary(Summary, Name1),
-              UnorderedElementsAre(ID1, ID2));
-  EXPECT_THAT(entitiesOfSummary(Summary, Name2), UnorderedElementsAre(ID1));
-  EXPECT_THAT(entitiesOfSummary(Summary, Name4), UnorderedElementsAre(ID2));
-
-  // Check the Name1 ID1 entity summary value was changed to 50.
-  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID1),
-              Optional(Field(&MockSummaryData1::Value, 50)));
-
-  // Check that the rest remained the same.
-  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name1, ID2),
+  // Check that the values remained the same except what we updated.
+  EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
+  EXPECT_THAT(entitiesOfSummary(Summary, Name), UnorderedElementsAre(ID));
+  EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID),
               Optional(Field(&MockSummaryData1::Value, 30)));
-  EXPECT_THAT(getAsEntitySummary<MockSummaryData2>(Summary, Name2, ID1),
-              Optional(Field(&MockSummaryData2::Text, "twenty")));
-  EXPECT_THAT(getAsEntitySummary<MockSummaryData3>(Summary, Name4, ID2),
-              Optional(Field(&MockSummaryData3::Flag, true)));
 }
 
 } // namespace

From d78514cdc14893ca264f855f1e65ba28fcedad72 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Tue, 17 Feb 2026 13:49:48 +0100
Subject: [PATCH 3/8] Rename addFact -> addSummary

---
 .../Scalable/TUSummary/TUSummaryBuilder.h     |  2 +-
 .../Scalable/TUSummary/TUSummaryBuilder.cpp   |  4 +-
 .../Scalable/TUSummaryBuilderTest.cpp         | 74 +++++++++----------
 3 files changed, 40 insertions(+), 40 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
index e6781a6348a9a..8f5c0d9956d94 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
@@ -35,7 +35,7 @@ class TUSummaryBuilder {
   /// \sa std::unique_ptr<EntitySummary>, otherwise the conversion operator 
will
   /// automatically consume the \p Data.
   std::pair<EntitySummary *, bool>
-  addFact(EntityId Entity, std::unique_ptr<EntitySummary> &&Data);
+  addSummary(EntityId Entity, std::unique_ptr<EntitySummary> &&Data);
 
 private:
   TUSummary &Summary;
diff --git a/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp 
b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
index 5df08c4ff069c..4015117d6944d 100644
--- a/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
+++ b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
@@ -13,8 +13,8 @@ EntityId TUSummaryBuilder::addEntity(const EntityName &E) {
 }
 
 std::pair<EntitySummary *, bool>
-TUSummaryBuilder::addFact(EntityId Entity,
-                          std::unique_ptr<EntitySummary> &&Data) {
+TUSummaryBuilder::addSummary(EntityId Entity,
+                             std::unique_ptr<EntitySummary> &&Data) {
   auto &EntitySummaries = Summary.Data[Data->getSummaryName()];
   auto [It, Inserted] = EntitySummaries.try_emplace(Entity, std::move(Data));
   return {It->second.get(), Inserted};
diff --git a/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp 
b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
index 9d6f3347e6d8e..cb0ad62d944c3 100644
--- a/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
+++ b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
@@ -37,20 +37,20 @@ static EntityId addTestEntity(TUSummaryBuilder &Builder, 
llvm::StringRef USR) {
   return Builder.addEntity(EntityName(USR, /*Suffix=*/"", /*Namespace=*/{}));
 }
 
-struct FactResult {
-  EntitySummary *Fact;
+struct SummaryResult {
+  EntitySummary *Summary;
   bool Inserted;
 };
 
 template <class ConcreteEntitySummary>
 [[nodiscard]]
-static auto addFactTo(TUSummaryBuilder &Builder, EntityId ID,
-                      ConcreteEntitySummary Fact) {
+static auto addSummaryTo(TUSummaryBuilder &Builder, EntityId ID,
+                         ConcreteEntitySummary Summary) {
   static_assert(std::is_base_of_v<EntitySummary, ConcreteEntitySummary>);
-  auto NewFact = std::make_unique<ConcreteEntitySummary>(std::move(Fact));
-  SummaryName Name = NewFact->getSummaryName();
-  auto [Place, Inserted] = Builder.addFact(ID, std::move(NewFact));
-  return std::pair{Name, FactResult{Place, Inserted}};
+  auto NewSummary = 
std::make_unique<ConcreteEntitySummary>(std::move(Summary));
+  SummaryName Name = NewSummary->getSummaryName();
+  auto [Place, Inserted] = Builder.addSummary(ID, std::move(NewSummary));
+  return std::pair{Name, SummaryResult{Place, Inserted}};
 }
 
 namespace {
@@ -142,11 +142,11 @@ TEST_F(TUSummaryBuilderTest, AddEntity) {
   EXPECT_TRUE(IdTable.contains(EN2));
 }
 
-TEST_F(TUSummaryBuilderTest, TUSummaryBuilderAddSingleFact) {
+TEST_F(TUSummaryBuilderTest, TUSummaryBuilderAddSingleSummary) {
   EntityId ID = addTestEntity(Builder, "c:@F@foo");
-  auto [Name, Res] = addFactTo(Builder, ID, MockSummaryData1(10));
+  auto [Name, Res] = addSummaryTo(Builder, ID, MockSummaryData1(10));
   ASSERT_TRUE(Res.Inserted);
-  ASSERT_TRUE(Res.Fact);
+  ASSERT_TRUE(Res.Summary);
 
   // Should have a summary type with an entity.
   EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
@@ -156,19 +156,19 @@ TEST_F(TUSummaryBuilderTest, 
TUSummaryBuilderAddSingleFact) {
               Optional(Field(&MockSummaryData1::Value, 10)));
 }
 
-TEST_F(TUSummaryBuilderTest, AddMultipleFactsToSameEntity) {
+TEST_F(TUSummaryBuilderTest, AddMultipleSummariesToSameEntity) {
   EntityId ID = addTestEntity(Builder, "c:@F@foo");
 
   // Add different summary types to the same entity.
-  auto [Name1, Res1] = addFactTo(Builder, ID, MockSummaryData1(42));
-  auto [Name2, Res2] = addFactTo(Builder, ID, MockSummaryData2("test data"));
-  auto [Name3, Res3] = addFactTo(Builder, ID, MockSummaryData3(true));
+  auto [Name1, Res1] = addSummaryTo(Builder, ID, MockSummaryData1(42));
+  auto [Name2, Res2] = addSummaryTo(Builder, ID, MockSummaryData2("test 
data"));
+  auto [Name3, Res3] = addSummaryTo(Builder, ID, MockSummaryData3(true));
   ASSERT_TRUE(Res1.Inserted);
   ASSERT_TRUE(Res2.Inserted);
   ASSERT_TRUE(Res3.Inserted);
-  ASSERT_TRUE(Res1.Fact);
-  ASSERT_TRUE(Res2.Fact);
-  ASSERT_TRUE(Res3.Fact);
+  ASSERT_TRUE(Res1.Summary);
+  ASSERT_TRUE(Res2.Summary);
+  ASSERT_TRUE(Res3.Summary);
 
   // All Names must be unique
   EXPECT_EQ((std::set<SummaryName>{Name1, Name2, Name3}.size()), 3U);
@@ -187,21 +187,21 @@ TEST_F(TUSummaryBuilderTest, 
AddMultipleFactsToSameEntity) {
               Optional(Field(&MockSummaryData3::Flag, true)));
 }
 
-TEST_F(TUSummaryBuilderTest, AddSameFactTypeToMultipleEntities) {
+TEST_F(TUSummaryBuilderTest, AddSameSummaryTypeToMultipleEntities) {
   EntityId ID1 = addTestEntity(Builder, "c:@F@foo");
   EntityId ID2 = addTestEntity(Builder, "c:@F@bar");
   EntityId ID3 = addTestEntity(Builder, "c:@F@baz");
 
   // Add the same summary type to different entities.
-  auto [Name1, Res1] = addFactTo(Builder, ID1, MockSummaryData1(1));
-  auto [Name2, Res2] = addFactTo(Builder, ID2, MockSummaryData1(2));
-  auto [Name3, Res3] = addFactTo(Builder, ID3, MockSummaryData1(3));
+  auto [Name1, Res1] = addSummaryTo(Builder, ID1, MockSummaryData1(1));
+  auto [Name2, Res2] = addSummaryTo(Builder, ID2, MockSummaryData1(2));
+  auto [Name3, Res3] = addSummaryTo(Builder, ID3, MockSummaryData1(3));
   ASSERT_TRUE(Res1.Inserted);
   ASSERT_TRUE(Res2.Inserted);
   ASSERT_TRUE(Res3.Inserted);
-  ASSERT_TRUE(Res1.Fact);
-  ASSERT_TRUE(Res2.Fact);
-  ASSERT_TRUE(Res3.Fact);
+  ASSERT_TRUE(Res1.Summary);
+  ASSERT_TRUE(Res2.Summary);
+  ASSERT_TRUE(Res3.Summary);
 
   // All 3 should be the same summary type.
   EXPECT_THAT((llvm::ArrayRef{Name1, Name2, Name3}), testing::Each(Name1));
@@ -219,12 +219,12 @@ TEST_F(TUSummaryBuilderTest, 
AddSameFactTypeToMultipleEntities) {
               Optional(Field(&MockSummaryData1::Value, 3)));
 }
 
-TEST_F(TUSummaryBuilderTest, AddConflictingFactToSameEntity) {
+TEST_F(TUSummaryBuilderTest, AddConflictingSummaryToSameEntity) {
   EntityId ID = addTestEntity(Builder, "c:@F@foo");
 
-  auto [Name, Res] = addFactTo(Builder, ID, MockSummaryData1(10));
+  auto [Name, Res] = addSummaryTo(Builder, ID, MockSummaryData1(10));
   ASSERT_TRUE(Res.Inserted);
-  ASSERT_TRUE(Res.Fact);
+  ASSERT_TRUE(Res.Summary);
 
   // Check the initial value.
   EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));
@@ -232,20 +232,20 @@ TEST_F(TUSummaryBuilderTest, 
AddConflictingFactToSameEntity) {
   EXPECT_THAT(getAsEntitySummary<MockSummaryData1>(Summary, Name, ID),
               Optional(Field(&MockSummaryData1::Value, 10)));
 
-  // This is a different fact of the same kind.
-  std::unique_ptr<EntitySummary> NewFact =
+  // This is a different summary of the same kind.
+  std::unique_ptr<EntitySummary> NewSummary =
       std::make_unique<MockSummaryData1>(20);
-  ASSERT_EQ(NewFact->getSummaryName(), Name);
+  ASSERT_EQ(NewSummary->getSummaryName(), Name);
 
-  // Let's add this different fact.
+  // Let's add this different summary.
   // This should keep the map intact and give us the existing entity summary.
-  auto [Slot, Inserted] = Builder.addFact(ID, std::move(NewFact));
+  auto [Slot, Inserted] = Builder.addSummary(ID, std::move(NewSummary));
   ASSERT_FALSE(Inserted);
   ASSERT_TRUE(Slot);
 
-  // Check that the fact object is not consumed and remained the same.
-  ASSERT_TRUE(NewFact);
-  ASSERT_EQ(static_cast<MockSummaryData1 *>(NewFact.get())->Value, 20);
+  // Check that the summary object is not consumed and remained the same.
+  ASSERT_TRUE(NewSummary);
+  ASSERT_EQ(static_cast<MockSummaryData1 *>(NewSummary.get())->Value, 20);
 
   // Check that the Slot refers to the existing entity summary.
   ASSERT_EQ(Slot->getSummaryName(), Name);
@@ -258,7 +258,7 @@ TEST_F(TUSummaryBuilderTest, 
AddConflictingFactToSameEntity) {
               Optional(Field(&MockSummaryData1::Value, 10)));
 
   // We can update the existing summary.
-  static_cast<MockSummaryData1 *>(Res.Fact)->Value = 30;
+  static_cast<MockSummaryData1 *>(Res.Summary)->Value = 30;
 
   // Check that the values remained the same except what we updated.
   EXPECT_THAT(summaryNames(Summary), UnorderedElementsAre(Name));

From 37984123724de4a72e72b4ef0be64e694f6ff18e Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Tue, 17 Feb 2026 14:37:53 +0100
Subject: [PATCH 4/8] Fix a -> the in doc comment

---
 .../clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h        | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
index 8f5c0d9956d94..89ed10eb276cc 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
@@ -27,7 +27,7 @@ class TUSummaryBuilder {
   /// If the entity already exists, returns the existing ID (idempotent).
   EntityId addEntity(const EntityName &E);
 
-  /// Associate a \p Data EntitySummary with the \p Entity.
+  /// Associate the \p Data EntitySummary with the \p Entity.
   /// This consumes the \p Data only if \p Entity wasn't associated yet with 
the
   /// same kind of EntitySummary.
   /// \returns a pointer to the EntitySummary and whether it inserted or not.

From 7becb2073777d5536532d1885dbc02d9293ba2f4 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Tue, 17 Feb 2026 14:42:25 +0100
Subject: [PATCH 5/8] Make addSummary a template to avoid uptr conversion ctor
 to kick in consuming Data

By wrapping addSummary we can restore the Data to be "unconsumed" even
on insertion failure to make it behave like "try_emplace" would.

The downside is some template metaprogram to ensure that the uptr has
the right type and we need to put this code into the header due to the
template.
---
 .../Scalable/TUSummary/EntitySummary.h        |  5 ++++
 .../Scalable/TUSummary/TUSummaryBuilder.h     | 23 ++++++++++++++-----
 .../Scalable/TUSummary/TUSummaryBuilder.cpp   |  4 ++--
 3 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/EntitySummary.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/EntitySummary.h
index 4bdb385d49a01..dddd69cb7d372 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/EntitySummary.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/EntitySummary.h
@@ -10,6 +10,7 @@
 #define LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_ENTITYSUMMARY_H
 
 #include "clang/Analysis/Scalable/Model/SummaryName.h"
+#include <type_traits>
 
 namespace clang::ssaf {
 
@@ -20,6 +21,10 @@ class EntitySummary {
   virtual SummaryName getSummaryName() const = 0;
 };
 
+template <typename Derived>
+using DerivesFromEntitySummary =
+    std::enable_if_t<std::is_base_of_v<EntitySummary, Derived>>;
+
 } // namespace clang::ssaf
 
 #endif // LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_ENTITYSUMMARY_H
diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
index 89ed10eb276cc..3a93178533bc6 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
@@ -9,14 +9,14 @@
 #ifndef LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_TUSUMMARYBUILDER_H
 #define LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_TUSUMMARYBUILDER_H
 
+#include "clang/Analysis/Scalable/Model/EntityId.h"
+#include "clang/Analysis/Scalable/TUSummary/EntitySummary.h"
 #include <memory>
 #include <utility>
 
 namespace clang::ssaf {
 
-class EntityId;
 class EntityName;
-class EntitySummary;
 class TUSummary;
 
 class TUSummaryBuilder {
@@ -31,14 +31,25 @@ class TUSummaryBuilder {
   /// This consumes the \p Data only if \p Entity wasn't associated yet with 
the
   /// same kind of EntitySummary.
   /// \returns a pointer to the EntitySummary and whether it inserted or not.
-  /// \note Be sure to pass exactly an expression of type
-  /// \sa std::unique_ptr<EntitySummary>, otherwise the conversion operator 
will
-  /// automatically consume the \p Data.
+  template <typename ConcreteEntitySummary,
+            DerivesFromEntitySummary<ConcreteEntitySummary> * = nullptr>
   std::pair<EntitySummary *, bool>
-  addSummary(EntityId Entity, std::unique_ptr<EntitySummary> &&Data);
+  addSummary(EntityId Entity, std::unique_ptr<ConcreteEntitySummary> &&Data) {
+    std::unique_ptr<EntitySummary> TypeErasedData = std::move(Data);
+    auto [It, Inserted] = addSummaryImpl(Entity, std::move(TypeErasedData));
+    // Move it back on failue to keep the `Data` unconsumed.
+    if (!Inserted) {
+      Data = std::unique_ptr<ConcreteEntitySummary>(
+          static_cast<ConcreteEntitySummary *>(TypeErasedData.release()));
+    }
+    return {It, Inserted};
+  }
 
 private:
   TUSummary &Summary;
+
+  std::pair<EntitySummary *, bool>
+  addSummaryImpl(EntityId Entity, std::unique_ptr<EntitySummary> &&Data);
 };
 
 } // namespace clang::ssaf
diff --git a/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp 
b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
index 4015117d6944d..61c332e831ccb 100644
--- a/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
+++ b/clang/lib/Analysis/Scalable/TUSummary/TUSummaryBuilder.cpp
@@ -13,8 +13,8 @@ EntityId TUSummaryBuilder::addEntity(const EntityName &E) {
 }
 
 std::pair<EntitySummary *, bool>
-TUSummaryBuilder::addSummary(EntityId Entity,
-                             std::unique_ptr<EntitySummary> &&Data) {
+TUSummaryBuilder::addSummaryImpl(EntityId Entity,
+                                 std::unique_ptr<EntitySummary> &&Data) {
   auto &EntitySummaries = Summary.Data[Data->getSummaryName()];
   auto [It, Inserted] = EntitySummaries.try_emplace(Entity, std::move(Data));
   return {It->second.get(), Inserted};

From 0e62ee5b6dbd7c4c2cf3258452e13c9f65f21d63 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Tue, 17 Feb 2026 14:51:33 +0100
Subject: [PATCH 6/8] Uplift test now that the uptr API issue is fixed

---
 clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp 
b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
index cb0ad62d944c3..f70b39b97b841 100644
--- a/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
+++ b/clang/unittests/Analysis/Scalable/TUSummaryBuilderTest.cpp
@@ -233,8 +233,7 @@ TEST_F(TUSummaryBuilderTest, 
AddConflictingSummaryToSameEntity) {
               Optional(Field(&MockSummaryData1::Value, 10)));
 
   // This is a different summary of the same kind.
-  std::unique_ptr<EntitySummary> NewSummary =
-      std::make_unique<MockSummaryData1>(20);
+  auto NewSummary = std::make_unique<MockSummaryData1>(20);
   ASSERT_EQ(NewSummary->getSummaryName(), Name);
 
   // Let's add this different summary.

From 7ee53633391b2ebcd8595a0b63b5c769eed0e85b Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Tue, 17 Feb 2026 19:03:48 +0100
Subject: [PATCH 7/8] Move addSummary out-of-line, add more doxygen markers

---
 .../Scalable/TUSummary/TUSummaryBuilder.h     | 32 +++++++++++--------
 1 file changed, 19 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
index 3a93178533bc6..a21eccae3d30a 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
@@ -27,23 +27,14 @@ class TUSummaryBuilder {
   /// If the entity already exists, returns the existing ID (idempotent).
   EntityId addEntity(const EntityName &E);
 
-  /// Associate the \p Data EntitySummary with the \p Entity.
+  /// Associate the \p Data \c EntitySummary with the \p Entity.
   /// This consumes the \p Data only if \p Entity wasn't associated yet with 
the
-  /// same kind of EntitySummary.
-  /// \returns a pointer to the EntitySummary and whether it inserted or not.
+  /// same kind of \c EntitySummary.
+  /// \returns a pointer to the \c EntitySummary and whether it inserted or 
not.
   template <typename ConcreteEntitySummary,
             DerivesFromEntitySummary<ConcreteEntitySummary> * = nullptr>
   std::pair<EntitySummary *, bool>
-  addSummary(EntityId Entity, std::unique_ptr<ConcreteEntitySummary> &&Data) {
-    std::unique_ptr<EntitySummary> TypeErasedData = std::move(Data);
-    auto [It, Inserted] = addSummaryImpl(Entity, std::move(TypeErasedData));
-    // Move it back on failue to keep the `Data` unconsumed.
-    if (!Inserted) {
-      Data = std::unique_ptr<ConcreteEntitySummary>(
-          static_cast<ConcreteEntitySummary *>(TypeErasedData.release()));
-    }
-    return {It, Inserted};
-  }
+  addSummary(EntityId Entity, std::unique_ptr<ConcreteEntitySummary> &&Data);
 
 private:
   TUSummary &Summary;
@@ -52,6 +43,21 @@ class TUSummaryBuilder {
   addSummaryImpl(EntityId Entity, std::unique_ptr<EntitySummary> &&Data);
 };
 
+template <typename ConcreteEntitySummary,
+          DerivesFromEntitySummary<ConcreteEntitySummary> *>
+std::pair<EntitySummary *, bool>
+TUSummaryBuilder::addSummary(EntityId Entity,
+                             std::unique_ptr<ConcreteEntitySummary> &&Data) {
+  std::unique_ptr<EntitySummary> TypeErasedData = std::move(Data);
+  auto [It, Inserted] = addSummaryImpl(Entity, std::move(TypeErasedData));
+  // Move it back on failue to keep the `Data` unconsumed.
+  if (!Inserted) {
+    Data = std::unique_ptr<ConcreteEntitySummary>(
+        static_cast<ConcreteEntitySummary *>(TypeErasedData.release()));
+  }
+  return {It, Inserted};
+}
+
 } // namespace clang::ssaf
 
 #endif // LLVM_CLANG_ANALYSIS_SCALABLE_TUSUMMARY_TUSUMMARYBUILDER_H

From 66d3cf0a9c1fef6bb27768835558591f82c5b716 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Tue, 17 Feb 2026 20:08:31 +0100
Subject: [PATCH 8/8] Add more comments

---
 .../Analysis/Scalable/TUSummary/TUSummaryBuilder.h  | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h 
b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
index a21eccae3d30a..64d8adc0791c1 100644
--- a/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
+++ b/clang/include/clang/Analysis/Scalable/TUSummary/TUSummaryBuilder.h
@@ -43,13 +43,26 @@ class TUSummaryBuilder {
   addSummaryImpl(EntityId Entity, std::unique_ptr<EntitySummary> &&Data);
 };
 
+// Why is this a template?
+//
+// We use template here to avoid an implicit conversion from
+// `std::unique_ptr<ConcreteEntitySummary>` to `std::unique_ptr<EntitySummary>`
+// because constructing that implicit temporary would unconditionally "consume"
+// the Data. This would make it impossible to recover from the call-site the
+// Data you pass in even if no insertion happens.
 template <typename ConcreteEntitySummary,
           DerivesFromEntitySummary<ConcreteEntitySummary> *>
 std::pair<EntitySummary *, bool>
 TUSummaryBuilder::addSummary(EntityId Entity,
                              std::unique_ptr<ConcreteEntitySummary> &&Data) {
+  // Prepare a unique_ptr of the base type to avoid implicit conversions at the
+  // call-site.
   std::unique_ptr<EntitySummary> TypeErasedData = std::move(Data);
+
+  // This only moves (consumes) TypeErasedData if insertion happened.
+  // Otherwise it doesn't touch the `TypeErasedData`.
   auto [It, Inserted] = addSummaryImpl(Entity, std::move(TypeErasedData));
+
   // Move it back on failue to keep the `Data` unconsumed.
   if (!Inserted) {
     Data = std::unique_ptr<ConcreteEntitySummary>(

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

Reply via email to