Hi all,
Recently, I've been on a quest to reimplement how the Swift compiler handles #if
directives. The first major piece of this is the SwiftIfConfig
library, which is part of the swift-syntax package that I introduced a few months ago in this forum thread.
Since then, I've managed to replace all of the #if
handling in the compiler with the new library:
- Implemented "configured regions" for the
#if
folding in IDEs and to help with code coverage - Replaced the logic for evaluating the
#if
condition itself in the C++ parser, making most ofParseIfConfig.cpp
dead code. - Added support for
#if
in ASTGen to support the use of the new parser. - Determined which syntax errors to print based on the
#if
s for the new parser. - Replacing the handling of "inlinable text" extraction.
We're in the home stretch. The next major task is to remove IfConfigDecl
from the C++ semantic AST. We no longer want to model #if
s at all in the C++ semantic AST, because doing so is what prevents us from being able to expand #if
usage to other parts of the grammar easily. It's also a nice simplification for the compiler, allowing us to eliminate log for dealing with inactive #if
regions in a lot of places.
So, I went ahead and turned off the generation of IfConfigDecl
in the compiler, and uncovered the next set of issues based on the compiler's test suite. Here they are!
Suppressing warnings based on what's in inactive code
Consider this code:
func f() {
let x = 10
#if DEBUG
print(x)
#endif
}
The variable x
is never used. Without the #if DEBUG
block, the compiler would emit a warning "initialization of immutable value 'x' was never used". However, the compiler looks into the inactive #if DEBUG
block and sees that x
was mentioned, so it suppresses the warning.
There's a similar thing for try
and throw
occurrences within an inactive #if
:
func f() {
do {
#if DEBUG
try g()
#endif
} catch {
// ... dead code ...
}
}
Normally, the compiler would warn about an unreachable catch
, but that warning is suppressed when there's a try
or throw
within an inactive #if
.
I think we want to preserve this behavior. The way I'm planning to do that is to have the warning go look for any inactive #if
clauses in the current scope (using the "configured regions" API mentioned earlier) and look for any tokens that refer to the variable name (for the unused variable warnings) or are try/throw (for the thrown-error one).
Anachronistic SourceKit queries
There are a number of SourceKit queries that do syntactic things based on the C++ IfConfigDecl
. These have effectively all been replaced with swift-syntax (and swift-format), which does a better job with unparsed code (e.g., code behind an inactive #if compiler(>=6.1)
) and is generally better for IDE usage. These are the tests from the Swift test suite that start failing because they test handling of inactive #if
regions based on IfConfigDecl
:
Swift(macosx-arm64) :: SourceKit/SyntaxMapData/syntaxmap.swift
Swift(macosx-arm64) :: SourceKit/CodeExpand/code-expand.swift
Swift(macosx-arm64) :: SourceKit/CodeFormat/indent-pound-if.swift
Swift(macosx-arm64) :: SourceKit/DocumentStructure/structure.swift
Swift(macosx-arm64) :: IDE/coloring_configs.swift
Swift(macosx-arm64) :: IDE/coloring_unclosed_hash_if.swift
The main SourceKit clients no longer use these SourceKit queries, so I propose to break their current handling of #if
in the upcoming Swift release (the next one to branch from main
) and deprecate the queries, then remove the queries entirely in the following Swift release.
Remove the swift-indent
frontend mode
Until I did this experiment, I hadn't realized that it's possible to ask the Swift compiler frontend to indent the given source file. It uses the C++ semantic AST and a bunch of heuristics, and predates swift-syntax and swift-format by years. We don't actually ship this as a tool anywhere, so I think we can outright remove it now without anyone being affected. If that's not possible, we can do the same staged deprecated/removal I'm proposing for SourceKit.
Given how far we've come with SwiftIfConfig, we're really close to getting this part of the compiler onto a nice, new swift-syntax library and simplifying the main compiler code base.
Doug