|
1 | 1 | @_spi(RawSyntax) import SwiftSyntax
|
2 | 2 | @_spi(RawSyntax) import SwiftParser
|
| 3 | +import SwiftDiagnostics |
3 | 4 |
|
4 | 5 | /// An individual interpolated syntax node.
|
5 | 6 | struct InterpolatedSyntaxNode {
|
@@ -90,26 +91,78 @@ public protocol SyntaxExpressibleByStringInterpolation:
|
90 | 91 | where Self.StringInterpolation == SyntaxStringInterpolation {
|
91 | 92 | /// Create an instance of this syntax node by parsing it from the given
|
92 | 93 | /// parser.
|
93 |
| - static func parse(from parser: inout Parser) -> Self |
| 94 | + static func parse(from parser: inout Parser) throws -> Self |
| 95 | +} |
| 96 | + |
| 97 | +enum SyntaxStringInterpolationError: Error, CustomStringConvertible { |
| 98 | + case didNotConsumeAllTokens(remainingTokens: [TokenSyntax]) |
| 99 | + case producedInvalidNodeType(expectedType: SyntaxProtocol.Type, actualType: SyntaxProtocol.Type) |
| 100 | + case diagnostics([Diagnostic]) |
| 101 | + |
| 102 | + var description: String { |
| 103 | + switch self { |
| 104 | + case .didNotConsumeAllTokens(remainingTokens: let tokens): |
| 105 | + return "Extraneous text in snippet: '\(tokens.map(\.description).joined())'" |
| 106 | + case .producedInvalidNodeType(expectedType: let expectedType, actualType: let actualType): |
| 107 | + return "Parsing the code snippet was expected to produce a \(expectedType) but produced a \(actualType)" |
| 108 | + case .diagnostics(let diagnostics): |
| 109 | + return diagnostics.map(\.debugDescription).joined(separator: "\n") |
| 110 | + } |
| 111 | + } |
94 | 112 | }
|
95 | 113 |
|
96 | 114 | extension SyntaxExpressibleByStringInterpolation {
|
97 | 115 | /// Initialize a syntax node by parsing the contents of the interpolation.
|
| 116 | + /// This function is marked `@_transparent` so that fatalErrors raised here |
| 117 | + /// are reported at the string literal itself. |
| 118 | + /// This makes debugging easier because Xcode will jump to the string literal |
| 119 | + /// that had a parsing error instead of the initializer that raised the `fatalError` |
| 120 | + @_transparent |
98 | 121 | public init(stringInterpolation: SyntaxStringInterpolation) {
|
99 |
| - self = stringInterpolation.sourceText.withUnsafeBufferPointer { buffer in |
| 122 | + do { |
| 123 | + try self.init(stringInterpolationOrThrow: stringInterpolation) |
| 124 | + } catch { |
| 125 | + fatalError(String(describing: error)) |
| 126 | + } |
| 127 | + } |
| 128 | + |
| 129 | + public init(stringInterpolationOrThrow stringInterpolation: SyntaxStringInterpolation) throws { |
| 130 | + self = try stringInterpolation.sourceText.withUnsafeBufferPointer { buffer in |
100 | 131 | var parser = Parser(buffer)
|
101 | 132 | // FIXME: When the parser supports incremental parsing, put the
|
102 | 133 | // interpolatedSyntaxNodes in so we don't have to parse them again.
|
103 |
| - return parser.arena.assumingSingleThread { |
104 |
| - return Self.parse(from: &parser) |
| 134 | + return try parser.arena.assumingSingleThread { |
| 135 | + let result = try Self.parse(from: &parser) |
| 136 | + if !parser.at(.eof) { |
| 137 | + var remainingTokens: [TokenSyntax] = [] |
| 138 | + while !parser.at(.eof) { |
| 139 | + remainingTokens.append(parser.consumeAnyToken().syntax) |
| 140 | + } |
| 141 | + throw SyntaxStringInterpolationError.didNotConsumeAllTokens(remainingTokens: remainingTokens) |
| 142 | + } |
| 143 | + if result.hasError { |
| 144 | + let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: result) |
| 145 | + assert(!diagnostics.isEmpty) |
| 146 | + throw SyntaxStringInterpolationError.diagnostics(diagnostics) |
| 147 | + } |
| 148 | + return result |
105 | 149 | }
|
106 | 150 | }
|
107 | 151 | }
|
108 | 152 |
|
109 |
| - /// Initialize a syntax node from a string literal. |
| 153 | + @_transparent |
110 | 154 | public init(stringLiteral value: String) {
|
| 155 | + do { |
| 156 | + try self.init(stringLiteralOrThrow: value) |
| 157 | + } catch { |
| 158 | + fatalError(String(describing: error)) |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + /// Initialize a syntax node from a string literal. |
| 163 | + public init(stringLiteralOrThrow value: String) throws { |
111 | 164 | var interpolation = SyntaxStringInterpolation()
|
112 | 165 | interpolation.appendLiteral(value)
|
113 |
| - self.init(stringInterpolation: interpolation) |
| 166 | + try self.init(stringInterpolationOrThrow: interpolation) |
114 | 167 | }
|
115 | 168 | }
|
0 commit comments