Skip to content

Commit f065010

Browse files
committed
Convert between computed properties and zero-parameters functions
[SwiftIfConfig] Add a new library for evaluating `#if` conditions. Building on top of the parser and operator-precedence parsing library, introduce a new library that evaluates `#if` conditions against a particular build configuration. The build configuration is described by the aptly named `BuildConfiguration` protocol, which has queries for various build settings (e.g., configuration flags), compiler capabilities (features and attributes), and target information (OS, architecture, endianness, etc.). At present, the only user-facing operation is the `IfConfigState` initializer, which takes in an expression (the `#if` condition) and a build configuration, then evaluates that expression against the build condition to determine whether code covered by that condition is active, inactive, or completely unparsed. This is a fairly low-level API, meant to be a building block for more useful higher-level APIs that query which `#if` clause is active and whether a particular syntax node is active. Add higher-level APIs for querying active code state `IfConfigDeclSyntax.activeClause(in:)` determines which clause is active within an `#if` syntax node. `SyntaxProtocol.isActive(in:)` determines whether a given syntax node is active in the program, based on the nested stack of `#if` configurations. Add support for evaluating `swift` and `compiler` conditionals These were introduced by SE-0212. Implement support for archaic `_compiler_version("X.Y.Z.W.V")` check Add support for `canImport` configuration checks. This is the last kind of check! Remove the `default` fallthrough from the main evaluation function. Improve documentation for `#if` configuration functions Add `ActiveSyntax(Any)Visitor` visitor classes. The `ActiveSyntax(Any)Visitor` visitor classes provide visitors that only visit the regions of a syntax tree that are active according to a particular build configuration, meaning that those nodes would be included in a program that is built with that configuration. Add/cleanup some TODO comments Add an API to rewrite a syntax tree by removing inactive regions. The operation `SyntaxProtocol.removingInactive(in:)` returns a syntax tree derived from `self` that has removed all inactive syntax nodes based on the provided configuration. Implement inactive clause rewriting support for postfix `#if` Postfix `#if` expressions have a different syntactic form than other `#if` clauses because they don't fit into a list-like position in the grammar. Implement a separate, recursive folding algorithm to handle these clauses. Add overview documentation for the SwiftIfConfig library Add CMake build system for SwiftIfConfig Format source Improve documentation for BuildConfiguration Improve documentation for internal `IfConfigFunctions`. Address a number of review comments (thanks, Alex!) [Build configuration] Drop optionality from protocol requirement result types The optional return was used to mean "don't know", but was always treated as false. Instead, make all of the result types non-optional, and allow these operations to throw to indicate failure. While here, drop the "syntax" parameters to all of these functions. We shouldn't be working with syntax inside the build configuration. Simplify `isActive(in:)` using Alex Hoppen's suggestion Use preconditionFailure Use editor placeholder for placeholder expression Minor cleanups Minor cleanups to the #if rewriter Rework use of deprecated APIs Teach active-clause visitors to handle diagnostics When the active-clause visitors encounter an error during evaluation of the `#if` conditions, translate the errors into diagnostics and and visit all of the clauses (because we don't know which is active). This behavior is configurable by ActiveSyntax(Any)Visitor subclasses. Add SwiftIfConfig to the set of documentation targets Fix typo in the release notes Adapt to optionality changes on main Add a method `Error.asDiagnostics(at:)` to unpack errors into diagnostics This is a useful convenience function to handle cases where we have a thrown error from some API and we want to translate it into a diagnostic that we need to emit at a particular location. Handle errors while removing inactive regions When removing inactive regions via a visitor, accumulate any diagnostics produced during this process so they can be reported. Introduce a test harness so we can test these diagnostics more easily. Handle diagnostics through #if condition evaluation Allow #if condition evaluation to produce proper diagnostics, including warnings, while performing the evaluation. This covers both warnings/errors directly coming from #if handling and also those thrown as errors by the build configuration. Introduce assertion functions to make it a lot easier to properly test the tests with calls to these assertions. Add the remaining diagnostics that were FIXME'd because we couldn't report them properly before. Teach #if condition evaluation to reason about "versioned" checks When a check is "versioned" and fails, we allow the failed #if block to have syntactic errors in it. Start to reflect this distinction when determining the `IfConfigState` for a particular condition, so that we finally use the "unparsed" case. Rework `isActive(in: configuration)` to distinguish inactive vs. unparsed Update the interface of this function to return an `IfConfigState` rather than just a `Bool`. Then, check the enclosing versioned conditions to distinguish between inactive vs. unparsed. Finally, add a marker-based assertion function that makes it easy to test the active state of any location in the source code. Use the new test to flush out an obvious bug in my original implementation of `isActive(in: configuration)`. NFC: Split out IfConfigError and the VersionTuple parsing into their own files Clarify documentation a little bit Update CMake build for SwiftIfConfig changes Add a new API to compute the "configured regions" of a syntax tree This API produces information similar to the "active regions" API used within the compiler and by SourceKit, a sorted array that indicates the #if clauses that are active or inactive (including distinguishing inactive vs. unparsed). When this array has already been computed for a syntax tree, one can then use the new `SyntaxProtocol.isActive(inConfiguredRegions:)` function to determine whether a given node is active. This can be more efficient than the existing `SyntaxProtocol.isActive(in:)` when querying for many nodes. Test the new functionality by cross-checking the two `isActive` implementations against each other on existing tests. DocC cleanups Test active code regions with postfix #if Rename IfConfigState -> ConfiguredRegionState The new name better describes that we're talking about regions that have to do with the build configuration. Handle invalid `#if` conditions as "unparsed" regions consistently When determining active regions, treat clauses with invalid conditions as "unparsed" regions, but don't abort the computation by throwing. This provides behavior that is more consistent with the compiler, and is also generally easy for most clients. Those clients that want to report diagnostics can certainly do so, but are not forced to work with throwing APIs for invalid code. While here, improve the active syntax rewriting operation by making it a two-pass operation. The first pass emits diagnostics and determines whether there is any rewriting to do, and the second pass performs the rewriting. This fixes an existing bug where the diagnostic locations were wrong because we were emitting them against partially-rewritten trees. Reshuffle and rename some source files in SwiftIfConfig Rename source files in SwiftIfConfig to better reflect what they do, move the public APIs up to the tops of files, and split the massive IfConfigEvaluation.swift into several files. The file itself defines the core logic for doing the evaluation (which is internal to the library), and other source files provide public APIs on top of it. Add support for _hasAtomicBitWidth to evaluation Declare rules_swift max_compatibility_version = 2, set rules_swift to 1.18.0 (swiftlang#2725) Add an article about using `SWIFTSYNTAX_ENABLE_RAWSYNTAX_VALIDATION` [MacroExamples] Update invalid URL example Workaround for a Foundation bug that can return non-nil URL for "https://not a url.com". [Syntax] Mark the parameter of casting initializers '__shared' Initializer parameters are "+1" in caller site, but these casting initializers only uses the ._syntaxNode and don't store the parameter itself. CONTRIBUTING.md: Update for new Swift.org section about release branch PRs Code action to convert between computed property and stored property improve diagnostic for unnamed closure parameters - instead of treating as unrecognized, try to parse the remaining tokens as a type even if the preceding colon is missing cc
1 parent 9cc77ac commit f065010

File tree

64 files changed

+4603
-331
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+4603
-331
lines changed

.spi.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ builder:
1212
- SwiftCompilerPlugin
1313
- SwiftDiagnostics
1414
- SwiftIDEUtils
15+
- SwiftIfConfig
1516
- SwiftLexicalLookup
1617
- SwiftOperators
1718
- SwiftParser

CONTRIBUTING.md

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,22 +84,9 @@ Once you've pushed your branch, you should see an option on this repository's pa
8484
8585
## Opening a PR for Release Branch
8686

87-
In order for a pull request to be considered for inclusion in a release branch (e.g. `release/6.0`) after it has been cut, it must meet the following requirements:
87+
See the [dedicated section][section] on the Swift project website.
8888

89-
1. The title of the PR should start with the tag `[{swift version number}]`. For example, `[6.0]` for the Swift 6.0 release branch.
90-
91-
1. [This][form] should be filled out in the description of the PR.
92-
To use this template when creating a PR, append the `template=release.md`
93-
query parameter to the current URL and refresh.
94-
For example:
95-
```diff
96-
-https://github.com/swiftlang/swift-syntax/compare/main...my-branch?quick_pull=1
97-
+https://github.com/swiftlang/swift-syntax/compare/main...my-branch?quick_pull=1&template=release.md
98-
```
99-
100-
All changes going into a release branch must go through pull requests that are approved and merged by the corresponding release manager.
101-
102-
[form]: https://github.com/swiftlang/.github/blob/main/PULL_REQUEST_TEMPLATE/release.md?plain=1
89+
[section]: https://www.swift.org/contributing/#release-branch-pull-requests
10390

10491
## Review and CI Testing
10592

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SwiftSyntaxDoccIndexTemplate.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ allows Swift tools to parse, inspect, generate, and transform Swift source code.
1818

1919
- <doc:Working-with-SwiftSyntax>
2020
- <doc:Macro-Versioning>
21+
- <doc:RawSyntaxValidation>
2122
- <doc:Glossary>
2223

2324
### Tutorials

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxBaseNodesFile.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ let syntaxBaseNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
183183
DeclSyntax(
184184
"""
185185
/// Create a \(raw: node.kind.doccLink) node from a specialized syntax node.
186-
public init(_ syntax: some \(node.kind.protocolType)) {
186+
public init(_ syntax: __shared some \(node.kind.protocolType)) {
187187
// We know this cast is going to succeed. Go through init(_: SyntaxData)
188188
// to do a sanity check and verify the kind matches in debug builds and get
189189
// maximum performance in release builds.
@@ -195,7 +195,7 @@ let syntaxBaseNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
195195
DeclSyntax(
196196
"""
197197
/// Create a \(raw: node.kind.doccLink) node from a specialized optional syntax node.
198-
public init?(_ syntax: (some \(node.kind.protocolType))?) {
198+
public init?(_ syntax: __shared (some \(node.kind.protocolType))?) {
199199
guard let syntax = syntax else { return nil }
200200
self.init(syntax)
201201
}
@@ -204,7 +204,7 @@ let syntaxBaseNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
204204

205205
DeclSyntax(
206206
"""
207-
public init(fromProtocol syntax: \(node.kind.protocolType)) {
207+
public init(fromProtocol syntax: __shared \(node.kind.protocolType)) {
208208
// We know this cast is going to succeed. Go through init(_: SyntaxData)
209209
// to do a sanity check and verify the kind matches in debug builds and get
210210
// maximum performance in release builds.
@@ -216,14 +216,14 @@ let syntaxBaseNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
216216
DeclSyntax(
217217
"""
218218
/// Create a \(raw: node.kind.doccLink) node from a specialized optional syntax node.
219-
public init?(fromProtocol syntax: \(node.kind.protocolType)?) {
219+
public init?(fromProtocol syntax: __shared \(node.kind.protocolType)?) {
220220
guard let syntax = syntax else { return nil }
221221
self.init(fromProtocol: syntax)
222222
}
223223
"""
224224
)
225225

226-
try InitializerDeclSyntax("public init?(_ node: some SyntaxProtocol)") {
226+
try InitializerDeclSyntax("public init?(_ node: __shared some SyntaxProtocol)") {
227227
try SwitchExprSyntax("switch node.raw.kind") {
228228
SwitchCaseListSyntax {
229229
SwitchCaseSyntax(

CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntax/SyntaxNodesFile.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func syntaxNode(nodesStartingWith: [Character]) -> SourceFileSyntax {
4747

4848
DeclSyntax(
4949
"""
50-
public init?(_ node: some SyntaxProtocol) {
50+
public init?(_ node: __shared some SyntaxProtocol) {
5151
guard node.raw.kind == .\(node.varOrCaseName) else { return nil }
5252
self._syntaxNode = node._syntaxNode
5353
}
@@ -256,7 +256,7 @@ private func generateSyntaxChildChoices(for child: Child) throws -> EnumDeclSynt
256256
}
257257
}
258258

259-
try! InitializerDeclSyntax("public init?(_ node: some SyntaxProtocol)") {
259+
try! InitializerDeclSyntax("public init?(_ node: __shared some SyntaxProtocol)") {
260260
for choice in choices {
261261
StmtSyntax(
262262
"""

Examples/Sources/MacroExamples/Playground/ExpressionMacrosPlayground.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func runExpressionMacrosPlayground() {
4747

4848
// let domain = "domain.com"
4949
//print(#URL("https://\(domain)/api/path")) // error: #URL requires a static string literal
50-
//print(#URL("https://not a url.com")) // error: Malformed url
50+
//print(#URL("https://not a url.com:invalid-port/")) // error: Malformed url
5151

5252
// MARK: - Warning
5353

Examples/Tests/MacroExamples/Implementation/Expression/URLMacroTests.swift

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,18 @@ final class URLMacroTests: XCTestCase {
2121
func testExpansionWithMalformedURLEmitsError() {
2222
assertMacroExpansion(
2323
"""
24-
let invalid = #URL("https://not a url.com")
24+
let invalid = #URL("https://not a url.com:invalid-port/")
2525
""",
2626
expandedSource: """
27-
let invalid = #URL("https://not a url.com")
27+
let invalid = #URL("https://not a url.com:invalid-port/")
2828
""",
2929
diagnostics: [
30-
DiagnosticSpec(message: #"malformed url: "https://not a url.com""#, line: 1, column: 15, severity: .error)
30+
DiagnosticSpec(
31+
message: #"malformed url: "https://not a url.com:invalid-port/""#,
32+
line: 1,
33+
column: 15,
34+
severity: .error
35+
)
3136
],
3237
macros: macros,
3338
indentationWidth: .spaces(2)

MODULE.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ module(
66

77
bazel_dep(name = "apple_support", version = "1.13.0", repo_name = "build_bazel_apple_support")
88
bazel_dep(name = "rules_apple", version = "3.3.0", repo_name = "build_bazel_rules_apple")
9-
bazel_dep(name = "rules_swift", version = "1.16.0", repo_name = "build_bazel_rules_swift")
9+
bazel_dep(name = "rules_swift", version = "1.18.0", max_compatibility_level = 2, repo_name = "build_bazel_rules_swift")

Package.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ let package = Package(
1717
.library(name: "SwiftCompilerPlugin", targets: ["SwiftCompilerPlugin"]),
1818
.library(name: "SwiftDiagnostics", targets: ["SwiftDiagnostics"]),
1919
.library(name: "SwiftIDEUtils", targets: ["SwiftIDEUtils"]),
20+
.library(name: "SwiftIfConfig", targets: ["SwiftIfConfig"]),
2021
.library(name: "SwiftLexicalLookup", targets: ["SwiftLexicalLookup"]),
2122
.library(name: "SwiftOperators", targets: ["SwiftOperators"]),
2223
.library(name: "SwiftParser", targets: ["SwiftParser"]),
@@ -138,6 +139,24 @@ let package = Package(
138139
dependencies: ["_SwiftSyntaxTestSupport", "SwiftIDEUtils", "SwiftParser", "SwiftSyntax"]
139140
),
140141

142+
// MARK: SwiftIfConfig
143+
144+
.target(
145+
name: "SwiftIfConfig",
146+
dependencies: ["SwiftSyntax", "SwiftOperators"],
147+
exclude: ["CMakeLists.txt"]
148+
),
149+
150+
.testTarget(
151+
name: "SwiftIfConfigTest",
152+
dependencies: [
153+
"_SwiftSyntaxTestSupport",
154+
"SwiftIfConfig",
155+
"SwiftParser",
156+
"SwiftSyntaxMacrosGenericTestSupport",
157+
]
158+
),
159+
141160
// MARK: SwiftLexicalLookup
142161

143162
.target(

Release Notes/600.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
- Pull request: https://github.com/swiftlang/swift-syntax/pull/2433
134134

135135
- `CanImportExprSyntax` and `CanImportVersionInfoSyntax`
136-
- Description: Instead of parsing `canImport` inside `#if` directives as a special expression node, parse it as a functionc call expression. This is in-line with how the `swift(>=6.0)` and `compiler(>=6.0)` directives are parsed.
136+
- Description: Instead of parsing `canImport` inside `#if` directives as a special expression node, parse it as a function call expression. This is in-line with how the `swift(>=6.0)` and `compiler(>=6.0)` directives are parsed.
137137
- Pull request: https://github.com/swiftlang/swift-syntax/pull/2025
138138

139139
- `SyntaxClassifiedRange.offset`, `length` and `endOffset`

Release Notes/601.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
- Description: Returns the node or the first ancestor that satisfies `condition`.
1212
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/2696
1313

14+
- `Error` protocol now has an `asDiagnostics(at:)` method.
15+
- Description: This method translates an error into one or more diagnostics, recognizing `DiagnosticsError` and `DiagnosticMessage` instances or providing its own `Diagnostic` as needed.
16+
- Pull Request: https://github.com/swiftlang/swift-syntax/pull/1816
17+
1418
## API Behavior Changes
1519

1620
- `SyntaxProtocol.trimmed` detaches the node

Sources/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ add_subdirectory(SwiftParser)
1616
add_subdirectory(SwiftParserDiagnostics)
1717
add_subdirectory(SwiftRefactor)
1818
add_subdirectory(SwiftOperators)
19+
add_subdirectory(SwiftIfConfig)
1920
add_subdirectory(SwiftSyntaxBuilder)
2021
add_subdirectory(SwiftSyntaxMacros)
2122
add_subdirectory(SwiftSyntaxMacroExpansion)

Sources/SwiftDiagnostics/Diagnostic.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,38 @@ public struct DiagnosticsError: Error, Sendable {
9292
)
9393
}
9494
}
95+
96+
/// Diagnostic message used for thrown errors.
97+
private struct DiagnosticFromError: DiagnosticMessage {
98+
let error: Error
99+
let severity: DiagnosticSeverity = .error
100+
101+
var message: String {
102+
return String(describing: error)
103+
}
104+
105+
var diagnosticID: MessageID {
106+
.init(domain: "SwiftDiagnostics", id: "\(type(of: error))")
107+
}
108+
}
109+
110+
extension Error {
111+
/// Given an error, produce an array of diagnostics reporting the error,
112+
/// using the given syntax node as the location if it wasn't otherwise known.
113+
///
114+
/// This operation will look for diagnostics of known type, such as
115+
/// `DiagnosticsError` and `DiagnosticMessage` to retain information. If
116+
/// none of those apply, it will produce an `error` diagnostic whose message
117+
/// comes from rendering the error as a string.
118+
public func asDiagnostics(at node: some SyntaxProtocol) -> [Diagnostic] {
119+
if let diagnosticsError = self as? DiagnosticsError {
120+
return diagnosticsError.diagnostics
121+
}
122+
123+
if let message = self as? DiagnosticMessage {
124+
return [Diagnostic(node: Syntax(node), message: message)]
125+
}
126+
127+
return [Diagnostic(node: Syntax(node), message: DiagnosticFromError(error: self))]
128+
}
129+
}

0 commit comments

Comments
 (0)