It is true for normal parameters, but we're no longer talking about normal parameters. If we extend the synthesized code for api property wrappers, we would align the rules and simplify the design of the synthetic code. If we say that a wrapper on a parameter always desugars to a local property wrapper which shadows the parameter, there is clearly an opportunity to allow mutation.
In fact, you (the proposal authors) already propose to synthesise setters if the PW's wrappedValue / projectedValue
has a nonmutating
setter. Sure this indicates that there will be an effect that the user can trigger. However for super simple PWs there will be no effect except that you will mutate the value in the backing storage.
As mentioned by someone in the previous thread, this isn't a basic feature anymore and I don't see the requirement to artificially restrict it. Api or implementation detail property wrapper, both should just produce a local property wrapper variable. That is far more flexible. If you recall in the early stages of the original PW proposal we had PWs that were forced to have a generic type parameter that represented the type of wrappedValue
. That restriction was totally unnecessary and would have caused more issues than the design we have today.
Here is a quick but simple example:
@propertyWrapper
struct Clamping<Value> where Value: Comparable {
var value: Value
let range: ClosedRange<Value>
init(wrappedValue: Value, range: ClosedRange<Value>) {
precondition(range.contains(wrappedValue))
self.value = wrappedValue
self.range = range
}
var wrappedValue: Value {
get {
return value
}
set {
if newValue < range.lowerBound {
value = range.lowerBound
} else if newValue > range.upperBound {
value = range.upperBound
} else {
value = newValue
}
}
}
}
@propertyWrapper
struct W {
@Clamping
var wrappedValue: Double
var projectedValue: Int {
get { Int(wrappedValue) }
set { wrappedValue = Double(newValue) }
}
init(wrappedValue: Double) {
self._wrappedValue = Clamping(wrappedValue: wrappedValue , range: 0 ... 10)
}
init(projectedValue: Int) {
self.init(wrappedValue: Double(projectedValue))
}
}
// func foo(@W value: Double)
func foo(value _value: W) {
// the key alignment between api and implementation detail PWs.
var _value: W = _value
// syntheized computed property
var value: Double {
get { _value.wrappedValue }
set { _value.wrappedValue = newValue }
}
// syntheized computed property
var $value: Int {
get { _value.projectedValue }
set { _value.projectedValue = newValue }
}
print(value, $value)
value = 5.5
print(value, $value)
$value = 4
print(value, $value)
}
foo(value: 20.5)
// foo(value: W(wrappedValue: 20.5))
// prints:
// 10.0 10
// 5.5 5
// 4.0 4
foo($value: 2)
// foo(value: W(projectedValue: 2))
// prints:
// 2.0 2
// 5.5 5
// 4.0 4
There is no reason to artificially restrict code that is "just sugar" for the most part.
As for the implementation detail PW from the proposal:
func insert(@Logged text: String) { ... }
The above code is sugar for:
func insert(text: String) {
@Logged var text = text
}
which the compiler further desugars to generate:
func insert(text: String) {
var _text: Logged<String> = Logged(wrappedValue: text)
var text: String { _text.wrappedValue }
}
That is incorrect. In Swift 5.4 the following code
func insert(text: String) {
@Logged var text = text
}
would already desugar to this:
func insert(text: String) {
var _text: Logged<String> = Logged(wrappedValue: text)
var text: String {
get { _text.wrappedValue }
set { _text.wrappedValue = newValue }
}
}
As I wrote this example I realized that one example in the proposal seems to be incomplete.
// The compiler will synthesize computed text and $text variables in the body of copy(text:):
func copy(_text: Traceable<String>) {
// shouldn't this be?
func copy(text _text: Traceable<String>) {