[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-28 Thread Chuanqi Xu via cfe-commits

https://github.com/ChuanqiXu9 approved this pull request.

LGTM now.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-28 Thread Ivo Popov via cfe-commits

https://github.com/ipopov edited 
https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-27 Thread Ivo Popov via cfe-commits




ipopov wrote:

Done.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-27 Thread Ivo Popov via cfe-commits


@@ -498,6 +498,9 @@ bool serialization::needsAnonymousDeclarationNumber(const 
NamedDecl *D) {
   if (D->getLexicalDeclContext()->isFunctionOrMethod()) {
 if (auto *VD = dyn_cast(D))
   return VD->isStaticLocal();
+if (const auto *TD = dyn_cast(D))
+  if (TD->getLexicalDeclContext()->isDependentContext())
+return false;

ipopov wrote:

Done.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-27 Thread Ivo Popov via cfe-commits

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

>From 5805a98439860b7ddbae7558ebd4ac6f0ee00f16 Mon Sep 17 00:00:00 2001
From: ipopov 
Date: Fri, 26 Jun 2026 15:28:02 -0700
Subject: [PATCH 1/3] Serialization: Skip anonymous declaration numbering for
 local tags in dependent contexts

Local tag declarations (classes, structs, enums, and lambdas) defined within 
function template bodies or class templates do not require ODR merging at the 
local declaration level across different modules. ODR consistency is already 
guaranteed because the instantiator only walks the canonical template 
definition body, which instantiates its own copy of the local class.

Merging them across different modules causes their member definitions (like 
methods or call operators) to be canonicalized to one module, while the 
instantiator walks the template body from another module. Since local variables 
within template bodies are not merged, this mismatch leads to assertion crashes 
during template instantiation in `LocalInstantiationScope::findInstantiationOf`.
---
 clang/lib/Serialization/ASTCommon.cpp |  3 +
 .../modules-lambda-dependent-crash.cppm   | 73 +++
 2 files changed, 76 insertions(+)
 create mode 100644 clang/test/Modules/modules-lambda-dependent-crash.cppm

diff --git a/clang/lib/Serialization/ASTCommon.cpp 
b/clang/lib/Serialization/ASTCommon.cpp
index 49e6fe8004cec..e3df196242919 100644
--- a/clang/lib/Serialization/ASTCommon.cpp
+++ b/clang/lib/Serialization/ASTCommon.cpp
@@ -498,6 +498,9 @@ bool serialization::needsAnonymousDeclarationNumber(const 
NamedDecl *D) {
   if (D->getLexicalDeclContext()->isFunctionOrMethod()) {
 if (auto *VD = dyn_cast(D))
   return VD->isStaticLocal();
+if (const auto *TD = dyn_cast(D))
+  if (TD->getLexicalDeclContext()->isDependentContext())
+return false;
 // FIXME: What about CapturedDecls (and declarations nested within them)?
 return isa(D);
   }
diff --git a/clang/test/Modules/modules-lambda-dependent-crash.cppm 
b/clang/test/Modules/modules-lambda-dependent-crash.cppm
new file mode 100644
index 0..a0701b54c1989
--- /dev/null
+++ b/clang/test/Modules/modules-lambda-dependent-crash.cppm
@@ -0,0 +1,73 @@
+// 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 \
+// RUN:-o m_template.pcm
+//
+// RUN: %clang_cc1 -std=c++20 -I. m_wrapper.cppm \
+// RUN:-emit-reduced-module-interface \
+// RUN:-fmodule-file=m_template=m_template.pcm \
+// RUN:-o m_wrapper.pcm
+//
+// RUN: %clang_cc1 -std=c++20 -I. main.cpp \
+// RUN:-fmodule-file=m_template=m_template.pcm \
+// RUN:-fmodule-file=m_wrapper=m_wrapper.pcm \
+// RUN:-fsyntax-only -verify
+
+//--- template_class.h
+#pragma once
+
+class TemplateClass {
+ public:
+  template 
+  auto f1(U u);
+};
+
+template 
+auto TemplateClass::f1(U u) {
+  U z = u;
+  return [=](auto) {
+(void)z;
+  };
+}
+
+template 
+auto trigger_ast_deserialization(U u) {
+  return []() {};
+}
+
+//--- m_template.cppm
+module;
+#include "template_class.h"
+export module m_template;
+export using ::TemplateClass;
+export using ::trigger_ast_deserialization;
+
+//--- m_wrapper.cppm
+module;
+#include "template_class.h"
+export module m_wrapper;
+import m_template;
+
+export inline void TriggerInstantiation() {
+  TemplateClass tc;
+  tc.f1(0);
+  trigger_ast_deserialization(0);
+}
+
+//--- main.cpp
+// expected-no-diagnostics
+import m_template;
+import m_wrapper;
+#include "template_class.h"
+
+struct Token {};
+
+int main() {
+  TriggerInstantiation();
+  trigger_ast_deserialization(Token{});
+  TemplateClass tc;
+  tc.f1(Token{});
+}

>From 24c9297bc2db4cd0b8af7982c048b11c603d6e02 Mon Sep 17 00:00:00 2001
From: Ivo Popov 
Date: Sat, 27 Jun 2026 18:47:23 -0400
Subject: [PATCH 2/3] Generalize the change, responding to review comments.

---
 clang/lib/Serialization/ASTCommon.cpp | 11 ++-
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/clang/lib/Serialization/ASTCommon.cpp 
b/clang/lib/Serialization/ASTCommon.cpp
index e3df196242919..9b454428457e2 100644
--- a/clang/lib/Serialization/ASTCommon.cpp
+++ b/clang/lib/Serialization/ASTCommon.cpp
@@ -493,14 +493,15 @@ bool serialization::needsAnonymousDeclarationNumber(const 
NamedDecl *D) {
 
   // At block scope, we number everything that we need to deduplicate, since we
   // can't just use name matching to keep things lined up.
-  // FIXME: This is only necessary for an inline function or a template or
-  // similar.
+  // FIXME: This is only necessary for an inline function or
+  // a template specialization or similar.
   if (D->getLexicalDeclContext()->isFunctionOrMethod()) {
+// An uninstantiated template pattern is never emitted; only its
+// instantiations are numbered, so its decls

[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-17 Thread Richard Smith via cfe-commits

zygoloid wrote:

It makes sense that the lambda and the enclosing function would both be 
imported, and we'd pick definitions from different TUs. Looking at the 
ASTReader code now, I don't see any real protection against that happening, and 
it would definitely lead to a broken AST.

In terms of how to fix this, perhaps:
* When we try to import a closure type, if the context declaration is a 
function, then import a definition of that function. In this case, import 
*only* a declaration of the closure type.
* When we import a lambda expression, import the definition of the closure type 
from the same source.
* When we import a definition of a member of a closure type, use the definition 
from the same source that we used to import the definition of the closure type 
itself, rather than another declaration it's been merged with.

That way, the definition of the closure type call operator and closure type 
captures that we import should come from the same source as the enclosing 
function, and hence should have the same declarations for local variables.

In terms of how to do the above in practice, I'm not sure; I've not worked on 
the ASTReader code in quite a long time. But I think it should all be doable.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-15 Thread Chuanqi Xu via cfe-commits


@@ -3454,9 +3454,21 @@ uint64_t 
ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
   if (DC->decls_empty())
 return 0;
 
-  // In reduced BMI, we don't care the declarations in functions.
-  if (GeneratingReducedBMI && DC->isFunctionOrMethod())
-return 0;
+  // 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()) {

ChuanqiXu9 wrote:

We have to understand **why** we need these function local declarations in 
lexical lookup table to continue. 

I don't feel bad with AI. But the contributor need to understand what their 
patches did. I think this matches LLVM's guidelines. 

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-15 Thread Chuanqi Xu via cfe-commits

https://github.com/ChuanqiXu9 requested changes to this pull request.


https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-14 Thread Chuanqi Xu via cfe-commits


@@ -3454,9 +3454,21 @@ uint64_t 
ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
   if (DC->decls_empty())
 return 0;
 
-  // In reduced BMI, we don't care the declarations in functions.
-  if (GeneratingReducedBMI && DC->isFunctionOrMethod())
-return 0;
+  // 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()) {

ChuanqiXu9 wrote:

It is possible to mix clang headers and named modules. My question is to 
understand the underlying problem. The code snippet is to avoid something to 
occur in the lexical lookup table. And my question is why we can't filter 
declarations in lexical lookup table.

If we can't understand this, we are not going to fix the real problem.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-11 Thread Ivo Popov via cfe-commits


@@ -3454,9 +3454,21 @@ uint64_t 
ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
   if (DC->decls_empty())
 return 0;
 
-  // In reduced BMI, we don't care the declarations in functions.
-  if (GeneratingReducedBMI && DC->isFunctionOrMethod())
-return 0;
+  // In reduced BMI, we don't care the declarations in functions, unless they
+  // are templated functions, because their bodies may contain nested 
class/lambda

ipopov wrote:

I've tried to reduce this to the cases that seem to matter to Sema. Does this 
seem like less overhead?

An alternative idea is to avoid fixing this bug for C++20 modules, but only do 
it for header-modules, in this PR; revert changes to ASTWriter.cpp here; and 
move them to another pull request. LMK what you would prefer!

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-11 Thread Ivo Popov via cfe-commits


@@ -0,0 +1,95 @@
+// RUN: rm -rf %t

ipopov wrote:

Done.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-11 Thread Ivo Popov via cfe-commits

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 
Date: Mon, 8 Jun 2026 02:49:00 +
Subject: [PATCH 1/5] [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>
+  CanonicalLocalDecls;
+
+  const Decl *getCanonicalLocalDecl(const Decl *D);
+
   typedef llvm::DenseMap>
   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(D)) {
 if (const FunctionDecl *FD = dyn_cast(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(D) || isa(D)) {
+return SemaRef.getCanonicalLocalDecl(D);
+  }
   return D;
 }
 
 llvm::PointerUnion *
 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 &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(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 &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(D)) {
+return VD->isLocalVarDeclOrParm() && !isa(VD);
+  }
+  return isa(D);
+}
+
+const Decl *Sema::getCanonicalLocalDecl(const Decl *D) {
+  if (isa(D)) {
+return D;
+  }
+
+  const auto *VD = dyn_cast(D);
+  const auto *BD = dyn_cast(D);
+  if (!VD && !BD) {
+return D;
+  }
+
+  if (VD && !VD->isLocalVarDeclOrParm()) {
+return D;
+  }
+
+  const DeclContext *DC = VD ? VD->g

[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-10 Thread Chuanqi Xu via cfe-commits


@@ -3454,9 +3454,21 @@ uint64_t 
ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
   if (DC->decls_empty())
 return 0;
 
-  // In reduced BMI, we don't care the declarations in functions.
-  if (GeneratingReducedBMI && DC->isFunctionOrMethod())
-return 0;
+  // In reduced BMI, we don't care the declarations in functions, unless they
+  // are templated functions, because their bodies may contain nested 
class/lambda

ChuanqiXu9 wrote:

I am not satisfying with this... this is too broad.  Maybe we need to figure 
out in such cases what declarations is needed for the lexical lookup. 

I don't feel the fix here is correct.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-10 Thread Chuanqi Xu via cfe-commits


@@ -0,0 +1,95 @@
+// RUN: rm -rf %t

ChuanqiXu9 wrote:

Not a formal policy, but we want such test for C++20 modules to end with .cppm 
instead of .cpp.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-10 Thread Ivo Popov via cfe-commits

https://github.com/ipopov edited 
https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-10 Thread Ivo Popov via cfe-commits


@@ -3454,9 +3454,21 @@ uint64_t 
ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
   if (DC->decls_empty())
 return 0;
 
-  // In reduced BMI, we don't care the declarations in functions.
-  if (GeneratingReducedBMI && DC->isFunctionOrMethod())
-return 0;
+  // 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()) {

ipopov wrote:

You're right about the existing test - sorry... (as Reduced BMI is C++20 
modules-specific.)

But I think that the underlying deserialization bug will recur when templates 
containing generic lambdas are defined in C++20 named modules (compiled with 
Reduced BMI) and then imported or mixed with header modules.

I added a separate C++20 named modules test 
(`modules-generic-lambda-local-mapping-bmi.cpp`) that compiles using 
`-emit-reduced-module-interface`. It crashes on the assertion without the 
`ASTWriter` fix, and passes with it.

LMK if this kind of mixing of C++20 modules with Clang header modules is 
unrealistic. I figured since it can trigger an alert, it's worth fixing.

Thanks for having a look!

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-10 Thread Ivo Popov via cfe-commits

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 
Date: Mon, 8 Jun 2026 02:49:00 +
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>
+  CanonicalLocalDecls;
+
+  const Decl *getCanonicalLocalDecl(const Decl *D);
+
   typedef llvm::DenseMap>
   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(D)) {
 if (const FunctionDecl *FD = dyn_cast(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(D) || isa(D)) {
+return SemaRef.getCanonicalLocalDecl(D);
+  }
   return D;
 }
 
 llvm::PointerUnion *
 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 &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(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 &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(D)) {
+return VD->isLocalVarDeclOrParm() && !isa(VD);
+  }
+  return isa(D);
+}
+
+const Decl *Sema::getCanonicalLocalDecl(const Decl *D) {
+  if (isa(D)) {
+return D;
+  }
+
+  const auto *VD = dyn_cast(D);
+  const auto *BD = dyn_cast(D);
+  if (!VD && !BD) {
+return D;
+  }
+
+  if (VD && !VD->isLocalVarDeclOrParm()) {
+return D;
+  }
+
+  const DeclContext *DC = VD ? VD->g

[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-08 Thread Chuanqi Xu via cfe-commits

https://github.com/ChuanqiXu9 commented:

Haven't looked into the details. But the description is not correct.

The attached test is not C++20 modules.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-08 Thread Ivo Popov via cfe-commits

ipopov wrote:

> BTW, it's nicer to not to force-push PR branches, and instead to just keep 
> adding new commits on top with each additional change. That way it's easier, 
> in the github UI, to see what the changes have been since last review.

Sorry about that! I have never used Git or Github for pull requests like this 
before. Ugh. Let me try to untangle. I will do one more force-push, back to the 
original version, then; and from there on I will push deltas.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-08 Thread James Y Knight via cfe-commits

jyknight wrote:

> @ipopov ipopov force-pushed the fix-modules-generic-lambda-capture branch 
> from 23489db to 2672ae5 

BTW, it's nicer to not to force-push PR branches, and instead to just keep 
adding new commits on top with each additional change. That way it's easier, in 
the github UI, to see what the changes have been since last review.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-08 Thread James Y Knight via cfe-commits


@@ -7421,3 +7421,67 @@ void Sema::PerformDependentDiagnostics(const DeclContext 
*Pattern,
 }
   }
 }
+
+static bool isMappedLocalDecl(const Decl *D) {
+  if (const auto *VD = dyn_cast(D)) {
+return VD->isLocalVarDeclOrParm() && !isa(VD);
+  }
+  return isa(D);
+}
+
+const Decl *Sema::getCanonicalLocalDecl(const Decl *D) {
+  if (isa(D)) {
+return D;
+  }
+
+  const auto *VD = dyn_cast(D);
+  const auto *BD = dyn_cast(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(DC);
+  if (!FD) {
+return D;
+  }
+
+  const auto *CanonFD = FD->getCanonicalDecl();

jyknight wrote:

We need the canonical _definition_, not just a decl, in order to be able to 
iterate `decls()` on it. Probably `FD->getDefinition()`? I think that's 
guaranteed to use return a canonical definition.

That'll end up with this using a different decl than the ParamVar mapping, 
which uses the  `getCanonicalDecl`. But maybe that's fine?

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-08 Thread James Y Knight via cfe-commits

https://github.com/jyknight commented:

FTR, running the provided test with current clang results in the following 
assertion error:
```
clang: clang/lib/Sema/SemaTemplateInstantiate.cpp:4772: llvm::PointerUnion 
*clang::LocalInstantiationScope::findInstantiationOf(const Decl *): Assertion 
`isa(D) && "declaration not instantiated in this scope"' failed.
```

I'm not really knowledgeable enough about Clang AST to appropriately review 
this change, but I commented on one problem in it.

I also note that this depends on the imported functions always having exactly 
the same local decls within the function body, so that mapping by index can 
work. I _think_ that is correct to assume, since we do ODR hashing of the ASTs 
on imports and raise an error if they mismatch. But seemed worth pointing out 
explicitly, in case there's some edge case where that isn't true I don't know 
about. (And the existing mapping only happens on params, which I think are 
_more_ guaranteed-equal.)


https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-08 Thread James Y Knight via cfe-commits


@@ -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 
+T&& move(T& t) noexcept { return static_cast(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 
+struct Consumer {
+  template 
+  void operator()(const U& x) const {}
+};
+
+template 
+struct Holder {
+  F f;
+  explicit Holder(F f) : f(std::move(f)) {}
+  void call() const { f(0); }
+};
+
+template 
+auto make_holder(F f) {
+  return Holder(std::move(f));
+}
+
+template 
+class TemplateClass {
+ public:
+  template 
+  explicit TemplateClass(Args&&... args) {

jyknight wrote:

Replacing this template fn with a decl, `explicit TemplateClass(Args&&... 
args);` and moving the definition out-of-line below the class, 
```
template 
template 
TemplateClass::TemplateClass(Args&&... args) {
  [...snip...]
}
```
demonstrates the issue mentioned above.

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-08 Thread James Y Knight via cfe-commits

https://github.com/jyknight edited 
https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-07 Thread Ivo Popov via cfe-commits

https://github.com/ipopov edited 
https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-07 Thread via cfe-commits

llvmorg-github-actions[bot] wrote:




@llvm/pr-subscribers-clang-modules

Author: Ivo Popov (ipopov)


Changes

### Intro

Human writing: I encountered this problem in Clang, and used some LLM help to 
nail it down to a very simple reproducer (the regression test added in this 
PR), and an "idiomatic" (so I am told, but I cannot fully assess) fix. I would 
appreciate some help seeing if this is indeed reasonable. What follows after 
here was written with the help of AI.

### Bug Description
When instantiating a dependent template definition in a C++20 modules context, 
Clang may select a locally-parsed definition instead of the canonical one from 
an imported module PCM. However, if the template contains nested generic 
lambdas or local classes, their closure types and call operators are eagerly 
canonicalized and merged with the imported module definitions.

This mismatch causes compilation errors during template instantiation: the 
outer function template is instantiated from the local pattern (meaning local 
variables in scope are from the local pattern), but the lambda body is 
instantiated from the imported canonical pattern (which references the imported 
canonical variables). Because `LocalInstantiationScope` only contains the local 
variables from the locally-parsed pattern, references to the canonical 
variables fail to map, causing instantiation failures.

### Solution
1. **Structural Mapping:** Introduce a structural mapping helper in `Sema` 
(`getCanonicalLocalDecl`) to map local variables (`VarDecl` and structured 
bindings `BindingDecl`) from a non-canonical function definition to their 
canonical counterpart using their relative index in the declaration context.
2. **Local Instantiation Scope integration:** Update `LocalInstantiationScope` 
to use this canonical mapping when resolving local variables.
3. **Reduced BMI Preservation:** Prevent stripping the lexical block of 
dependent context template definitions under Reduced BMI, ensuring the 
canonical declarations are preserved and available for mapping.

### Test Coverage
Added a new regression test under 
`clang/test/Modules/modules-generic-lambda-local-capture.cpp` that exercises 
generic lambdas and structured bindings inside templates across module 
boundaries.


---
Full diff: https://github.com/llvm/llvm-project/pull/202248.diff


5 Files Affected:

- (modified) clang/include/clang/Sema/Sema.h (+8) 
- (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+9-12) 
- (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+64) 
- (modified) clang/lib/Serialization/ASTWriter.cpp (+8-1) 
- (added) clang/test/Modules/modules-generic-lambda-local-capture.cpp (+137) 


``diff
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>
+  CanonicalLocalDecls;
+
+  const Decl *getCanonicalLocalDecl(const Decl *D);
+
   typedef llvm::DenseMap>
   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(D)) {
 if (const FunctionDecl *FD = dyn_cast(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(D) || isa(D)) {
+return SemaRef.getCanonicalLocalDecl(D);
+  }
   return D;
 }
 
 llvm::PointerUnion *
 LocalInstantiationScope::getInstantiationOfIfExists(const Decl *D) {
-  D = getCanonicalParmVarDecl(D);
+  D = getCanonicalLocalDecl(D, SemaRef);
   for (LocalInstantiationScope *Current = this; Current;
Current = Current->Outer) {
 
@@ -448

[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-07 Thread via cfe-commits

github-actions[bot] wrote:



Hello @ipopov :wave:

Thank you for submitting a Pull Request (PR) to the LLVM Project. Since this is 
your first PR, here are a few useful links covering our main contribution 
policies and review practices.

* All contributions to LLVM must follow our [LLVM AI Tool Use 
Policy](https://llvm.org/docs/AIToolPolicy.html). In particular, if you used AI 
while working on this PR, remember to add a note to the PR description.
* The [LLVM Code-Review Policy and 
Practices](https://llvm.org/docs/CodeReview.html) document contains practical 
information about the PR process, including how patches are reviewed and 
accepted, and who can review a PR.
* Our [LLVM Developer Policy](https://llvm.org/docs/DeveloperPolicy.html) 
describes our expectations for code quality, commit summaries and contains 
notes on our CI system.

Please reply to this message to confirm that you have read these policies, 
especially the LLVM AI Tool Use Policy, and that any AI tool usage has been 
noted in the PR description.

---

### Frequently asked questions

**How do I add reviewers?**

This PR will be automatically labeled, and the relevant teams will be notified. 
For some parts of the project, reviewers may also be added automatically.

You can also add reviewers manually using the **Reviewers** section on this 
page. If you cannot use that section, it is probably because you do not have 
write permissions for the repository. In that case, you can request a review by 
tagging reviewers in a comment using `@` followed by their GitHub username.

**What if there are no comments?**

If you have not received any comments on your PR after a week, you can request 
a review by pinging the PR with a comment such as “Ping”. The common courtesy 
ping rate is once a week. Please remember that you are asking for volunteer 
time from other developers.

**Are any special GitHub settings required to contribute to LLVM?**

We only require contributors to have a public email address associated with 
their GitHub commits, see this 
[section](https://llvm.org/docs/DeveloperPolicy.html#email-addresses) of LLVM 
Developer Policy for details.

---

If you have questions, feel free to leave a comment on this PR, or ask on [LLVM 
Discord](https://discord.com/invite/xS7Z362) or [LLVM 
Discourse](https://discourse.llvm.org/).

Thank you,
The LLVM Community

https://github.com/llvm/llvm-project/pull/202248
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits


[clang] [Clang][Modules] Fix generic lambda local capture mapping mismatch in template instantiation (PR #202248)

2026-06-07 Thread Ivo Popov via cfe-commits

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

### Intro

Human writing: I encountered this problem in Clang, and used some LLM help to 
nail it down to a very simple reproducer (the regression test added in this 
PR), and an "idiomatic" (so I am told, but I cannot fully assess) fix. I would 
appreciate some help seeing if this is indeed reasonable. What follows after 
here was written with the help of AI.

### Bug Description
When instantiating a dependent template definition in a C++20 modules context, 
Clang may select a locally-parsed definition instead of the canonical one from 
an imported module PCM. However, if the template contains nested generic 
lambdas or local classes, their closure types and call operators are eagerly 
canonicalized and merged with the imported module definitions.

This mismatch causes compilation errors during template instantiation: the 
outer function template is instantiated from the local pattern (meaning local 
variables in scope are from the local pattern), but the lambda body is 
instantiated from the imported canonical pattern (which references the imported 
canonical variables). Because `LocalInstantiationScope` only contains the local 
variables from the locally-parsed pattern, references to the canonical 
variables fail to map, causing instantiation failures.

### Solution
1. **Structural Mapping:** Introduce a structural mapping helper in `Sema` 
(`getCanonicalLocalDecl`) to map local variables (`VarDecl` and structured 
bindings `BindingDecl`) from a non-canonical function definition to their 
canonical counterpart using their relative index in the declaration context.
2. **Local Instantiation Scope integration:** Update `LocalInstantiationScope` 
to use this canonical mapping when resolving local variables.
3. **Reduced BMI Preservation:** Prevent stripping the lexical block of 
dependent context template definitions under Reduced BMI, ensuring the 
canonical declarations are preserved and available for mapping.

### Test Coverage
Added a new regression test under 
`clang/test/Modules/modules-generic-lambda-local-capture.cpp` that exercises 
generic lambdas and structured bindings inside templates across module 
boundaries.


>From b2b8d4fae2b3674a04db48afe4daaf81b61975ea Mon Sep 17 00:00:00 2001
From: Ivo Popov 
Date: Mon, 8 Jun 2026 02:49:00 +
Subject: [PATCH] [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>
+  CanonicalLocalDecls;
+
+  const Decl *getCanonicalLocalDecl(const Decl *D);
+
   typedef llvm::DenseMap>
   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(D)) {
 if (const FunctionDecl *FD = dyn_cast(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(D) || isa(D)) {
+return SemaRef.getCanonicalLocalDecl(D);
+  }
   return D;
 }
 
 llvm::PointerUnion *
 LocalInstantiationScope::g