Skip to content

[Macros] Introduce SwiftSyntaxMacroExpansion module #1654

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 1 commit into from
May 12, 2023
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
9 changes: 8 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ let package = Package(
.library(name: "SwiftSyntax", targets: ["SwiftSyntax"]),
.library(name: "SwiftSyntaxBuilder", targets: ["SwiftSyntaxBuilder"]),
.library(name: "SwiftSyntaxMacros", targets: ["SwiftSyntaxMacros"]),
.library(name: "SwiftSyntaxMacroExpansion", targets: ["SwiftSyntaxMacroExpansion"]),
.library(name: "SwiftSyntaxMacrosTestSupport", targets: ["SwiftSyntaxMacrosTestSupport"]),
],
targets: [
Expand Down Expand Up @@ -113,7 +114,7 @@ let package = Package(

.target(
name: "SwiftCompilerPluginMessageHandling",
dependencies: ["SwiftDiagnostics", "SwiftOperators", "SwiftParser", "SwiftSyntax", "SwiftSyntaxMacros"],
dependencies: ["SwiftDiagnostics", "SwiftOperators", "SwiftParser", "SwiftSyntax", "SwiftSyntaxMacros", "SwiftSyntaxMacroExpansion"],
exclude: ["CMakeLists.txt"]
),

Expand Down Expand Up @@ -178,6 +179,12 @@ let package = Package(
exclude: ["CMakeLists.txt"]
),

.target(
name: "SwiftSyntaxMacroExpansion",
dependencies: ["SwiftSyntax", "SwiftSyntaxMacros"],
exclude: ["CMakeLists.txt"]
),

Comment on lines +182 to +187
Copy link
Member

Choose a reason for hiding this comment

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

Since this is a new target, could you add a new MARK section for it? Each MARK section only contains the target + its test target right now

Copy link
Member Author

Choose a reason for hiding this comment

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

Huh, I didn't realize that, thank you, will do.

.testTarget(
name: "SwiftSyntaxMacrosTest",
dependencies: ["_SwiftSyntaxTestSupport", "SwiftDiagnostics", "SwiftOperators", "SwiftParser", "SwiftSyntaxBuilder", "SwiftSyntaxMacros", "SwiftSyntaxMacrosTestSupport"]
Expand Down
1 change: 1 addition & 0 deletions Sources/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ add_subdirectory(SwiftParserDiagnostics)
add_subdirectory(SwiftOperators)
add_subdirectory(SwiftSyntaxBuilder)
add_subdirectory(SwiftSyntaxMacros)
add_subdirectory(SwiftSyntaxMacroExpansion)
add_subdirectory(SwiftCompilerPluginMessageHandling)
add_subdirectory(SwiftIDEUtils)
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ target_link_libraries(SwiftCompilerPluginMessageHandling PUBLIC
SwiftDiagnostics
SwiftParser
SwiftSyntaxMacros
SwiftSyntaxMacroExpansion
SwiftOperators)
186 changes: 30 additions & 156 deletions Sources/SwiftCompilerPluginMessageHandling/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import SwiftBasicFormat
import SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxMacros
import SwiftSyntaxMacroExpansion

extension CompilerPluginMessageHandler {
/// Get concrete macro type from a pair of module name and type name.
Expand All @@ -35,7 +36,7 @@ extension CompilerPluginMessageHandler {
expansionDiscriminator: discriminator
)

let expandedSource: String
let expandedSource: String?
do {
guard let macroSyntax = syntax.asProtocol(FreestandingMacroExpansionSyntax.self) else {
throw MacroExpansionError.freestandingMacroSyntaxIsNotMacro
Expand All @@ -44,34 +45,14 @@ extension CompilerPluginMessageHandler {
throw MacroExpansionError.macroTypeNotFound
}

switch macroDefinition {
case let exprMacroDef as ExpressionMacro.Type:
func _expand(node: some FreestandingMacroExpansionSyntax) throws -> ExprSyntax {
try exprMacroDef.expansion(of: node, in: context)
}
let rewritten = try _openExistential(macroSyntax, do: _expand)
expandedSource = rewritten.formattedExpansion(macroDefinition.formatMode)

case let declMacroDef as DeclarationMacro.Type:
func _expand(node: some FreestandingMacroExpansionSyntax) throws -> [DeclSyntax] {
try declMacroDef.expansion(of: node, in: context)
}
let rewritten = try _openExistential(macroSyntax, do: _expand)
expandedSource = CodeBlockItemListSyntax(rewritten.map { CodeBlockItemSyntax(item: .decl($0)) }).formattedExpansion(macroDefinition.formatMode)

case let codeItemMacroDef as CodeItemMacro.Type:
func _expand(node: some FreestandingMacroExpansionSyntax) throws -> [CodeBlockItemSyntax] {
try codeItemMacroDef.expansion(of: node, in: context)
}
let rewritten = try _openExistential(macroSyntax, do: _expand)
expandedSource = CodeBlockItemListSyntax(rewritten).formattedExpansion(macroDefinition.formatMode)

default:
throw MacroExpansionError.unmathedMacroRole
}
expandedSource = SwiftSyntaxMacroExpansion.expandFreestandingMacro(
definition: macroDefinition,
node: macroSyntax,
in: context
)
} catch {
context.addDiagnostics(from: error, node: syntax)
expandedSource = ""
expandedSource = nil
}

let diagnostics = context.diagnostics.map {
Expand Down Expand Up @@ -99,132 +80,25 @@ extension CompilerPluginMessageHandler {

let attributeNode = sourceManager.add(attributeSyntax).cast(AttributeSyntax.self)
let declarationNode = sourceManager.add(declSyntax).cast(DeclSyntax.self)
let parentDeclNode = parentDeclSyntax.map { sourceManager.add($0).cast(DeclSyntax.self) }

let expandedSources: [String]
let expandedSources: [String]?
do {
guard let macroDefinition = resolveMacro(macro) else {
throw MacroExpansionError.macroTypeNotFound
}

switch (macroDefinition, macroRole) {
case (let attachedMacro as AccessorMacro.Type, .accessor):
let accessors = try attachedMacro.expansion(
of: attributeNode,
providingAccessorsOf: declarationNode,
in: context
)
expandedSources = accessors.map {
$0.formattedExpansion(macroDefinition.formatMode)
}

case (let attachedMacro as MemberAttributeMacro.Type, .memberAttribute):
guard
let parentDeclSyntax = parentDeclSyntax,
let parentDeclGroup = sourceManager.add(parentDeclSyntax).asProtocol(DeclGroupSyntax.self)
else {
// Compiler error: 'parentDecl' is mandatory for MemberAttributeMacro.
throw MacroExpansionError.invalidExpansionMessage
}

// Local function to expand a member atribute macro once we've opened up
// the existential.
func expandMemberAttributeMacro(
_ node: some DeclGroupSyntax
) throws -> [AttributeSyntax] {
return try attachedMacro.expansion(
of: attributeNode,
attachedTo: node,
providingAttributesFor: declarationNode,
in: context
)
}

let attributes = try _openExistential(
parentDeclGroup,
do: expandMemberAttributeMacro
)

// Form a buffer containing an attribute list to return to the caller.
expandedSources = attributes.map {
$0.formattedExpansion(macroDefinition.formatMode)
}

case (let attachedMacro as MemberMacro.Type, .member):
guard let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self)
else {
// Compiler error: declNode for member macro must be DeclGroupSyntax.
throw MacroExpansionError.invalidExpansionMessage
}

// Local function to expand a member macro once we've opened up
// the existential.
func expandMemberMacro(
_ node: some DeclGroupSyntax
) throws -> [DeclSyntax] {
return try attachedMacro.expansion(
of: attributeNode,
providingMembersOf: node,
in: context
)
}

let members = try _openExistential(declGroup, do: expandMemberMacro)

// Form a buffer of member declarations to return to the caller.
expandedSources = members.map { $0.formattedExpansion(macroDefinition.formatMode) }

case (let attachedMacro as PeerMacro.Type, .peer):
let peers = try attachedMacro.expansion(
of: attributeNode,
providingPeersOf: declarationNode,
in: context
)

// Form a buffer of peer declarations to return to the caller.
expandedSources = peers.map {
$0.formattedExpansion(macroDefinition.formatMode)
}

case (let attachedMacro as ConformanceMacro.Type, .conformance):
guard
let declGroup = declarationNode.asProtocol(DeclGroupSyntax.self),
let identified = declarationNode.asProtocol(IdentifiedDeclSyntax.self)
else {
// Compiler error: type mismatch.
throw MacroExpansionError.invalidExpansionMessage
}

// Local function to expand a conformance macro once we've opened up
// the existential.
func expandConformanceMacro(
_ node: some DeclGroupSyntax
) throws -> [(TypeSyntax, GenericWhereClauseSyntax?)] {
return try attachedMacro.expansion(
of: attributeNode,
providingConformancesOf: node,
in: context
)
}

let conformances = try _openExistential(
declGroup,
do: expandConformanceMacro
)

// Form a buffer of extension declarations to return to the caller.
expandedSources = conformances.map { typeSyntax, whereClause in
let typeName = identified.identifier.trimmedDescription
let protocolName = typeSyntax.trimmedDescription
let whereClause = whereClause?.trimmedDescription ?? ""
return "extension \(typeName) : \(protocolName) \(whereClause) {}"
}

default:
throw MacroExpansionError.unmathedMacroRole
}
expandedSources = SwiftSyntaxMacroExpansion.expandAttachedMacro(
definition: macroDefinition,
macroRole: MacroRole(messageMacroRole: macroRole),
attributeNode: attributeNode,
declarationNode: declarationNode,
parentDeclNode: parentDeclNode,
in: context
)
} catch {
context.addDiagnostics(from: error, node: attributeNode)
expandedSources = []
expandedSources = nil
}

let diagnostics = context.diagnostics.map {
Expand All @@ -236,17 +110,17 @@ extension CompilerPluginMessageHandler {
}
}

fileprivate extension SyntaxProtocol {
/// Perform a format if required and then trim any leading/trailing
/// whitespace.
func formattedExpansion(_ mode: FormatMode) -> String {
let formatted: Syntax
switch mode {
case .auto:
formatted = self.formatted()
case .disabled:
formatted = Syntax(self)
private extension MacroRole {
init(messageMacroRole: PluginMessage.MacroRole) {
switch messageMacroRole {
case .expression: self = .expression
case .declaration: self = .declaration
case .accessor: self = .accessor
case .memberAttribute: self = .memberAttribute
case .member: self = .member
case .peer: self = .peer
case .conformance: self = .conformance
case .codeItem: self = .codeItem
}
return formatted.trimmedDescription(matching: { $0.isWhitespace })
}
}
8 changes: 8 additions & 0 deletions Sources/SwiftSyntaxMacroExpansion/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
add_swift_host_library(SwiftSyntaxMacroExpansion
MacroExpansion.swift
)

target_link_libraries(SwiftSyntaxMacroExpansion PUBLIC
SwiftSyntax
SwiftSyntaxMacros
)
Loading