Skip to content

Add keydecls to modules instead of just expanding them.#172898

Closed
Sterling-Augustine wants to merge 1 commit intollvm:mainfrom
Sterling-Augustine:keydecl
Closed

Add keydecls to modules instead of just expanding them.#172898
Sterling-Augustine wants to merge 1 commit intollvm:mainfrom
Sterling-Augustine:keydecl

Conversation

@Sterling-Augustine
Copy link
Contributor

pr/171769 added a nice optimization for namespace lookups, but has a bug where if a key decl does not also appear in the chain, it will be omitted from the module. Fix that.

This may not be exactly the correct fix, but it does resolve the issue described in pr/171769. Another possible fix is to ensure that the key decl always appears in the chain. But I suspect what is happening is that it isn't written out the first time the ast file is written.

I still don't have a good reduced test case for this--I actually know almost nothing at all about Clang modules and KeyDecls. Perhaps someone more familiar with how it all works can propose one.

The symptom this fixes is below. The description isn't enough to reproduce the problem, unfortunately, and I haven't been able to reduce it beyond several hundred files.

header_b.h:

#include "header_a.h" // Declares namespace_a, and various things inside it. 
namespace namespace_b = namespace_a;

and the error is:

<header_b.h>:13:34: error: declaration of 'namespace_a' must be imported from module 'module_c' before it is required
   13 | namespace namespace_b = ::<namespace_a>;
      |                                  ^
1 error generated.

Module_c declares more things in namespace_a, as do many other modules.

pr/171769 added a nice optimization for namespace lookups, but has a
bug where if a key decl does not also appear in the chain, it will be
omitted from the module. Fix that.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:modules C++20 modules and Clang Header Modules labels Dec 18, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 18, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-modules

Author: None (Sterling-Augustine)

Changes

pr/171769 added a nice optimization for namespace lookups, but has a bug where if a key decl does not also appear in the chain, it will be omitted from the module. Fix that.

This may not be exactly the correct fix, but it does resolve the issue described in pr/171769. Another possible fix is to ensure that the key decl always appears in the chain. But I suspect what is happening is that it isn't written out the first time the ast file is written.

I still don't have a good reduced test case for this--I actually know almost nothing at all about Clang modules and KeyDecls. Perhaps someone more familiar with how it all works can propose one.

The symptom this fixes is below. The description isn't enough to reproduce the problem, unfortunately, and I haven't been able to reduce it beyond several hundred files.

header_b.h:

#include "header_a.h" // Declares namespace_a, and various things inside it. 
namespace namespace_b = namespace_a;

and the error is:

&lt;header_b.h&gt;:13:34: error: declaration of 'namespace_a' must be imported from module 'module_c' before it is required
   13 | namespace namespace_b = ::&lt;namespace_a&gt;;
      |                                  ^
1 error generated.

Module_c declares more things in namespace_a, as do many other modules.


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

1 Files Affected:

  • (modified) clang/lib/Serialization/ASTWriter.cpp (+1-2)
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 234615522e773..4154584cc30e2 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -4444,6 +4444,7 @@ class ASTDeclContextNameLookupTrait
     };
     ASTReader *Chain = Writer.getChain();
     for (NamedDecl *D : Decls) {
+      AddDecl(D);
       if (Chain && isa<NamespaceDecl>(D) && D->isFromASTFile() &&
           D == Chain->getKeyDeclaration(D)) {
         // In ASTReader, we stored only the key declaration of a namespace decl
@@ -4456,8 +4457,6 @@ class ASTDeclContextNameLookupTrait
         Chain->forEachImportedKeyDecl(D, [&AddDecl](const Decl *D) {
           AddDecl(cast<NamedDecl>(const_cast<Decl *>(D)));
         });
-      } else {
-        AddDecl(D);
       }
     }
     return std::make_pair(Start, DeclIDs.size());

@mpark
Copy link
Member

mpark commented Dec 18, 2025

Hi @Sterling-Augustine, thanks for this! Curious, are you able to share the several hundred line repro you do have? Maybe with some obfuscating?

@Sterling-Augustine
Copy link
Contributor Author

Hi @Sterling-Augustine, thanks for this! Curious, are you able to share the several hundred line repro you do have? Maybe with some obfuscating?

Alas, it is hundreds of files still, not hundreds of lines. And many are confidential.

@mpark
Copy link
Member

mpark commented Dec 19, 2025

Hi @Sterling-Augustine, thanks for this! Curious, are you able to share the several hundred line repro you do have? Maybe with some obfuscating?

Alas, it is hundreds of files still, not hundreds of lines. And many are confidential.

Ohh I see. I misread that. Okay, thanks!

Copy link
Member

@ChuanqiXu9 ChuanqiXu9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except what the patch is, I really hope we can have a test. It is really important for the maintainability.

};
ASTReader *Chain = Writer.getChain();
for (NamedDecl *D : Decls) {
AddDecl(D);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix looks odd to me. Since I think the following

Chain->forEachImportedKeyDecl(D, [&AddDecl](const Decl *D) {
          AddDecl(cast<NamedDecl>(const_cast<Decl *>(D)));
        });

will always add decl for D itself.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is what I'm confused about as well 🤔

Copy link
Member

@mpark mpark Dec 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sterling-Augustine I understand you cannot share the repro you have, but would you be able to insert some code here like:

for (NamedDecl *D : Decls) {
  if (...) {
    bool Found = false;
    Chain->forEachImportedKeyDecl(D, [&AddDecl, &Found](const Decl *D2) {
      AddDecl(cast<NamedDecl>(const_cast<Decl *>(D2)));
      if(D == D2) Found = true;
    });
    if (!Found) {
      D->dump();
      D->getCanonicalDecl()->dump();
    }
  } else {
    AddDecl(D);
  }
}

and let us know what that prints?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After more testing, I don't think this fixes it, so closing as not useful while I work on further reductions.

@Sterling-Augustine
Copy link
Contributor Author

The original PR now has a testcase created by my colleague that demonstrates the problem.

@mpark
Copy link
Member

mpark commented Dec 22, 2025

The original PR now has a testcase created by my colleague that demonstrates the problem.

This is great. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants