In build2 we scan a preprocessed TU in order to discover named modules
that are needed to compile said TU. Clang, however, tries to load named
modules during preprocessing, because, I believe, Clang's module map-
based modules can also be named and such modules can export macros. This
poses a problem for build systems that are only attempting to support
Prior to Clang 9 we hacked around this by simply not enabling (i.e., not
passing -fmodules-ts) modules during preprocessing. However, starting
with Clang 9, modules are enabled by default in the c++2a mode and
there doesn't seem to be a way to disable them. Plus, disabling them is
really a hack rather than a solution: for example, it will fall apart
once we try to support header units (e.g., via the module mapper).
I would therefore like to propose a "strict" C++20 modules mode either
as a default or as an opt-in. Another approach (suggested by Richard
in private communication) is to try to load such a module but to not
fail if it doesn't exist. I see two issues with this approach:
1. It would be hard to distinguish intentional and unintentional
"absence" of a BMI.
2. More importantly, the BMI might exist but be out of date.
On the other hand, the strict mode sounds like a fairly simple solution
(but I am happy to explore Richard's suggestion if this is preferred).
I've done a quick survey of the modules-related options and modes in
-f[no-]modules # Enable/Disable Clang modules.
-f[no-]cxx-modules # Enable/Disable Clang modules in C++ TUs.
-fmodules-ts # Enable Modules TS.
-std=c++2a # Enables C++20 modules.
The current mapping of the options to LangOpts is best captured by this
fragment from lib/Frontend/CompilerInvocation.cpp:
Opts.CPlusPlusModules = Opts.CPlusPlus2a;
Opts.ModulesTS = Args.hasArg(OPT_fmodules_ts);
Args.hasArg(OPT_fmodules) || Opts.ModulesTS || Opts.CPlusPlusModules;
To support strict C++20 modules at the LangOpts level, it seems natural
to introduce ClangModules (there is already a precedent in
lib/Driver/ToolChains/Clang.cpp:RenderModuleOptions()) with the
following basic semantics:
Opts.ClangModules = Args.hasArg(OPT_fmodules);
Opts.Modules = Opts.ClangModules || Opts.ModulesTS || Opts.CPlusPlusModules;
At the options level, the approach depends on whether we want the default
to be strict or if we want to make it opt-in. If it's strict by default,
then using -fmodules in addition to -std=c++2a seems like a natural way to
additionally request the Clang modules semantics. If we want it as opt-in,
then -fno-modules again in combination with -std=c++2a seems natural (in
fact, I was surprised that neither -fno-modules nor -fno-cxx-modules had
any effect when -std=c++2a was specified).
To me personally, strict by default seems cleaner (the Clang modules
semantics would have been more appropriate for something like
-std=clang++2a). But I am also ok with opt-in.
What do you thinks? If this sounds sensible, I can come up with a patch.