I would like to pitch a new top level statement called compilerSettings that can be used to modify the compiler settings that are enabled on a specific source file. Today, enabling compiler settings can only be done at the module level via the command line meaning that it is impossible to enable compiler settings only on a specific file causing developer friction that we would like to reduce especially given new features like default isolation control and strict memory safety. It also will allow for users to update code for new language modes one file at a time using warningsAsErrors instead of doing it over an entire large module using just warnings.
How will changes to the compilerSettings declaration impact incremental builds? Some features, like NonFrozenEnumExhaustivity, could be applied to the declarations in one file but will have non-local impact on how the rest of the translation unit is compiled. Can this be modeled in the swiftdeps format such that the entire module doesn't need to rebuild on every change?
Will compilerSettings entries be allowed to contradict flags passed on the command line, and if so, which wins?
Some compiler settings impact how clang modules are built and imported into Swift, and varying those within a TU has the potential to introduce multiple module variants, which Swift historically hasn't allowed within a module. Should settings impacting the clang importer be explicitly disallowed?
In what order are module- and file-level settings combined? For instance, if I wanted to enable warnings-as-errors on a module level and then opt out of that for a specific file, is that possible? (I don’t think the option to disable warnings-as-errors exists at the moment, but that should be fairly straightforward to add as long as the options are combined in the correct way as a result of this proposal)
a custom fixed enum defined as a subtype of CompilerSetting (e.x.: StrictConcurrencyCompilerSetting)
I think this should say "nested type", since a "subtype" relation is something else.
a custom enum defined as a subtype of CompilerSetting that the compiler synthesizes cases for depending on internal compiler data (e.x.: features for UpcomingFeatureCompilerSetting)
I would argue that since only a subset of experimental/upcoming features are meaningful to set on a per-file basis, perhaps this subset should be explicitly spelled out with case declarations?
Other compiler flags whose impacts would necessitate exposing compilerSettings in textual modules. We view supported compilerSettings as an anti-goal since appearing in the module header makes enablingcompilerSettings affect the way the module is interpreted by users, while we are designing compilerSettings to have an impact strictly local to the file. NOTE: This does not preclude compiler options that modify the way the code in the file is exposed in textual modules by modifying the code's representation in the textual module directly.
What about features like ExistentialAny or FullTypedThrows that change type checker behavior in inlinable code? It seems we'd either need special constructs to change compilerSettings in the middle of a swiftinterface (since the printed declarations can come from different source files), or we disallow such features from being set with compilerSettings.
Sounds great! Was there discussion about a "future direction" of clang-style pragma push and pragma pop support? So that something like .warningAsError(.concurrency) could be added with more fine-grained control over one piece of code (as opposed to all the code in a file) in the future?
You would not suspect how often security people ask me how to tell whether a file enables a specific compiler setting or not. There is an industry trend (outlined at this morning's first LLVM memory safety working group meetup) that compilers could do more to inventorize the code that benefits (and does not benefit) from security-relevant compiler options. With the introduction of this feature, it's no longer sufficient to look at the build command log to find out whether a feature is enabled or disabled. As we add another configuration vector, are we also thinking of how to report the final configuration used for a file or function?
Do we think that we will extend this mechanism further? For instance, Clang can control certain behaviors (like warnings) at a smaller granularity than file-level. Do we think Swift could be headed in that direction?
Very keen for this, I'd love to be able to enable strict concurrency checking and the upcoming memory safety settings on a per-file basis rather than having to play module chicken & migrate code in dependency order.
It sounds like it'll have to be very cautious about which settings can be changed, though.
The concept seems useful, 100%, but I'm not understanding how it's supposed to work exactly after reading the 0000-compiler-settings.md. It says "top level" but for some reason that's not translating to being able to imagine using it clearly for me.
Is this saying that to enable this feature a person would need to put the compilerSettings[] set at the top of the file and the compiler would find it and behave accordingly? Will it be easy to find all the files in a project/package that have compiler opinions?
Would this then translate to all build systems? CMake swift-mode? Or is this specifically for XCode/SwiftPM builds?
My understanding is that handling of the new source statement happens inside the frontend, so the build system is not really involved directly at all. (So a compiler setting for adding a module search path, for example, would not be a good fit for this model.)
Sorry, but I don't think I understand what you mean by happens inside the frontend - Like this is an XCode setting?
I compile Swift in approximately 4 different ways, but I still have more of gcc mentality than a clang mentality, so seriously apologies if this is way too 101 of a question.
Xcode GUI button
swift run
CMake with a occasional companion shell script
direct calls to swiftc
My understanding is that this should make it so I don't have to drop to CMake/Ninja or hand crafted scripts to get fancy? So then... WHERE are these particular compilerSettings[] being set and what's intercepting them?
Slava means this is a feature within the compiler itself. compilerSetting is written directly in source code and the compiler will specifically look for the statement when determining what the language options are while type checking the code in that file, for example. There are no build system changes involved, and all ways of invoking the compiler should "just work" with this feature.
Dumb technical question about "we require that compilerSettings be the first AST node in the file before any other statements including import statements": presumably in actual projects, this will have to be staged in as:
Would it be feasible to include compiler optimization level as one of the CompilerSetting cases? This would enable using debug mode for the project as a whole, but release mode for a rarely-changed hot path.
This will be a good thing to have. I don't see any need for the completely novel syntax, however. This should be spelled like any other build-time directive:
These days yes, because of @Douglas_Gregor's work to get rid of IfConfigDecl. We now expand such conditions while building the AST.
Unless compilerSettings impact parsing though, which I hope they do not, there's probably no reason to impose this restriction.
We allow import declarations to appear anywhere in a file, for example; we collect and process them after parsing, but before any other type checking is performed. We could similarly check for a compilerSettings statement before doing anything else.
Finally, I think the proposal calls this it a "statement", but it's really a declaration (Decl in the AST) and not a statement (Stmt). Top-level statements can only appear in the main source file of a module, or in the single file of a script.
We are not intending to support features with non-local impacts that cannot be summarized by a change in the AST that would trigger an incremental build update.
I think this will most likely depend on the specific flag being specified. My gut feeling is that file should win over module if we allow for this. But it is probably something that could be configurable.
A compiler setting that modifies how clang modules are built is not a compiler setting that we would support.