Hi all,
The expansion
operation of a macro implementation only receives source code corresponding to how a macro is spelled (e.g., #stringify(x + y)
or @AddCompletionHandler
) and any syntax it directly operates on (e.g., the declaration node to which @AddCompletionHandler
was attached). These macros are not provided with any "context" information, e.g., is the attached node a member of a struct? Is the expansion inside a function?
I suggest that we introduce an API on MacroExpansionContext
that provides the "parent" context node for a given syntax node. The API could look like this:
protocol MacroExpansionContext {
// ...
/// Determine the parent context of the given syntax node.
///
/// For a syntax node that is part of the syntax provided to a macro
/// expansion, find the innermost enclosing context node. A context
/// node is an entity such as a function declaration, type declaration,
/// or extension that can have other entities nested inside it.
/// The resulting context node will have any information about nested
/// entities removed from it, to prevent macro expansion operations from
/// seeing unrelated code within the program.
func parentContext<Node: SyntaxProtocol>(of node: Node) -> Syntax?
}
As noted in the comment, the resulting parent node will have much of the syntax stripped, including the bodies of functions and the members of types and extensions. For example, consider this code:
extension A {
struct B {
func f(a: Int) {
if a != 0 {
#printContext
}
}
func g(b: Int) { code code code }
}
func h(i: Int) { code code code }
}
The parent context of the #printContext
macro expansion would be the function f(a: Int)
, but with the body removed:
func f(a: Int)
The parent context of that node will be the enclosing struct B
, again with no members:
struct B { }
and the same for its parent context:
extension A { }
Parent contexts therefore give the full syntactic nesting structure of the nodes provided to a macro, but without exposing any information that is outside of the macro expansion's "slice" of the program. We're looking for the sweet spot where we don't compromise our incremental build performance, but we can write useful macros.
I have stubbed out and partially implemented this API in this pull request, and would love to hear everyone's thoughts on the API design and whether it's providing enough information to implement the macros you have in mind.
Doug