Add shared storage to property wrappers

Right. The idea is that you write this:

@propertyWrapper
struct Clamped<Value: Comparable> {
  @shared private var bounds: Range<Value>
  private var value: Value

  init(wrappedValue: Value, @shared _: Range<Value>) {
    // There must be a @shared initializer that's at least as
    // accessible as this which has exactly the same parameters
    // as this initializer's @shared parameters.
    // This body can refer to @shared properties, but not directly
    // to its @shared parameters.
    ...
  }

  @shared init(_ bounds: Range<Value>) {
    // This body must initialize the @shared stored properties
    // and cannot use any non-@shared members.
    self.bounds = bounds
  }

  var wrappedValue: Value {
    get { value }
    set { value = bounds.clamping(newValue) }
  }
}

That definition gets rewritten/lowered to something functionally like this:

extension Clamped {
  struct Shared {
    // @shared instance properties from the original type go here.
    private var bounds: Range<Value>

    // @shared initializers from the original type go here.
    init(_ bounds: Range<Value>) {
      self.bounds = bounds
    }
  }

  struct Instance {
    // Non-@shared instance properties from the original type go here.
    private var value: Value

    // Non-@shared initializers from the original type go here.
    // @shared parameters are removed and replaced with an
    // implicit _shared parameter.  References to @shared
    // properties in the body are rewritten to use the implicit
    // _shared parameter.
    init(wrappedValue: Value, _shared: UnsafePointer<Shared>) { ... }

    // No other methods go here.
  }

  struct Composite {
    // Static stored properties from the original type go here.
    // The instance properties are always exactly as follows:
    var _shared: UnsafePointer<Shared>
    var _instance: UnsafeMutablePointer<Instance>

    // Methods from the original type go here.  References to
    // shared properties are rewritten to use _shared.
    // References to instance properties are rewritten to use
    // _instance.
    var wrappedValue: Value {
      get { _instance.pointee.value }
      set { _instance.pointee.value = _shared.pointee.bounds.clamping(newValue) }
    }
  }
}

Then if you have a use site:

@Clamped(1..<1024) var count: Int

This would get lowered like so:

static var count$shared = Clamped<Int>.Shared(1..<1024)
var count$instance: Clamped<Int>.Instance
var count: Clamped<Int>.Composite {
  _read {
    yield Clamped.Composite(_shared: &count$shared,
                            _instance: &count$instance)
  }
  _modify {
    var temp = Clamped.Composite(_shared: &count$shared,
                                 _instance: &count$instance)
    yield &temp
  }
}
1 Like