Skip to content

Commit 2a9810b

Browse files
authored
Merge pull request #1790 from ahoppen/ahoppen/log-string-interpolation-parsing-errors
When creation of a syntax node using string interpolation failed, log the diagnostics
2 parents a66496f + 96557f7 commit 2a9810b

11 files changed

+144
-77
lines changed

CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntaxbuilder/SyntaxExpressibleByStringInterpolationConformancesFile.swift

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,41 +17,8 @@ import Utils
1717

1818
let syntaxExpressibleByStringInterpolationConformancesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) {
1919
DeclSyntax("import SwiftSyntax")
20-
DeclSyntax("import SwiftParser")
21-
DeclSyntax("import SwiftParserDiagnostics")
22-
23-
try! ExtensionDeclSyntax("extension SyntaxParseable") {
24-
DeclSyntax("public typealias StringInterpolation = SyntaxStringInterpolation")
25-
26-
DeclSyntax(
27-
"""
28-
public init(stringInterpolation: SyntaxStringInterpolation) {
29-
self = performParse(source: stringInterpolation.sourceText, parse: { parser in
30-
return Self.parse(from: &parser)
31-
})
32-
}
33-
"""
34-
)
35-
}
3620

3721
for node in SYNTAX_NODES where node.parserFunction != nil {
3822
DeclSyntax("extension \(node.kind.syntaxType): SyntaxExpressibleByStringInterpolation {}")
3923
}
40-
41-
DeclSyntax(
42-
"""
43-
// TODO: This should be inlined in SyntaxParseable.init(stringInterpolation:),
44-
// but is currently used in `ConvenienceInitializers.swift`.
45-
// See the corresponding TODO there.
46-
func performParse<SyntaxType: SyntaxProtocol>(source: [UInt8], parse: (inout Parser) -> SyntaxType) -> SyntaxType {
47-
return source.withUnsafeBufferPointer { buffer in
48-
var parser = Parser(buffer)
49-
// FIXME: When the parser supports incremental parsing, put the
50-
// interpolatedSyntaxNodes in so we don't have to parse them again.
51-
let result = parse(&parser)
52-
return result
53-
}
54-
}
55-
"""
56-
)
5724
}

Package.swift

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,30 @@
33
import PackageDescription
44
import Foundation
55

6+
var swiftSyntaxSwiftSettings: [SwiftSetting] = []
7+
var swiftSyntaxBuilderSwiftSettings: [SwiftSetting] = []
8+
var swiftParserSwiftSettings: [SwiftSetting] = []
9+
610
/// If we are in a controlled CI environment, we can use internal compiler flags
711
/// to speed up the build or improve it.
8-
var swiftSyntaxSwiftSettings: [SwiftSetting] = []
912
if ProcessInfo.processInfo.environment["SWIFT_BUILD_SCRIPT_ENVIRONMENT"] != nil {
1013
swiftSyntaxSwiftSettings += [
1114
.define("SWIFTSYNTAX_ENABLE_ASSERTIONS")
1215
]
16+
swiftSyntaxBuilderSwiftSettings += [
17+
.define("SWIFTSYNTAX_NO_OSLOG_DEPENDENCY")
18+
]
19+
swiftParserSwiftSettings += [
20+
.define("SWIFTSYNTAX_NO_OSLOG_DEPENDENCY")
21+
]
1322
}
23+
1424
if ProcessInfo.processInfo.environment["SWIFTSYNTAX_ENABLE_RAWSYNTAX_VALIDATION"] != nil {
1525
swiftSyntaxSwiftSettings += [
1626
.define("SWIFTSYNTAX_ENABLE_RAWSYNTAX_VALIDATION")
1727
]
1828
}
1929

20-
var swiftParserSwiftSettings: [SwiftSetting] = []
2130
if ProcessInfo.processInfo.environment["SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION"] != nil {
2231
swiftParserSwiftSettings += [
2332
.define("SWIFTPARSER_ENABLE_ALTERNATE_TOKEN_INTROSPECTION")
@@ -151,8 +160,9 @@ let package = Package(
151160

152161
.target(
153162
name: "SwiftSyntaxBuilder",
154-
dependencies: ["SwiftBasicFormat", "SwiftParser", "SwiftParserDiagnostics", "SwiftSyntax"],
155-
exclude: ["CMakeLists.txt"]
163+
dependencies: ["SwiftBasicFormat", "SwiftParser", "SwiftDiagnostics", "SwiftParserDiagnostics", "SwiftSyntax"],
164+
exclude: ["CMakeLists.txt"],
165+
swiftSettings: swiftSyntaxBuilderSwiftSettings
156166
),
157167

158168
.testTarget(

Sources/SwiftSyntaxBuilder/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_swift_host_library(SwiftSyntaxBuilder
1313
SwiftSyntaxBuilderCompatibility.swift
1414
Syntax+StringInterpolation.swift
1515
SyntaxNodeWithBody.swift
16+
SyntaxParsable+ExpressibleByStringInterpolation.swift
1617
ValidatingSyntaxNodes.swift
1718
WithTrailingCommaSyntax+EnsuringTrailingComma.swift
1819

@@ -24,6 +25,11 @@ add_swift_host_library(SwiftSyntaxBuilder
2425
generated/SyntaxExpressibleByStringInterpolationConformances.swift
2526
)
2627

28+
# Don't depend on OSLog when we are building for the compiler.
29+
# In that case we don't want any dependencies on the SDK.
30+
target_compile_options(SwiftSyntaxBuilder PRIVATE
31+
$<$<COMPILE_LANGUAGE:Swift>:-D;SWIFTSYNTAX_NO_OSLOG_DEPENDENCY>)
32+
2733
target_link_libraries(SwiftSyntaxBuilder PUBLIC
2834
SwiftBasicFormat
2935
SwiftParser
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import SwiftDiagnostics
14+
import SwiftSyntax
15+
import SwiftParser
16+
import SwiftParserDiagnostics
17+
// Don't introduce a dependency on OSLog when building SwiftSyntax using CMake
18+
// for the compiler.
19+
#if canImport(OSLog) && !SWIFTSYNTAX_NO_OSLOG_DEPENDENCY
20+
import OSLog
21+
#endif
22+
23+
extension SyntaxParseable {
24+
public typealias StringInterpolation = SyntaxStringInterpolation
25+
26+
/// Assuming that this node contains a syntax error, log it using OSLog if we
27+
/// are on a platform that supports OSLog, otherwise don't do anything.
28+
private func logStringInterpolationParsingError() {
29+
#if canImport(OSLog) && !SWIFTSYNTAX_NO_OSLOG_DEPENDENCY
30+
if #available(macOS 11.0, *) {
31+
let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: self)
32+
let formattedDiagnostics = DiagnosticsFormatter().annotatedSource(tree: self, diags: diagnostics)
33+
Logger(subsystem: "SwiftSyntax", category: "ParseError").fault(
34+
"""
35+
Parsing a `\(Self.self)` node from string interpolation produced the following parsing errors.
36+
Set a breakpoint in `SyntaxParseable.logStringInterpolationParsingError()` to debug the failure.
37+
\(formattedDiagnostics)
38+
"""
39+
)
40+
}
41+
#endif
42+
}
43+
44+
/// Initialize the syntax node from a string interpolation.
45+
///
46+
/// - Important: This asssumes that the string interpolation produces a valid
47+
/// syntax tree. If the syntax tree is not valid, a fault will
48+
/// be logged using OSLog on Darwin platforms.
49+
public init(stringInterpolation: SyntaxStringInterpolation) {
50+
self = stringInterpolation.sourceText.withUnsafeBufferPointer { buffer in
51+
var parser = Parser(buffer)
52+
// FIXME: When the parser supports incremental parsing, put the
53+
// interpolatedSyntaxNodes in so we don't have to parse them again.
54+
let result = Self.parse(from: &parser)
55+
return result
56+
}
57+
if self.hasError {
58+
self.logStringInterpolationParsingError()
59+
}
60+
}
61+
}

Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,6 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import SwiftSyntax
16-
import SwiftParser
17-
import SwiftParserDiagnostics
18-
19-
extension SyntaxParseable {
20-
public typealias StringInterpolation = SyntaxStringInterpolation
21-
22-
public init(stringInterpolation: SyntaxStringInterpolation) {
23-
self = performParse(source: stringInterpolation.sourceText, parse: { parser in
24-
return Self.parse(from: &parser)
25-
})
26-
}
27-
}
2816

2917
extension AccessorDeclSyntax: SyntaxExpressibleByStringInterpolation {}
3018

@@ -57,16 +45,3 @@ extension StmtSyntax: SyntaxExpressibleByStringInterpolation {}
5745
extension SwitchCaseSyntax: SyntaxExpressibleByStringInterpolation {}
5846

5947
extension TypeSyntax: SyntaxExpressibleByStringInterpolation {}
60-
61-
// TODO: This should be inlined in SyntaxParseable.init(stringInterpolation:),
62-
// but is currently used in `ConvenienceInitializers.swift`.
63-
// See the corresponding TODO there.
64-
func performParse<SyntaxType: SyntaxProtocol>(source: [UInt8], parse: (inout Parser) -> SyntaxType) -> SyntaxType {
65-
return source.withUnsafeBufferPointer { buffer in
66-
var parser = Parser(buffer)
67-
// FIXME: When the parser supports incremental parsing, put the
68-
// interpolatedSyntaxNodes in so we don't have to parse them again.
69-
let result = parse(&parser)
70-
return result
71-
}
72-
}

Tests/SwiftParserTest/LinkageTests.swift

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,27 @@ final class LinkageTest: XCTestCase {
6969
.library("-lswiftCompatibilityConcurrency"),
7070
.library("-lswiftCompatibilityPacks", condition: .mayBeAbsent("Only in newer compilers")),
7171
.library("-lswiftCore"),
72+
.library("-lswiftCoreFoundation", condition: .when(compilationCondition: .osLogDependency)),
73+
.library("-lswiftDarwin", condition: .when(compilationCondition: .osLogDependency)),
74+
.library("-lswiftDispatch", condition: .when(compilationCondition: .osLogDependency)),
75+
.library("-lswiftFoundation", condition: .when(compilationCondition: .osLogDependency)),
76+
.library("-lswiftIOKit", condition: .when(compilationCondition: .osLogDependency)),
77+
.library("-lswiftOSLog", condition: .when(compilationCondition: .osLogDependency)),
78+
.library("-lswiftObjectiveC", condition: .when(compilationCondition: .osLogDependency)),
7279
.library("-lswiftSwiftOnoneSupport", condition: .when(configuration: .debug)),
80+
.library("-lswiftXPC", condition: .when(compilationCondition: .osLogDependency)),
7381
.library("-lswift_Concurrency"),
7482
.library("-lswift_StringProcessing", condition: .mayBeAbsent("Starting in Xcode 14 this library is not always autolinked")),
83+
.library("-lswiftos", condition: .when(compilationCondition: .osLogDependency)),
84+
.framework("CFNetwork", condition: .when(compilationCondition: .osLogDependency)),
85+
.framework("Combine", condition: .when(compilationCondition: .osLogDependency)),
86+
.framework("CoreFoundation", condition: .when(compilationCondition: .osLogDependency)),
87+
.framework("CoreServices", condition: .when(compilationCondition: .osLogDependency)),
88+
.framework("DiskArbitration", condition: .when(compilationCondition: .osLogDependency)),
89+
.framework("Foundation", condition: .when(compilationCondition: .osLogDependency)),
90+
.framework("IOKit", condition: .when(compilationCondition: .osLogDependency)),
91+
.framework("OSLog", condition: .when(compilationCondition: .osLogDependency)),
92+
.framework("Security", condition: .when(compilationCondition: .osLogDependency)),
7593
]
7694
)
7795

@@ -120,7 +138,9 @@ extension LinkageTest {
120138
private func assertLinkage(
121139
of library: String,
122140
in bundle: EnclosingTestBundle,
123-
assertions: [Linkage.Assertion]
141+
assertions: [Linkage.Assertion],
142+
file: StaticString = #file,
143+
line: UInt = #line
124144
) throws {
125145
let linkages = try bundle.objectFiles(for: library).reduce(into: []) { acc, next in
126146
acc += try self.extractAutolinkingHints(in: next)
@@ -175,7 +195,11 @@ extension LinkageTest {
175195
}
176196

177197
for superfluousLinkage in sortedLinkages[expectedLinkagesIdx..<sortedLinkages.endIndex] {
178-
XCTFail("Found unasserted link-time dependency: \(superfluousLinkage.linkage)")
198+
XCTFail(
199+
"Found unasserted link-time dependency: \(superfluousLinkage.linkage)",
200+
file: file,
201+
line: line
202+
)
179203
}
180204
}
181205

@@ -314,6 +338,7 @@ extension Linkage.Assertion {
314338
fileprivate enum Condition {
315339
case swiftVersionAtLeast(versionBound: SwiftVersion)
316340
case configuration(ProductConfiguration)
341+
case compilationCondition(CompilationCondition)
317342
case flaky
318343

319344
enum SwiftVersion: Comparable {
@@ -328,6 +353,10 @@ extension Linkage.Assertion {
328353
case release
329354
}
330355

356+
enum CompilationCondition {
357+
case osLogDependency
358+
}
359+
331360
fileprivate static func when(swiftVersionAtLeast version: SwiftVersion) -> Condition {
332361
return .swiftVersionAtLeast(versionBound: version)
333362
}
@@ -336,6 +365,10 @@ extension Linkage.Assertion {
336365
return .configuration(configuration)
337366
}
338367

368+
fileprivate static func when(compilationCondition: CompilationCondition) -> Condition {
369+
return .compilationCondition(compilationCondition)
370+
}
371+
339372
fileprivate static func mayBeAbsent(_ reason: StaticString) -> Condition {
340373
return .flaky
341374
}
@@ -360,6 +393,12 @@ extension Linkage.Assertion {
360393
let config: ProductConfiguration = .release
361394
#endif
362395
return config == expectation
396+
case .compilationCondition(.osLogDependency):
397+
#if SWIFTSYNTAX_NO_OSLOG_DEPENDENCY
398+
return false
399+
#else
400+
return true
401+
#endif
363402
case .flaky:
364403
return true
365404
}

Tests/SwiftParserTest/StringLiteralRepresentedLiteralValueTests.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ public class StringLiteralRepresentedLiteralValueTests: XCTestCase {
250250
// MARK: literal value not available
251251

252252
func testMissingQuoteStringLiteral() throws {
253-
let stringLiteral = StringLiteralExprSyntax(#""a"# as ExprSyntax)!
253+
var parser = Parser(#""a"#)
254+
let stringLiteral = StringLiteralExprSyntax(ExprSyntax.parse(from: &parser))!
254255
XCTAssertNil(stringLiteral.representedLiteralValue, "only fully parsed string literals should produce a literal value")
255256
}
256257

@@ -260,7 +261,8 @@ public class StringLiteralRepresentedLiteralValueTests: XCTestCase {
260261
}
261262

262263
func testMalformedMultiLineStringLiteral() throws {
263-
let stringLiteral = StringLiteralExprSyntax(#""""a""""# as ExprSyntax)!
264+
var parser = Parser(#""""a""""#)
265+
let stringLiteral = StringLiteralExprSyntax(ExprSyntax.parse(from: &parser))!
264266
XCTAssertNil(stringLiteral.representedLiteralValue, "missing newline in multiline string literal cannot produce a literal value")
265267
}
266268

Tests/SwiftRefactorTest/ExpandEditorPlaceholderTests.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import SwiftBasicFormat
1414
import SwiftRefactor
15+
import SwiftParser
1516
import SwiftSyntax
1617
import SwiftSyntaxBuilder
1718
import XCTest
@@ -100,7 +101,8 @@ fileprivate func assertRefactorPlaceholder(
100101
if wrap {
101102
token = "\(raw: ExpandEditorPlaceholder.wrapInPlaceholder(placeholder))"
102103
} else {
103-
let expr: ExprSyntax = "\(raw: placeholder)"
104+
var parser = Parser(placeholder)
105+
let expr = ExprSyntax.parse(from: &parser)
104106
token = try XCTUnwrap(expr.as(EditorPlaceholderExprSyntax.self)?.identifier, file: file, line: line)
105107
}
106108

Tests/SwiftRefactorTest/ExpandEditorPlaceholdersTests.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,8 @@ fileprivate func assertRefactorPlaceholderCall(
109109
file: StaticString = #file,
110110
line: UInt = #line
111111
) throws {
112-
let call = try XCTUnwrap(ExprSyntax("\(raw: expr)").as(FunctionCallExprSyntax.self), file: file, line: line)
112+
var parser = Parser(expr)
113+
let call = try XCTUnwrap(ExprSyntax.parse(from: &parser).as(FunctionCallExprSyntax.self), file: file, line: line)
113114
let arg = try XCTUnwrap(call.argumentList[placeholder].as(TupleExprElementSyntax.self), file: file, line: line)
114115
let token: TokenSyntax = try XCTUnwrap(arg.expression.as(EditorPlaceholderExprSyntax.self), file: file, line: line).identifier
115116

@@ -123,7 +124,8 @@ fileprivate func assertRefactorPlaceholderToken(
123124
file: StaticString = #file,
124125
line: UInt = #line
125126
) throws {
126-
let call = try XCTUnwrap(ExprSyntax("\(raw: expr)").as(FunctionCallExprSyntax.self), file: file, line: line)
127+
var parser = Parser(expr)
128+
let call = try XCTUnwrap(ExprSyntax.parse(from: &parser).as(FunctionCallExprSyntax.self), file: file, line: line)
127129
let arg = try XCTUnwrap(call.argumentList[placeholder].as(TupleExprElementSyntax.self), file: file, line: line)
128130
let token: TokenSyntax = try XCTUnwrap(arg.expression.as(EditorPlaceholderExprSyntax.self), file: file, line: line).identifier
129131

Tests/SwiftSyntaxBuilderTest/StringInterpolationTests.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -446,10 +446,11 @@ final class StringInterpolationTests: XCTestCase {
446446
}
447447

448448
func testInvalidSyntax() {
449-
let invalid = DeclSyntax("return 1")
449+
var parser = Parser("return 1")
450+
let invalid = DeclSyntax.parse(from: &parser)
450451
XCTAssert(invalid.hasError)
451452

452-
XCTAssertThrowsError(try DeclSyntax(validating: "return 1")) { error in
453+
XCTAssertThrowsError(try DeclSyntax(validating: invalid)) { error in
453454
assertStringsEqualWithDiff(
454455
String(describing: error),
455456
"""
@@ -464,10 +465,11 @@ final class StringInterpolationTests: XCTestCase {
464465
}
465466

466467
func testInvalidSyntax2() {
467-
let invalid = StmtSyntax("struct Foo {}")
468+
var parser = Parser("struct Foo {}")
469+
let invalid = StmtSyntax.parse(from: &parser)
468470
XCTAssert(invalid.hasError)
469471

470-
XCTAssertThrowsError(try StmtSyntax(validating: "struct Foo {}")) { error in
472+
XCTAssertThrowsError(try StmtSyntax(validating: invalid)) { error in
471473
assertStringsEqualWithDiff(
472474
String(describing: error),
473475
"""
@@ -482,10 +484,11 @@ final class StringInterpolationTests: XCTestCase {
482484
}
483485

484486
func testInvalidSyntax3() {
485-
let invalid: CodeBlockItemSyntax = " "
487+
var parser = Parser(" ")
488+
let invalid = CodeBlockItemSyntax.parse(from: &parser)
486489

487490
XCTAssert(invalid.hasError)
488-
XCTAssertThrowsError(try CodeBlockItemSyntax(validating: " ")) { error in
491+
XCTAssertThrowsError(try CodeBlockItemSyntax(validating: invalid)) { error in
489492
assertStringsEqualWithDiff(
490493
String(describing: error),
491494
"""

0 commit comments

Comments
 (0)