EDIT: You can ignore the ideas from this post, see my reasoning a few posts bellow, the proposal already suggests a more flexible future direction.
@Douglas_Gregor thank you again for the hard work. Do you think the following would be a reasonable generalization to consider?
Currently you require projectedValue to have the same access level as the property wrapper type. Together with the synthetization rule for the $ prefixed projected value we get an equal exposure.
Can we make this behavior a little lighter?
Here is what I have in mind:
- Do not require
projectedValue to have the same access level as the property wrapper
- If the compiler can see
projectedValue it will project a $ prefixed property but its exposure can be controlled
- The projected property can only have a lower or equal access level then the wrapped property
In your own module you could have a public property Wrapper A with an internal property projectedValue. Then you apply that wrapper to a public property on the public type B. The compiler will generate internal var $foo: Value and limit the exposure to remain internal in B.
// In module X
@propertyWrapper
public struct A<Value> {
...
internal var projectedValue: Value { ... }
}
// In module X - in file `B.swift`
public struct B {
@A public var property_1: Value
// results in
public var property_1: Value { ... }
internal var $property_1: Value { ... }
private var _property_1: A<Value> = ...
}
Such an approach do not require any public(wrapper) addition in the future, but only require the property wrapper designer to apply the correct access level to projectedValue to control the exposure.
Also if C is a property wrapper type that is not nested in B and it has a private projectedValue then the compiler won‘t generate a projection in B at all.
// In module X
@propertyWrapper
public struct C<Value> {
...
private var projectedValue: Value
}
// In module X - in file `B.swift`
public struct B {
@C public var property_2: Value
// results in
public var property_2: Value { ... }
private var _property_2: C<Value> = ...
}
If D has a private projectedValue but is nested in B then the compiler will create a private projected property in B.
// In module X - in file `B.swift`
public struct B {
@propertyWrapper
public struct D<Value> {
...
private var projectedValue: Value
}
@D public var property_3: Value
// results in
public var property_3: Value { ... }
private var $property_3: Value { ... }
private var _property_3: D<Value> = ...
}
If E has a fileprivate projectedValue and it‘s in the same file as B the compiler can again synthetize a projected value.
// In module X - in file `B.swift`
@propertyWrapper
public struct E<Value> {
...
fileprivate var projectedValue: Value
}
public struct B {
@E public var property_4: Value
// results in
public var property_4: Value { ... }
fileprivate var $property_4: Value { ... }
private var _property_4: E<Value> = ...
}
These are just the same rules we have today and we could completely control the projection with it today. No additional syntax would be required.
If F is public and has a public projectedValue then the compiler will project a public property.
// In module X - in file `B.swift`
@propertyWrapper
public struct F<Value> {
...
public var projectedValue: Value
}
public struct B {
@F public var property_5: Value
// results in
public var property_5: Value { ... }
public var $property_5: Value { ... }
private var _property_5: F<Value> = ...
}
I do think that we should control the projection right from the beginning as it would be a breaking change to hide leaked projections later on. And having an ability to have internal projections that do not leak but provide you the $ syntax would be a great benefit.