Skip to content

Commit 56a9e17

Browse files
committed
[Macros] Expand peer macros.
1 parent 3c90feb commit 56a9e17

File tree

6 files changed

+170
-0
lines changed

6 files changed

+170
-0
lines changed

lib/Sema/LookupVisibleDecls.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,14 @@ static void synthesizeMemberDeclsForLookup(NominalTypeDecl *NTD,
611611
ExpandSynthesizedMemberMacroRequest{NTD},
612612
false);
613613

614+
// Expand peer macros.
615+
for (auto *member : NTD->getMembers()) {
616+
(void)evaluateOrDefault(
617+
ctx.evaluator,
618+
ExpandPeerMacroRequest{member},
619+
false);
620+
}
621+
614622
synthesizePropertyWrapperVariables(NTD);
615623
}
616624

lib/Sema/TypeCheckDecl.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2802,6 +2802,12 @@ static ArrayRef<Decl *> evaluateMembersRequest(
28022802
nullptr);
28032803

28042804
for (auto *member : idc->getMembers()) {
2805+
// Expand peer macros.
2806+
(void)evaluateOrDefault(
2807+
ctx.evaluator,
2808+
ExpandPeerMacroRequest{member},
2809+
false);
2810+
28052811
if (auto *var = dyn_cast<VarDecl>(member)) {
28062812
// The projected storage wrapper ($foo) might have
28072813
// dynamically-dispatched accessors, so force them to be synthesized.

lib/Sema/TypeCheckMacros.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,9 @@ static std::string adjustMacroExpansionBufferName(StringRef name) {
386386

387387
bool ExpandMemberAttributeMacros::evaluate(Evaluator &evaluator,
388388
Decl *decl) const {
389+
if (decl->isImplicit())
390+
return false;
391+
389392
auto *parentDecl = decl->getDeclContext()->getAsDecl();
390393
if (!parentDecl)
391394
return false;

lib/Sema/TypeCheckStorage.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ static void computeLoweredStoredProperties(NominalTypeDecl *decl,
117117
// Just walk over the members of the type, forcing backing storage
118118
// for lazy properties and property wrappers to be synthesized.
119119
for (auto *member : implDecl->getMembers()) {
120+
// Expand peer macros.
121+
(void)evaluateOrDefault(
122+
ctx.evaluator,
123+
ExpandPeerMacroRequest{member},
124+
false);
125+
120126
auto *var = dyn_cast<VarDecl>(member);
121127
if (!var || var->isStatic())
122128
continue;

test/Macros/Inputs/syntax_macro_definitions.swift

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,127 @@ public enum LeftHandOperandFinderMacro: ExpressionMacro {
611611
}
612612
}
613613

614+
public struct AddCompletionHandler: PeerMacro {
615+
public static func expansion(
616+
of node: AttributeSyntax,
617+
providingPeersOf declaration: some DeclSyntaxProtocol,
618+
in context: some MacroExpansionContext
619+
) throws -> [DeclSyntax] {
620+
// Only on functions at the moment. We could handle initializers as well
621+
// with a bit of work.
622+
guard let funcDecl = declaration.as(FunctionDeclSyntax.self) else {
623+
throw CustomError.message("@addCompletionHandler only works on functions")
624+
}
625+
626+
// This only makes sense for async functions.
627+
if funcDecl.signature.effectSpecifiers?.asyncSpecifier == nil {
628+
throw CustomError.message(
629+
"@addCompletionHandler requires an async function"
630+
)
631+
}
632+
633+
// Form the completion handler parameter.
634+
let resultType: TypeSyntax? = funcDecl.signature.output?.returnType.with(\.leadingTrivia, []).with(\.trailingTrivia, [])
635+
636+
let completionHandlerParam =
637+
FunctionParameterSyntax(
638+
firstName: .identifier("completionHandler"),
639+
colon: .colonToken(trailingTrivia: .space),
640+
type: "(\(resultType ?? "")) -> Void" as TypeSyntax
641+
)
642+
643+
// Add the completion handler parameter to the parameter list.
644+
let parameterList = funcDecl.signature.input.parameterList
645+
let newParameterList: FunctionParameterListSyntax
646+
if let lastParam = parameterList.last {
647+
// We need to add a trailing comma to the preceding list.
648+
newParameterList = parameterList.removingLast()
649+
.appending(
650+
lastParam.with(
651+
\.trailingComma,
652+
.commaToken(trailingTrivia: .space)
653+
)
654+
)
655+
.appending(completionHandlerParam)
656+
} else {
657+
newParameterList = parameterList.appending(completionHandlerParam)
658+
}
659+
660+
let callArguments: [String] = try parameterList.map { param in
661+
guard let argName = param.secondName ?? param.firstName else {
662+
throw CustomError.message(
663+
"@addCompletionHandler argument must have a name"
664+
)
665+
}
666+
667+
if let paramName = param.firstName, paramName.text != "_" {
668+
return "\(paramName.text): \(argName.text)"
669+
}
670+
671+
return "\(argName.text)"
672+
}
673+
674+
let call: ExprSyntax =
675+
"\(funcDecl.identifier)(\(raw: callArguments.joined(separator: ", ")))"
676+
677+
// FIXME: We should make CodeBlockSyntax ExpressibleByStringInterpolation,
678+
// so that the full body could go here.
679+
let newBody: ExprSyntax =
680+
"""
681+
682+
Task {
683+
completionHandler(await \(call))
684+
}
685+
686+
"""
687+
688+
// Drop the @addCompletionHandler attribute from the new declaration.
689+
let newAttributeList = AttributeListSyntax(
690+
funcDecl.attributes?.filter {
691+
guard case let .attribute(attribute) = $0,
692+
let attributeType = attribute.attributeName.as(SimpleTypeIdentifierSyntax.self),
693+
let nodeType = node.attributeName.as(SimpleTypeIdentifierSyntax.self)
694+
else {
695+
return true
696+
}
697+
698+
return attributeType.name.text != nodeType.name.text
699+
} ?? []
700+
)
701+
702+
let newFunc =
703+
funcDecl
704+
.with(
705+
\.signature,
706+
funcDecl.signature
707+
.with(
708+
\.effectSpecifiers,
709+
funcDecl.signature.effectSpecifiers?.with(\.asyncSpecifier, nil) // drop async
710+
)
711+
.with(\.output, nil) // drop result type
712+
.with(
713+
\.input, // add completion handler parameter
714+
funcDecl.signature.input.with(\.parameterList, newParameterList)
715+
.with(\.trailingTrivia, [])
716+
)
717+
)
718+
.with(
719+
\.body,
720+
CodeBlockSyntax(
721+
leftBrace: .leftBraceToken(leadingTrivia: .space),
722+
statements: CodeBlockItemListSyntax(
723+
[CodeBlockItemSyntax(item: .expr(newBody))]
724+
),
725+
rightBrace: .rightBraceToken(leadingTrivia: .newline)
726+
)
727+
)
728+
.with(\.attributes, newAttributeList)
729+
.with(\.leadingTrivia, .newlines(2))
730+
731+
return [DeclSyntax(newFunc)]
732+
}
733+
}
734+
614735
private extension DeclSyntaxProtocol {
615736
var isObservableStoredProperty: Bool {
616737
if let property = self.as(VariableDeclSyntax.self),

test/Macros/macro_expand_peers.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift -swift-version 5 -I %swift-host-lib-dir -L %swift-host-lib-dir -emit-library -o %t/%target-library-name(MacroDefinition) -module-name=MacroDefinition %S/Inputs/syntax_macro_definitions.swift -g -no-toolchain-stdlib-rpath
3+
// RUN: %target-typecheck-verify-swift -swift-version 5 -enable-experimental-feature Macros -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir -disable-availability-checking
4+
// RUN: %target-swift-frontend -swift-version 5 -typecheck -enable-experimental-feature Macros -enable-experimental-feature Macros -load-plugin-library %t/%target-library-name(MacroDefinition) -I %swift-host-lib-dir %s -disable-availability-checking -dump-macro-expansions > %t/expansions-dump.txt 2>&1
5+
// RUN: %FileCheck -check-prefix=CHECK-DUMP %s < %t/expansions-dump.txt
6+
7+
8+
// FIXME: Swift parser is not enabled on Linux CI yet.
9+
// REQUIRES: OS=macosx
10+
11+
@attached(peer)
12+
macro addCompletionHandler() = #externalMacro(module: "MacroDefinition", type: "AddCompletionHandler")
13+
14+
struct S {
15+
@addCompletionHandler
16+
func f(a: Int, for b: String, _ value: Double) async -> String {
17+
return b
18+
}
19+
20+
// CHECK-DUMP: @__swiftmacro_18macro_expand_peers1SV1f1a3for_SSSi_SSSdtYaF20addCompletionHandlerfMp_.swift
21+
// CHECK-DUMP: func f(a: Int, for b: String, _ value: Double, completionHandler: (String) -> Void) {
22+
// CHECK-DUMP: Task {
23+
// CHECK-DUMP: completionHandler(await f(a: a, for: b, value))
24+
// CHECK-DUMP: }
25+
// CHECK-DUMP: }
26+
}

0 commit comments

Comments
 (0)