|
10 | 10 | //
|
11 | 11 | //===----------------------------------------------------------------------===//
|
12 | 12 |
|
| 13 | +import CASTBridging |
13 | 14 | import SwiftDiagnostics
|
14 | 15 | import SwiftOperators
|
15 | 16 | import SwiftParser
|
@@ -155,14 +156,240 @@ func allocateUTF8String(
|
155 | 156 | }
|
156 | 157 | }
|
157 | 158 |
|
158 |
| -/// Diagnostic message used for thrown errors. |
159 |
| -fileprivate struct ThrownErrorDiagnostic: DiagnosticMessage { |
160 |
| - let message: String |
| 159 | +/// Diagnostics produced here. |
| 160 | +enum ASTGenMacroDiagnostic: DiagnosticMessage, FixItMessage { |
| 161 | + case thrownError(Error) |
| 162 | + case oldStyleExternalMacro |
| 163 | + case useExternalMacro |
| 164 | + case unknownBuiltin(String) |
| 165 | + case notStringLiteralArgument(String) |
161 | 166 |
|
162 |
| - var severity: DiagnosticSeverity { .error } |
| 167 | + var message: String { |
| 168 | + switch self { |
| 169 | + case .thrownError(let error): |
| 170 | + return String(describing: error) |
| 171 | + |
| 172 | + case .oldStyleExternalMacro: |
| 173 | + return "external macro definitions are now written using #externalMacro" |
| 174 | + |
| 175 | + case .useExternalMacro: |
| 176 | + return "use '#externalMacro'" |
| 177 | + |
| 178 | + case .unknownBuiltin(let type): |
| 179 | + return "ignoring definition of unknown builtin macro \(type)" |
| 180 | + |
| 181 | + case .notStringLiteralArgument(let kind): |
| 182 | + return "argument to `#externalMacro` must be a string literal naming the external macro's \(kind)" |
| 183 | + } |
| 184 | + } |
| 185 | + |
| 186 | + var severity: DiagnosticSeverity { |
| 187 | + switch self { |
| 188 | + case .thrownError, .notStringLiteralArgument: |
| 189 | + return .error |
| 190 | + |
| 191 | + case .oldStyleExternalMacro, .unknownBuiltin: |
| 192 | + return .warning |
| 193 | + |
| 194 | + case .useExternalMacro: |
| 195 | + return .note |
| 196 | + } |
| 197 | + } |
163 | 198 |
|
164 | 199 | var diagnosticID: MessageID {
|
165 |
| - .init(domain: "SwiftSyntaxMacros", id: "ThrownErrorDiagnostic") |
| 200 | + .init(domain: "Swift", id: "\(self)") |
| 201 | + } |
| 202 | + |
| 203 | + var fixItID: MessageID { diagnosticID } |
| 204 | +} |
| 205 | + |
| 206 | +/// Treat the given expression as a string literal, which should contain a |
| 207 | +/// single identifier. |
| 208 | +fileprivate func identifierFromStringLiteral(_ node: ExprSyntax) -> String? { |
| 209 | + guard let stringLiteral = node.as(StringLiteralExprSyntax.self), |
| 210 | + stringLiteral.segments.count == 1, |
| 211 | + let segment = stringLiteral.segments.first, |
| 212 | + case .stringSegment(let stringSegment) = segment else { |
| 213 | + return nil |
| 214 | + } |
| 215 | + |
| 216 | + return stringSegment.content.text |
| 217 | +} |
| 218 | + |
| 219 | +/// Check a macro definition, producing a description of that macro definition |
| 220 | +/// for use in macro expansion. |
| 221 | +/// |
| 222 | +/// When the resulting macro requires expansion, the result will come in |
| 223 | +/// two parts: |
| 224 | +/// |
| 225 | +/// - Returns: -1 on failure, BridgedMacroDefinitionKind on success. When the |
| 226 | +/// successful result is "expanded macro", `replacementsPtr` will point to a |
| 227 | +/// number of "replacements" to perform when expanding that macro. Each |
| 228 | +/// replacement is a textual replacement of use of a macro parameter with the |
| 229 | +/// source text of the corresponding argument, and is represented as a triple |
| 230 | +/// (start offset, end offset, parameter index): the [start offset, end offset) |
| 231 | +/// range in the macro expansion expression should be replaced with the |
| 232 | +/// argument matching the corresponding parameter. |
| 233 | +@_cdecl("swift_ASTGen_checkMacroDefinition") |
| 234 | +func checkMacroDefinition( |
| 235 | + diagEnginePtr: UnsafeMutablePointer<UInt8>, |
| 236 | + sourceFilePtr: UnsafeRawPointer, |
| 237 | + macroLocationPtr: UnsafePointer<UInt8>, |
| 238 | + externalMacroPointer: UnsafeMutablePointer<UnsafePointer<UInt8>?>, |
| 239 | + externalMacroLength: UnsafeMutablePointer<Int>, |
| 240 | + replacementsPtr: UnsafeMutablePointer<UnsafeMutablePointer<Int>?>, |
| 241 | + numReplacementsPtr: UnsafeMutablePointer<Int> |
| 242 | +) -> Int { |
| 243 | + // Clear out the "out" parameters. |
| 244 | + externalMacroPointer.pointee = nil |
| 245 | + externalMacroLength.pointee = 0 |
| 246 | + replacementsPtr.pointee = nil |
| 247 | + numReplacementsPtr.pointee = 0 |
| 248 | + |
| 249 | + let sourceFilePtr = sourceFilePtr.bindMemory(to: ExportedSourceFile.self, capacity: 1) |
| 250 | + |
| 251 | + // Find the macro declaration. |
| 252 | + guard let macroDecl = findSyntaxNodeInSourceFile( |
| 253 | + sourceFilePtr: sourceFilePtr, |
| 254 | + sourceLocationPtr: macroLocationPtr, |
| 255 | + type: MacroDeclSyntax.self |
| 256 | + ) else { |
| 257 | + // FIXME: Produce an error |
| 258 | + return -1 |
| 259 | + } |
| 260 | + |
| 261 | + // Check the definition |
| 262 | + do { |
| 263 | + let definition = try macroDecl.checkDefinition() |
| 264 | + switch definition { |
| 265 | + case let .deprecatedExternal(node: node, module: module, type: type): |
| 266 | + // Check for known builtins. |
| 267 | + if module == "Builtin" { |
| 268 | + switch type { |
| 269 | + case "ExternalMacro": |
| 270 | + return BridgedMacroDefinitionKind.builtinExternalMacro.rawValue |
| 271 | + |
| 272 | + default: |
| 273 | + // Warn about the unknown builtin. |
| 274 | + let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr) |
| 275 | + srcMgr.insert(sourceFilePtr) |
| 276 | + srcMgr.diagnose( |
| 277 | + diagnostic: .init( |
| 278 | + node: node, |
| 279 | + message: ASTGenMacroDiagnostic.unknownBuiltin(type) |
| 280 | + ) |
| 281 | + ) |
| 282 | + |
| 283 | + return -1 |
| 284 | + } |
| 285 | + } |
| 286 | + |
| 287 | + // Form the "ModuleName.TypeName" result string. |
| 288 | + (externalMacroPointer.pointee, externalMacroLength.pointee) = |
| 289 | + allocateUTF8String("\(module).\(type)", nullTerminated: true) |
| 290 | + |
| 291 | + // Translate this into a use of #externalMacro. |
| 292 | + let expansionSourceSyntax: ExprSyntax = |
| 293 | + "#externalMacro(module: \(literal: module), type: \(literal: type))" |
| 294 | + |
| 295 | + // Warn about the use of old-style external macro syntax here. |
| 296 | + let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr) |
| 297 | + srcMgr.insert(sourceFilePtr) |
| 298 | + srcMgr.diagnose( |
| 299 | + diagnostic: .init( |
| 300 | + node: node, |
| 301 | + message: ASTGenMacroDiagnostic.oldStyleExternalMacro, |
| 302 | + fixIts: [ |
| 303 | + FixIt( |
| 304 | + message: ASTGenMacroDiagnostic.useExternalMacro, |
| 305 | + changes: [ |
| 306 | + FixIt.Change.replace( |
| 307 | + oldNode: node, |
| 308 | + newNode: Syntax(expansionSourceSyntax) |
| 309 | + ) |
| 310 | + ] |
| 311 | + ) |
| 312 | + ] |
| 313 | + ) |
| 314 | + ) |
| 315 | + return BridgedMacroDefinitionKind.externalMacro.rawValue |
| 316 | + |
| 317 | + case let .expansion(expansionSyntax, replacements: replacements) |
| 318 | + where expansionSyntax.macro.text == "externalMacro": |
| 319 | + // Extract the identifier from the "module" argument. |
| 320 | + guard let firstArg = expansionSyntax.argumentList.first, |
| 321 | + let firstArgLabel = firstArg.label?.text, |
| 322 | + firstArgLabel == "module", |
| 323 | + let module = identifierFromStringLiteral(firstArg.expression) else { |
| 324 | + let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr) |
| 325 | + srcMgr.insert(sourceFilePtr) |
| 326 | + srcMgr.diagnose( |
| 327 | + diagnostic: .init( |
| 328 | + node: Syntax(expansionSyntax), |
| 329 | + message: ASTGenMacroDiagnostic.notStringLiteralArgument("module") |
| 330 | + ) |
| 331 | + ) |
| 332 | + return -1 |
| 333 | + } |
| 334 | + |
| 335 | + // Extract the identifier from the "type" argument. |
| 336 | + guard let secondArg = expansionSyntax.argumentList.dropFirst().first, |
| 337 | + let secondArgLabel = secondArg.label?.text, |
| 338 | + secondArgLabel == "type", |
| 339 | + let type = identifierFromStringLiteral(secondArg.expression) else { |
| 340 | + let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr) |
| 341 | + srcMgr.insert(sourceFilePtr) |
| 342 | + srcMgr.diagnose( |
| 343 | + diagnostic: .init( |
| 344 | + node: Syntax(expansionSyntax), |
| 345 | + message: ASTGenMacroDiagnostic.notStringLiteralArgument("type") |
| 346 | + ) |
| 347 | + ) |
| 348 | + return -1 |
| 349 | + } |
| 350 | + |
| 351 | + // Form the "ModuleName.TypeName" result string. |
| 352 | + (externalMacroPointer.pointee, externalMacroLength.pointee) = |
| 353 | + allocateUTF8String("\(module).\(type)", nullTerminated: true) |
| 354 | + return BridgedMacroDefinitionKind.externalMacro.rawValue |
| 355 | + |
| 356 | + case let .expansion(expansionSyntax, replacements: replacements): |
| 357 | + // If there are no replacements, we're done. |
| 358 | + if replacements.isEmpty { |
| 359 | + return BridgedMacroDefinitionKind.expandedMacro.rawValue |
| 360 | + } |
| 361 | + |
| 362 | + // The replacements are triples: (startOffset, endOffset, parameter index). |
| 363 | + let replacementBuffer = UnsafeMutableBufferPointer<Int>.allocate(capacity: 3 * replacements.count) |
| 364 | + for (index, replacement) in replacements.enumerated() { |
| 365 | + let expansionStart = expansionSyntax.positionAfterSkippingLeadingTrivia.utf8Offset |
| 366 | + |
| 367 | + replacementBuffer[index * 3] = replacement.reference.positionAfterSkippingLeadingTrivia.utf8Offset - expansionStart |
| 368 | + replacementBuffer[index * 3 + 1] = replacement.reference.endPositionBeforeTrailingTrivia.utf8Offset - expansionStart |
| 369 | + replacementBuffer[index * 3 + 2] = replacement.parameterIndex |
| 370 | + } |
| 371 | + |
| 372 | + replacementsPtr.pointee = replacementBuffer.baseAddress |
| 373 | + numReplacementsPtr.pointee = replacements.count |
| 374 | + return BridgedMacroDefinitionKind.expandedMacro.rawValue |
| 375 | + } |
| 376 | + } catch let errDiags as DiagnosticsError { |
| 377 | + let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr) |
| 378 | + srcMgr.insert(sourceFilePtr) |
| 379 | + for diag in errDiags.diagnostics { |
| 380 | + srcMgr.diagnose(diagnostic: diag) |
| 381 | + } |
| 382 | + return -1 |
| 383 | + } catch let error { |
| 384 | + let srcMgr = SourceManager(cxxDiagnosticEngine: diagEnginePtr) |
| 385 | + srcMgr.insert(sourceFilePtr) |
| 386 | + srcMgr.diagnose( |
| 387 | + diagnostic: .init( |
| 388 | + node: Syntax(macroDecl), |
| 389 | + message: ASTGenMacroDiagnostic.thrownError(error) |
| 390 | + ) |
| 391 | + ) |
| 392 | + return -1 |
166 | 393 | }
|
167 | 394 | }
|
168 | 395 |
|
@@ -286,7 +513,7 @@ func expandFreestandingMacroIPC(
|
286 | 513 | node: macroSyntax,
|
287 | 514 | // FIXME: This is probably a plugin communication error.
|
288 | 515 | // The error might not be relevant as the diagnostic message.
|
289 |
| - message: ThrownErrorDiagnostic(message: String(describing: error)) |
| 516 | + message: ASTGenMacroDiagnostic.thrownError(error) |
290 | 517 | ),
|
291 | 518 | messageSuffix: " (from macro '\(macroName)')"
|
292 | 519 | )
|
@@ -378,7 +605,7 @@ func expandFreestandingMacroInProcess(
|
378 | 605 | sourceManager.diagnose(
|
379 | 606 | diagnostic: Diagnostic(
|
380 | 607 | node: macroSyntax,
|
381 |
| - message: ThrownErrorDiagnostic(message: String(describing: error)) |
| 608 | + message: ASTGenMacroDiagnostic.thrownError(error) |
382 | 609 | ),
|
383 | 610 | messageSuffix: " (from macro '\(macroName)')"
|
384 | 611 | )
|
@@ -631,7 +858,7 @@ func expandAttachedMacroIPC(
|
631 | 858 | node: Syntax(declarationNode),
|
632 | 859 | // FIXME: This is probably a plugin communication error.
|
633 | 860 | // The error might not be relevant as the diagnostic message.
|
634 |
| - message: ThrownErrorDiagnostic(message: String(describing: error)) |
| 861 | + message: ASTGenMacroDiagnostic.thrownError(error) |
635 | 862 | ),
|
636 | 863 | messageSuffix: " (from macro '\(macroName)')"
|
637 | 864 | )
|
@@ -811,7 +1038,7 @@ func expandAttachedMacroInProcess(
|
811 | 1038 | sourceManager.diagnose(
|
812 | 1039 | diagnostic: Diagnostic(
|
813 | 1040 | node: Syntax(declarationNode),
|
814 |
| - message: ThrownErrorDiagnostic(message: String(describing: error)) |
| 1041 | + message: ASTGenMacroDiagnostic.thrownError(error) |
815 | 1042 | ),
|
816 | 1043 | messageSuffix: " (from macro '\(macroName)')"
|
817 | 1044 | )
|
|
0 commit comments