diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/ChildNameForKeyPathFile.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/ChildNameForKeyPathFile.swift index fc5f3d11a90..47a6e5d6008 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/ChildNameForKeyPathFile.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/ChildNameForKeyPathFile.swift @@ -20,7 +20,8 @@ let childNameForKeyPathFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { """ /// If the keyPath is one from a layout structure, return the property name /// of it. - internal func childName(_ keyPath: AnyKeyPath) -> String? + @_spi(RawSyntax) + public func childName(_ keyPath: AnyKeyPath) -> String? """ ) { try! SwitchExprSyntax("switch keyPath") { diff --git a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift index 350200b9375..35215544ca8 100644 --- a/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift +++ b/Sources/SwiftSyntax/generated/ChildNameForKeyPath.swift @@ -14,7 +14,8 @@ /// If the keyPath is one from a layout structure, return the property name /// of it. -internal func childName(_ keyPath: AnyKeyPath) -> String? { +@_spi(RawSyntax) +public func childName(_ keyPath: AnyKeyPath) -> String? { switch keyPath { case \AccessPathComponentSyntax.unexpectedBeforeName: return "unexpectedBeforeName" diff --git a/Sources/_SwiftSyntaxTestSupport/SyntaxProtocol+Initializer.swift b/Sources/_SwiftSyntaxTestSupport/SyntaxProtocol+Initializer.swift index 69f6be18ccd..98ca95c9d42 100644 --- a/Sources/_SwiftSyntaxTestSupport/SyntaxProtocol+Initializer.swift +++ b/Sources/_SwiftSyntaxTestSupport/SyntaxProtocol+Initializer.swift @@ -110,21 +110,36 @@ extension SyntaxProtocol { /// (or at least an expression that's very close to constructing this node, the addition of a few manual upcast by hand is still needed). /// The intended use case for this is to print a syntax tree and create a substructure assertion from the generated expression. /// When `includeTrivia` is set to `false`, the token's leading and trailing trivia will not be included in the generated expression. + /// + /// - Warning: This is only designed for use in the debugger. Do not call it outside of the debugger. + @available(*, deprecated, message: "For use in debugger only") public func debugInitCall(includeTrivia: Bool = true) -> String { return self.debugInitCallExpr(includeTrivia: includeTrivia).formatted(using: InitializerExprFormat()).description } private func debugInitCallExpr(includeTrivia: Bool) -> ExprSyntax { - let mirror = Mirror(reflecting: self) - if self.kind.isSyntaxCollection { + if type(of: self) != self.syntaxNodeType { + let nestedInitCall = Syntax(self).asProtocol(SyntaxProtocol.self).debugInitCallExpr(includeTrivia: includeTrivia) + var typeName = "\(type(of: self))" + // If the type is `SyntaxChildChoices`, it is a nested type that needs to be qualified. + if self is SyntaxChildChoices, let parent = parent { + typeName = "\(parent.syntaxNodeType).\(typeName)" + } + return ExprSyntax( + FunctionCallExprSyntax(callee: ExprSyntax("\(raw: typeName)")) { + TupleExprElementSyntax(expression: nestedInitCall) + } + ) + } + + if case .collection(let collectionElementType) = self.syntaxNodeType.structure { let typeName = String(describing: type(of: self)) return ExprSyntax( FunctionCallExprSyntax(callee: IdentifierExprSyntax(identifier: .identifier(typeName))) { TupleExprElementSyntax( expression: ArrayExprSyntax { - for child in mirror.children { - let value = child.value as! SyntaxProtocol? - ArrayElementSyntax(expression: value?.debugInitCallExpr(includeTrivia: includeTrivia) ?? ExprSyntax(NilLiteralExprSyntax())) + for child in self.children(viewMode: .all) { + ArrayElementSyntax(expression: child.as(collectionElementType)!.debugInitCallExpr(includeTrivia: includeTrivia)) } } ) @@ -134,12 +149,12 @@ extension SyntaxProtocol { let tokenKind = token.tokenKind let tokenInitializerName: String let tokenKindArgument: ExprSyntax? - if tokenKind.isLexerClassifiedKeyword || tokenKind == .eof { - tokenInitializerName = String(describing: tokenKind) - tokenKindArgument = nil - } else if case .keyword(let keyword) = tokenKind { + if case .keyword(let keyword) = tokenKind { tokenInitializerName = "keyword" tokenKindArgument = ExprSyntax(".\(raw: keyword)") + } else if tokenKind.isLexerClassifiedKeyword || tokenKind == .eof { + tokenInitializerName = String(describing: tokenKind) + tokenKindArgument = nil } else if tokenKind.decomposeToRaw().rawKind.defaultText != nil { tokenInitializerName = "\(String(describing: tokenKind))Token" tokenKindArgument = nil @@ -176,15 +191,15 @@ extension SyntaxProtocol { } } ) - } else { + } else if case .layout(let layout) = self.syntaxNodeType.structure { let typeName = String(describing: type(of: self)) return ExprSyntax( FunctionCallExprSyntax(callee: IdentifierExprSyntax(identifier: .identifier(typeName))) { - for child in mirror.children { - let label = child.label! - let value = child.value as! SyntaxProtocol? + for keyPath in layout { + let label = childName(keyPath) ?? "" + let value = self[keyPath: keyPath as! PartialKeyPath] as! SyntaxProtocol? let isUnexpected = label.hasPrefix("unexpected") - if !isUnexpected || value != nil { + if value != nil { TupleExprElementSyntax( label: isUnexpected ? nil : .identifier(label), colon: isUnexpected ? nil : .colonToken(), @@ -194,6 +209,8 @@ extension SyntaxProtocol { } } ) + } else { + fatalError() } } } diff --git a/Tests/SwiftSyntaxTest/DebugDescriptionTests.swift b/Tests/SwiftSyntaxTest/DebugDescriptionTests.swift index 7a83ab142c4..b7d36da2273 100644 --- a/Tests/SwiftSyntaxTest/DebugDescriptionTests.swift +++ b/Tests/SwiftSyntaxTest/DebugDescriptionTests.swift @@ -13,6 +13,7 @@ import XCTest import SwiftSyntax import _SwiftSyntaxTestSupport +import SwiftParser private extension String { // This implementation is really slow; to use it outside a test it should be optimized. @@ -158,7 +159,8 @@ public class DebugDescriptionTests: XCTestCase { testCases.forEach { keyAndValue in let (key:line, value:testCase) = keyAndValue - let actualDumped = dumped(testCase.syntax) + var actualDumped = "" + dump(testCase.syntax, to: &actualDumped) assertStringsEqualWithDiff( actualDumped.trimmingTrailingWhitespace(), testCase.expectedDumped.trimmingTrailingWhitespace(), @@ -167,9 +169,59 @@ public class DebugDescriptionTests: XCTestCase { } } - public func dumped(_ syntax: Any) -> String { - var result = "" - dump(syntax, to: &result) - return result + #if DEBUG + // debugInitCall is only available in debug builds, so we can only test it in those. + @available(*, deprecated, message: "Purposefully tests debugInitCall, which is marked deprecated for debugger use only") + func testDebugInitCall() { + let sourceFile: SourceFileSyntax = """ + test(1, 2) + """ + + assertStringsEqualWithDiff( + sourceFile.debugInitCall(includeTrivia: true).trimmingTrailingWhitespace(), + """ + SourceFileSyntax( + statements: CodeBlockItemListSyntax([ + CodeBlockItemSyntax(item: CodeBlockItemSyntax.Item(FunctionCallExprSyntax( + calledExpression: ExprSyntax(IdentifierExprSyntax(identifier: .identifier("test"))), + leftParen: .leftParenToken(), + argumentList: TupleExprElementListSyntax([ + TupleExprElementSyntax( + expression: ExprSyntax(IntegerLiteralExprSyntax(digits: .integerLiteral("1"))), + trailingComma: .commaToken(trailingTrivia: .space) + ), + TupleExprElementSyntax(expression: ExprSyntax(IntegerLiteralExprSyntax(digits: .integerLiteral("2")))) + ]), + rightParen: .rightParenToken() + ))) + ]), + eofToken: .eof() + ) + """ + ) + + assertStringsEqualWithDiff( + sourceFile.debugInitCall(includeTrivia: false).trimmingTrailingWhitespace(), + """ + SourceFileSyntax( + statements: CodeBlockItemListSyntax([ + CodeBlockItemSyntax(item: CodeBlockItemSyntax.Item(FunctionCallExprSyntax( + calledExpression: ExprSyntax(IdentifierExprSyntax(identifier: .identifier("test"))), + leftParen: .leftParenToken(), + argumentList: TupleExprElementListSyntax([ + TupleExprElementSyntax( + expression: ExprSyntax(IntegerLiteralExprSyntax(digits: .integerLiteral("1"))), + trailingComma: .commaToken() + ), + TupleExprElementSyntax(expression: ExprSyntax(IntegerLiteralExprSyntax(digits: .integerLiteral("2")))) + ]), + rightParen: .rightParenToken() + ))) + ]), + eofToken: .eof() + ) + """ + ) } + #endif }