为什么“循环引用扩展对等宏”在这里

问题描述 投票:0回答:1

我正在制作这样的宏:

@attached(peer, names: arbitrary)
public macro Lazify(name: String, lock: Protectable) = #externalMacro(module: "MacroModule", type: "LazifyMacro")

它应该用于附加到函数声明,以生成一些其他变量。

但是我得到了错误

Circular reference expanding peer macros on <function_name>
,代码如下:

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))
  }
}

如果我将

lock: Protectable
更改为
lock: String
那么就可以了,但是使用宏时不会进行类型检查。

此外,如果我使用实例属性锁

_internal_lock
,错误将更改为
Instance member '_internal_lock' cannot be used on type 'SomeClass'; did you mean to use a value of this type instead?

所以我想知道为什么我不能在这里使用

classLock
_internal_lcok


  • 编辑1

如果我使用全局实例锁,那么它可以编译:

let globalLock: NSLock = .init()

class SomeClass {
  @Lazify(name: "internalClassName", lock: globalLock)
  func createLazyVariable() -> String {
    return "__" + NSStringFromClass(type(of: self))
  }
}
swift swift-macro
1个回答
0
投票

我不知道为什么会发生这种情况。它可能与 this bug 有关,已在 Swift 5.10 中修复(尽管我还没有 5.10 来检查)。

无论如何,似乎

Lazify
都会添加一个声明,其中包含传递给其
name
参数的任何字符串的名称。这不是一个好的设计,因为至少使用 Xcode 中的“Refactor -> Rename”选项来重命名它会很困难。

您可以使用

suffixed
中的
prefixed
@attached(peer, names: ...)
选项,而不是生成用户想要的任何名称,并且始终根据宏所附加的声明的名称生成名称。例如,使用:

@attached(peer, names: suffixed(_lazify)) 
// you can write multiple "suffixed(...)" here, if you need

那么,

@Lazify(lock: SomeClass.classLock)
func createLazyVariable() -> String { ... }

应该被实现来生成

createLazyVariable_lazify


您不能在此处使用实例属性,原因与类似的方法不起作用的原因类似:

class Foo {
    let x = 1
    let y = x // "self" is not available here!
}

我认为这是在宏扩展之前检查的,所以这本身不是宏的问题。

要允许实例成员,您可以添加如下重载:

public macro Lazify<T, P: Protectable>(lock: KeyPath<T, P>) = ...

用途:

@Lazify(lock: \SomeClass._internal_lock)

// in the macro implementation, you would generate something like this:
self[keyPath: \SomeClass._internal_lock]
// to access the lock.

从您的上一个问题来看,更好的设计似乎是只使用属性包装器:

@propertyWrapper
struct Lazify<T> {
    let lock: Protectable
    let initialiser: () -> T
    
    var wrappedValue: T {
        mutating get { 
            if let x = lazy {
                return x
            }
            return lock.around {
                if let x = self.lazy {
                    return x
                }
                let temp = initialiser()
                self.lazy = temp
                return temp
            }
        }

        set { lazy = newValue }
    }
    
    private(set) var lazy: T?
}
© www.soinside.com 2019 - 2024. All rights reserved.