Skip to content

Commit

Permalink
SNP-1654 completion handler macro
Browse files Browse the repository at this point in the history
  • Loading branch information
Goldilocks97 committed Jul 2, 2024
1 parent a2577e8 commit 8ac9067
Show file tree
Hide file tree
Showing 11 changed files with 712 additions and 365 deletions.
11 changes: 11 additions & 0 deletions Sources/SurfCore/Closures/Closures.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//
// Copyright © Surf. All rights reserved.
//

import Foundation

/// Замыкание без параметра
public typealias EmptyClosure = () -> Void

/// Замыкание с одним параметром
public typealias Closure<T> = (T) -> Void
133 changes: 133 additions & 0 deletions Sources/SurfMacros/Implementation/Signals/CompletionHandlerMacro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SurfMacrosSupport

public struct CompletionHandlerMacro: PeerMacro {

// MARK: - Names

private enum Names {
static let completion = "completion"
static let emptyClosure = "EmptyClosure"

static var `protocol` = ""

static var `class`: String {
return `protocol` + "Handler"
}

}

// MARK: - Macro

public static func expansion(
of node: AttributeSyntax,
providingPeersOf declaration: some DeclSyntaxProtocol,
in context: some MacroExpansionContext
) throws -> [DeclSyntax] {
guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else {
throw DeclarationError.wrongAttaching(expected: .protocol)
}
Names.protocol = protocolDecl.name.text
try SignalsMacroGroupSupport.checkProtocolDeclaration(protocolDecl)
let protocolFuncDecls = protocolDecl.memberBlock.functionDecls
let handlerClass = createHandlerClass(with: protocolFuncDecls)
return [.init(handlerClass)]
}

}

// MARK: - Creations

private extension CompletionHandlerMacro {

static func createHandlerClass(with protocolFuncDecls: [FunctionDeclSyntax]) -> ClassDeclSyntax {
let privateModifier = DeclModifierSyntax(name: .keyword(.private))
let protocolIdentifier = createProtocolIdentifier()
let memberBlock = createMemberBlock(with: protocolFuncDecls)
return .init(
modifiers: [privateModifier],
name: .identifier(Names.class),
inheritanceClause: .init(inheritedTypes: [.init(type: protocolIdentifier)]),
memberBlock: memberBlock
)
}

static func createProtocolIdentifier() -> IdentifierTypeSyntax {
return .init(name: .identifier(Names.protocol))
}

static func createMemberBlock(with protocolFuncDecls: [FunctionDeclSyntax]) -> MemberBlockSyntax {
let itemList = MemberBlockItemListSyntax {
createCompletionProperty()
createInit()
for funcDecl in protocolFuncDecls {
SignalsMacroGroupSupport.createFuncDecl(
from: funcDecl,
with: createFuncBody()
)
}
}
return .init(members: itemList)
}

static func createCompletionProperty() -> VariableDeclSyntax {
let privateModifier = DeclModifierSyntax(name: .keyword(.private))
let type = createCompletionType()
let patternBinding = PatternBindingSyntax(
pattern: IdentifierPatternSyntax(identifier: .identifier(Names.completion)),
typeAnnotation: .init(type: type)
)
return .init(
modifiers: [privateModifier],
bindingSpecifier: .keyword(.let),
bindings: [patternBinding]
)
}

static func createInit() -> InitializerDeclSyntax {
let signature = createInitSignature()
let body = createInitBody()
return .init(signature: signature, body: body)
}

static func createInitSignature() -> FunctionSignatureSyntax {
let type = createCompletionType()
let defaultNil = InitializerClauseSyntax(value: NilLiteralExprSyntax())
let completionParameter = FunctionParameterSyntax(
firstName: .identifier(Names.completion),
type: type,
defaultValue: defaultNil
)
return .init(parameterClause: .init(parameters: [completionParameter]))
}

static func createInitBody() -> CodeBlockSyntax {
let selfCompletion = MemberAccessExprSyntax(
base: DeclReferenceExprSyntax(baseName: .keyword(.`self`)),
declName: .init(baseName: .identifier(Names.completion))
)
let completion = DeclReferenceExprSyntax(baseName: .identifier(Names.completion))
let assigmentOperator = InfixOperatorExprSyntax(
leftOperand: selfCompletion,
operator: AssignmentExprSyntax(),
rightOperand: completion
)
return .init(statements: [.init(item: .expr(.init(assigmentOperator)))])
}

static func createCompletionType() -> TypeSyntaxProtocol {
return OptionalTypeSyntax(wrappedType: IdentifierTypeSyntax(name: .identifier(Names.emptyClosure)))
}

static func createFuncBody() -> CodeBlockSyntax {
let completion = DeclReferenceExprSyntax(baseName: .identifier(Names.completion))
let completionCall = FunctionCallExprSyntax(
calledExpression: OptionalChainingExprSyntax(expression: completion)
)
return .init(statements: [.init(item: .expr(.init(completionCall)))])
}

}
23 changes: 0 additions & 23 deletions Sources/SurfMacros/Implementation/Signals/MulticastError.swift

This file was deleted.

59 changes: 12 additions & 47 deletions Sources/SurfMacros/Implementation/Signals/MulticastMacro.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public struct MulticastMacro: PeerMacro {
guard let protocolDecl = declaration.as(ProtocolDeclSyntax.self) else {
throw DeclarationError.wrongAttaching(expected: .protocol)
}
try checkProtocolDeclaration(protocolDecl)
try SignalsMacroGroupSupport.checkProtocolDeclaration(protocolDecl)
Names.protocol = protocolDecl.name.text
let protocolFuncDecls = protocolDecl.memberBlock.functionDecls
let signalsClass = createSignalsClass(with: protocolFuncDecls)
Expand All @@ -42,16 +42,6 @@ public struct MulticastMacro: PeerMacro {

}

// MARK: - Checks

private extension MulticastMacro {

static func checkProtocolDeclaration(_ declaration: ProtocolDeclSyntax) throws {
try checkMembers(of: declaration)
}

}

// MARK: - Creations

private extension MulticastMacro {
Expand All @@ -75,36 +65,6 @@ private extension MulticastMacro {

private extension MulticastMacro {

static func checkMembers(of decl: ProtocolDeclSyntax) throws {
try decl.memberBlock.members.forEach { member in
if let funcDecl = member.decl.as(FunctionDeclSyntax.self),
!isAppropriateFuncDecl(funcDecl) {
throw MulticastError.wrongFunctionFormat
} else if member.decl.is(VariableDeclSyntax.self) {
throw DeclarationError.unexpectedVariable
} else if member.decl.is(AssociatedTypeDeclSyntax.self) {
throw DeclarationError.unexpectedAssociatedType
}
}
}

static func isAppropriateFuncDecl(_ decl: FunctionDeclSyntax) -> Bool {
if !decl.modifiers.contains(where: isStatic),
case .identifier = decl.name.tokenKind,
decl.signature.returnClause == nil,
decl.signature.effectSpecifiers == nil,
decl.genericWhereClause == nil,
decl.genericParameterClause == nil,
decl.attributes.isEmpty {
return true
}
return false
}

static func isStatic(_ modifier: DeclModifierSyntax) -> Bool {
modifier.name.tokenKind == .keyword(.static)
}

static func createPublicModifier() -> DeclModifierSyntax {
return .init(name: .keyword(.public))
}
Expand Down Expand Up @@ -182,6 +142,16 @@ private extension MulticastMacro {
}

static func transformIntoSignalsClassFuncDecl(_ funcDecl: FunctionDeclSyntax) -> FunctionDeclSyntax {
let publicModifier = createPublicModifier()
let body = createFuncBody(withCalling: funcDecl)
return SignalsMacroGroupSupport.createFuncDecl(
from: funcDecl,
with: body,
modifiers: [publicModifier]
)
}

static func createFuncBody(withCalling funcDecl: FunctionDeclSyntax) -> CodeBlockSyntax {
let funcCall = transformIntoCall(functionDecl: funcDecl)
let trailingClosure = ClosureExprSyntax(statements: [.init(item: .expr(.init(funcCall)))])
let forEachCall = FunctionCallExprSyntax(
Expand All @@ -192,12 +162,7 @@ private extension MulticastMacro {
trailingClosure: trailingClosure,
argumentsBuilder: {}
)
let publicModifier = createPublicModifier()

var signalsClassFuncDecl = funcDecl.trimmed
signalsClassFuncDecl.body = .init(statements: [.init(item: .expr(.init(forEachCall)))])
signalsClassFuncDecl.modifiers = [publicModifier]
return signalsClassFuncDecl
return .init(statements: [.init(item: .expr(.init(forEachCall)))])
}

static func transformIntoCall(functionDecl: FunctionDeclSyntax) -> FunctionCallExprSyntax {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import SwiftSyntaxMacros

struct SignalsPlugin {
static let providingMacros: [Macro.Type] = [
MulticastMacro.self
MulticastMacro.self,
CompletionHandlerMacro.self
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// SignalsMacroGroupSupport.swift
//
//
// Created by pavlov on 01.07.2024.
//

import SwiftCompilerPlugin
import SwiftSyntax
import SwiftSyntaxBuilder
import SwiftSyntaxMacros
import SurfMacrosSupport

struct SignalsMacroGroupSupport {}

// MARK: - Checks

extension SignalsMacroGroupSupport {

static func checkProtocolDeclaration(_ declaration: ProtocolDeclSyntax) throws {
try checkMembers(of: declaration)
}

}

// MARK: - Creations

extension SignalsMacroGroupSupport {

static func createFuncDecl(
from protocolFuncDecl: FunctionDeclSyntax,
with body: CodeBlockSyntax,
modifiers: DeclModifierListSyntax = []
) -> FunctionDeclSyntax {
var funcDecl = protocolFuncDecl.trimmed
funcDecl.body = body
funcDecl.modifiers = modifiers
return funcDecl
}

}

// MARK: - Private Methods

private extension SignalsMacroGroupSupport {

static func checkMembers(of decl: ProtocolDeclSyntax) throws {
try decl.memberBlock.members.forEach { member in
if let funcDecl = member.decl.as(FunctionDeclSyntax.self),
!isAppropriateFuncDecl(funcDecl) {
throw CustomError(
description: """
The only allowed format of a function is the following:
func <name>(<argument>, ...)
"""
)
} else if member.decl.is(VariableDeclSyntax.self) {
throw DeclarationError.unexpectedVariable
} else if member.decl.is(AssociatedTypeDeclSyntax.self) {
throw DeclarationError.unexpectedAssociatedType
}
}
}

static func isAppropriateFuncDecl(_ decl: FunctionDeclSyntax) -> Bool {
if !decl.modifiers.contains(where: isStatic),
case .identifier = decl.name.tokenKind,
decl.signature.returnClause == nil,
decl.signature.effectSpecifiers == nil,
decl.genericWhereClause == nil,
decl.genericParameterClause == nil,
decl.attributes.isEmpty {
return true
}
return false
}

static func isStatic(_ modifier: DeclModifierSyntax) -> Bool {
modifier.name.tokenKind == .keyword(.static)
}

}
2 changes: 2 additions & 0 deletions Sources/SurfMacros/Macros/Signals/CompletionHandler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@attached(peer, names: suffixed(Handler))
public macro CompletionHandler() = #externalMacro(module: "SurfMacroBody", type: "CompletionHandlerMacro")
Loading

0 comments on commit 8ac9067

Please sign in to comment.