From 0ae92fdd9908164f950f67ac3be05d8a65c73e6f Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Thu, 16 Mar 2023 16:16:13 -0700 Subject: [PATCH 1/6] [Macros] Add API for expanding a macro defined in terms of another macro --- .../SwiftSyntaxMacros/MacroReplacement.swift | 253 ++++++++++++++++++ .../MacroReplacementTests.swift | 90 +++++++ 2 files changed, 343 insertions(+) create mode 100644 Sources/SwiftSyntaxMacros/MacroReplacement.swift create mode 100644 Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift diff --git a/Sources/SwiftSyntaxMacros/MacroReplacement.swift b/Sources/SwiftSyntaxMacros/MacroReplacement.swift new file mode 100644 index 00000000000..5a0a45d4afc --- /dev/null +++ b/Sources/SwiftSyntaxMacros/MacroReplacement.swift @@ -0,0 +1,253 @@ +import SwiftDiagnostics +import SwiftSyntax + +/// The replacement of a parameter. +@_spi(Testing) +public struct ParameterReplacement { + /// A reference to a parameter as it occurs in the macro expansion expression. + public let reference: IdentifierExprSyntax + + /// The index of the parameter + public let parameterIndex: Int +} + +extension FunctionParameterSyntax { + /// Retrieve the name of the parameter as it is used in source. + /// + /// Example: + /// + /// func f(a: Int, _ b: Int, c see: Int) { ... } + /// + /// The parameter names for these three parameters are `a`, `b`, and `see`, + /// respectively. + var parameterName: TokenSyntax? { + // If there were two names, the second is the parameter name. + if let secondName = secondName { + if secondName.text == "_" { + return nil + } + + return secondName + } + + if let firstName = firstName { + if firstName.text == "_" { + return nil + } + + return firstName + } + + return nil + } +} + +enum MacroExpanderError: DiagnosticMessage { + case undefined + case nonParameterReference(TokenSyntax) + case nonLiteralOrParameter(ExprSyntax) + + var message: String { + switch self { + case .undefined: + return "macro expansion requires a definition" + + case .nonParameterReference(let name): + return "reference to value '\(name.text)' that is not a macro parameter in expansion" + + case .nonLiteralOrParameter: + return "only literals and macro parameters are permitted in expansion" + } + } + + var diagnosticID: MessageID { + .init(domain: "SwiftMacros", id: "\(self)") + } + + var severity: DiagnosticSeverity { + .error + } +} + +fileprivate class ParameterReplacementVisitor: SyntaxAnyVisitor { + let macro: MacroDeclSyntax + var replacements: [ParameterReplacement] = [] + var diagnostics: [Diagnostic] = [] + + init(macro: MacroDeclSyntax) { + self.macro = macro + super.init(viewMode: .fixedUp) + } + + // Integer literals + override func visit(_ node: IntegerLiteralExprSyntax) -> SyntaxVisitorContinueKind { + .visitChildren + } + + // Floating point literals + override func visit(_ node: FloatLiteralExprSyntax) -> SyntaxVisitorContinueKind { + .visitChildren + } + + // nil literals + override func visit(_ node: NilLiteralExprSyntax) -> SyntaxVisitorContinueKind { + .visitChildren + } + + // String literals + override func visit(_ node: StringLiteralExprSyntax) -> SyntaxVisitorContinueKind { + .visitChildren + } + + // Array literals + override func visit(_ node: ArrayExprSyntax) -> SyntaxVisitorContinueKind { + .visitChildren + } + + // Dictionary literals + override func visit(_ node: DictionaryExprSyntax) -> SyntaxVisitorContinueKind { + .visitChildren + } + + // Tuple literals + override func visit(_ node: TupleExprSyntax) -> SyntaxVisitorContinueKind { + .visitChildren + } + + // Macro uses. + override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind { + .visitChildren + } + + // References to declarations. Only accept those that refer to a parameter + // of a macro. + override func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind { + let identifier = node.identifier + + // FIXME: This will go away. + guard case let .functionLike(signature) = macro.signature else { + return .visitChildren + } + + let matchedParameter = signature.input.parameterList.enumerated().first { (index, parameter) in + if identifier.text == "_" { + return false + } + + guard let parameterName = parameter.parameterName else { + return false + } + + return identifier.text == parameterName.text + } + + guard let (parameterIndex, _) = matchedParameter else { + // We have a reference to something that isn't a parameter of the macro. + diagnostics.append( + Diagnostic( + node: Syntax(identifier), + message: MacroExpanderError.nonParameterReference(identifier) + ) + ) + + return .visitChildren + } + + replacements.append(.init(reference: node, parameterIndex: parameterIndex)) + return .visitChildren + } + + override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind { + if let expr = node.as(ExprSyntax.self) { + // We have an expression that is not one of the allowed forms, + diagnostics.append( + Diagnostic( + node: node, + message: MacroExpanderError.nonLiteralOrParameter(expr) + ) + ) + + return .skipChildren + } + + return .visitChildren + } + +} + +extension MacroDeclSyntax { + /// Compute the sequence of parameter replacements required when expanding + /// the definition of a non-external macro. + @_spi(Testing) + public func expansionParameterReplacements() -> (replacements: [ParameterReplacement], diagnostics: [Diagnostic]) { + // Cannot compute replacements for an undefined macro. + guard let definition = definition?.value else { + let undefinedDiag = Diagnostic( + node: Syntax(self), + message: MacroExpanderError.undefined + ) + + return (replacements: [], diagnostics: [undefinedDiag]) + } + + let visitor = ParameterReplacementVisitor(macro: self) + visitor.walk(definition) + + return (replacements: visitor.replacements, diagnostics: visitor.diagnostics) + } +} + +/// Syntax rewrite that performs macro expansion by textually replacing +/// uses of macro parameters with their corresponding arguments. +private final class MacroExpansionRewriter: SyntaxRewriter { + let parameterReplacements: [IdentifierExprSyntax: Int] + let arguments: [ExprSyntax] + + init(parameterReplacements: [IdentifierExprSyntax: Int], arguments: [ExprSyntax]) { + self.parameterReplacements = parameterReplacements + self.arguments = arguments + } + + override func visit(_ node: IdentifierExprSyntax) -> ExprSyntax { + guard let parameterIndex = parameterReplacements[node] else { + return super.visit(node) + } + + // Swap in the argument for this parameter + return arguments[parameterIndex].trimmed + } +} + +extension MacroDeclSyntax { + /// Given a freestanding macro expansion syntax node that references this + /// macro declaration, expand the macro by substituting the arguments from + /// the macro expansion into the parameters that are used in the definition. + /// + /// If there are any errors, the function will throw with all diagnostics + /// placed in a `DiagnosticsError`. + public func expandDefinition( + _ node: some FreestandingMacroExpansionSyntax + ) throws -> ExprSyntax { + let (replacements, diagnostics) = expansionParameterReplacements() + + // If there were any diagnostics, don't allow replacement. + if !diagnostics.isEmpty { + throw DiagnosticsError(diagnostics: diagnostics) + } + + // FIXME: Do real call-argument matching between the argument list and the + // macro parameter list, porting over from the compiler. + let arguments: [ExprSyntax] = node.argumentList.map { element in + element.expression + } + + return MacroExpansionRewriter( + parameterReplacements: Dictionary( + uniqueKeysWithValues: replacements.map { replacement in + (replacement.reference, replacement.parameterIndex) + } + ), + arguments: arguments + ).visit(definition!.value) + } +} diff --git a/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift b/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift new file mode 100644 index 00000000000..a6da5cc058e --- /dev/null +++ b/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift @@ -0,0 +1,90 @@ +//===----------------------------------------------------------------------===// +// +// 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 SwiftParser +import SwiftSyntax +import SwiftSyntaxBuilder +@_spi(Testing) import SwiftSyntaxMacros +import _SwiftSyntaxTestSupport +import XCTest + +final class MacroReplacementTests: XCTestCase { + func testMacroDefinitionGood() { + let macro: DeclSyntax = + """ + macro expand1(a: Int, b: Int) = #otherMacro(first: b, second: ["a": a], third: [3.14159, 2.71828], fourth: 4) + """ + + let (replacements, diags) = macro.as(MacroDeclSyntax.self)! + .expansionParameterReplacements() + XCTAssertEqual(diags.count, 0) + XCTAssertEqual(replacements.count, 2) + XCTAssertEqual(replacements[0].parameterIndex, 1) + XCTAssertEqual(replacements[1].parameterIndex, 0) + } + + func testMacroDefinitionBad() { + let macro: DeclSyntax = + """ + macro expand1(a: Int, b: Int) = #otherMacro(first: b + 1, c) + """ + + let (_, diags) = macro.as(MacroDeclSyntax.self)! + .expansionParameterReplacements() + XCTAssertEqual(diags.count, 2) + XCTAssertEqual( + diags[0].diagMessage.message, + "only literals and macro parameters are permitted in expansion" + ) + XCTAssertEqual( + diags[1].diagMessage.message, + "reference to value 'c' that is not a macro parameter in expansion" + ) + } + + func testMacroUndefined() { + let macro: DeclSyntax = + """ + macro expand1(a: Int, b: Int) + """ + + let (_, diags) = macro.as(MacroDeclSyntax.self)! + .expansionParameterReplacements() + XCTAssertEqual(diags.count, 1) + XCTAssertEqual( + diags[0].diagMessage.message, + "macro expansion requires a definition" + ) + } + + func testMacroExpansion() { + let macro: DeclSyntax = + """ + macro expand1(a: Int, b: Int) = #otherMacro(first: b, second: ["a": a], third: [3.14159, 2.71828], fourth: 4) + """ + + let use: ExprSyntax = + """ + #expand1(a: 5, b: 17) + """ + + let expandedSyntax = try! macro.as(MacroDeclSyntax.self)! + .expandDefinition(use.as(MacroExpansionExprSyntax.self)!) + AssertStringsEqualWithDiff( + expandedSyntax.description, + """ + #otherMacro(first: 17, second: ["a": 5], third: [3.14159, 2.71828], fourth: 4) + """ + ) + } +} From d31be382d9ecfd14971a3c40660923f312a11be4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 20 Mar 2023 10:56:39 -0700 Subject: [PATCH 2/6] [Macros] Rework API of macro definition and expansion. Split the API for macro definition checking and expansion into two distinct parts: a definition-checking operation that can fail (producing error diagnostics) and an expansion operation that cannot fail (for expansion). This better matches the structure that clients (i.e., the compiler) should use. --- .../SwiftSyntaxMacros/MacroReplacement.swift | 161 ++++++++++++++---- .../MacroReplacementTests.swift | 55 ++++-- 2 files changed, 166 insertions(+), 50 deletions(-) diff --git a/Sources/SwiftSyntaxMacros/MacroReplacement.swift b/Sources/SwiftSyntaxMacros/MacroReplacement.swift index 5a0a45d4afc..74aa41e2238 100644 --- a/Sources/SwiftSyntaxMacros/MacroReplacement.swift +++ b/Sources/SwiftSyntaxMacros/MacroReplacement.swift @@ -1,15 +1,6 @@ import SwiftDiagnostics import SwiftSyntax - -/// The replacement of a parameter. -@_spi(Testing) -public struct ParameterReplacement { - /// A reference to a parameter as it occurs in the macro expansion expression. - public let reference: IdentifierExprSyntax - - /// The index of the parameter - public let parameterIndex: Int -} +import SwiftSyntaxBuilder extension FunctionParameterSyntax { /// Retrieve the name of the parameter as it is used in source. @@ -44,6 +35,7 @@ extension FunctionParameterSyntax { enum MacroExpanderError: DiagnosticMessage { case undefined + case definitionNotMacroExpansion case nonParameterReference(TokenSyntax) case nonLiteralOrParameter(ExprSyntax) @@ -52,6 +44,9 @@ enum MacroExpanderError: DiagnosticMessage { case .undefined: return "macro expansion requires a definition" + case .definitionNotMacroExpansion: + return "macro definition must itself by a macro expansion expression (starting with '#')" + case .nonParameterReference(let name): return "reference to value '\(name.text)' that is not a macro parameter in expansion" @@ -69,9 +64,36 @@ enum MacroExpanderError: DiagnosticMessage { } } +/// Provide the definition of a macro +public enum MacroDefinition { + /// An externally-defined macro, known by its type name and the module in + /// which that type resides, which uses the deprecated syntax `A.B`. + case deprecatedExternal(node: Syntax, module: String, type: String) + + /// A macro that is defined by expansion of another macro. + /// + /// The definition has the macro expansion expression itself, along with + /// sequence of replacements for subtrees that refer to parameters of the + /// defining macro. These subtrees will need to be replaced with the text of + /// the corresponding argument to the macro, which can be accomplished with + /// `MacroDeclSyntax.expandDefinition`. + case expansion(MacroExpansionExprSyntax, replacements: [Replacement]) +} + +extension MacroDefinition { + /// A replacement that occurs as part of an expanded macro definition. + public struct Replacement { + /// A reference to a parameter as it occurs in the macro expansion expression. + public let reference: IdentifierExprSyntax + + /// The index of the parameter in the defining macro. + public let parameterIndex: Int + } +} + fileprivate class ParameterReplacementVisitor: SyntaxAnyVisitor { let macro: MacroDeclSyntax - var replacements: [ParameterReplacement] = [] + var replacements: [MacroDefinition.Replacement] = [] var diagnostics: [Diagnostic] = [] init(macro: MacroDeclSyntax) { @@ -159,7 +181,8 @@ fileprivate class ParameterReplacementVisitor: SyntaxAnyVisitor { override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind { if let expr = node.as(ExprSyntax.self) { - // We have an expression that is not one of the allowed forms, + // We have an expression that is not one of the allowed forms, so + // diagnose it. diagnostics.append( Diagnostic( node: node, @@ -176,24 +199,61 @@ fileprivate class ParameterReplacementVisitor: SyntaxAnyVisitor { } extension MacroDeclSyntax { + /// Check the definition of the given macro. + /// + /// Macros are defined by an expression, which must itself be a macro + /// expansion. Check the definition and produce a semantic representation of + /// it or one of the "builtin" + /// /// Compute the sequence of parameter replacements required when expanding /// the definition of a non-external macro. - @_spi(Testing) - public func expansionParameterReplacements() -> (replacements: [ParameterReplacement], diagnostics: [Diagnostic]) { + /// + /// If there are an errors that prevent expansion, the diagnostics will be + /// wrapped into a an error that prevents expansion, that error is thrown. + public func checkDefinition() throws -> MacroDefinition { // Cannot compute replacements for an undefined macro. - guard let definition = definition?.value else { + guard let originalDefinition = definition?.value else { let undefinedDiag = Diagnostic( node: Syntax(self), message: MacroExpanderError.undefined ) - return (replacements: [], diagnostics: [undefinedDiag]) + throw DiagnosticsError(diagnostics: [undefinedDiag]) + } + + /// Recognize the deprecated syntax A.B. Clients will need to + /// handle this themselves. + if let memberAccess = originalDefinition.as(MemberAccessExprSyntax.self), + let base = memberAccess.base, + let baseName = base.as(IdentifierExprSyntax.self)?.identifier + { + let memberName = memberAccess.name + return .deprecatedExternal( + node: Syntax(memberAccess), + module: baseName.trimmedDescription, + type: memberName.trimmedDescription + ) + } + + // Make sure we have a macro expansion expression. + guard let definition = originalDefinition.as(MacroExpansionExprSyntax.self) else { + let badDefinitionDiag = + Diagnostic( + node: Syntax(originalDefinition), + message: MacroExpanderError.definitionNotMacroExpansion + ) + + throw DiagnosticsError(diagnostics: [badDefinitionDiag]) } let visitor = ParameterReplacementVisitor(macro: self) visitor.walk(definition) - return (replacements: visitor.replacements, diagnostics: visitor.diagnostics) + if !visitor.diagnostics.isEmpty { + throw DiagnosticsError(diagnostics: visitor.diagnostics) + } + + return .expansion(definition, replacements: visitor.replacements) } } @@ -219,27 +279,18 @@ private final class MacroExpansionRewriter: SyntaxRewriter { } extension MacroDeclSyntax { - /// Given a freestanding macro expansion syntax node that references this - /// macro declaration, expand the macro by substituting the arguments from - /// the macro expansion into the parameters that are used in the definition. - /// - /// If there are any errors, the function will throw with all diagnostics - /// placed in a `DiagnosticsError`. - public func expandDefinition( - _ node: some FreestandingMacroExpansionSyntax - ) throws -> ExprSyntax { - let (replacements, diagnostics) = expansionParameterReplacements() - - // If there were any diagnostics, don't allow replacement. - if !diagnostics.isEmpty { - throw DiagnosticsError(diagnostics: diagnostics) - } - + /// Expand the definition of this macro when provided with the given + /// argument list. + private func expand( + argumentList: TupleExprElementListSyntax?, + definition: MacroExpansionExprSyntax, + replacements: [MacroDefinition.Replacement] + ) -> ExprSyntax { // FIXME: Do real call-argument matching between the argument list and the // macro parameter list, porting over from the compiler. - let arguments: [ExprSyntax] = node.argumentList.map { element in + let arguments: [ExprSyntax] = argumentList?.map { element in element.expression - } + } ?? [] return MacroExpansionRewriter( parameterReplacements: Dictionary( @@ -248,6 +299,44 @@ extension MacroDeclSyntax { } ), arguments: arguments - ).visit(definition!.value) + ).visit(definition) + } + + /// Given a freestanding macro expansion syntax node that references this + /// macro declaration, expand the macro by substituting the arguments from + /// the macro expansion into the parameters that are used in the definition. + public func expand( + _ node: some FreestandingMacroExpansionSyntax, + definition: MacroExpansionExprSyntax, + replacements: [MacroDefinition.Replacement] + ) -> ExprSyntax { + return try expand( + argumentList: node.argumentList, + definition: definition, + replacements: replacements + ) + } + + /// Given an attached macro expansion syntax node that references this + /// macro declaration, expand the macro by substituting the arguments from + /// the expansion into the parameters that are used in the definition. + public func expand( + _ node: AttributeSyntax, + definition: MacroExpansionExprSyntax, + replacements: [MacroDefinition.Replacement] + ) -> ExprSyntax { + // Dig out the argument list. + let argumentList: TupleExprElementListSyntax? + if case let .argumentList(argList) = node.argument { + argumentList = argList + } else { + argumentList = nil + } + + return try expand( + argumentList: argumentList, + definition: definition, + replacements: replacements + ) } } diff --git a/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift b/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift index a6da5cc058e..3dd8dc3fb15 100644 --- a/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift +++ b/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift @@ -14,33 +14,43 @@ import SwiftDiagnostics import SwiftParser import SwiftSyntax import SwiftSyntaxBuilder -@_spi(Testing) import SwiftSyntaxMacros +import SwiftSyntaxMacros import _SwiftSyntaxTestSupport import XCTest final class MacroReplacementTests: XCTestCase { - func testMacroDefinitionGood() { + func testMacroDefinitionGood() throws { let macro: DeclSyntax = """ macro expand1(a: Int, b: Int) = #otherMacro(first: b, second: ["a": a], third: [3.14159, 2.71828], fourth: 4) """ - let (replacements, diags) = macro.as(MacroDeclSyntax.self)! - .expansionParameterReplacements() - XCTAssertEqual(diags.count, 0) + let definition = try macro.as(MacroDeclSyntax.self)!.checkDefinition() + guard case let .expansion(_, replacements) = definition else { + XCTFail("not an expansion definition") + fatalError() + } + XCTAssertEqual(replacements.count, 2) XCTAssertEqual(replacements[0].parameterIndex, 1) XCTAssertEqual(replacements[1].parameterIndex, 0) } - func testMacroDefinitionBad() { + func testMacroDefinitionBad() throws { let macro: DeclSyntax = """ macro expand1(a: Int, b: Int) = #otherMacro(first: b + 1, c) """ - let (_, diags) = macro.as(MacroDeclSyntax.self)! - .expansionParameterReplacements() + let diags: [Diagnostic] + do { + _ = try macro.as(MacroDeclSyntax.self)!.checkDefinition() + XCTFail("should have failed with an error") + fatalError() + } catch let diagError as DiagnosticsError { + diags = diagError.diagnostics + } + XCTAssertEqual(diags.count, 2) XCTAssertEqual( diags[0].diagMessage.message, @@ -52,14 +62,21 @@ final class MacroReplacementTests: XCTestCase { ) } - func testMacroUndefined() { + func testMacroUndefined() throws { let macro: DeclSyntax = """ macro expand1(a: Int, b: Int) """ - let (_, diags) = macro.as(MacroDeclSyntax.self)! - .expansionParameterReplacements() + let diags: [Diagnostic] + do { + _ = try macro.as(MacroDeclSyntax.self)!.checkDefinition() + XCTFail("should have failed with an error") + fatalError() + } catch let diagError as DiagnosticsError { + diags = diagError.diagnostics + } + XCTAssertEqual(diags.count, 1) XCTAssertEqual( diags[0].diagMessage.message, @@ -67,7 +84,7 @@ final class MacroReplacementTests: XCTestCase { ) } - func testMacroExpansion() { + func testMacroExpansion() throws { let macro: DeclSyntax = """ macro expand1(a: Int, b: Int) = #otherMacro(first: b, second: ["a": a], third: [3.14159, 2.71828], fourth: 4) @@ -78,8 +95,18 @@ final class MacroReplacementTests: XCTestCase { #expand1(a: 5, b: 17) """ - let expandedSyntax = try! macro.as(MacroDeclSyntax.self)! - .expandDefinition(use.as(MacroExpansionExprSyntax.self)!) + let macroDecl = macro.as(MacroDeclSyntax.self)! + let definition = try macroDecl.checkDefinition() + guard case let .expansion(expansion, replacements) = definition else { + XCTFail("not a normal expansion") + fatalError() + } + + let expandedSyntax = macroDecl.expand( + use.as(MacroExpansionExprSyntax.self)!, + definition: expansion, + replacements: replacements + ) AssertStringsEqualWithDiff( expandedSyntax.description, """ From bfe1695cdfb7dca1bcc972d94eb46b9714ec953a Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 20 Mar 2023 11:01:54 -0700 Subject: [PATCH 3/6] Factor function-parameter utilities into a separate file We are likely to grow more of these utilities, and they might eventually be sunk down into a common library, so keep the factored out. --- Sources/SwiftSyntaxMacros/CMakeLists.txt | 2 ++ .../FunctionParameterUtils.swift | 32 +++++++++++++++++++ .../SwiftSyntaxMacros/MacroReplacement.swift | 31 ------------------ 3 files changed, 34 insertions(+), 31 deletions(-) create mode 100644 Sources/SwiftSyntaxMacros/FunctionParameterUtils.swift diff --git a/Sources/SwiftSyntaxMacros/CMakeLists.txt b/Sources/SwiftSyntaxMacros/CMakeLists.txt index f8fdc38f639..f95e55fa732 100644 --- a/Sources/SwiftSyntaxMacros/CMakeLists.txt +++ b/Sources/SwiftSyntaxMacros/CMakeLists.txt @@ -21,7 +21,9 @@ add_swift_host_library(SwiftSyntaxMacros AbstractSourceLocation.swift BasicMacroExpansionContext.swift + FunctionParameterUtils.swift MacroExpansionContext.swift + MacroReplacement.swift MacroSystem.swift Syntax+MacroEvaluation.swift ) diff --git a/Sources/SwiftSyntaxMacros/FunctionParameterUtils.swift b/Sources/SwiftSyntaxMacros/FunctionParameterUtils.swift new file mode 100644 index 00000000000..9ad940b3ff0 --- /dev/null +++ b/Sources/SwiftSyntaxMacros/FunctionParameterUtils.swift @@ -0,0 +1,32 @@ +import SwiftSyntax + +extension FunctionParameterSyntax { + /// Retrieve the name of the parameter as it is used in source. + /// + /// Example: + /// + /// func f(a: Int, _ b: Int, c see: Int) { ... } + /// + /// The parameter names for these three parameters are `a`, `b`, and `see`, + /// respectively. + var parameterName: TokenSyntax? { + // If there were two names, the second is the parameter name. + if let secondName = secondName { + if secondName.text == "_" { + return nil + } + + return secondName + } + + if let firstName = firstName { + if firstName.text == "_" { + return nil + } + + return firstName + } + + return nil + } +} diff --git a/Sources/SwiftSyntaxMacros/MacroReplacement.swift b/Sources/SwiftSyntaxMacros/MacroReplacement.swift index 74aa41e2238..4003227b93a 100644 --- a/Sources/SwiftSyntaxMacros/MacroReplacement.swift +++ b/Sources/SwiftSyntaxMacros/MacroReplacement.swift @@ -2,37 +2,6 @@ import SwiftDiagnostics import SwiftSyntax import SwiftSyntaxBuilder -extension FunctionParameterSyntax { - /// Retrieve the name of the parameter as it is used in source. - /// - /// Example: - /// - /// func f(a: Int, _ b: Int, c see: Int) { ... } - /// - /// The parameter names for these three parameters are `a`, `b`, and `see`, - /// respectively. - var parameterName: TokenSyntax? { - // If there were two names, the second is the parameter name. - if let secondName = secondName { - if secondName.text == "_" { - return nil - } - - return secondName - } - - if let firstName = firstName { - if firstName.text == "_" { - return nil - } - - return firstName - } - - return nil - } -} - enum MacroExpanderError: DiagnosticMessage { case undefined case definitionNotMacroExpansion From efbd09f0d83cb820b58628b3ee0a07aee66730db Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 20 Mar 2023 11:09:51 -0700 Subject: [PATCH 4/6] Reformat --- Sources/SwiftSyntaxMacros/MacroReplacement.swift | 11 ++++++----- .../SwiftSyntaxMacrosTest/MacroReplacementTests.swift | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Sources/SwiftSyntaxMacros/MacroReplacement.swift b/Sources/SwiftSyntaxMacros/MacroReplacement.swift index 4003227b93a..3c8ccdfbcae 100644 --- a/Sources/SwiftSyntaxMacros/MacroReplacement.swift +++ b/Sources/SwiftSyntaxMacros/MacroReplacement.swift @@ -193,8 +193,8 @@ extension MacroDeclSyntax { /// Recognize the deprecated syntax A.B. Clients will need to /// handle this themselves. if let memberAccess = originalDefinition.as(MemberAccessExprSyntax.self), - let base = memberAccess.base, - let baseName = base.as(IdentifierExprSyntax.self)?.identifier + let base = memberAccess.base, + let baseName = base.as(IdentifierExprSyntax.self)?.identifier { let memberName = memberAccess.name return .deprecatedExternal( @@ -257,9 +257,10 @@ extension MacroDeclSyntax { ) -> ExprSyntax { // FIXME: Do real call-argument matching between the argument list and the // macro parameter list, porting over from the compiler. - let arguments: [ExprSyntax] = argumentList?.map { element in - element.expression - } ?? [] + let arguments: [ExprSyntax] = + argumentList?.map { element in + element.expression + } ?? [] return MacroExpansionRewriter( parameterReplacements: Dictionary( diff --git a/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift b/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift index 3dd8dc3fb15..3ddf8a9d153 100644 --- a/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift +++ b/Tests/SwiftSyntaxMacrosTest/MacroReplacementTests.swift @@ -44,7 +44,7 @@ final class MacroReplacementTests: XCTestCase { let diags: [Diagnostic] do { - _ = try macro.as(MacroDeclSyntax.self)!.checkDefinition() + _ = try macro.as(MacroDeclSyntax.self)!.checkDefinition() XCTFail("should have failed with an error") fatalError() } catch let diagError as DiagnosticsError { From 1a8726b95b9933d7dca5f6a0a5711dd942d5d293 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 20 Mar 2023 11:36:43 -0700 Subject: [PATCH 5/6] Don't use opaque result types in parameters :( --- Sources/SwiftSyntaxMacros/MacroReplacement.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftSyntaxMacros/MacroReplacement.swift b/Sources/SwiftSyntaxMacros/MacroReplacement.swift index 3c8ccdfbcae..f2f169f6726 100644 --- a/Sources/SwiftSyntaxMacros/MacroReplacement.swift +++ b/Sources/SwiftSyntaxMacros/MacroReplacement.swift @@ -275,8 +275,8 @@ extension MacroDeclSyntax { /// Given a freestanding macro expansion syntax node that references this /// macro declaration, expand the macro by substituting the arguments from /// the macro expansion into the parameters that are used in the definition. - public func expand( - _ node: some FreestandingMacroExpansionSyntax, + public func expand( + _ node: Node, definition: MacroExpansionExprSyntax, replacements: [MacroDefinition.Replacement] ) -> ExprSyntax { From ba00e61b4f71418a606d46c03c9d7e0509093782 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 20 Mar 2023 11:37:27 -0700 Subject: [PATCH 6/6] Remove some unnecessary `try`s --- Sources/SwiftSyntaxMacros/MacroReplacement.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/SwiftSyntaxMacros/MacroReplacement.swift b/Sources/SwiftSyntaxMacros/MacroReplacement.swift index f2f169f6726..edbef9a1288 100644 --- a/Sources/SwiftSyntaxMacros/MacroReplacement.swift +++ b/Sources/SwiftSyntaxMacros/MacroReplacement.swift @@ -280,7 +280,7 @@ extension MacroDeclSyntax { definition: MacroExpansionExprSyntax, replacements: [MacroDefinition.Replacement] ) -> ExprSyntax { - return try expand( + return expand( argumentList: node.argumentList, definition: definition, replacements: replacements @@ -303,7 +303,7 @@ extension MacroDeclSyntax { argumentList = nil } - return try expand( + return expand( argumentList: argumentList, definition: definition, replacements: replacements