Skip to content

Prepend SyntaxStringInterpolationError errors in macro expansion with Internal macro error: #2176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
2 changes: 1 addition & 1 deletion Sources/SwiftSyntaxBuilder/DeclSyntaxParseable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public extension DeclSyntaxParseable {
if let castedDecl = node.as(Self.self) {
self = castedDecl
} else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualNode: node)
throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: node)
}
}
}
Expand Down
34 changes: 21 additions & 13 deletions Sources/SwiftSyntaxBuilder/Syntax+StringInterpolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -161,23 +161,31 @@ where Self.StringInterpolation == SyntaxStringInterpolation {
init(stringInterpolation: SyntaxStringInterpolation)
}

enum SyntaxStringInterpolationError: Error, CustomStringConvertible {
case producedInvalidNodeType(expectedType: SyntaxProtocol.Type, actualType: SyntaxProtocol.Type)
case diagnostics([Diagnostic], tree: Syntax)
/// Describes an error when building a syntax node with string interpolation resulted in an unexpected node type.
public struct SyntaxStringInterpolationInvalidNodeTypeError: Error, CustomStringConvertible {
let expectedType: SyntaxProtocol.Type
let actualType: SyntaxProtocol.Type

/// Initialize the invalid node type error providing an expected type, and the actual node that resulted.
public init<S: SyntaxProtocol>(expectedType: SyntaxProtocol.Type, actualNode: S) {
self.expectedType = expectedType
self.actualType = type(of: actualNode)
}

static func producedInvalidNodeType<S: SyntaxProtocol>(expectedType: SyntaxProtocol.Type, actualNode: S) -> Self {
return .producedInvalidNodeType(expectedType: expectedType, actualType: type(of: actualNode))
public var description: String {
return "Parsing the code snippet was expected to produce a \(expectedType) but produced a \(actualType)"
}
}

/// A string interpolation error based on a ``SwiftDiagnostics/Diagnostic``.
struct SyntaxStringInterpolationDiagnosticError: Error, CustomStringConvertible {
let diagnostics: [Diagnostic]
let tree: Syntax

var description: String {
switch self {
case .producedInvalidNodeType(expectedType: let expectedType, actualType: let actualType):
return "Parsing the code snippet was expected to produce a \(expectedType) but produced a \(actualType)"
case .diagnostics(let diagnostics, let tree):
// Start the diagnostic on a new line so it isn't prefixed with the file, which messes up the
// column-aligned message from ``DiagnosticsFormatter``.
Comment on lines -177 to -178
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we keep this comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, added that comment back <3

return "\n" + DiagnosticsFormatter.annotatedSource(tree: tree, diags: diagnostics)
}
// Start the diagnostic on a new line so it isn't prefixed with the file, which messes up the
// column-aligned message from ``DiagnosticsFormatter``.
return "\n" + DiagnosticsFormatter.annotatedSource(tree: tree, diags: diagnostics)
}
}

Expand Down
14 changes: 7 additions & 7 deletions Sources/SwiftSyntaxBuilder/SyntaxNodeWithBody.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public extension HasTrailingCodeBlock where Self: StmtSyntaxProtocol {
init(_ header: SyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax) throws {
let stmt = StmtSyntax("\(header) {}")
guard let castedStmt = stmt.as(Self.self) else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualNode: stmt)
throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: stmt)
}
self = castedStmt
self.body = try CodeBlockSyntax(statements: bodyBuilder())
Expand Down Expand Up @@ -121,7 +121,7 @@ public extension HasTrailingOptionalCodeBlock where Self: DeclSyntaxProtocol {
init(_ header: SyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax) throws {
let decl = DeclSyntax("\(header) {}")
guard let castedDecl = decl.as(Self.self) else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualNode: decl)
throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: decl)
}
self = castedDecl
self.body = try CodeBlockSyntax(statements: bodyBuilder())
Expand Down Expand Up @@ -166,7 +166,7 @@ public extension HasTrailingMemberDeclBlock where Self: DeclSyntaxProtocol {
init(_ header: SyntaxNodeString, @MemberBlockItemListBuilder membersBuilder: () throws -> MemberBlockItemListSyntax) throws {
let decl = DeclSyntax("\(header) {}")
guard let castedDecl = decl.as(Self.self) else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualNode: decl)
throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: decl)
}
self = castedDecl
self.memberBlock = try MemberBlockSyntax(members: membersBuilder())
Expand Down Expand Up @@ -209,7 +209,7 @@ public extension IfExprSyntax {
) throws {
let expr = ExprSyntax("\(header) {}")
guard let ifExpr = expr.as(Self.self) else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualNode: expr)
throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: expr)
}
self = ifExpr
self.body = try CodeBlockSyntax(statements: bodyBuilder())
Expand Down Expand Up @@ -254,7 +254,7 @@ public extension IfExprSyntax {
init(_ header: SyntaxNodeString, @CodeBlockItemListBuilder bodyBuilder: () throws -> CodeBlockItemListSyntax, elseIf: IfExprSyntax) throws {
let expr = ExprSyntax("\(header) {}")
guard let ifExpr = expr.as(Self.self) else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualNode: expr)
throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: expr)
}
self = ifExpr
self.body = CodeBlockSyntax(statements: try bodyBuilder())
Expand Down Expand Up @@ -321,7 +321,7 @@ public extension SwitchExprSyntax {
init(_ header: SyntaxNodeString, @SwitchCaseListBuilder casesBuilder: () throws -> SwitchCaseListSyntax = { SwitchCaseListSyntax([]) }) throws {
let expr = ExprSyntax("\(header) {}")
guard let switchExpr = expr.as(Self.self) else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualNode: expr)
throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: expr)
}
self = switchExpr
self.cases = try casesBuilder()
Expand Down Expand Up @@ -355,7 +355,7 @@ public extension VariableDeclSyntax {
init(_ header: SyntaxNodeString, @CodeBlockItemListBuilder accessor: () throws -> CodeBlockItemListSyntax) throws {
let decl = DeclSyntax("\(header) {}")
guard let castedDecl = decl.as(Self.self) else {
throw SyntaxStringInterpolationError.producedInvalidNodeType(expectedType: Self.self, actualNode: decl)
throw SyntaxStringInterpolationInvalidNodeTypeError(expectedType: Self.self, actualNode: decl)
}
self = castedDecl
precondition(self.bindings.count == 1)
Expand Down
4 changes: 2 additions & 2 deletions Sources/SwiftSyntaxBuilder/ValidatingSyntaxNodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ extension SyntaxProtocol {
if node.hasError {
let diagnostics = ParseDiagnosticsGenerator.diagnostics(for: node)
precondition(!diagnostics.isEmpty)
throw SyntaxStringInterpolationError.diagnostics(diagnostics, tree: Syntax(node))
throw SyntaxStringInterpolationDiagnosticError(diagnostics: diagnostics, tree: Syntax(node))
}
self = node
}
Expand All @@ -52,7 +52,7 @@ extension Trivia {
}
offset += piece.sourceLength.utf8Length
}
throw SyntaxStringInterpolationError.diagnostics(diagnostics, tree: Syntax(tree))
throw SyntaxStringInterpolationDiagnosticError(diagnostics: diagnostics, tree: Syntax(tree))
}
}
}
6 changes: 5 additions & 1 deletion Sources/SwiftSyntaxMacros/MacroExpansionContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxBuilder

/// Interface to extract information about the context in which a given
/// macro is expanded.
Expand Down Expand Up @@ -77,14 +78,17 @@ private struct ThrownErrorDiagnostic: DiagnosticMessage {
}

extension MacroExpansionContext {
/// Add diagnostics from the error thrown during macro expansion.
/// Adds diagnostics from the error thrown during a macro expansion.
public func addDiagnostics(from error: Error, node: some SyntaxProtocol) {
// Inspect the error to form an appropriate set of diagnostics.
var diagnostics: [Diagnostic]
if let diagnosticsError = error as? DiagnosticsError {
diagnostics = diagnosticsError.diagnostics
} else if let message = error as? DiagnosticMessage {
diagnostics = [Diagnostic(node: Syntax(node), message: message)]
} else if let error = error as? SyntaxStringInterpolationInvalidNodeTypeError {
let diagnostic = Diagnostic(node: Syntax(node), message: ThrownErrorDiagnostic(message: "Internal macro error: \(error.description)"))
diagnostics = [diagnostic]
} else {
diagnostics = [Diagnostic(node: Syntax(node), message: ThrownErrorDiagnostic(message: String(describing: error)))]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
//===----------------------------------------------------------------------===//
//
// 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 SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacroExpansion
import SwiftSyntaxMacros
import SwiftSyntaxMacrosTestSupport
import XCTest

// An error that is not `SyntaxStringInterpolationError`, only used to verify
// that other error types won't get prefixed with `Internal macro error:` when
// passed to `MacroExpansionContext.addDiagnostics`.
private struct DummyError: Error {
static let diagnosticTestError = DummyError()
}

// An extension macro that will fail with
// `SyntaxStringInterpolationError.producedInvalidNodeType`
private struct DummyMacro: ExtensionMacro {
static func expansion(
of node: AttributeSyntax,
attachedTo declaration: some DeclGroupSyntax,
providingExtensionsOf type: some TypeSyntaxProtocol,
conformingTo protocols: [TypeSyntax],
in context: some MacroExpansionContext
) throws -> [ExtensionDeclSyntax] {
let ext = try ExtensionDeclSyntax("var x: Int")
return [ext]
}
}

final class StringInterpolationErrorTests: XCTestCase {

func testMacroExpansionContextAddDiagnosticsAddsSwiftSyntaxInterpolationErrorsWithWrappingMessage() throws {
let context = BasicMacroExpansionContext()
let error = SyntaxStringInterpolationInvalidNodeTypeError(expectedType: DeclSyntax.self, actualNode: ExprSyntax("test"))

// Since we only care about the error switch inside of addDagnostics, we don't care about the particular node we're passing in
context.addDiagnostics(from: error, node: ExprSyntax("1"))
XCTAssertEqual(context.diagnostics.count, 1)
let diagnostic = try XCTUnwrap(context.diagnostics.first)
XCTAssertTrue(diagnostic.message.starts(with: "Internal macro error:"))
}

// Verify that any other error messages do not get "Internal macro error:" prefix.
func testMacroExpansionContextAddDiagnosticsUsesErrorDescriptionForDiagMessage() throws {
let context = BasicMacroExpansionContext()
let error = DummyError.diagnosticTestError

context.addDiagnostics(from: error, node: ExprSyntax("1"))
XCTAssertEqual(context.diagnostics.count, 1)
let diagnostic = try XCTUnwrap(context.diagnostics.first)
XCTAssertEqual(diagnostic.message, String(describing: error))
}

func testMacroExpansionSyntaxInterpolationErrorGetsPrefixed() {
let expectedDiagnostic = DiagnosticSpec(
message: "Internal macro error: Parsing the code snippet was expected to produce a ExtensionDeclSyntax but produced a DeclSyntax",
line: 1,
column: 1
)

assertMacroExpansion(
"@dummy struct Foo {}",
expandedSource: "struct Foo {}",
diagnostics: [expectedDiagnostic],
macros: ["dummy": DummyMacro.self]
)
}
}