Search code examples
swiftswift-macroswift5.9

Swift Macros - Variable is not getting generated


I'm trying to build a macro for mocking properties and I stumbled upon the following issue:

I have this protocol and I attached my macro to it

@MyMock
protocol SomeProtocolHere {
    var thisIsMyProperty: Bool { get } 
}

Behind the scenes, I want the macro to create me a mock class for that protocol

class SomeProtocolHereMock: SomeProtocolHere {
    var thisIsMyPropertyMocked: Bool!
    var thisIsMyProperty: Bool { thisIsMyPropertyMocked }
}

The issue that I'm having is that I only get generated the declaration of the property, without the accessor block. Why doesn't it generate the accessor block?

final class SomeProtocolHereMock: SomeProtocolHere {
    var somethingToReturnReturnValue: Bool!
    var somethingToReturn: Bool
}

This is the code that I'm using to generate properties

    static func makeVariableMock(from variableDecl: VariableDeclSyntax) -> MemberBlockItemListSyntax {
        let returnValueVariableName = "\(variableDecl.name)ReturnValue"
        
        let mockedUnwrappedProperty = VariableDeclSyntax(
            modifiers:
                DeclModifierListSyntax {
                    if variableDecl.isPublic {
                        DeclModifierSyntax(name: .keyword(.public))
                    }
                },
            bindingSpecifier: .keyword(.var),
            bindings:
                PatternBindingListSyntax {
                    PatternBindingSyntax(
                        pattern:
                            IdentifierPatternSyntax(identifier: .identifier(returnValueVariableName)),
                        typeAnnotation:
                            TypeAnnotationSyntax(type:
                                                    ImplicitlyUnwrappedOptionalTypeSyntax(
                                                        wrappedType: IdentifierTypeSyntax(
                                                            name: TokenSyntax.identifier(variableDecl.type)))
                                                    )
                    )
                    
                }
        )
        
        let originalVariable = VariableDeclSyntax(
            modifiers:
                DeclModifierListSyntax {
                    if variableDecl.isPublic {
                        DeclModifierSyntax(name: .keyword(.public))
                    }
                },
            bindingSpecifier: .keyword(.var),
            bindings:
                PatternBindingListSyntax {
                    PatternBindingSyntax(
                        pattern:
                            IdentifierPatternSyntax(identifier: .identifier(variableDecl.name)),
                        typeAnnotation:
                            TypeAnnotationSyntax(type: IdentifierTypeSyntax(name: TokenSyntax.identifier(variableDecl.type))),
                        accessorBlock: AccessorBlockSyntax(
                            CodeBlockItemListSyntax {
                                CodeBlockItemSyntax(
                                    item: .expr(ExprSyntax(fromProtocol: DeclReferenceExprSyntax(baseName: TokenSyntax.identifier(returnValueVariableName))))
                                )
                            }
                        ))
            }
        )
        
        return MemberBlockItemListSyntax {
            MemberBlockItemSyntax(decl: mockedUnwrappedProperty)
            MemberBlockItemSyntax(decl: originalVariable)
        }
    }

Solution

  • After digging a bit more I found the issue. The accessorBlock parameter was nil. In order to fix it, I had to use the init(accessors:) initializer rather than init?(_ node:)

    AccessorBlockSyntax(accessors: .getter(
        CodeBlockItemListSyntax {
            CodeBlockItemSyntax(
                item: .expr(ExprSyntax(fromProtocol: DeclReferenceExprSyntax(baseName: TokenSyntax.identifier(returnValueVariableName))))
            )
        })