Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ let syntaxExpressibleByStringInterpolationConformancesFile = SourceFileSyntax {
DeclSyntax("import SwiftParserDiagnostics")

try! ExtensionDeclSyntax("extension SyntaxParseable") {
DeclSyntax("public typealias StringInterpolation = SyntaxStringInterpolation")

DeclSyntax(
"""
public init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws {
self = try performParse(source: stringInterpolation.sourceText, parse: { parser in
public init(stringInterpolation: SyntaxStringInterpolation) {
self = performParse(source: stringInterpolation.sourceText, parse: { parser in
return Self.parse(from: &parser)
})
}
Expand All @@ -41,19 +43,15 @@ let syntaxExpressibleByStringInterpolationConformancesFile = SourceFileSyntax {

DeclSyntax(
"""
// TODO: This should be fileprivate, but is currently used in
// `ConvenienceInitializers.swift`. See the corresponding TODO there.
func performParse<SyntaxType: SyntaxProtocol>(source: [UInt8], parse: (inout Parser) throws -> SyntaxType) throws -> SyntaxType {
return try source.withUnsafeBufferPointer { buffer in
// TODO: This should be inlined in SyntaxParseable.init(stringInterpolation:),
// but is currently used in `ConvenienceInitializers.swift`.
// See the corresponding TODO there.
func performParse<SyntaxType: SyntaxProtocol>(source: [UInt8], parse: (inout Parser) -> SyntaxType) -> SyntaxType {
return source.withUnsafeBufferPointer { buffer in
var parser = Parser(buffer)
// FIXME: When the parser supports incremental parsing, put the
// interpolatedSyntaxNodes in so we don't have to parse them again.
let result = try parse(&parser)
if result.hasError {
let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: result)
assert(!diagnostics.isEmpty)
throw SyntaxStringInterpolationError.diagnostics(diagnostics, tree: Syntax(result))
}
let result = parse(&parser)
return result
}
}
Expand Down
2 changes: 0 additions & 2 deletions Sources/SwiftParserDiagnostics/MissingNodesError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,6 @@ extension ParseDiagnosticsGenerator {
break
}
}
} else {
missingNodes = []
}

let changes = missingNodes.enumerated().map { (index, missingNode) -> FixIt.Changes in
Expand Down
1 change: 1 addition & 0 deletions Sources/SwiftSyntaxBuilder/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ add_swift_host_library(SwiftSyntaxBuilder
ResultBuilderExtensions.swift
Syntax+StringInterpolation.swift
SyntaxNodeWithBody.swift
ValidatingSyntaxNodes.swift
WithTrailingCommaSyntax+EnsuringTrailingComma.swift


Expand Down
2 changes: 1 addition & 1 deletion Sources/SwiftSyntaxBuilder/ConvenienceInitializers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ extension FunctionParameterSyntax {
_ source: String,
for subject: Parser.ParameterSubject
) {
self = try! performParse(
self = performParse(
source: Array(source.utf8),
parse: {
let raw = RawSyntax($0.parseFunctionParameter(for: subject))
Expand Down
75 changes: 4 additions & 71 deletions Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ extension SyntaxStringInterpolation: StringInterpolationProtocol {
public protocol SyntaxExpressibleByStringInterpolation:
ExpressibleByStringInterpolation
where Self.StringInterpolation == SyntaxStringInterpolation {
init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws
init(stringInterpolation: SyntaxStringInterpolation)
}

enum SyntaxStringInterpolationError: Error, CustomStringConvertible {
Expand Down Expand Up @@ -221,34 +221,10 @@ public protocol ExpressibleByLiteralSyntax {
}

extension SyntaxExpressibleByStringInterpolation {
/// Initialize a syntax node by parsing the contents of the interpolation.
/// This function is marked `@_transparent` so that fatalErrors raised here
/// are reported at the string literal itself.
/// This makes debugging easier because Xcode will jump to the string literal
/// that had a parsing error instead of the initializer that raised the `fatalError`
@_transparent
public init(stringInterpolation: SyntaxStringInterpolation) {
do {
try self.init(stringInterpolationOrThrow: stringInterpolation)
} catch {
fatalError(String(describing: error))
}
}

@_transparent
public init(stringLiteral value: String) {
do {
try self.init(stringLiteralOrThrow: value)
} catch {
fatalError(String(describing: error))
}
}

/// Initialize a syntax node from a string literal.
public init(stringLiteralOrThrow value: String) throws {
var interpolation = SyntaxStringInterpolation()
interpolation.appendLiteral(value)
try self.init(stringInterpolationOrThrow: interpolation)
self.init(stringInterpolation: interpolation)
}
}

Expand Down Expand Up @@ -445,7 +421,7 @@ extension Optional: ExpressibleByLiteralSyntax where Wrapped: ExpressibleByLiter
}

extension TokenSyntax: SyntaxExpressibleByStringInterpolation {
public init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws {
public init(stringInterpolation: SyntaxStringInterpolation) {
let string = stringInterpolation.sourceText.withUnsafeBufferPointer { buf in
return String(syntaxText: SyntaxText(buffer: buf))
}
Expand Down Expand Up @@ -476,21 +452,7 @@ struct UnexpectedTrivia: DiagnosticMessage {
}

extension Trivia: ExpressibleByStringInterpolation {
/// Initialize a syntax node by parsing the contents of the interpolation.
/// This function is marked `@_transparent` so that fatalErrors raised here
/// are reported at the string literal itself.
/// This makes debugging easier because Xcode will jump to the string literal
/// that had a parsing error instead of the initializer that raised the `fatalError`
@_transparent
public init(stringInterpolation: String.StringInterpolation) {
do {
try self.init(stringInterpolationOrThrow: stringInterpolation)
} catch {
fatalError(String(describing: error))
}
}

public init(stringInterpolationOrThrow stringInterpolation: String.StringInterpolation) throws {
var text = String(stringInterpolation: stringInterpolation)
let pieces = text.withUTF8 { (buf) -> [TriviaPiece] in
// The leading trivia position is a little bit less restrictive (it allows a shebang), so let's use it.
Expand All @@ -499,40 +461,11 @@ extension Trivia: ExpressibleByStringInterpolation {
}

self.init(pieces: pieces)

if pieces.contains(where: { $0.isUnexpected }) {
var diagnostics: [Diagnostic] = []
let tree = SourceFileSyntax(statements: [], eofToken: .eof(leadingTrivia: self))
var offset = 0
for piece in pieces {
if case .unexpectedText(let contents) = piece {
diagnostics.append(
Diagnostic(
node: Syntax(tree),
position: tree.position.advanced(by: offset),
message: UnexpectedTrivia(triviaContents: contents)
)
)
}
offset += piece.sourceLength.utf8Length
}
throw SyntaxStringInterpolationError.diagnostics(diagnostics, tree: Syntax(tree))
}
}

@_transparent
public init(stringLiteral value: String) {
do {
try self.init(stringLiteralOrThrow: value)
} catch {
fatalError(String(describing: error))
}
}

/// Initialize a syntax node from a string literal.
public init(stringLiteralOrThrow value: String) throws {
var interpolation = String.StringInterpolation(literalCapacity: 1, interpolationCount: 0)
interpolation.appendLiteral(value)
try self.init(stringInterpolationOrThrow: interpolation)
self.init(stringInterpolation: interpolation)
}
}
2 changes: 1 addition & 1 deletion Sources/SwiftSyntaxBuilder/SyntaxNodeWithBody.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import SwiftSyntax
public struct PartialSyntaxNodeString: SyntaxExpressibleByStringInterpolation {
let sourceText: [UInt8]

public init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws {
public init(stringInterpolation: SyntaxStringInterpolation) {
self.sourceText = stringInterpolation.sourceText
}
}
Expand Down
58 changes: 58 additions & 0 deletions Sources/SwiftSyntaxBuilder/ValidatingSyntaxNodes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import SwiftSyntax
import SwiftDiagnostics
import SwiftParserDiagnostics

extension SyntaxProtocol {
/// If `node` has contains no syntax errors, return `node`, otherwise
/// throw an error with diagnostics describing the syntax errors.
///
/// This allows clients to e.g. write `try DeclSyntax(validating: "struct Foo {}")`
/// which constructs a `DeclSyntax` that's guaranteed to be free of syntax
/// errors.
public init(validating node: Self) throws {
if node.hasError {
let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: node)
assert(!diagnostics.isEmpty)
throw SyntaxStringInterpolationError.diagnostics(diagnostics, tree: Syntax(node))
}
self = node
}
}

extension Trivia {
/// If `trivia` has contains no unexpected trivia, return `trivia`, otherwise
/// throw an error with diagnostics describing the unexpected trivia.
public init(validating trivia: Trivia) throws {
self = trivia
if pieces.contains(where: { $0.isUnexpected }) {
var diagnostics: [Diagnostic] = []
let tree = SourceFileSyntax(statements: [], eofToken: .eof(leadingTrivia: self))
var offset = 0
for piece in pieces {
if case .unexpectedText(let contents) = piece {
diagnostics.append(
Diagnostic(
node: Syntax(tree),
position: tree.position.advanced(by: offset),
message: UnexpectedTrivia(triviaContents: contents)
)
)
}
offset += piece.sourceLength.utf8Length
}
throw SyntaxStringInterpolationError.diagnostics(diagnostics, tree: Syntax(tree))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import SwiftParser
import SwiftParserDiagnostics

extension SyntaxParseable {
public init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws {
self = try performParse(source: stringInterpolation.sourceText, parse: { parser in
public typealias StringInterpolation = SyntaxStringInterpolation

public init(stringInterpolation: SyntaxStringInterpolation) {
self = performParse(source: stringInterpolation.sourceText, parse: { parser in
return Self.parse(from: &parser)
})
}
Expand Down Expand Up @@ -62,19 +64,15 @@ extension SwitchCaseSyntax: SyntaxExpressibleByStringInterpolation {
extension TypeSyntax: SyntaxExpressibleByStringInterpolation {
}

// TODO: This should be fileprivate, but is currently used in
// `ConvenienceInitializers.swift`. See the corresponding TODO there.
func performParse<SyntaxType: SyntaxProtocol>(source: [UInt8], parse: (inout Parser) throws -> SyntaxType) throws -> SyntaxType {
return try source.withUnsafeBufferPointer { buffer in
// TODO: This should be inlined in SyntaxParseable.init(stringInterpolation:),
// but is currently used in `ConvenienceInitializers.swift`.
// See the corresponding TODO there.
func performParse<SyntaxType: SyntaxProtocol>(source: [UInt8], parse: (inout Parser) -> SyntaxType) -> SyntaxType {
return source.withUnsafeBufferPointer { buffer in
var parser = Parser(buffer)
// FIXME: When the parser supports incremental parsing, put the
// interpolatedSyntaxNodes in so we don't have to parse them again.
let result = try parse(&parser)
if result.hasError {
let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: result)
assert(!diagnostics.isEmpty)
throw SyntaxStringInterpolationError.diagnostics(diagnostics, tree: Syntax(result))
}
let result = parse(&parser)
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -471,9 +471,10 @@ final class StringInterpolationTests: XCTestCase {
}

func testInvalidTrivia() {
var interpolation = String.StringInterpolation(literalCapacity: 1, interpolationCount: 0)
interpolation.appendLiteral("/*comment*/ invalid /*comm*/")
XCTAssertThrowsError(try Trivia(stringInterpolationOrThrow: interpolation)) { error in
let invalid = Trivia("/*comment*/ invalid /*comm*/")
XCTAssertEqual(invalid, [.blockComment("/*comment*/"), .spaces(1), .unexpectedText("invalid"), .spaces(1), .blockComment("/*comm*/")])

XCTAssertThrowsError(try Trivia(validating: "/*comment*/ invalid /*comm*/")) { error in
AssertStringsEqualWithDiff(
String(describing: error),
"""
Expand All @@ -485,4 +486,22 @@ final class StringInterpolationTests: XCTestCase {
)
}
}

func testInvalidSyntax() {
let invalid = DeclSyntax("return 1")
XCTAssert(invalid.hasError)

XCTAssertThrowsError(try DeclSyntax(validating: "return 1")) { error in
AssertStringsEqualWithDiff(
String(describing: error),
"""

1 │ return 1
∣ │ ╰─ expected declaration
∣ ╰─ unexpected code 'return 1' before declaration

"""
)
}
}
}