Skip to content

[Modules] Don't fail when an unused textual header is missing. #138227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2025

Conversation

vsapsai
Copy link
Collaborator

@vsapsai vsapsai commented May 2, 2025

According to the documentation

A header declaration that does not contain exclude nor textual specifies a header that contributes to the enclosing module.

Which means that exclude and textual header don't contribute to the enclosing module and their presence isn't required to build such a module. The keywords tell clang how a header should be treated in a context of the module but they don't add headers to the module.

When a textual header is used, clang still emits "file not found" error pointing to the location where the missing file is included.

According to the documentation
> A header declaration that does not contain `exclude` nor `textual` specifies a header that contributes to the enclosing module.

Which means that `exclude` and `textual` header don't contribute to the
enclosing module and their presence isn't required to build such a module.
The keywords tell clang how a header should be treated in a context of
the module but they don't add headers to the module.

When a textual header *is* used, clang still emits "file not found" error
pointing to the location where the missing file is included.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules labels May 2, 2025
@llvmbot
Copy link
Member

llvmbot commented May 2, 2025

@llvm/pr-subscribers-clang

Author: Volodymyr Sapsai (vsapsai)

Changes

According to the documentation
> A header declaration that does not contain exclude nor textual specifies a header that contributes to the enclosing module.

Which means that exclude and textual header don't contribute to the enclosing module and their presence isn't required to build such a module. The keywords tell clang how a header should be treated in a context of the module but they don't add headers to the module.

When a textual header is used, clang still emits "file not found" error pointing to the location where the missing file is included.


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

3 Files Affected:

  • (modified) clang/lib/Lex/ModuleMap.cpp (+4-2)
  • (modified) clang/test/Modules/Inputs/submodules/module.modulemap (+4)
  • (modified) clang/test/Modules/missing-header.m (+3)
diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp
index c2f13fa48d464..1ba02b919a22a 100644
--- a/clang/lib/Lex/ModuleMap.cpp
+++ b/clang/lib/Lex/ModuleMap.cpp
@@ -310,8 +310,10 @@ void ModuleMap::resolveHeader(Module *Mod,
   } else if (Header.HasBuiltinHeader && !Header.Size && !Header.ModTime) {
     // There's a builtin header but no corresponding on-disk header. Assume
     // this was supposed to modularize the builtin header alone.
-  } else if (Header.Kind == Module::HK_Excluded) {
-    // Ignore missing excluded header files. They're optional anyway.
+  } else if ((Header.Kind == Module::HK_Excluded) ||
+             (Header.Kind == Module::HK_Textual)) {
+    // Ignore excluded and textual header files as a module can be built with
+    // such headers missing.
   } else {
     // If we find a module that has a missing header, we mark this module as
     // unavailable and store the header directive for displaying diagnostics.
diff --git a/clang/test/Modules/Inputs/submodules/module.modulemap b/clang/test/Modules/Inputs/submodules/module.modulemap
index 1c1b76a08969e..9e8143b8101de 100644
--- a/clang/test/Modules/Inputs/submodules/module.modulemap
+++ b/clang/test/Modules/Inputs/submodules/module.modulemap
@@ -30,3 +30,7 @@ module missing_umbrella_with_inferred_submodules {
   module * { export * }
   export *
 }
+
+module missing_textual_header {
+  textual header "missing_textual.h"
+}
diff --git a/clang/test/Modules/missing-header.m b/clang/test/Modules/missing-header.m
index c162e1b5f08b3..84d82e5ceda32 100644
--- a/clang/test/Modules/missing-header.m
+++ b/clang/test/Modules/missing-header.m
@@ -8,6 +8,9 @@
 @import missing_unavailable_headers.not_missing; // OK
 // CHECK-NOT: missing_unavailable_headers
 
+@import missing_textual_header; // OK
+// CHECK-NOT: missing_textual_header
+
 @import missing_headers;
 // CHECK: module.modulemap:15:27: error: header 'missing.h' not found
 // CHECK: could not build module 'missing_headers'

@llvmbot
Copy link
Member

llvmbot commented May 2, 2025

@llvm/pr-subscribers-clang-modules

Author: Volodymyr Sapsai (vsapsai)

Changes

According to the documentation
> A header declaration that does not contain exclude nor textual specifies a header that contributes to the enclosing module.

Which means that exclude and textual header don't contribute to the enclosing module and their presence isn't required to build such a module. The keywords tell clang how a header should be treated in a context of the module but they don't add headers to the module.

When a textual header is used, clang still emits "file not found" error pointing to the location where the missing file is included.


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

3 Files Affected:

  • (modified) clang/lib/Lex/ModuleMap.cpp (+4-2)
  • (modified) clang/test/Modules/Inputs/submodules/module.modulemap (+4)
  • (modified) clang/test/Modules/missing-header.m (+3)
diff --git a/clang/lib/Lex/ModuleMap.cpp b/clang/lib/Lex/ModuleMap.cpp
index c2f13fa48d464..1ba02b919a22a 100644
--- a/clang/lib/Lex/ModuleMap.cpp
+++ b/clang/lib/Lex/ModuleMap.cpp
@@ -310,8 +310,10 @@ void ModuleMap::resolveHeader(Module *Mod,
   } else if (Header.HasBuiltinHeader && !Header.Size && !Header.ModTime) {
     // There's a builtin header but no corresponding on-disk header. Assume
     // this was supposed to modularize the builtin header alone.
-  } else if (Header.Kind == Module::HK_Excluded) {
-    // Ignore missing excluded header files. They're optional anyway.
+  } else if ((Header.Kind == Module::HK_Excluded) ||
+             (Header.Kind == Module::HK_Textual)) {
+    // Ignore excluded and textual header files as a module can be built with
+    // such headers missing.
   } else {
     // If we find a module that has a missing header, we mark this module as
     // unavailable and store the header directive for displaying diagnostics.
diff --git a/clang/test/Modules/Inputs/submodules/module.modulemap b/clang/test/Modules/Inputs/submodules/module.modulemap
index 1c1b76a08969e..9e8143b8101de 100644
--- a/clang/test/Modules/Inputs/submodules/module.modulemap
+++ b/clang/test/Modules/Inputs/submodules/module.modulemap
@@ -30,3 +30,7 @@ module missing_umbrella_with_inferred_submodules {
   module * { export * }
   export *
 }
+
+module missing_textual_header {
+  textual header "missing_textual.h"
+}
diff --git a/clang/test/Modules/missing-header.m b/clang/test/Modules/missing-header.m
index c162e1b5f08b3..84d82e5ceda32 100644
--- a/clang/test/Modules/missing-header.m
+++ b/clang/test/Modules/missing-header.m
@@ -8,6 +8,9 @@
 @import missing_unavailable_headers.not_missing; // OK
 // CHECK-NOT: missing_unavailable_headers
 
+@import missing_textual_header; // OK
+// CHECK-NOT: missing_textual_header
+
 @import missing_headers;
 // CHECK: module.modulemap:15:27: error: header 'missing.h' not found
 // CHECK: could not build module 'missing_headers'

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 2, 2025

The change for exclude headers was done in 040e126.

Copy link
Contributor

@Bigcheese Bigcheese left a comment

Choose a reason for hiding this comment

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

Just looking at the missing_textual_header module alone this is a bit odd, but that's just the semantics of textual headers. Really textual headers just exist to control the non-modular include warning, so this is fine.

@vsapsai vsapsai merged commit 64bb60a into llvm:main May 8, 2025
15 checks passed
@vsapsai vsapsai deleted the accept-missing-textual-header branch May 8, 2025 16:07
@vsapsai
Copy link
Collaborator Author

vsapsai commented May 8, 2025

Thanks for the review!

@bgra8
Copy link
Contributor

bgra8 commented May 16, 2025

We have bisected build breakages internally at google at this change. Ironically the compiler complains about a missing header while before it didn't.

We do not have a reproducer yet.

@ilya-biryukov
Copy link
Contributor

Here's a reproducer for our breakage:

// RUN: rm -rf %t
// RUN: split-file %s %t
//
// First, build a module with a header.
//
// RUN: %clang_cc1 -fmodules -fmodule-map-file=%t/modules1.map -fmodule-name=a -emit-module -xc++ -fmodules-embed-all-files -o %t/a.pcm %t/modules1.map
// RUN: %clang_cc1 -fmodules -fmodule-map-file=%t/modules1.map -fmodule-map-file=%t/modules2.map -fmodule-name=b -emit-module \
// RUN:   -fmodule-file=%t/a.pcm -xc++ -fmodules-embed-all-files -o %t/b.pcm %t/modules2.map
// 
// Remove the header and check we can still build the code that finds it in a PCM.
//
// RUN: rm %t/foo.h
// RUN: %clang_cc1 -fmodules -fmodule-map-file=%t/modules2.map -fmodule-file=%t/b.pcm -fsyntax-only %t/use.cpp

//--- modules1.map
module a {
  module foo {
    header "foo.h"
    export *
  }
  export *
}

//--- modules2.map
module all_textual {
  module foo {
    textual header "foo.h"
    export *
  }
  module wrap_foo {
    textual header "wrap_foo.h"
    export *
  }
  export *
}

module b {
  module wrap_foo {
    private header "wrap_foo.h"
    export *
  }
  export *
}


//--- foo.h
#ifndef FOO_H
#define FOO_H
void foo();
#endif

//--- wrap_foo.h
#include "foo.h"

//--- use.cpp
#include "wrap_foo.h"

void test() {
  foo();
}

It previously built fine because the all_textual.foo and its parent module used to be marked as unavailable because foo.h could not be found here:

Mod->markUnavailable(/*Unimportable=*/false);

and therefore it made us pick the module b.wrap_foo later when resolving wrap_foo.h instead of all_textual.wrap_foo based on the code here:

After the change, we no longer mark all_textual as unavailable and pick all_textual.wrap_foo, which causes us to reprocess the header.

I know what we do is cheesy and we might need to reconsider how to approach it, but this is a breaking change and it would be great to find a resolution for it before committing to the new behavior. Could we revert this while we discuss the potential ways out of it?

@alexfh
Copy link
Contributor

alexfh commented May 20, 2025

@vsapsai Please revert if there's no obvious fix.

@vsapsai
Copy link
Collaborator Author

vsapsai commented May 21, 2025

I know what we do is cheesy and we might need to reconsider how to approach it, but this is a breaking change and it would be great to find a resolution for it before committing to the new behavior. Could we revert this while we discuss the potential ways out of it?

What is your timeframe for stopping putting the same header ("wrap_foo.h") into multiple modules? If you do that it was never guaranteed which module would be used for a header. I'm willing to unblock you but I'd like to know if you are committed to fixing the issue in the module maps.

@ilya-biryukov
Copy link
Contributor

What is your timeframe for stopping putting the same header ("wrap_foo.h") into multiple modules? If you do that it was never guaranteed which module would be used for a header. I'm willing to unblock you but I'd like to know if you are committed to fixing the issue in the module maps.

I don't think we can ever get rid of that pattern and we will keep relying on Clang supporting this. I am sure there are ways to meet both your needs and our needs.

To reiterate, Clang supports having the same header in multiple module maps and we don't view our current setup as having at a high level. I just want to explicit that our stance is that there are no issues that need "fixing in the module maps".
Relying on Clang picking a header from a module when it's available is also something we'd like to keep.

How we achieve this today is shaky (relying on availability doesn't look right), but we absolutely need:

  • to allow multiple modules own the same header,
  • whenever resolving module for the header, pick one that has the header as modular over textual.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" 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.

6 participants