https://github.com/ipopov updated 
https://github.com/llvm/llvm-project/pull/202248

>From b2b8d4fae2b3674a04db48afe4daaf81b61975ea Mon Sep 17 00:00:00 2001
From: Ivo Popov <[email protected]>
Date: Mon, 8 Jun 2026 02:49:00 +0000
Subject: [PATCH 1/4] [Clang][Modules] Fix generic lambda local capture mapping
 mismatch in template instantiation

---
 clang/include/clang/Sema/Sema.h               |   8 +
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |  21 ++-
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  64 ++++++++
 clang/lib/Serialization/ASTWriter.cpp         |   9 +-
 .../modules-generic-lambda-local-capture.cpp  | 137 ++++++++++++++++++
 5 files changed, 226 insertions(+), 13 deletions(-)
 create mode 100644 clang/test/Modules/modules-generic-lambda-local-capture.cpp

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ff474fdd99562..bcd4dfe0728e7 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -13100,6 +13100,14 @@ class Sema final : public SemaBase {
   /// variables.
   LocalInstantiationScope *CurrentInstantiationScope;
 
+  /// A mapping from canonical function definitions to maps of their local
+  /// declarations to canonical local declarations.
+  llvm::DenseMap<const DeclContext *,
+                 llvm::DenseMap<const Decl *, const Decl *>>
+      CanonicalLocalDecls;
+
+  const Decl *getCanonicalLocalDecl(const Decl *D);
+
   typedef llvm::DenseMap<ParmVarDecl *, llvm::TinyPtrVector<ParmVarDecl *>>
       UnparsedDefaultArgInstantiationsMap;
 
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp 
b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index 6df6d5505c61c..da5b8b412404d 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -4399,26 +4399,23 @@ Sema::SubstTemplateName(SourceLocation TemplateKWLoc,
                                             NameLoc);
 }
 
-static const Decl *getCanonicalParmVarDecl(const Decl *D) {
-  // When storing ParmVarDecls in the local instantiation scope, we always
-  // want to use the ParmVarDecl from the canonical function declaration,
-  // since the map is then valid for any redeclaration or definition of that
-  // function.
+static const Decl *getCanonicalLocalDecl(const Decl *D, Sema &SemaRef) {
   if (const ParmVarDecl *PV = dyn_cast<ParmVarDecl>(D)) {
     if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(PV->getDeclContext())) 
{
       unsigned i = PV->getFunctionScopeIndex();
-      // This parameter might be from a freestanding function type within the
-      // function and isn't necessarily referring to one of FD's parameters.
-      if (i < FD->getNumParams() && FD->getParamDecl(i) == PV)
+      if (i < FD->getNumParams() && FD->getParamDecl(i) == PV) {
         return FD->getCanonicalDecl()->getParamDecl(i);
     }
   }
+  } else if (isa<VarDecl>(D) || isa<BindingDecl>(D)) {
+    return SemaRef.getCanonicalLocalDecl(D);
+  }
   return D;
 }
 
 llvm::PointerUnion<Decl *, LocalInstantiationScope::DeclArgumentPack *> *
 LocalInstantiationScope::getInstantiationOfIfExists(const Decl *D) {
-  D = getCanonicalParmVarDecl(D);
+  D = getCanonicalLocalDecl(D, SemaRef);
   for (LocalInstantiationScope *Current = this; Current;
        Current = Current->Outer) {
 
@@ -4480,7 +4477,7 @@ LocalInstantiationScope::findInstantiationOf(const Decl 
*D) {
 }
 
 void LocalInstantiationScope::InstantiatedLocal(const Decl *D, Decl *Inst) {
-  D = getCanonicalParmVarDecl(D);
+  D = getCanonicalLocalDecl(D, SemaRef);
   llvm::PointerUnion<Decl *, DeclArgumentPack *> &Stored = LocalDecls[D];
   if (Stored.isNull()) {
 #ifndef NDEBUG
@@ -4502,7 +4499,7 @@ void LocalInstantiationScope::InstantiatedLocal(const 
Decl *D, Decl *Inst) {
 
 void LocalInstantiationScope::InstantiatedLocalPackArg(const Decl *D,
                                                        VarDecl *Inst) {
-  D = getCanonicalParmVarDecl(D);
+  D = getCanonicalLocalDecl(D, SemaRef);
   DeclArgumentPack *Pack = cast<DeclArgumentPack *>(LocalDecls[D]);
   Pack->push_back(Inst);
 }
@@ -4516,7 +4513,7 @@ void 
LocalInstantiationScope::MakeInstantiatedLocalArgPack(const Decl *D) {
            "Creating local pack after instantiation of local");
 #endif
 
-  D = getCanonicalParmVarDecl(D);
+  D = getCanonicalLocalDecl(D, SemaRef);
   llvm::PointerUnion<Decl *, DeclArgumentPack *> &Stored = LocalDecls[D];
   DeclArgumentPack *Pack = new DeclArgumentPack;
   Stored = Pack;
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp 
b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index aa381f09138de..1cf5497078cc3 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -7421,3 +7421,67 @@ void Sema::PerformDependentDiagnostics(const DeclContext 
*Pattern,
     }
   }
 }
+
+static bool isMappedLocalDecl(const Decl *D) {
+  if (const auto *VD = dyn_cast<VarDecl>(D)) {
+    return VD->isLocalVarDeclOrParm() && !isa<ParmVarDecl>(VD);
+  }
+  return isa<BindingDecl>(D);
+}
+
+const Decl *Sema::getCanonicalLocalDecl(const Decl *D) {
+  if (isa<ParmVarDecl>(D)) {
+    return D;
+  }
+
+  const auto *VD = dyn_cast<VarDecl>(D);
+  const auto *BD = dyn_cast<BindingDecl>(D);
+  if (!VD && !BD) {
+    return D;
+  }
+
+  if (VD && !VD->isLocalVarDeclOrParm()) {
+    return D;
+  }
+
+  const DeclContext *DC = VD ? VD->getDeclContext() : BD->getDeclContext();
+  const auto *FD = dyn_cast<FunctionDecl>(DC);
+  if (!FD) {
+    return D;
+  }
+
+  const auto *CanonFD = FD->getCanonicalDecl();
+  if (FD == CanonFD) {
+    return D;
+  }
+
+  auto &Map = CanonicalLocalDecls[CanonFD];
+  if (Map.empty()) {
+    SmallVector<const Decl *, 8> CanonDecls;
+    for (const auto *De : CanonFD->decls()) {
+      if (isMappedLocalDecl(De)) {
+        CanonDecls.push_back(De);
+      }
+    }
+
+    SmallVector<const Decl *, 8> LocalDecls;
+    for (const auto *De : FD->decls()) {
+      if (isMappedLocalDecl(De)) {
+        LocalDecls.push_back(De);
+      }
+    }
+
+    if (CanonDecls.size() == LocalDecls.size()) {
+      for (size_t I = 0; I < LocalDecls.size(); ++I) {
+        Map[LocalDecls[I]] = CanonDecls[I];
+      }
+    }
+  }
+
+  const auto It = Map.find(D);
+  if (It != Map.end()) {
+    return It->second;
+  }
+
+  return D;
+}
diff --git a/clang/lib/Serialization/ASTWriter.cpp 
b/clang/lib/Serialization/ASTWriter.cpp
index 6497c22a762ae..8f0174d6f470c 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -3455,8 +3455,15 @@ uint64_t 
ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
     return 0;
 
   // In reduced BMI, we don't care the declarations in functions.
-  if (GeneratingReducedBMI && DC->isFunctionOrMethod())
+  if (GeneratingReducedBMI && DC->isFunctionOrMethod()) {
+    if (const auto *FD = dyn_cast<FunctionDecl>(DC)) {
+      if (!FD->isDependentContext() || !FD->isThisDeclarationADefinition()) {
     return 0;
+      }
+    } else {
+      return 0;
+    }
+  }
 
   uint64_t Offset = Stream.GetCurrentBitNo();
   SmallVector<DeclID, 128> KindDeclPairs;
diff --git a/clang/test/Modules/modules-generic-lambda-local-capture.cpp 
b/clang/test/Modules/modules-generic-lambda-local-capture.cpp
new file mode 100644
index 0000000000000..ca307ac796a90
--- /dev/null
+++ b/clang/test/Modules/modules-generic-lambda-local-capture.cpp
@@ -0,0 +1,137 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: cd %t
+//
+// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
+// RUN:            -fmodules-local-submodule-visibility \
+// RUN:            -fmodule-map-file=module.modulemap \
+// RUN:            -fmodule-name=repro_module_a -emit-module \
+// RUN:            -fmodules-embed-all-files -x c++ module.modulemap \
+// RUN:            -o repro_module_a.pcm
+//
+// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
+// RUN:            -fmodules-local-submodule-visibility \
+// RUN:            -fmodule-map-file=module.modulemap \
+// RUN:            -fmodule-name=repro_wrapper_mock \
+// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
+// RUN:            -emit-module -fmodules-embed-all-files -x c++ 
module.modulemap \
+// RUN:            -o repro_wrapper_mock.pcm
+//
+// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
+// RUN:            -fmodules-local-submodule-visibility \
+// RUN:            -fmodule-map-file=module.modulemap \
+// RUN:            -fmodule-name=repro \
+// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
+// RUN:            -fmodule-file=repro_wrapper_mock=repro_wrapper_mock.pcm \
+// RUN:            -fsyntax-only repro_main.cpp
+
+//--- module.modulemap
+module TemplateClassModule {
+  textual header "template_class.h"
+}
+module repro_module_a {
+  header "module_a.h"
+  export *
+  use TemplateClassModule
+}
+module repro_wrapper_mock {
+  header "repro_wrapper.h"
+  export *
+  use repro_module_a
+  use TemplateClassModule
+}
+module repro {
+  export *
+  use repro_wrapper_mock
+}
+
+//--- template_class.h
+#ifndef TEMPLATE_CLASS_H_
+#define TEMPLATE_CLASS_H_
+namespace std {
+template <typename T>
+T&& move(T& t) noexcept { return static_cast<T&&>(t); }
+}
+
+enum class MyEnum { kValue1, kValue2 };
+inline MyEnum GetLocalValue() { return MyEnum::kValue2; }
+
+struct Pair { MyEnum first; MyEnum second; };
+inline Pair GetLocalPair() { return Pair{MyEnum::kValue1, MyEnum::kValue2}; }
+
+template <typename T>
+struct Consumer {
+  template <typename U>
+  void operator()(const U& x) const {}
+};
+
+template <typename F>
+struct Holder {
+  F f;
+  explicit Holder(F f) : f(std::move(f)) {}
+  void call() const { f(0); }
+};
+
+template <typename F>
+auto make_holder(F f) {
+  return Holder<F>(std::move(f));
+}
+
+template <typename T>
+class TemplateClass {
+ public:
+  template <typename... Args>
+  explicit TemplateClass(Args&&... args) {
+    const auto local_val = GetLocalValue();
+    const auto [a, b] = GetLocalPair();
+    auto holder = make_holder([&](auto x) {
+      Consumer<decltype(x)>{}(local_val);
+      Consumer<decltype(x)>{}(a);
+    });
+    holder.call();
+  }
+};
+#endif
+
+//--- module_a.h
+#pragma once
+#include "template_class.h"
+
+//--- repro_wrapper.h
+#ifndef REPRO_WRAPPER_H_
+#define REPRO_WRAPPER_H_
+#include "template_class.h"
+#include "module_a.h"
+inline void TriggerInstantiation() {
+  TemplateClass<void> p;
+}
+#endif
+
+//--- repro_token.h
+#ifndef REPRO_TOKEN_H_
+#define REPRO_TOKEN_H_
+#include "template_class.h"
+#include "module_a.h"
+template <typename... Ts>
+struct MyOverload : Ts... {
+  constexpr MyOverload(Ts... ts) : Ts(std::move(ts))... {}
+};
+template <typename... Ts>
+MyOverload(Ts...) -> MyOverload<Ts...>;
+inline auto my_lambda = [](int x) { return x; };
+inline MyOverload visitor{my_lambda};
+
+struct Token {
+  void TriggerMethod() const {
+    TemplateClass<void> p(*this);
+  }
+};
+#endif
+
+//--- repro_main.cpp
+#include "repro_wrapper.h"
+#include "repro_token.h"
+int main() {
+  Token t;
+  t.TriggerMethod();
+}

>From bc6879f00114b03914e983e1296647d4af71eaad Mon Sep 17 00:00:00 2001
From: Ivo Popov <[email protected]>
Date: Mon, 8 Jun 2026 19:38:00 +0000
Subject: [PATCH 2/4] Address jyknight's comments related to definitions and
 declarations.

Update the test similarly, to trigger the bug jyknight observed. (I've
also renamed the test, to show that its expected failure is assert in
debug, crash in opt). I've also added another test that fails with
assert in debug, spurious warning in opt.
---
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |   2 +-
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |   8 +-
 clang/lib/Serialization/ASTWriter.cpp         |   2 +-
 ...es-generic-lambda-local-capture-crash.cpp} |  31 ++-
 ...s-generic-lambda-local-capture-warning.cpp | 181 ++++++++++++++++++
 5 files changed, 208 insertions(+), 16 deletions(-)
 rename clang/test/Modules/{modules-generic-lambda-local-capture.cpp => 
modules-generic-lambda-local-capture-crash.cpp} (80%)
 create mode 100644 
clang/test/Modules/modules-generic-lambda-local-capture-warning.cpp

diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp 
b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index da5b8b412404d..fa847f8558c29 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -4405,8 +4405,8 @@ static const Decl *getCanonicalLocalDecl(const Decl *D, 
Sema &SemaRef) {
       unsigned i = PV->getFunctionScopeIndex();
       if (i < FD->getNumParams() && FD->getParamDecl(i) == PV) {
         return FD->getCanonicalDecl()->getParamDecl(i);
+      }
     }
-  }
   } else if (isa<VarDecl>(D) || isa<BindingDecl>(D)) {
     return SemaRef.getCanonicalLocalDecl(D);
   }
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp 
b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 1cf5497078cc3..71b6f9f99eb04 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -7450,15 +7450,15 @@ const Decl *Sema::getCanonicalLocalDecl(const Decl *D) {
     return D;
   }
 
-  const auto *CanonFD = FD->getCanonicalDecl();
-  if (FD == CanonFD) {
+  const auto *DefFD = FD->getDefinition();
+  if (!DefFD || FD == DefFD) {
     return D;
   }
 
-  auto &Map = CanonicalLocalDecls[CanonFD];
+  auto &Map = CanonicalLocalDecls[DefFD];
   if (Map.empty()) {
     SmallVector<const Decl *, 8> CanonDecls;
-    for (const auto *De : CanonFD->decls()) {
+    for (const auto *De : DefFD->decls()) {
       if (isMappedLocalDecl(De)) {
         CanonDecls.push_back(De);
       }
diff --git a/clang/lib/Serialization/ASTWriter.cpp 
b/clang/lib/Serialization/ASTWriter.cpp
index 8f0174d6f470c..2c0f18989af1a 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -3458,7 +3458,7 @@ uint64_t 
ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
   if (GeneratingReducedBMI && DC->isFunctionOrMethod()) {
     if (const auto *FD = dyn_cast<FunctionDecl>(DC)) {
       if (!FD->isDependentContext() || !FD->isThisDeclarationADefinition()) {
-    return 0;
+        return 0;
       }
     } else {
       return 0;
diff --git a/clang/test/Modules/modules-generic-lambda-local-capture.cpp 
b/clang/test/Modules/modules-generic-lambda-local-capture-crash.cpp
similarity index 80%
rename from clang/test/Modules/modules-generic-lambda-local-capture.cpp
rename to clang/test/Modules/modules-generic-lambda-local-capture-crash.cpp
index ca307ac796a90..0866f652a27d8 100644
--- a/clang/test/Modules/modules-generic-lambda-local-capture.cpp
+++ b/clang/test/Modules/modules-generic-lambda-local-capture-crash.cpp
@@ -1,3 +1,9 @@
+// This test illustrates the compiler assertion/crash symptom of the bug.
+// - In debug mode (asserts enabled), it fails with an assertion in
+//   LocalInstantiationScope::findInstantiationOf.
+// - In release mode (asserts disabled, e.g. -c opt), it fails with a compiler
+//   segmentation fault (SIGSEGV) during compilation.
+//
 // RUN: rm -rf %t
 // RUN: split-file %s %t
 // RUN: cd %t
@@ -23,7 +29,7 @@
 // RUN:            -fmodule-name=repro \
 // RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
 // RUN:            -fmodule-file=repro_wrapper_mock=repro_wrapper_mock.pcm \
-// RUN:            -fsyntax-only repro_main.cpp
+// RUN:            -verify -fsyntax-only repro_main.cpp
 
 //--- module.modulemap
 module TemplateClassModule {
@@ -81,16 +87,20 @@ template <typename T>
 class TemplateClass {
  public:
   template <typename... Args>
-  explicit TemplateClass(Args&&... args) {
-    const auto local_val = GetLocalValue();
-    const auto [a, b] = GetLocalPair();
-    auto holder = make_holder([&](auto x) {
-      Consumer<decltype(x)>{}(local_val);
-      Consumer<decltype(x)>{}(a);
-    });
-    holder.call();
-  }
+  explicit TemplateClass(Args&&... args);
 };
+
+template <typename T>
+template <typename... Args>
+TemplateClass<T>::TemplateClass(Args&&... args) {
+  const auto local_val = GetLocalValue();
+  const auto [a, b] = GetLocalPair();
+  auto holder = make_holder([&](auto x) {
+    Consumer<decltype(x)>{}(local_val);
+    Consumer<decltype(x)>{}(a);
+  });
+  holder.call();
+}
 #endif
 
 //--- module_a.h
@@ -129,6 +139,7 @@ struct Token {
 #endif
 
 //--- repro_main.cpp
+// expected-no-diagnostics
 #include "repro_wrapper.h"
 #include "repro_token.h"
 int main() {
diff --git 
a/clang/test/Modules/modules-generic-lambda-local-capture-warning.cpp 
b/clang/test/Modules/modules-generic-lambda-local-capture-warning.cpp
new file mode 100644
index 0000000000000..3a5be24383d75
--- /dev/null
+++ b/clang/test/Modules/modules-generic-lambda-local-capture-warning.cpp
@@ -0,0 +1,181 @@
+// This test illustrates the spurious warning symptom of the bug.
+// - In debug mode (asserts enabled), it fails with an assertion in
+//   LocalInstantiationScope::findInstantiationOf.
+// - In release mode (asserts disabled, e.g. -c opt), it successfully compiles
+//   but fails the test because it emits a spurious uninitialized warning.
+//
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: cd %t
+//
+// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
+// RUN:            -fmodules-local-submodule-visibility \
+// RUN:            -fmodule-map-file=module.modulemap \
+// RUN:            -fmodule-name=repro_module_a -emit-module \
+// RUN:            -fmodules-embed-all-files -x c++ module.modulemap \
+// RUN:            -Wuninitialized -Wuninitialized-const-reference \
+// RUN:            -o repro_module_a.pcm
+//
+// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
+// RUN:            -fmodules-local-submodule-visibility \
+// RUN:            -fmodule-map-file=module.modulemap \
+// RUN:            -fmodule-name=repro_wrapper_mock \
+// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
+// RUN:            -emit-module -fmodules-embed-all-files -x c++ 
module.modulemap \
+// RUN:            -Wuninitialized -Wuninitialized-const-reference \
+// RUN:            -o repro_wrapper_mock.pcm
+//
+// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
+// RUN:            -fmodules-local-submodule-visibility \
+// RUN:            -fmodule-map-file=module.modulemap \
+// RUN:            -fmodule-name=repro \
+// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
+// RUN:            -fmodule-file=repro_wrapper_mock=repro_wrapper_mock.pcm \
+// RUN:            -verify -Wuninitialized -Wuninitialized-const-reference 
-DEXTRA_DECL \
+// RUN:            -fsyntax-only repro_main.cpp
+
+//--- module.modulemap
+module TemplateClassModule {
+  textual header "template_class.h"
+}
+module repro_module_a {
+  header "module_a.h"
+  export *
+  use TemplateClassModule
+}
+module repro_wrapper_mock {
+  header "repro_wrapper.h"
+  export *
+  use repro_module_a
+  use TemplateClassModule
+}
+module repro {
+  export *
+  use repro_wrapper_mock
+}
+
+//--- template_class.h
+#ifndef TEMPLATE_CLASS_H_
+#define TEMPLATE_CLASS_H_
+namespace std {
+template <typename T>
+T&& move(T& t) noexcept { return static_cast<T&&>(t); }
+}
+
+enum class MyEnum { kValue1, kValue2 };
+inline MyEnum GetLocalValue() { return MyEnum::kValue2; }
+
+struct Pair { MyEnum first; MyEnum second; };
+inline Pair GetLocalPair() { return Pair{MyEnum::kValue1, MyEnum::kValue2}; }
+
+template <typename Stream, typename T>
+void MockConsumer(Stream& strm, const T& val) {
+  int x = 0;
+}
+
+template <typename T> struct RemovePointer { using type = T; };
+template <typename T> struct RemovePointer<T*> { using type = T; };
+
+template <typename Stream>
+struct MockPrinter {
+  Stream* strm;
+  template <typename T>
+  void operator()(const T& val) {
+    MockConsumer(*strm, val);
+  }
+};
+
+template <typename F>
+struct MockDumpVars {
+  F f;
+  explicit MockDumpVars(F f) : f(std::move(f)) {}
+  
+  template <typename Stream>
+  void DoStream(Stream& strm) const {
+    f(&strm, 0, nullptr, nullptr, nullptr);
+  }
+};
+
+template <typename F>
+auto mock_make_dump_vars(F f) {
+  return MockDumpVars<F>(std::move(f));
+}
+
+struct MockStream {};
+
+template <typename F>
+MockStream& operator<<(MockStream& strm, const MockDumpVars<F>& lazy) {
+  lazy.DoStream(strm);
+  return strm;
+}
+
+#define MOCK_DUMP_VARS(var) \
+  mock_make_dump_vars([&](auto* _strm, const auto& _writer, \
+                          const char* _fsep, const char* _kvsep, \
+                          const char* const* _names) { \
+    MockPrinter<typename RemovePointer<decltype(_strm)>::type>{_strm}(var); \
+  })
+
+template <typename T>
+class TemplateClass {
+ public:
+  template <typename... Args>
+  explicit TemplateClass(Args&&... args) {
+#ifdef EXTRA_DECL
+    const auto extra_var = 0;
+#endif
+    switch (const auto local_val = GetLocalValue(); local_val) {
+      default:
+        {
+          MockStream strm;
+          strm << MOCK_DUMP_VARS(local_val);
+        }
+    }
+  }
+};
+#endif
+
+//--- module_a.h
+#pragma once
+#include "template_class.h"
+
+//--- repro_wrapper.h
+#ifndef REPRO_WRAPPER_H_
+#define REPRO_WRAPPER_H_
+#include "template_class.h"
+#include "module_a.h"
+inline void TriggerInstantiation() {
+  TemplateClass<void> p;
+}
+#endif
+
+//--- repro_token.h
+#ifndef REPRO_TOKEN_H_
+#define REPRO_TOKEN_H_
+#include "template_class.h"
+#include "module_a.h"
+template <typename... Ts>
+struct MyOverload : Ts... {
+  constexpr MyOverload(Ts... ts) : Ts(std::move(ts))... {}
+};
+template <typename... Ts>
+MyOverload(Ts...) -> MyOverload<Ts...>;
+inline auto my_lambda = [](int x) { return x; };
+inline MyOverload visitor{my_lambda};
+
+struct Token {
+  void TriggerMethod() const {
+    TemplateClass<int> p;
+  }
+};
+#endif
+
+//--- repro_main.cpp
+// expected-no-diagnostics
+#include "repro_wrapper.h"
+#include "repro_token.h"
+int main() {
+  TriggerInstantiation();
+  Token t;
+  t.TriggerMethod();
+}

>From 411c3af86e01d4d8bad0ad754adf6c9062513a4d Mon Sep 17 00:00:00 2001
From: ipopov <[email protected]>
Date: Tue, 9 Jun 2026 17:18:55 +0000
Subject: [PATCH 3/4] Unify test cases, and cover more instances -- TagDecl,
 UsingDecl...

Extend the Sema and ASTWriter change to cover these additional cases.
---
 clang/include/clang/Sema/Sema.h               |   1 +
 clang/lib/Sema/SemaTemplateInstantiate.cpp    |  15 +-
 .../lib/Sema/SemaTemplateInstantiateDecl.cpp  |  62 +--
 clang/lib/Serialization/ASTWriter.cpp         |  13 +-
 ...les-generic-lambda-local-capture-crash.cpp | 148 -------
 ...s-generic-lambda-local-capture-warning.cpp | 181 ---------
 .../modules-generic-lambda-local-mapping.cpp  | 380 ++++++++++++++++++
 7 files changed, 434 insertions(+), 366 deletions(-)
 delete mode 100644 
clang/test/Modules/modules-generic-lambda-local-capture-crash.cpp
 delete mode 100644 
clang/test/Modules/modules-generic-lambda-local-capture-warning.cpp
 create mode 100644 clang/test/Modules/modules-generic-lambda-local-mapping.cpp

diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index bcd4dfe0728e7..a9a30a16918e3 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -13108,6 +13108,7 @@ class Sema final : public SemaBase {
 
   const Decl *getCanonicalLocalDecl(const Decl *D);
 
+
   typedef llvm::DenseMap<ParmVarDecl *, llvm::TinyPtrVector<ParmVarDecl *>>
       UnparsedDefaultArgInstantiationsMap;
 
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp 
b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index fa847f8558c29..47b0da132d801 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -4400,19 +4400,24 @@ Sema::SubstTemplateName(SourceLocation TemplateKWLoc,
 }
 
 static const Decl *getCanonicalLocalDecl(const Decl *D, Sema &SemaRef) {
+  // When storing ParmVarDecls in the local instantiation scope, we always
+  // want to use the ParmVarDecl from the canonical function declaration,
+  // since the map is then valid for any redeclaration or definition of that
+  // function.
   if (const ParmVarDecl *PV = dyn_cast<ParmVarDecl>(D)) {
     if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(PV->getDeclContext())) 
{
       unsigned i = PV->getFunctionScopeIndex();
-      if (i < FD->getNumParams() && FD->getParamDecl(i) == PV) {
+      // This parameter might be from a freestanding function type within the
+      // function and isn't necessarily referring to one of FD's parameters.
+      if (i < FD->getNumParams() && FD->getParamDecl(i) == PV)
         return FD->getCanonicalDecl()->getParamDecl(i);
-      }
     }
-  } else if (isa<VarDecl>(D) || isa<BindingDecl>(D)) {
-    return SemaRef.getCanonicalLocalDecl(D);
+    return D;
   }
-  return D;
+  return SemaRef.getCanonicalLocalDecl(D);
 }
 
+
 llvm::PointerUnion<Decl *, LocalInstantiationScope::DeclArgumentPack *> *
 LocalInstantiationScope::getInstantiationOfIfExists(const Decl *D) {
   D = getCanonicalLocalDecl(D, SemaRef);
diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp 
b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
index 71b6f9f99eb04..359d62453bd61 100644
--- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
@@ -1125,10 +1125,10 @@ template<typename DeclT>
 static DeclT *getPreviousDeclForInstantiation(DeclT *D) {
   DeclT *Result = D->getPreviousDecl();
 
-  // If the declaration is within a class, and the previous declaration was
-  // merged from a different definition of that class, then we don't have a
-  // previous declaration for the purpose of template instantiation.
-  if (Result && isa<CXXRecordDecl>(D->getDeclContext()) &&
+  // If the declaration was merged from a different definition of the enclosing
+  // template, then we don't have a previous declaration for the purpose of
+  // template instantiation.
+  if (Result &&
       D->getLexicalDeclContext() != Result->getLexicalDeclContext())
     return nullptr;
 
@@ -7424,33 +7424,46 @@ void Sema::PerformDependentDiagnostics(const 
DeclContext *Pattern,
 
 static bool isMappedLocalDecl(const Decl *D) {
   if (const auto *VD = dyn_cast<VarDecl>(D)) {
-    return VD->isLocalVarDeclOrParm() && !isa<ParmVarDecl>(VD);
+    return VD->isLocalVarDecl();
   }
-  return isa<BindingDecl>(D);
+  return isa<BindingDecl>(D) || isa<TypedefNameDecl>(D) ||
+         isa<TagDecl>(D) || isa<UsingDecl>(D) || isa<UsingShadowDecl>(D);
 }
 
-const Decl *Sema::getCanonicalLocalDecl(const Decl *D) {
-  if (isa<ParmVarDecl>(D)) {
-    return D;
+static void CollectMappedDecls(const DeclContext *DC,
+                               SmallVectorImpl<const Decl *> &Decls) {
+  for (const Decl *D : DC->decls()) {
+    if (isMappedLocalDecl(D)) {
+      Decls.push_back(D);
+    }
+    if (const auto *ND = dyn_cast<DeclContext>(D)) {
+      if (isa<BlockDecl>(ND) || isa<CapturedDecl>(ND)) {
+        CollectMappedDecls(ND, Decls);
+      }
+    }
   }
+}
 
-  const auto *VD = dyn_cast<VarDecl>(D);
-  const auto *BD = dyn_cast<BindingDecl>(D);
-  if (!VD && !BD) {
+const Decl *Sema::getCanonicalLocalDecl(const Decl *D) {
+  if (!isMappedLocalDecl(D)) {
     return D;
   }
 
-  if (VD && !VD->isLocalVarDeclOrParm()) {
-    return D;
+  // Find the enclosing function.
+  const DeclContext *DC = D->getDeclContext();
+  const FunctionDecl *FD = nullptr;
+  while (DC) {
+    if (const auto *F = dyn_cast<FunctionDecl>(DC)) {
+      FD = F;
+      break;
+    }
+    DC = DC->getParent();
   }
-
-  const DeclContext *DC = VD ? VD->getDeclContext() : BD->getDeclContext();
-  const auto *FD = dyn_cast<FunctionDecl>(DC);
   if (!FD) {
     return D;
   }
 
-  const auto *DefFD = FD->getDefinition();
+  const FunctionDecl *DefFD = FD->getDefinition();
   if (!DefFD || FD == DefFD) {
     return D;
   }
@@ -7458,18 +7471,10 @@ const Decl *Sema::getCanonicalLocalDecl(const Decl *D) {
   auto &Map = CanonicalLocalDecls[DefFD];
   if (Map.empty()) {
     SmallVector<const Decl *, 8> CanonDecls;
-    for (const auto *De : DefFD->decls()) {
-      if (isMappedLocalDecl(De)) {
-        CanonDecls.push_back(De);
-      }
-    }
+    CollectMappedDecls(DefFD, CanonDecls);
 
     SmallVector<const Decl *, 8> LocalDecls;
-    for (const auto *De : FD->decls()) {
-      if (isMappedLocalDecl(De)) {
-        LocalDecls.push_back(De);
-      }
-    }
+    CollectMappedDecls(FD, LocalDecls);
 
     if (CanonDecls.size() == LocalDecls.size()) {
       for (size_t I = 0; I < LocalDecls.size(); ++I) {
@@ -7485,3 +7490,4 @@ const Decl *Sema::getCanonicalLocalDecl(const Decl *D) {
 
   return D;
 }
+
diff --git a/clang/lib/Serialization/ASTWriter.cpp 
b/clang/lib/Serialization/ASTWriter.cpp
index 2c0f18989af1a..1349271443737 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -3454,12 +3454,17 @@ uint64_t 
ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
   if (DC->decls_empty())
     return 0;
 
-  // In reduced BMI, we don't care the declarations in functions.
+  // In reduced BMI, we don't care the declarations in functions, unless they
+  // are templated functions, because their bodies may contain nested 
class/lambda
+  // definitions that are canonicalized and need mapping of local declarations.
   if (GeneratingReducedBMI && DC->isFunctionOrMethod()) {
-    if (const auto *FD = dyn_cast<FunctionDecl>(DC)) {
-      if (!FD->isDependentContext() || !FD->isThisDeclarationADefinition()) {
+    const DeclContext *ParentFD = DC;
+    while (ParentFD && !isa<FunctionDecl>(ParentFD)) {
+      ParentFD = ParentFD->getParent();
+    }
+    if (const auto *FD = dyn_cast_or_null<FunctionDecl>(ParentFD)) {
+      if (!FD->isTemplated())
         return 0;
-      }
     } else {
       return 0;
     }
diff --git a/clang/test/Modules/modules-generic-lambda-local-capture-crash.cpp 
b/clang/test/Modules/modules-generic-lambda-local-capture-crash.cpp
deleted file mode 100644
index 0866f652a27d8..0000000000000
--- a/clang/test/Modules/modules-generic-lambda-local-capture-crash.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-// This test illustrates the compiler assertion/crash symptom of the bug.
-// - In debug mode (asserts enabled), it fails with an assertion in
-//   LocalInstantiationScope::findInstantiationOf.
-// - In release mode (asserts disabled, e.g. -c opt), it fails with a compiler
-//   segmentation fault (SIGSEGV) during compilation.
-//
-// RUN: rm -rf %t
-// RUN: split-file %s %t
-// RUN: cd %t
-//
-// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
-// RUN:            -fmodules-local-submodule-visibility \
-// RUN:            -fmodule-map-file=module.modulemap \
-// RUN:            -fmodule-name=repro_module_a -emit-module \
-// RUN:            -fmodules-embed-all-files -x c++ module.modulemap \
-// RUN:            -o repro_module_a.pcm
-//
-// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
-// RUN:            -fmodules-local-submodule-visibility \
-// RUN:            -fmodule-map-file=module.modulemap \
-// RUN:            -fmodule-name=repro_wrapper_mock \
-// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
-// RUN:            -emit-module -fmodules-embed-all-files -x c++ 
module.modulemap \
-// RUN:            -o repro_wrapper_mock.pcm
-//
-// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
-// RUN:            -fmodules-local-submodule-visibility \
-// RUN:            -fmodule-map-file=module.modulemap \
-// RUN:            -fmodule-name=repro \
-// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
-// RUN:            -fmodule-file=repro_wrapper_mock=repro_wrapper_mock.pcm \
-// RUN:            -verify -fsyntax-only repro_main.cpp
-
-//--- module.modulemap
-module TemplateClassModule {
-  textual header "template_class.h"
-}
-module repro_module_a {
-  header "module_a.h"
-  export *
-  use TemplateClassModule
-}
-module repro_wrapper_mock {
-  header "repro_wrapper.h"
-  export *
-  use repro_module_a
-  use TemplateClassModule
-}
-module repro {
-  export *
-  use repro_wrapper_mock
-}
-
-//--- template_class.h
-#ifndef TEMPLATE_CLASS_H_
-#define TEMPLATE_CLASS_H_
-namespace std {
-template <typename T>
-T&& move(T& t) noexcept { return static_cast<T&&>(t); }
-}
-
-enum class MyEnum { kValue1, kValue2 };
-inline MyEnum GetLocalValue() { return MyEnum::kValue2; }
-
-struct Pair { MyEnum first; MyEnum second; };
-inline Pair GetLocalPair() { return Pair{MyEnum::kValue1, MyEnum::kValue2}; }
-
-template <typename T>
-struct Consumer {
-  template <typename U>
-  void operator()(const U& x) const {}
-};
-
-template <typename F>
-struct Holder {
-  F f;
-  explicit Holder(F f) : f(std::move(f)) {}
-  void call() const { f(0); }
-};
-
-template <typename F>
-auto make_holder(F f) {
-  return Holder<F>(std::move(f));
-}
-
-template <typename T>
-class TemplateClass {
- public:
-  template <typename... Args>
-  explicit TemplateClass(Args&&... args);
-};
-
-template <typename T>
-template <typename... Args>
-TemplateClass<T>::TemplateClass(Args&&... args) {
-  const auto local_val = GetLocalValue();
-  const auto [a, b] = GetLocalPair();
-  auto holder = make_holder([&](auto x) {
-    Consumer<decltype(x)>{}(local_val);
-    Consumer<decltype(x)>{}(a);
-  });
-  holder.call();
-}
-#endif
-
-//--- module_a.h
-#pragma once
-#include "template_class.h"
-
-//--- repro_wrapper.h
-#ifndef REPRO_WRAPPER_H_
-#define REPRO_WRAPPER_H_
-#include "template_class.h"
-#include "module_a.h"
-inline void TriggerInstantiation() {
-  TemplateClass<void> p;
-}
-#endif
-
-//--- repro_token.h
-#ifndef REPRO_TOKEN_H_
-#define REPRO_TOKEN_H_
-#include "template_class.h"
-#include "module_a.h"
-template <typename... Ts>
-struct MyOverload : Ts... {
-  constexpr MyOverload(Ts... ts) : Ts(std::move(ts))... {}
-};
-template <typename... Ts>
-MyOverload(Ts...) -> MyOverload<Ts...>;
-inline auto my_lambda = [](int x) { return x; };
-inline MyOverload visitor{my_lambda};
-
-struct Token {
-  void TriggerMethod() const {
-    TemplateClass<void> p(*this);
-  }
-};
-#endif
-
-//--- repro_main.cpp
-// expected-no-diagnostics
-#include "repro_wrapper.h"
-#include "repro_token.h"
-int main() {
-  Token t;
-  t.TriggerMethod();
-}
diff --git 
a/clang/test/Modules/modules-generic-lambda-local-capture-warning.cpp 
b/clang/test/Modules/modules-generic-lambda-local-capture-warning.cpp
deleted file mode 100644
index 3a5be24383d75..0000000000000
--- a/clang/test/Modules/modules-generic-lambda-local-capture-warning.cpp
+++ /dev/null
@@ -1,181 +0,0 @@
-// This test illustrates the spurious warning symptom of the bug.
-// - In debug mode (asserts enabled), it fails with an assertion in
-//   LocalInstantiationScope::findInstantiationOf.
-// - In release mode (asserts disabled, e.g. -c opt), it successfully compiles
-//   but fails the test because it emits a spurious uninitialized warning.
-//
-// RUN: rm -rf %t
-// RUN: split-file %s %t
-// RUN: cd %t
-//
-// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
-// RUN:            -fmodules-local-submodule-visibility \
-// RUN:            -fmodule-map-file=module.modulemap \
-// RUN:            -fmodule-name=repro_module_a -emit-module \
-// RUN:            -fmodules-embed-all-files -x c++ module.modulemap \
-// RUN:            -Wuninitialized -Wuninitialized-const-reference \
-// RUN:            -o repro_module_a.pcm
-//
-// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
-// RUN:            -fmodules-local-submodule-visibility \
-// RUN:            -fmodule-map-file=module.modulemap \
-// RUN:            -fmodule-name=repro_wrapper_mock \
-// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
-// RUN:            -emit-module -fmodules-embed-all-files -x c++ 
module.modulemap \
-// RUN:            -Wuninitialized -Wuninitialized-const-reference \
-// RUN:            -o repro_wrapper_mock.pcm
-//
-// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
-// RUN:            -fmodules-local-submodule-visibility \
-// RUN:            -fmodule-map-file=module.modulemap \
-// RUN:            -fmodule-name=repro \
-// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
-// RUN:            -fmodule-file=repro_wrapper_mock=repro_wrapper_mock.pcm \
-// RUN:            -verify -Wuninitialized -Wuninitialized-const-reference 
-DEXTRA_DECL \
-// RUN:            -fsyntax-only repro_main.cpp
-
-//--- module.modulemap
-module TemplateClassModule {
-  textual header "template_class.h"
-}
-module repro_module_a {
-  header "module_a.h"
-  export *
-  use TemplateClassModule
-}
-module repro_wrapper_mock {
-  header "repro_wrapper.h"
-  export *
-  use repro_module_a
-  use TemplateClassModule
-}
-module repro {
-  export *
-  use repro_wrapper_mock
-}
-
-//--- template_class.h
-#ifndef TEMPLATE_CLASS_H_
-#define TEMPLATE_CLASS_H_
-namespace std {
-template <typename T>
-T&& move(T& t) noexcept { return static_cast<T&&>(t); }
-}
-
-enum class MyEnum { kValue1, kValue2 };
-inline MyEnum GetLocalValue() { return MyEnum::kValue2; }
-
-struct Pair { MyEnum first; MyEnum second; };
-inline Pair GetLocalPair() { return Pair{MyEnum::kValue1, MyEnum::kValue2}; }
-
-template <typename Stream, typename T>
-void MockConsumer(Stream& strm, const T& val) {
-  int x = 0;
-}
-
-template <typename T> struct RemovePointer { using type = T; };
-template <typename T> struct RemovePointer<T*> { using type = T; };
-
-template <typename Stream>
-struct MockPrinter {
-  Stream* strm;
-  template <typename T>
-  void operator()(const T& val) {
-    MockConsumer(*strm, val);
-  }
-};
-
-template <typename F>
-struct MockDumpVars {
-  F f;
-  explicit MockDumpVars(F f) : f(std::move(f)) {}
-  
-  template <typename Stream>
-  void DoStream(Stream& strm) const {
-    f(&strm, 0, nullptr, nullptr, nullptr);
-  }
-};
-
-template <typename F>
-auto mock_make_dump_vars(F f) {
-  return MockDumpVars<F>(std::move(f));
-}
-
-struct MockStream {};
-
-template <typename F>
-MockStream& operator<<(MockStream& strm, const MockDumpVars<F>& lazy) {
-  lazy.DoStream(strm);
-  return strm;
-}
-
-#define MOCK_DUMP_VARS(var) \
-  mock_make_dump_vars([&](auto* _strm, const auto& _writer, \
-                          const char* _fsep, const char* _kvsep, \
-                          const char* const* _names) { \
-    MockPrinter<typename RemovePointer<decltype(_strm)>::type>{_strm}(var); \
-  })
-
-template <typename T>
-class TemplateClass {
- public:
-  template <typename... Args>
-  explicit TemplateClass(Args&&... args) {
-#ifdef EXTRA_DECL
-    const auto extra_var = 0;
-#endif
-    switch (const auto local_val = GetLocalValue(); local_val) {
-      default:
-        {
-          MockStream strm;
-          strm << MOCK_DUMP_VARS(local_val);
-        }
-    }
-  }
-};
-#endif
-
-//--- module_a.h
-#pragma once
-#include "template_class.h"
-
-//--- repro_wrapper.h
-#ifndef REPRO_WRAPPER_H_
-#define REPRO_WRAPPER_H_
-#include "template_class.h"
-#include "module_a.h"
-inline void TriggerInstantiation() {
-  TemplateClass<void> p;
-}
-#endif
-
-//--- repro_token.h
-#ifndef REPRO_TOKEN_H_
-#define REPRO_TOKEN_H_
-#include "template_class.h"
-#include "module_a.h"
-template <typename... Ts>
-struct MyOverload : Ts... {
-  constexpr MyOverload(Ts... ts) : Ts(std::move(ts))... {}
-};
-template <typename... Ts>
-MyOverload(Ts...) -> MyOverload<Ts...>;
-inline auto my_lambda = [](int x) { return x; };
-inline MyOverload visitor{my_lambda};
-
-struct Token {
-  void TriggerMethod() const {
-    TemplateClass<int> p;
-  }
-};
-#endif
-
-//--- repro_main.cpp
-// expected-no-diagnostics
-#include "repro_wrapper.h"
-#include "repro_token.h"
-int main() {
-  TriggerInstantiation();
-  Token t;
-  t.TriggerMethod();
-}
diff --git a/clang/test/Modules/modules-generic-lambda-local-mapping.cpp 
b/clang/test/Modules/modules-generic-lambda-local-mapping.cpp
new file mode 100644
index 0000000000000..a3d190d8a7b1f
--- /dev/null
+++ b/clang/test/Modules/modules-generic-lambda-local-mapping.cpp
@@ -0,0 +1,380 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: cd %t
+//
+// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
+// RUN:            -fmodules-local-submodule-visibility \
+// RUN:            -fmodule-map-file=module.modulemap \
+// RUN:            -fmodule-name=repro_module_a -emit-module \
+// RUN:            -fmodules-embed-all-files -x c++ module.modulemap \
+// RUN:            -Wuninitialized -Wuninitialized-const-reference \
+// RUN:            -o repro_module_a.pcm
+//
+// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
+// RUN:            -fmodules-local-submodule-visibility \
+// RUN:            -fmodule-map-file=module.modulemap \
+// RUN:            -fmodule-name=repro_wrapper_mock \
+// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
+// RUN:            -emit-module -fmodules-embed-all-files -x c++ 
module.modulemap \
+// RUN:            -Wuninitialized -Wuninitialized-const-reference \
+// RUN:            -o repro_wrapper_mock.pcm
+//
+// RUN: %clang_cc1 -std=c++20 -fmodules -fno-implicit-modules \
+// RUN:            -fmodules-local-submodule-visibility \
+// RUN:            -fmodule-map-file=module.modulemap \
+// RUN:            -fmodule-name=repro \
+// RUN:            -fmodule-file=repro_module_a=repro_module_a.pcm \
+// RUN:            -fmodule-file=repro_wrapper_mock=repro_wrapper_mock.pcm \
+// RUN:            -verify -Wuninitialized -Wuninitialized-const-reference \
+// RUN:            -fsyntax-only repro_main.cpp
+
+//--- module.modulemap
+module TemplateClassModule {
+  textual header "template_class.h"
+}
+module repro_module_a {
+  header "module_a.h"
+  export *
+  use TemplateClassModule
+}
+module repro_wrapper_mock {
+  header "repro_wrapper.h"
+  export *
+  use repro_module_a
+  use TemplateClassModule
+}
+module repro {
+  export *
+  use repro_wrapper_mock
+}
+
+//--- template_class.h
+#ifndef TEMPLATE_CLASS_H_
+#define TEMPLATE_CLASS_H_
+
+namespace std {
+template <typename T>
+T&& move(T& t) noexcept { return static_cast<T&&>(t); }
+
+template <typename R, typename... Args>
+struct coroutine_traits {
+  using promise_type = typename R::promise_type;
+};
+
+template <typename Promise = void> struct coroutine_handle {
+  static coroutine_handle from_address(void *addr) noexcept { return {}; }
+};
+
+struct suspend_always {
+  bool await_ready() const noexcept { return false; }
+  template <typename P> void await_suspend(coroutine_handle<P>) const noexcept 
{}
+  void await_resume() const noexcept {}
+};
+
+struct suspend_never {
+  bool await_ready() const noexcept { return true; }
+  template <typename P> void await_suspend(coroutine_handle<P>) const noexcept 
{}
+  void await_resume() const noexcept {}
+};
+}
+
+template <typename F>
+struct Holder {
+  F f;
+  explicit Holder(F f) : f(std::move(f)) {}
+  void call() const { f(0); }
+};
+
+template <typename F>
+auto make_holder(F f) {
+  return Holder<F>(std::move(f));
+}
+
+enum class MyEnum { kValue1, kValue2 };
+inline MyEnum GetLocalValue() { return MyEnum::kValue2; }
+
+struct Pair { MyEnum first; MyEnum second; };
+inline Pair GetLocalPair() { return Pair{MyEnum::kValue1, MyEnum::kValue2}; }
+
+namespace test_namespace {
+  inline int ns_foo = 42;
+  void overloaded_func(int);
+#if EXTRA_OVERLOAD
+  void overloaded_func(double);
+#endif
+}
+
+// 1. Basic local variables, type aliases, and decomposition declarations 
mapping
+template <typename T>
+class TemplateClassBasic {
+ public:
+  template <typename... Args>
+  explicit TemplateClassBasic(Args&&... args);
+};
+
+template <typename T>
+template <typename... Args>
+TemplateClassBasic<T>::TemplateClassBasic(Args&&... args) {
+  using LocalType = T*;
+  const auto local_val = GetLocalValue();
+  const auto [a, b] = GetLocalPair();
+  auto holder = make_holder([&](auto x) {
+    LocalType val = nullptr;
+    (void)val;
+    (void)local_val;
+    (void)a;
+  });
+  holder.call();
+}
+
+// 2. Local structs and local enums mapping
+template <typename T>
+class TemplateClassLocalStruct {
+ public:
+  template <typename... Args>
+  explicit TemplateClassLocalStruct(Args&&... args);
+};
+
+template <typename T>
+template <typename... Args>
+TemplateClassLocalStruct<T>::TemplateClassLocalStruct(Args&&... args) {
+  struct LocalStruct { int x; };
+  enum LocalEnum { kValue = 42 };
+  using test_namespace::ns_foo;
+  auto holder = make_holder([&](auto x) {
+    LocalStruct l{0};
+    int y = l.x + kValue + ns_foo;
+    (void)y;
+  });
+  holder.call();
+}
+
+// 3. Spurious warning / switch-statement test
+template <typename Stream, typename U>
+void MockConsumer(Stream& strm, const U& val) {}
+
+template <typename T> struct RemovePointer { using type = T; };
+template <typename T> struct RemovePointer<T*> { using type = T; };
+
+template <typename Stream>
+struct MockPrinter {
+  Stream* strm;
+  template <typename U>
+  void operator()(const U& val) {
+    MockConsumer(*strm, val);
+  }
+};
+
+template <typename F>
+struct MockDumpVars {
+  F f;
+  explicit MockDumpVars(F f) : f(std::move(f)) {}
+  template <typename Stream>
+  void DoStream(Stream& strm) const { f(&strm); }
+};
+
+template <typename F>
+auto mock_make_dump_vars(F f) { return MockDumpVars<F>(std::move(f)); }
+
+struct MockStream {};
+template <typename F>
+MockStream& operator<<(MockStream& strm, const MockDumpVars<F>& lazy) {
+  lazy.DoStream(strm);
+  return strm;
+}
+
+#define MOCK_DUMP_VARS(var) \
+  mock_make_dump_vars([&](auto* _strm) { \
+    MockPrinter<typename RemovePointer<decltype(_strm)>::type>{_strm}(var); \
+  })
+
+template <typename T>
+class TemplateClassWarning {
+ public:
+  template <typename... Args>
+  explicit TemplateClassWarning(Args&&... args) {
+    switch (const auto local_val = GetLocalValue(); local_val) {
+      default:
+        {
+          MockStream strm;
+          strm << MOCK_DUMP_VARS(local_val);
+        }
+    }
+  }
+};
+
+// 4. Coroutines template method mapping
+template <typename T>
+struct MyTask {
+  struct promise_type {
+    MyTask get_return_object() { return {}; }
+    std::suspend_never initial_suspend() { return {}; }
+    std::suspend_never final_suspend() noexcept { return {}; }
+    void return_void() {}
+    void unhandled_exception() {}
+  };
+};
+
+template <typename T>
+class TemplateClassCoroutine {
+ public:
+  template <typename U>
+  MyTask<U> coroutine_method() {
+    using LocalType = T*;
+    auto lam = [&](auto x) {
+      LocalType val = nullptr;
+      (void)val;
+    };
+    lam(0);
+    co_return;
+  }
+};
+
+// 5. Local class type alias mapping
+template <typename T>
+class TemplateClassLocalClass {
+ public:
+  template <typename... Args>
+  explicit TemplateClassLocalClass(Args&&... args) {
+    struct LocalClass {
+      using LocalType = T*;
+    };
+    auto lam = [&](auto x) {
+      typename LocalClass::LocalType val = nullptr;
+      (void)val;
+    };
+    lam(0);
+  }
+};
+
+// 6. Local function declaration mapping
+template <typename T>
+class TemplateClassLocalFunc {
+ public:
+  template <typename... Args>
+  explicit TemplateClassLocalFunc(Args&&... args) {
+    void local_func();
+    auto lam = [&](auto x) {
+      local_func();
+    };
+    lam(0);
+  }
+};
+
+// 7. Local namespace alias mapping
+template <typename T>
+class TemplateClassNamespaceAlias {
+ public:
+  template <typename... Args>
+  explicit TemplateClassNamespaceAlias(Args&&... args) {
+    namespace my_alias = test_namespace;
+    auto lam = [&](auto x) {
+      int y = my_alias::ns_foo;
+      (void)y;
+    };
+    lam(0);
+  }
+};
+
+// 8. Nested generic lambda mapping
+template <typename T>
+class TemplateClassNestedLambda {
+ public:
+  template <typename... Args>
+  explicit TemplateClassNestedLambda(Args&&... args) {
+    using LocalType = T*;
+    auto lam1 = [&](auto x) {
+      auto lam2 = [&](auto y) {
+        LocalType val = nullptr;
+        (void)val;
+      };
+      lam2(0);
+    };
+    lam1(0);
+  }
+};
+
+// 9. Overloaded local using declaration mapping
+template <typename T>
+class TemplateClassOverloadedUsing {
+ public:
+  template <typename... Args>
+  explicit TemplateClassOverloadedUsing(Args&&... args) {
+    using test_namespace::overloaded_func;
+    auto lam = [&](auto x) {
+      overloaded_func(0);
+    };
+    lam(0);
+  }
+};
+
+#endif
+
+//--- module_a.h
+#pragma once
+#include "template_class.h"
+
+//--- repro_wrapper.h
+#ifndef REPRO_WRAPPER_H_
+#define REPRO_WRAPPER_H_
+#define EXTRA_OVERLOAD 1
+#include "template_class.h"
+#include "module_a.h"
+
+inline void TriggerInstantiation() {
+  TemplateClassBasic<void> p1;
+  TemplateClassLocalStruct<void> p2;
+  TemplateClassWarning<void> p3;
+  
+  TemplateClassCoroutine<void> p4;
+  p4.coroutine_method<int>();
+  
+  TemplateClassLocalClass<void> p5;
+  TemplateClassLocalFunc<void> p6;
+  TemplateClassNamespaceAlias<void> p7;
+  TemplateClassNestedLambda<void> p8;
+  TemplateClassOverloadedUsing<void> p9;
+}
+#endif
+
+//--- repro_token.h
+#ifndef REPRO_TOKEN_H_
+#define REPRO_TOKEN_H_
+#include "template_class.h"
+#include "module_a.h"
+template <typename... Ts>
+struct MyOverload : Ts... {
+  constexpr MyOverload(Ts... ts) : Ts(std::move(ts))... {}
+};
+template <typename... Ts>
+MyOverload(Ts...) -> MyOverload<Ts...>;
+inline auto my_lambda = [](int x) { return x; };
+inline MyOverload visitor{my_lambda};
+
+struct Token {
+  void TriggerMethod() const {
+    TemplateClassBasic<void> p1(*this);
+    TemplateClassLocalStruct<void> p2(*this);
+    TemplateClassWarning<void> p3(*this);
+    
+    TemplateClassCoroutine<void> p4;
+    p4.coroutine_method<double>();
+    
+    TemplateClassLocalClass<void> p5(*this);
+    TemplateClassLocalFunc<void> p6(*this);
+    TemplateClassNamespaceAlias<void> p7(*this);
+    TemplateClassNestedLambda<void> p8(*this);
+    TemplateClassOverloadedUsing<void> p9(*this);
+  }
+};
+#endif
+
+//--- repro_main.cpp
+// expected-no-diagnostics
+#include "repro_wrapper.h"
+#include "repro_token.h"
+int main() {
+  TriggerInstantiation();
+  Token t;
+  t.TriggerMethod();
+}

>From 7b24bc53fe82fe8d185bfd3e88962c0b0a9125af Mon Sep 17 00:00:00 2001
From: ipopov <[email protected]>
Date: Wed, 10 Jun 2026 14:54:38 +0000
Subject: [PATCH 4/4] Add Reduced BMI regression test for generic lambda local
 mapping

Add a C++20 Named Modules regression test using Reduced BMI
(`-emit-reduced-module-interface`) to verify that `ASTWriter` correctly
preserves templated function local declarations.

This test ensures that structural mapping of local declarations inside
generic lambdas succeeds when the template is instantiated across module
boundaries, preventing compiler assertion crashes during
deserialization.
---
 ...dules-generic-lambda-local-mapping-bmi.cpp | 95 +++++++++++++++++++
 1 file changed, 95 insertions(+)
 create mode 100644 
clang/test/Modules/modules-generic-lambda-local-mapping-bmi.cpp

diff --git a/clang/test/Modules/modules-generic-lambda-local-mapping-bmi.cpp 
b/clang/test/Modules/modules-generic-lambda-local-mapping-bmi.cpp
new file mode 100644
index 0000000000000..abea0d3ee2814
--- /dev/null
+++ b/clang/test/Modules/modules-generic-lambda-local-mapping-bmi.cpp
@@ -0,0 +1,95 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+// RUN: cd %t
+//
+// RUN: %clang_cc1 -std=c++20 -I. m_template.cppm 
-emit-reduced-module-interface -o m_template.pcm
+// RUN: %clang_cc1 -std=c++20 -I. m_wrapper.cppm 
-emit-reduced-module-interface -fmodule-file=m_template=m_template.pcm -o 
m_wrapper.pcm
+// RUN: %clang_cc1 -std=c++20 -I. main.cpp 
-fmodule-file=m_template=m_template.pcm -fmodule-file=m_wrapper=m_wrapper.pcm 
-fsyntax-only -verify
+
+//--- template_class.h
+#pragma once
+
+namespace std {
+template <typename T>
+T&& move(T& t) noexcept { return static_cast<T&&>(t); }
+}
+
+template <typename F>
+struct Holder {
+  F f;
+  explicit Holder(F f) : f(std::move(f)) {}
+  void call() const { f(0); }
+};
+
+template <typename F>
+auto make_holder(F f) {
+  return Holder<F>(std::move(f));
+}
+
+template <typename T>
+class TemplateClass1 {
+ public:
+  template <typename... Args>
+  explicit TemplateClass1(Args&&... args);
+};
+
+template <typename T>
+template <typename... Args>
+TemplateClass1<T>::TemplateClass1(Args&&... args) {
+  using LocalType = T*;
+  auto holder = make_holder([&](auto x) {
+    LocalType val = nullptr;
+  });
+  holder.call();
+}
+
+template <typename T>
+class TemplateClass2 {
+ public:
+  template <typename... Args>
+  explicit TemplateClass2(Args&&... args);
+};
+
+template <typename T>
+template <typename... Args>
+TemplateClass2<T>::TemplateClass2(Args&&... args) {
+  using LocalType = T*;
+  auto holder = make_holder([&](auto x) {
+    LocalType val = nullptr;
+  });
+  holder.call();
+}
+
+//--- m_template.cppm
+module;
+#include "template_class.h"
+export module m_template;
+export using ::TemplateClass1;
+export using ::TemplateClass2;
+
+//--- m_wrapper.cppm
+module;
+#include "template_class.h"
+export module m_wrapper;
+import m_template;
+
+export inline void TriggerInstantiation() {
+  TemplateClass1<void> p1;
+  TemplateClass2<void> p2;
+}
+
+//--- main.cpp
+// expected-no-diagnostics
+import m_template;
+import m_wrapper;
+#include "template_class.h"
+
+struct Token {};
+
+int main() {
+  TriggerInstantiation();
+  Token t;
+  TemplateClass1<void> p1(t);
+  TemplateClass2<void> p2(t);
+}

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

Reply via email to