From 895f67b3c41da900e35a495b7304e888b1a64ccb Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Fri, 28 Mar 2025 09:28:21 +0100 Subject: [PATCH 1/2] Add string interpolation for `FunctionParameterClauseSyntax` --- .../swiftparser/LayoutNodesParsableFile.swift | 15 +++++++++++ ...yStringInterpolationConformancesFile.swift | 11 ++++++++ .../generated/LayoutNodes+Parsable.swift | 10 +++++++ ...bleByStringInterpolationConformances.swift | 6 +++++ .../FunctionParameterClauseSyntaxTests.swift | 27 +++++++++++++++++++ 5 files changed, 69 insertions(+) create mode 100644 Tests/SwiftSyntaxBuilderTest/FunctionParameterClauseSyntaxTests.swift diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift index bb6006711f6..14d6662d678 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftparser/LayoutNodesParsableFile.swift @@ -64,6 +64,21 @@ let layoutNodesParsableFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { } } + // Custom implementations for nodes with special handling. + DeclSyntax( + """ + extension FunctionParameterClauseSyntax: SyntaxParseable { + public static func parse(from parser: inout Parser) -> Self { + parse(from: &parser) { + $0.parseParameterClause(RawFunctionParameterClauseSyntax.self) { parser in + parser.parseFunctionParameter() + } + } + } + } + """ + ) + try! ExtensionDeclSyntax("fileprivate extension Parser") { DeclSyntax( """ diff --git a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntaxbuilder/SyntaxExpressibleByStringInterpolationConformancesFile.swift b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntaxbuilder/SyntaxExpressibleByStringInterpolationConformancesFile.swift index ba5909e2b00..ea472de74ff 100644 --- a/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntaxbuilder/SyntaxExpressibleByStringInterpolationConformancesFile.swift +++ b/CodeGeneration/Sources/generate-swift-syntax/templates/swiftsyntaxbuilder/SyntaxExpressibleByStringInterpolationConformancesFile.swift @@ -40,4 +40,15 @@ let syntaxExpressibleByStringInterpolationConformancesFile = SourceFileSyntax(le """ ) } + + // Due to we cannot parse the `FunctionParameterClauseSyntax` from string interpolation in the normal way + // we need to hand-write the conformance. + DeclSyntax("extension FunctionParameterClauseSyntax: SyntaxExpressibleByStringInterpolation {}") + DeclSyntax( + """ + #if compiler(>=6) + extension FunctionParameterClauseSyntax: Swift.ExpressibleByStringInterpolation {} + #endif + """ + ) } diff --git a/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift b/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift index c880b12f8e5..24bcd2492a8 100644 --- a/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift +++ b/Sources/SwiftParser/generated/LayoutNodes+Parsable.swift @@ -236,6 +236,16 @@ extension VersionTupleSyntax: SyntaxParseable { } } +extension FunctionParameterClauseSyntax: SyntaxParseable { + public static func parse(from parser: inout Parser) -> Self { + parse(from: &parser) { + $0.parseParameterClause(RawFunctionParameterClauseSyntax.self) { parser in + parser.parseFunctionParameter() + } + } + } +} + fileprivate extension Parser { mutating func parseNonOptionalCodeBlockItem() -> RawCodeBlockItemSyntax { guard let node = self.parseCodeBlockItem(isAtTopLevel: false, allowInitDecl: true) else { diff --git a/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift b/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift index eaab028c7a1..dfd6fec5b17 100644 --- a/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift +++ b/Sources/SwiftSyntaxBuilder/generated/SyntaxExpressibleByStringInterpolationConformances.swift @@ -187,3 +187,9 @@ extension MemberBlockItemListSyntax: SyntaxExpressibleByStringInterpolation {} extension MemberBlockItemListSyntax: Swift.ExpressibleByStringInterpolation {} #endif +extension FunctionParameterClauseSyntax: SyntaxExpressibleByStringInterpolation {} + +#if compiler(>=6) +extension FunctionParameterClauseSyntax: Swift.ExpressibleByStringInterpolation {} +#endif + diff --git a/Tests/SwiftSyntaxBuilderTest/FunctionParameterClauseSyntaxTests.swift b/Tests/SwiftSyntaxBuilderTest/FunctionParameterClauseSyntaxTests.swift new file mode 100644 index 00000000000..64ee2b6d82a --- /dev/null +++ b/Tests/SwiftSyntaxBuilderTest/FunctionParameterClauseSyntaxTests.swift @@ -0,0 +1,27 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 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 SwiftSyntaxBuilder +import XCTest + +final class FunctionParameterClauseSyntaxTests: XCTestCase { + func testFunctionParameterClauseSyntax() { + let builder = FunctionParameterClauseSyntax("(x: Int)") + assertBuildResult( + builder, + """ + (x: Int) + """ + ) + } +} From a8fb3cc294beb88401bc8de4ca752c7d64d07902 Mon Sep 17 00:00:00 2001 From: Kim de Vos Date: Mon, 7 Apr 2025 21:19:10 +0200 Subject: [PATCH 2/2] wip --- ...abeledExpListSyntax+MatchedArguments.swift | 58 +++++++++ .../MatchedArgumentsTests.swift | 112 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 Sources/SwiftSyntaxMacros/LabeledExpListSyntax+MatchedArguments.swift create mode 100644 Tests/SwiftSyntaxMacroExpansionTest/MatchedArgumentsTests.swift diff --git a/Sources/SwiftSyntaxMacros/LabeledExpListSyntax+MatchedArguments.swift b/Sources/SwiftSyntaxMacros/LabeledExpListSyntax+MatchedArguments.swift new file mode 100644 index 00000000000..b083e941424 --- /dev/null +++ b/Sources/SwiftSyntaxMacros/LabeledExpListSyntax+MatchedArguments.swift @@ -0,0 +1,58 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2025 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 + +extension LabeledExprListSyntax { + public func matchArguments( + against parameters: FunctionParameterClauseSyntax, + isSubscript: Bool = false + ) -> MatchedArguments { + var expressions: [[ExprSyntax]] = [] + + var iterator = makeIterator() + + var elements: [ExprSyntax] = [] + while let element = iterator.next() { + elements.append(element.expression) + + guard element.colon != nil else { + continue + } + + expressions.append(elements) + } + + var result: [String: [ExprSyntax]] = [:] + for (expressions, parameter) in zip(expressions, parameters.parameters) { + let key = parameter.name + + result[key] = expressions + } + + return MatchedArguments(matchedArguments: result) + } +} + +public struct MatchedArguments { + let matchedArguments: [String: [ExprSyntax]] + + public func argument(for internalLabel: String) -> [ExprSyntax]? { + return matchedArguments[internalLabel] + } +} + +extension FunctionParameterSyntax { + fileprivate var name: String { + return secondName?.text ?? firstName.text + } +} diff --git a/Tests/SwiftSyntaxMacroExpansionTest/MatchedArgumentsTests.swift b/Tests/SwiftSyntaxMacroExpansionTest/MatchedArgumentsTests.swift new file mode 100644 index 00000000000..b3d193dab7d --- /dev/null +++ b/Tests/SwiftSyntaxMacroExpansionTest/MatchedArgumentsTests.swift @@ -0,0 +1,112 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftSyntaxMacroExpansion +import SwiftSyntaxMacros +import SwiftSyntaxMacrosTestSupport +import XCTest +import _SwiftSyntaxTestSupport + +final class MatchedArgumentsTests: XCTestCase { + func testWithSingleName() { + let arg = LabeledExprListSyntax { + LabeledExprSyntax(label: "x", expression: ExprSyntax("2")) + } + + let arguments = arg.matchArguments(against: "(x: Int)") + + XCTAssertEqual(arguments.argument(for: "x")?.first?.description, "2") + } + + func testWithTwoNames() { + let arg = LabeledExprListSyntax { + LabeledExprSyntax( + label: "x", + expression: ExprSyntax("2") + ) + } + + let arguments = arg.matchArguments(against: "(x y: Int)") + + XCTAssertEqual(arguments.argument(for: "y")?.first?.description, "2") + XCTAssertNil(arguments.argument(for: "x")) + } + + func testWithMultipleArguments() { + let arg = LabeledExprListSyntax { + LabeledExprSyntax( + label: "x", + expression: ExprSyntax("2") + ) + + LabeledExprSyntax( + label: "x", + expression: ExprSyntax("3") + ) + } + + let arguments = arg.matchArguments(against: "(x y: Int, x z: Int)") + + XCTAssertEqual(arguments.argument(for: "y")?.first?.description, "2") + XCTAssertEqual(arguments.argument(for: "z")?.first?.description, "3") + } + + func testWithVariadicArguments() { + let arg = LabeledExprListSyntax { + LabeledExprSyntax( + label: "x", + expression: ExprSyntax("2") + ) + + LabeledExprSyntax( + expression: ExprSyntax("3") + ) + } + + let arguments = arg.matchArguments(against: "(x: Int...)") + + XCTAssertEqual(arguments.argument(for: "x")?.first?.description, "2") + XCTAssertEqual(arguments.argument(for: "x")?.last?.description, "3") + } + + func testWithSubscript() { + let arg = LabeledExprListSyntax { + LabeledExprSyntax( + label: "x", + expression: ExprSyntax("2") + ) + } + + let arguments = arg.matchArguments(against: "(x: Int)", isSubscript: true) + + XCTAssertEqual(arguments.argument(for: "x")?.first?.description, "2") + } + + func testWithMultipleArgumentsWithNoLabel() { + let arg = LabeledExprListSyntax { + LabeledExprSyntax( + expression: ExprSyntax("2") + ) + + LabeledExprSyntax( + expression: ExprSyntax("3") + ) + } + + let arguments = arg.matchArguments(against: "(_ x: Int, _ y: Int)") + + XCTAssertEqual(arguments.argument(for: "y")?.first?.description, "2") + XCTAssertEqual(arguments.argument(for: "z")?.first?.description, "3") + } +}