Skip to content

[Macros] Create macro expansion contexts based on the SourceManager #63407

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
1 change: 1 addition & 0 deletions lib/ASTGen/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ if (SWIFT_SWIFT_PARSER)
Sources/ASTGen/Misc.swift
Sources/ASTGen/SourceFile.swift
Sources/ASTGen/SourceManager.swift
Sources/ASTGen/SourceManager+MacroExpansionContext.swift
Sources/ASTGen/Stmts.swift
Sources/ASTGen/Types.swift
)
Expand Down
30 changes: 20 additions & 10 deletions lib/ASTGen/Sources/ASTGen/Macros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func expandFreestandingMacro(
) throws -> ExprSyntax {
return try exprMacro.expansion(
of: sourceManager.detach(
node, in: context,
node,
foldingWith: OperatorTable.standardOperators
),
in: context
Expand All @@ -201,7 +201,10 @@ func expandFreestandingMacro(
}
macroName = parentExpansion.macro.text
let decls = try declMacro.expansion(
of: sourceManager.detach(parentExpansion, in: context),
of: sourceManager.detach(
parentExpansion,
foldingWith: OperatorTable.standardOperators
),
in: context
)
evaluatedSyntax = Syntax(CodeBlockItemListSyntax(
Expand Down Expand Up @@ -348,8 +351,11 @@ func expandAttachedMacro(
switch (macro, macroRole) {
case (let attachedMacro as AccessorMacro.Type, .Accessor):
let accessors = try attachedMacro.expansion(
of: context.detach(customAttrNode),
providingAccessorsOf: context.detach(declarationNode),
of: sourceManager.detach(
customAttrNode,
foldingWith: OperatorTable.standardOperators
),
providingAccessorsOf: sourceManager.detach(declarationNode),
in: context
)

Expand Down Expand Up @@ -377,11 +383,12 @@ func expandAttachedMacro(
_ node: Node
) throws -> [AttributeSyntax] {
return try attachedMacro.expansion(
of: context.detach(customAttrNode),
attachedTo: sourceManager.detach(node, in: context),
providingAttributesFor: sourceManager.detach(
declarationNode, in: context
of: sourceManager.detach(
customAttrNode,
foldingWith: OperatorTable.standardOperators
),
attachedTo: sourceManager.detach(node),
providingAttributesFor: sourceManager.detach(declarationNode),
in: context
)
}
Expand All @@ -407,8 +414,11 @@ func expandAttachedMacro(
_ node: Node
) throws -> [DeclSyntax] {
return try attachedMacro.expansion(
of: sourceManager.detach(customAttrNode, in: context),
providingMembersOf: sourceManager.detach(node, in: context),
of: sourceManager.detach(
customAttrNode,
foldingWith: OperatorTable.standardOperators
),
providingMembersOf: sourceManager.detach(node),
in: context
)
}
Expand Down
132 changes: 132 additions & 0 deletions lib/ASTGen/Sources/ASTGen/SourceManager+MacroExpansionContext.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import SwiftDiagnostics
import SwiftSyntax
import SwiftSyntaxMacros

extension SourceManager {
class MacroExpansionContext {
/// The source manager.
private let sourceManager: SourceManager

/// The set of diagnostics that were emitted as part of expanding the
/// macro.
var diagnostics: [Diagnostic] = []

/// The macro expansion discriminator, which is used to form unique names
/// when requested.
///
/// The expansion discriminator is combined with the `uniqueNames` counters
/// to produce unique names.
private var discriminator: String

/// Counter for each of the uniqued names.
///
/// Used in conjunction with `expansionDiscriminator`.
private var uniqueNames: [String: Int] = [:]

init(sourceManager: SourceManager, discriminator: String) {
self.sourceManager = sourceManager
self.discriminator = discriminator
}
}

/// Create a new macro expansion context
func createMacroExpansionContext(
discriminator: String = ""
) -> MacroExpansionContext {
return MacroExpansionContext(
sourceManager: self, discriminator: discriminator
)
}
}

extension String {
/// Retrieve the base name of a string that represents a path, removing the
/// directory.
fileprivate var basename: String {
guard let lastSlash = lastIndex(of: "/") else {
return self
}

return String(self[index(after: lastSlash)...])
}
}

extension SourceManager.MacroExpansionContext: MacroExpansionContext {
/// Generate a unique name for use in the macro.
public func createUniqueName(_ providedName: String) -> TokenSyntax {
// If provided with an empty name, substitute in something.
let name = providedName.isEmpty ? "__local" : providedName

// Grab a unique index value for this name.
let uniqueIndex = uniqueNames[name, default: 0]
uniqueNames[name] = uniqueIndex + 1

// Start with the discriminator.
var resultString = discriminator

// Mangle the name
resultString += "\(name.count)\(name)"

// Mangle the operator for unique macro names.
resultString += "fMu"

// Mangle the index.
if uniqueIndex > 0 {
resultString += "\(uniqueIndex - 1)"
}
resultString += "_"

return TokenSyntax(.identifier(resultString), presence: .present)
}

/// Produce a diagnostic while expanding the macro.
public func diagnose(_ diagnostic: Diagnostic) {
diagnostics.append(diagnostic)
}

public func location<Node: SyntaxProtocol>(
of node: Node,
at position: PositionInSyntaxNode,
filePathMode: SourceLocationFilePathMode
) -> SourceLocation? {
guard let (sourceFile, rootPosition) = sourceManager.rootSourceFile(of: node),
let exportedSourceFile =
sourceManager.exportedSourceFilesBySyntax[sourceFile]?.pointee
else {
return nil
}

// Determine the filename to use in the resulting location.
let fileName: String
switch filePathMode {
case .fileID:
fileName = "\(exportedSourceFile.moduleName)/\(exportedSourceFile.fileName.basename)"

case .filePath:
fileName = exportedSourceFile.fileName
}

// Find the node's offset relative to its root.
let rawPosition: AbsolutePosition
switch position {
case .beforeLeadingTrivia:
rawPosition = node.position

case .afterLeadingTrivia:
rawPosition = node.positionAfterSkippingLeadingTrivia

case .beforeTrailingTrivia:
rawPosition = node.endPositionBeforeTrailingTrivia

case .afterTrailingTrivia:
rawPosition = node.endPosition
}

let offsetWithinSyntaxNode =
rawPosition.utf8Offset - node.position.utf8Offset

// Do the location lookup.
let converter = SourceLocationConverter(file: fileName, tree: sourceFile)
return converter.location(for: rootPosition.advanced(by: offsetWithinSyntaxNode))
}
}
55 changes: 11 additions & 44 deletions lib/ASTGen/Sources/ASTGen/SourceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,20 @@ extension SourceManager {
extension SourceManager {
/// Detach a given node from its parent, keeping track of where it
/// occurred in the program.
func detach<Node: SyntaxProtocol>(_ node: Node) -> Node {
func detach<Node: SyntaxProtocol>(
_ node: Node,
foldingWith operatorTable: OperatorTable? = nil
) -> Node {
// Already detached
if node.parent == nil { return node }

let detached = node.detach()
let detached: Node
if let operatorTable = operatorTable {
detached = operatorTable.foldAll(node) { _ in }.as(Node.self)!.detach()
} else {
detached = node.detach()
}

detachedNodes[Syntax(detached)] = (node.root, node.position.utf8Offset)
return detached
}
Expand Down Expand Up @@ -122,45 +131,3 @@ extension SourceManager {
return CxxSourceLoc(mutating: address)
}
}

/// Interaction with BasicMacroExpansionContext.
extension SourceManager {
/// Detach a node within a macro expansion context.
///
/// TODO: This ties together BasicMacroExpansionContext and SourceManager
/// in a rather redundant manner. We'll probably want to drop use of
/// BasicMacroExpansionContext entirely.
func detach<Node: SyntaxProtocol>(
_ node: Node,
in context: BasicMacroExpansionContext,
foldingWith operatorTable: OperatorTable? = nil
) -> Node {
// Already detached
if node.parent == nil { return node }

var detached = context.detach(node)

if let operatorTable = operatorTable {
detached = operatorTable.foldAll(node) { _ in }.as(Node.self)!
}

detachedNodes[Syntax(detached)] = (node.root, node.position.utf8Offset)
return detached
}

/// Create a new macro expansion context
func createMacroExpansionContext(discriminator: String = "") -> BasicMacroExpansionContext {
// Collect the set of source files for this context.
var sourceFiles: [SourceFileSyntax : BasicMacroExpansionContext.KnownSourceFile] = [:]
for (syntax, exported) in exportedSourceFilesBySyntax {
sourceFiles[syntax] = .init(
moduleName: exported.pointee.moduleName,
fullFilePath: exported.pointee.fileName
)
}

return BasicMacroExpansionContext(
expansionDiscriminator: discriminator, sourceFiles: sourceFiles
)
}
}
36 changes: 36 additions & 0 deletions test/Macros/Inputs/syntax_macro_definitions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,39 @@ extension DeclGroupSyntax {
}
}
}

public enum LeftHandOperandFinderMacro: ExpressionMacro {
class Visitor<Context: MacroExpansionContext>: SyntaxVisitor {
let context: Context

init(context: Context) {
self.context = context
super.init(viewMode: .sourceAccurate)
}

override func visit(
_ node: InfixOperatorExprSyntax
) -> SyntaxVisitorContinueKind {
guard let lhsStartLoc = context.location(of: node.leftOperand),
let lhsEndLoc = context.location(
of: node.leftOperand, at: .beforeTrailingTrivia, filePathMode: .fileID
) else {
fatalError("missing source location information")
}

print("Source range for LHS is \(lhsStartLoc.file!): \(lhsStartLoc.line!):\(lhsStartLoc.column!)-\(lhsEndLoc.line!):\(lhsEndLoc.column!)")

return .visitChildren
}
}

public static func expansion(
of node: some FreestandingMacroExpansionSyntax,
in context: some MacroExpansionContext
) -> ExprSyntax {
let visitor = Visitor(context: context)
visitor.walk(node)

return node.argumentList.first!.expression
}
}
16 changes: 15 additions & 1 deletion test/Macros/macro_expand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
// Diagnostics testing
// RUN: %target-typecheck-verify-swift -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir -module-name MacroUser -DTEST_DIAGNOSTICS

// RUN: not %target-swift-frontend -typecheck -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir -module-name MacroUser -DTEST_DIAGNOSTICS -serialize-diagnostics-path %t/macro_expand.dia %s -emit-macro-expansion-files no-diagnostics
// RUN: not %target-swift-frontend -typecheck -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir -module-name MacroUser -DTEST_DIAGNOSTICS -serialize-diagnostics-path %t/macro_expand.dia %s -emit-macro-expansion-files no-diagnostics > %t/macro-printing.txt
// RUN: c-index-test -read-diagnostics %t/macro_expand.dia 2>&1 | %FileCheck -check-prefix CHECK-DIAGS %s

// RUN: %FileCheck %s --check-prefix CHECK-MACRO-PRINTED < %t/macro-printing.txt

// Debug info SIL testing
// RUN: %target-swift-frontend -emit-sil -enable-experimental-feature Macros -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir %s -module-name MacroUser -o - -g | %FileCheck --check-prefix CHECK-SIL %s

Expand Down Expand Up @@ -117,6 +119,18 @@ func testAddBlocker(a: Int, b: Int, c: Int, oa: OnlyAdds) {
#endif
}

@freestanding(expression) macro leftHandOperandFinder<T>(_ value: T) -> T = #externalMacro(module: "MacroDefinition", type: "LeftHandOperandFinderMacro")


// Test source location information.
func testSourceLocations(x: Int, yolo: Int, zulu: Int) {
// CHECK-MACRO-PRINTED: Source range for LHS is MacroUser/macro_expand.swift: [[@LINE+3]]:5-[[@LINE+3]]:13
// CHECK-MACRO-PRINTED: Source range for LHS is MacroUser/macro_expand.swift: [[@LINE+2]]:5-[[@LINE+2]]:6
_ = #leftHandOperandFinder(
x + yolo + zulu
)
}

// Make sure we don't crash with declarations produced by expansions.
@freestanding(expression) macro nestedDeclInExpr() -> () -> Void = #externalMacro(module: "MacroDefinition", type: "NestedDeclInExprMacro")

Expand Down