-
Notifications
You must be signed in to change notification settings - Fork 15.1k
[clang][docs] assert.h is not a good candidate for a textual header #165057
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
Conversation
The C standard behavior of `assert` cannot be accomplished with clang modules, either as a normal modular header, or a textual header. As a normal modular header: #define NDEBUG #include <assert.h> This pattern doesn't work, NDEBUG has to be passed on the command line to take effect, and then will effect all `assert`s in the includer. As a textual header: #define NDEBUG #include <modular_header_that_has_an_assert.h> This pattern doesn't work for similar reasons, modular_header_that_has_an_assert.h captured the value of NDEBUG when its module built and won't pick it up from the includer. -DNDEBUG can be passed when building the module, but will similarly effect the entire module. This has the additional problem that every module will contain a declaration for `assert`, which can possibly conflict with each other if they use different values of NDEBUG. So really <assert.h> just doesn't work properly with clang modules. Avoid the issue by not mentioning it in the Modules documentation, and use "X macros" as the example for textual headers. Don't use [extern_c] in the example modules, that should very rarely be used. Don't put multiple `header` declarations in a submodule, that has the confusing effect of "fusing" the headers. e.g. <sys/errno.h> does not include <errno.h>, but if it's in the same submodule, then an `#include <sys/errno.h>` will mysteriously also include <errno.h>.
|
@llvm/pr-subscribers-clang Author: Ian Anderson (ian-twilightcoder) ChangesThe C standard behavior of As a normal modular header: As a textual header: So really <assert.h> just doesn't work properly with clang modules. Avoid the issue by not mentioning it in the Modules documentation, and use "X macros" as the example for textual headers. Don't use [extern_c] in the example modules, that should very rarely be used. Don't put multiple Full diff: https://github.com/llvm/llvm-project/pull/165057.diff 1 Files Affected:
diff --git a/clang/docs/Modules.rst b/clang/docs/Modules.rst
index acbe45e0be970..e45ee9ff9eac2 100644
--- a/clang/docs/Modules.rst
+++ b/clang/docs/Modules.rst
@@ -421,13 +421,7 @@ As an example, the module map file for the C standard library might look a bit l
.. parsed-literal::
- module std [system] [extern_c] {
- module assert {
- textual header "assert.h"
- header "bits/assert-decls.h"
- export *
- }
-
+ module std [system] {
module complex {
header "complex.h"
export *
@@ -440,7 +434,6 @@ As an example, the module map file for the C standard library might look a bit l
module errno {
header "errno.h"
- header "sys/errno.h"
export *
}
@@ -673,14 +666,14 @@ of checking *use-declaration*\s, and must still be a lexically-valid header
file. In the future, we intend to pre-tokenize such headers and include the
token sequence within the prebuilt module representation.
-A header with the ``exclude`` specifier is excluded from the module. It will not be included when the module is built, nor will it be considered to be part of the module, even if an ``umbrella`` header or directory would otherwise make it part of the module.
+A header with the ``exclude`` specifier is excluded from the module. It will not be included when the module is built, nor will it be considered to be part of the module, even if an ``umbrella`` directory would otherwise make it part of the module.
-**Example:** The C header ``assert.h`` is an excellent candidate for a textual header, because it is meant to be included multiple times (possibly with different ``NDEBUG`` settings). However, declarations within it should typically be split into a separate modular header.
+**Example:** A "X macro" header is an excellent candidate for a textual header, because it is can't be compiled standalone, and by itself does not contain any declarations.
.. parsed-literal::
- module std [system] {
- textual header "assert.h"
+ module MyLib [system] {
+ textual header "xmacros.h"
}
A given header shall not be referenced by more than one *header-declaration*.
|
This isn't a problem with |
Nonetheless I think X-macros are a better example to use here than |
|
|
||
| .. parsed-literal:: | ||
| module std [system] [extern_c] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove the extern_c here? This seems like a pretty good example of a time when it'd make sense to use this attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you mentioned this in the PR description. I suppose whether this is appropriate depends on whether these headers are all pure C headers or if they themselves try to support inclusion from C++. I don't really mind whether we keep this or remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem with [extern_c] is it sticks all of the includes in the module inside of extern C which runs you into -Wmodule-import-in-extern-c which is promoted to an error by default. Plus you never know when an include will transitively include a C++ header like <stdint.h>, so you really don't want your includes to be extern C. It's kind of a flawed attribute, at some point I would propose that we remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm.
It's definitely flawed in that it's viral and requires strong layering between your C headers and your C++ headers, but if you have proper layering, if your C library isn't including C++ headers, and if you can add the attribute to your C library and its transitive dependencies, it can work well. Not everyone has a setup where the C library includes C++ headers rather than being layered entirely beneath the C++ library.
But you make a good point that it's got enough problems that making a blanket recommendation to use it would be unreasonable. Yeah, I'm convinced we shouldn't be using it in this example.
Sure, but if you can't include |
What do you mean by "modular header" here? The terminology I'm familiar with considers textual headers and modular headers to be mutually exclusive -- modular headers are the ones that don't (intend to) depend on the state of translation at the point at which they're entered, and textual headers are the ones that do. I agree (using that definition) that The purpose of
I'm not following something here -- I think you're suggesting that you'd see some kind of difference when |
We've (Apple) found What even should happen if modules A and B include <assert.h>, but B defines NDEBUG before doing so? If I just import A and B and don't include <assert.h> myself, which definition of
I thought even strict
I'm trying to say that |
|
The problem is that there's a lot of code that uses Maybe it would be best if we had a way to keep #define NDEBUG // or #undef NDEBUG
#include <modular_header_that_has_an_assert.h>can emit an error if |
Wouldn't that just result in an error if I did this? |
Conversely, we've (Google) found them to be essential for modeling dependency relationships between notional modules, where not all To what extent is this a Swift import problem rather than a Clang modules problem? (To be clear: I'm not suggesting that would make it any less severe, but it might allow us to better separate the concerns.) The treatment of the module name as part of the entity name sounds Swift-specific, at least.
If you try to use the
That's the difference between
I mean, I suppose, but this would be a potential risk any time you include a textual header from a non-textual header in a module, just like it'd be a risk any time you include an entirely undeclared header from a modular header, because a textual header could likewise depend on enclosing state. If you want a warning for this kind of issue, I wonder if really we have two different kinds of textual headers we should be distinguishing here -- those like That said, it seems that when you mark If we want some detection of cases where a modular build behaves differently from a textual inclusion build, I think we could probably build that without building too much new infrastructure, at least for local submodule visibility builds. In essence, we'd enable use of module maps but not precompiled modules, and textually enter all modular headers even if they're from a different top-level module. Then if we see a use of an identifier that has a macro definition that isn't visible, but would be visible if all modules were visible, we produce an error (and likewise for name lookup and reachability checks). |
|
Maybe this is a bigger discussion than this PR. Maybe we should just leave with "it's complicated" and not talk about assert.h at all in the documentation? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had a really productive discussion about this (and other modules stuff) at the dev meeting. For me the most relevant takeaways were:
config_macrosshould be transitive -- if module A has a config macro, and B imports A, then that config macro is a config macro for B too.- With transitive
config_macrochecking, it would be reasonable for an implementation to choose to make<assert.h>a modular header, and to not support in-file setting ofNDEBUG.
And with that in mind, it seems like we shouldn't be taking a stance on whether <assert.h> is treated as a modular header by a particular implementation (eg, by the libc or SDK's module map).
So yeah, I agree that the best path forward is to just not mention assert.h in our documentation at all. Thanks for your patience here!
…lvm#165057) The C standard behavior of `assert` cannot be accomplished with clang modules, either as a normal modular header, or a textual header. As a normal modular header: #define NDEBUG #include <assert.h> This pattern doesn't work, NDEBUG has to be passed on the command line to take effect, and then will effect all `assert`s in the includer. As a textual header: #define NDEBUG #include <modular_header_that_has_an_assert.h> This pattern doesn't work for similar reasons, modular_header_that_has_an_assert.h captured the value of NDEBUG when its module built and won't pick it up from the includer. -DNDEBUG can be passed when building the module, but will similarly effect the entire module. This has the additional problem that every module will contain a declaration for `assert`, which can possibly conflict with each other if they use different values of NDEBUG. So really <assert.h> just doesn't work properly with clang modules. Avoid the issue by not mentioning it in the Modules documentation, and use "X macros" as the example for textual headers. Don't use [extern_c] in the example modules, that should very rarely be used. Don't put multiple `header` declarations in a submodule, that has the confusing effect of "fusing" the headers. e.g. <sys/errno.h> does not include <errno.h>, but if it's in the same submodule, then an `#include <sys/errno.h>` will mysteriously also include <errno.h>.
…lvm#165057) The C standard behavior of `assert` cannot be accomplished with clang modules, either as a normal modular header, or a textual header. As a normal modular header: #define NDEBUG #include <assert.h> This pattern doesn't work, NDEBUG has to be passed on the command line to take effect, and then will effect all `assert`s in the includer. As a textual header: #define NDEBUG #include <modular_header_that_has_an_assert.h> This pattern doesn't work for similar reasons, modular_header_that_has_an_assert.h captured the value of NDEBUG when its module built and won't pick it up from the includer. -DNDEBUG can be passed when building the module, but will similarly effect the entire module. This has the additional problem that every module will contain a declaration for `assert`, which can possibly conflict with each other if they use different values of NDEBUG. So really <assert.h> just doesn't work properly with clang modules. Avoid the issue by not mentioning it in the Modules documentation, and use "X macros" as the example for textual headers. Don't use [extern_c] in the example modules, that should very rarely be used. Don't put multiple `header` declarations in a submodule, that has the confusing effect of "fusing" the headers. e.g. <sys/errno.h> does not include <errno.h>, but if it's in the same submodule, then an `#include <sys/errno.h>` will mysteriously also include <errno.h>.
The C standard behavior of
assertcannot be accomplished with clang modules, either as a normal modular header, or a textual header.As a normal modular header:
#define NDEBUG
#include <assert.h>
This pattern doesn't work, NDEBUG has to be passed on the command line to take effect, and then will effect all
asserts in the includer.As a textual header:
#define NDEBUG
#include <modular_header_that_has_an_assert.h>
This pattern doesn't work for similar reasons, modular_header_that_has_an_assert.h captured the value of NDEBUG when its module built and won't pick it up from the includer. -DNDEBUG can be passed when building the module, but will similarly effect the entire module. This has the additional problem that every module will contain a declaration for
assert, which can possibly conflict with each other if they use different values of NDEBUG.So really <assert.h> just doesn't work properly with clang modules. Avoid the issue by not mentioning it in the Modules documentation, and use "X macros" as the example for textual headers.
Don't use [extern_c] in the example modules, that should very rarely be used. Don't put multiple
headerdeclarations in a submodule, that has the confusing effect of "fusing" the headers. e.g. <sys/errno.h> does not include <errno.h>, but if it's in the same submodule, then an#include <sys/errno.h>will mysteriously also include <errno.h>.