I'm making a macro like this:
@attached(peer, names: arbitrary)
public macro Lazify(name: String, lock: Protectable) = #externalMacro(module: "MacroModule", type: "LazifyMacro")
It should be used to attach to a function declaration, to generate some other variables.
But I got the error Circular reference expanding peer macros on <function_name>, the code is below:
class SomeClass {
var _internal_lock: NSLock = .init()
static let classLock: NSLock = .init()
// Error: Circular reference expanding peer macros on 'createLazyVariable()'
@Lazify(name: "internalClassName", lock: SomeClass.classLock)
func createLazyVariable() -> String {
return "__" + NSStringFromClass(type(of: self))
}
}
If I change the lock: Protectable to lock: String then it's ok, but it won't have type check when using the macro.
And further if I use a instance property lock _internal_lock and the error will change to Instance member '_internal_lock' cannot be used on type 'SomeClass'; did you mean to use a value of this type instead?
So I wonder why I can't use the classLock or the _internal_lcok here?
- Edit1
If I use a global instance lock then it can compile:
let globalLock: NSLock = .init()
class SomeClass {
@Lazify(name: "internalClassName", lock: globalLock)
func createLazyVariable() -> String {
return "__" + NSStringFromClass(type(of: self))
}
}
This is a circular reference because to resolve the name
SomeClass.classLock, Swift must first expandLazify. Why? BecauseLazifycan introducearbitrarynames, so the meaning ofSomeClass.classLockmight be different after@Lazifyis expanded.@Lazifycan't affect names outside of its scope, so if you use a name that is declared in a different class, or globallet, it works. I'm not sure about this explanation though - it might as well be a bug, perhaps related to this.In any case, it seems like
Lazifywould add a declaration with the name of whatever string is passed to itsnameparameter. This is not a good design, since, at least, it would be difficult to rename this using the "Refactor -> Rename" option in Xcode.Instead of generating whatever name the user wants, you can use the
suffixedorprefixedoptions in@attached(peer, names: ...), and always generate a name from the name of the declaration to which the macro is attached. For example, use:Then,
should be implemented to generate
createLazyVariable_lazify.You can't use instance properties here for a similar reason to why something like this doesn't work:
I think this is checked before the macro expansion, so this is not a problem of your macro, per se.
To allow instance members, you can add an overload like this:
Usage:
From your previous question, it seems like a better design would be to just use a property wrapper:
If you really want a macro, you can make
Lazifyanaccessormacro as well. Declare theinternalClassNameproperty yourself and pass the code increateLazyVariableto the macro as a closure.