Skip to content

Commit 380bad4

Browse files
committed
Implement preamble macros to augment a function body
Implement type checking support for preamble macros, which expands the preamble macros and introduces them at the beginning of the function body prior to type checking. Ensure that the resulting function bodies type-check properly, including when composing multiple preamble macros and with a preamble macro applied to the body of a function that itself came from a body macro.
1 parent 36a2dcd commit 380bad4

File tree

7 files changed

+172
-7
lines changed

7 files changed

+172
-7
lines changed

lib/AST/ASTScopeCreation.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,14 @@ ASTSourceFileScope::ASTSourceFileScope(SourceFile *SF,
275275
case MacroRole::MemberAttribute:
276276
case MacroRole::Conformance:
277277
case MacroRole::Extension:
278-
case MacroRole::Preamble:
279278
parentLoc = expansion.getStartLoc();
280279
break;
280+
case MacroRole::Preamble: {
281+
// Preamble macro roles start at the beginning of the macro body.
282+
auto func = cast<AbstractFunctionDecl>(expansion.get<Decl *>());
283+
parentLoc = func->getMacroExpandedBody()->getStartLoc();
284+
break;
285+
}
281286
case MacroRole::Body:
282287
parentLoc = expansion.getEndLoc();
283288
bodyForDecl = cast<AbstractFunctionDecl>(expansion.get<Decl *>());

lib/AST/ASTVerifier.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,13 @@ class Verifier : public ASTWalker {
258258
abort();
259259
}
260260

261+
ModuleDecl *getModuleContext() const {
262+
if (auto sourceFile = M.dyn_cast<SourceFile *>())
263+
return sourceFile->getParentModule();
264+
265+
return M.get<ModuleDecl *>();
266+
}
267+
261268
public:
262269
Verifier(ModuleDecl *M, DeclContext *DC)
263270
: Verifier(PointerUnion<ModuleDecl *, SourceFile *>(M), DC) {}
@@ -3813,7 +3820,17 @@ class Verifier : public ASTWalker {
38133820
} else {
38143821
llvm_unreachable("impossible parent node");
38153822
}
3816-
3823+
3824+
if (AltEnclosing.isInvalid()) {
3825+
// A preamble macro introduces child nodes directly into the tree.
3826+
auto *sourceFile =
3827+
getModuleContext()->getSourceFileContainingLocation(Current.Start);
3828+
if (sourceFile &&
3829+
sourceFile->getFulfilledMacroRole() == MacroRole::Preamble) {
3830+
AltEnclosing = Current;
3831+
}
3832+
}
3833+
38173834
if (!Ctx.SourceMgr.rangeContains(Enclosing, Current) &&
38183835
!(AltEnclosing.isValid() &&
38193836
Ctx.SourceMgr.rangeContains(AltEnclosing, Current))) {

lib/AST/Module.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,11 +1209,12 @@ llvm::Optional<MacroRole> SourceFile::getFulfilledMacroRole() const {
12091209
}
12101210

12111211
SourceFile *SourceFile::getEnclosingSourceFile() const {
1212-
auto macroExpansion = getMacroExpansion();
1213-
if (!macroExpansion)
1212+
if (Kind != SourceFileKind::MacroExpansion)
12141213
return nullptr;
12151214

1216-
auto sourceLoc = macroExpansion.getStartLoc();
1215+
auto genInfo =
1216+
*getASTContext().SourceMgr.getGeneratedSourceInfo(*getBufferID());
1217+
auto sourceLoc = genInfo.originalSourceRange.getStart();
12171218
return getParentModule()->getSourceFileContainingLocation(sourceLoc);
12181219
}
12191220

lib/Sema/TypeCheckMacros.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,7 @@ static CharSourceRange getExpansionInsertionRange(MacroRole role,
909909
case MacroRole::Preamble: {
910910
SourceLoc inBodyLoc;
911911
if (auto fn = dyn_cast<AbstractFunctionDecl>(target.get<Decl *>())) {
912-
inBodyLoc = fn->getBodySourceRange().Start;
912+
inBodyLoc = fn->getMacroExpandedBody()->getStartLoc();
913913
}
914914

915915
if (inBodyLoc.isInvalid())
@@ -1581,6 +1581,7 @@ ArrayRef<unsigned> ExpandPreambleMacroRequest::evaluate(
15811581
bufferIDs.push_back(*bufferID);
15821582
});
15831583

1584+
std::reverse(bufferIDs.begin(), bufferIDs.end());
15841585
return fn->getASTContext().AllocateCopy(bufferIDs);
15851586
}
15861587

lib/Sema/TypeCheckStmt.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2764,6 +2764,24 @@ static bool requiresNoDefinition(Decl *decl) {
27642764
return false;
27652765
}
27662766

2767+
/// Expand all preamble macros attached to the given function declaration.
2768+
static std::vector<ASTNode> expandPreamble(AbstractFunctionDecl *func) {
2769+
std::vector<ASTNode> preamble;
2770+
2771+
ASTContext &ctx = func->getASTContext();
2772+
ExpandPreambleMacroRequest request{func};
2773+
auto module = func->getParentModule();
2774+
for (auto bufferID : evaluateOrDefault(ctx.evaluator, request, { })) {
2775+
auto bufferStart = ctx.SourceMgr.getLocForBufferStart(bufferID);
2776+
auto preambleSF = module->getSourceFileContainingLocation(bufferStart);
2777+
preamble.insert(preamble.end(),
2778+
preambleSF->getTopLevelItems().begin(),
2779+
preambleSF->getTopLevelItems().end());
2780+
}
2781+
2782+
return preamble;
2783+
}
2784+
27672785
BraceStmt *
27682786
TypeCheckFunctionBodyRequest::evaluate(Evaluator &eval,
27692787
AbstractFunctionDecl *AFD) const {
@@ -2839,6 +2857,17 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &eval,
28392857
}
28402858
}
28412859

2860+
// Expand any preamble macros and introduce them into the body.
2861+
auto preamble = expandPreamble(AFD);
2862+
if (!preamble.empty()) {
2863+
auto newBody = std::move(preamble);
2864+
newBody.insert(
2865+
newBody.end(), body->getElements().begin(), body->getElements().end());
2866+
2867+
body = BraceStmt::create(
2868+
ctx, body->getLBraceLoc(), newBody, body->getRBraceLoc());
2869+
}
2870+
28422871
// Typechecking, in particular ApplySolution is going to replace closures
28432872
// with OpaqueValueExprs and then try to do lookups into the closures.
28442873
// So, build out the body now.
@@ -2859,6 +2888,8 @@ TypeCheckFunctionBodyRequest::evaluate(Evaluator &eval,
28592888
hadError = SC.typeCheckBody(body);
28602889
}
28612890

2891+
2892+
28622893
// If this was a function with a single expression body, let's see
28632894
// if implicit return statement came out to be `Never` which means
28642895
// that we have eagerly converted something like `{ fatalError() }`

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2140,3 +2140,85 @@ public struct RemoteBodyMacro: BodyMacro {
21402140
]
21412141
}
21422142
}
2143+
2144+
@_spi(ExperimentalLanguageFeature)
2145+
public struct TracedPreambleMacro: PreambleMacro {
2146+
public static func expansion(
2147+
of node: AttributeSyntax,
2148+
providingPreambleFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax,
2149+
in context: some MacroExpansionContext
2150+
) throws -> [CodeBlockItemSyntax] {
2151+
// FIXME: Should be able to support (de-)initializers and accessors as
2152+
// well, but this is a lazy implementation.
2153+
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
2154+
return []
2155+
}
2156+
2157+
let funcBaseName = funcDecl.name
2158+
let paramNames = funcDecl.signature.parameterClause.parameters.map { param in
2159+
param.parameterName?.text ?? "_"
2160+
}
2161+
2162+
let passedArgs = paramNames.map { "\($0): \\(\($0))" }.joined(separator: ", ")
2163+
2164+
let entry: CodeBlockItemSyntax = """
2165+
log("Entering \(funcBaseName)(\(raw: passedArgs))")
2166+
"""
2167+
2168+
let argLabels = paramNames.map { "\($0):" }.joined()
2169+
2170+
let exit: CodeBlockItemSyntax = """
2171+
log("Exiting \(funcBaseName)(\(raw: argLabels))")
2172+
"""
2173+
2174+
return [
2175+
entry,
2176+
"""
2177+
defer {
2178+
\(exit)
2179+
}
2180+
""",
2181+
]
2182+
}
2183+
}
2184+
2185+
@_spi(ExperimentalLanguageFeature)
2186+
public struct Log2PreambleMacro: PreambleMacro {
2187+
public static func expansion(
2188+
of node: AttributeSyntax,
2189+
providingPreambleFor declaration: some DeclSyntaxProtocol & WithOptionalCodeBlockSyntax,
2190+
in context: some MacroExpansionContext
2191+
) throws -> [CodeBlockItemSyntax] {
2192+
// FIXME: Should be able to support (de-)initializers and accessors as
2193+
// well, but this is a lazy implementation.
2194+
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
2195+
return []
2196+
}
2197+
2198+
let funcBaseName = funcDecl.name
2199+
let paramNames = funcDecl.signature.parameterClause.parameters.map { param in
2200+
param.parameterName?.text ?? "_"
2201+
}
2202+
2203+
let passedArgs = paramNames.map { "\($0): \\(\($0))" }.joined(separator: ", ")
2204+
2205+
let entry: CodeBlockItemSyntax = """
2206+
log2("Entering \(funcBaseName)(\(raw: passedArgs))")
2207+
"""
2208+
2209+
let argLabels = paramNames.map { "\($0):" }.joined()
2210+
2211+
let exit: CodeBlockItemSyntax = """
2212+
log2("Exiting \(funcBaseName)(\(raw: argLabels))")
2213+
"""
2214+
2215+
return [
2216+
entry,
2217+
"""
2218+
defer {
2219+
\(exit)
2220+
}
2221+
""",
2222+
]
2223+
}
2224+
}

test/Macros/macro_expand_body.swift

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
@attached(body)
1414
macro Remote() = #externalMacro(module: "MacroDefinition", type: "RemoteBodyMacro")
1515

16+
@attached(preamble)
17+
macro Traced() = #externalMacro(module: "MacroDefinition", type: "TracedPreambleMacro")
18+
19+
@attached(preamble)
20+
macro Log2() = #externalMacro(module: "MacroDefinition", type: "Log2PreambleMacro")
21+
1622
protocol ConjureRemoteValue {
1723
static func conjureValue() -> Self
1824
}
@@ -30,13 +36,27 @@ func remoteCall<Result: ConjureRemoteValue>(function: String, arguments: [String
3036
return Result.conjureValue()
3137
}
3238

39+
func log(_ value: String) {
40+
print(value)
41+
}
42+
43+
func log2(_ value: String) {
44+
print("log2(\(value))")
45+
}
46+
47+
@Traced
48+
func doubleTheValue(value: Int) -> Int {
49+
return value * 2
50+
}
51+
3352
@available(SwiftStdlib 5.1, *)
3453
@Remote
3554
func f(a: Int, b: String) async throws -> String
3655

37-
3856
@available(SwiftStdlib 5.1, *)
3957
@Remote
58+
@Traced
59+
@Log2
4060
func g(a: Int, b: String) async throws -> String {
4161
doesNotTypeCheck()
4262
}
@@ -51,10 +71,18 @@ func h(a: Int, b: String) async throws -> String {
5171
}
5272
#endif
5373

74+
// CHECK: Entering doubleTheValue(value: 7)
75+
// CHECK-NEXT: Exiting doubleTheValue(value:)
76+
_ = doubleTheValue(value: 7)
77+
5478
if #available(SwiftStdlib 5.1, *) {
5579
// CHECK: Remote call f(a: 5, b: Hello)
5680
print(try await f(a: 5, b: "Hello"))
5781

82+
// CHECK: Entering g(a: 5, b: World)
83+
// CHECK: log2(Entering g(a: 5, b: World))
5884
// CHECK: Remote call g(a: 5, b: World)
85+
// CHECK: log2(Exiting g(a:b:))
86+
// CHECK: Exiting g(a:b:)
5987
print(try await g(a: 5, b: "World"))
6088
}

0 commit comments

Comments
 (0)