@@ -611,6 +611,127 @@ public enum LeftHandOperandFinderMacro: ExpressionMacro {
611
611
}
612
612
}
613
613
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
+
614
735
private extension DeclSyntaxProtocol {
615
736
var isObservableStoredProperty : Bool {
616
737
if let property = self . as ( VariableDeclSyntax . self) ,
0 commit comments