Skip to content

[SR-11255] Mutating property wrappers with _modify accessor result in spurious 'get' accesses when mutated #53656

@swift-ci

Description

@swift-ci
Previous ID SR-11255
Radar None
Original Reporter luiz (JIRA User)
Type Bug
Status Closed
Resolution Done
Environment

Apple Swift version 5.1 (swiftlang-1100.0.266.1 clang-1100.0.32.1)
Target: x86_64-apple-darwin19.0.0
macOS Catalina 10.15 Beta (19A526h)

Additional Detail from JIRA
Votes 0
Component/s Compiler
Labels Bug, PropertyWrappers
Assignee @theblixguy
Priority Medium

md5: a8b0cafcd9338e5a48ba93f8a7954ca9

Issue Description:

In the following code where a property wrapper implements a '_modify' accessor, I expect the 'get' accessor to not be invoked at all when mutating MyWrapper as a property wrapper, just like it doesn't when mutating MyWrapper as an ordinary type, but modifying 'self.wrapper' or 'self.$wrapper' result in a 'get' access followed by a '_modify' access, resulting in potentially duplicated work; note that a modifying access to 'self._wrapper.wrappedValue' does not result in a spurious 'get' access.

@propertyWrapper
public final class MyWrapper<T> {
    @usableFromInline
    var getterInvokeCount = 0
    @usableFromInline
    var modifyInvokeCount = 0
    
    @usableFromInline
    var _value: T

    @inlinable
    public var wrappedValue: T {
        get {
            getterInvokeCount += 1
            return _value
        }
        _modify {
            modifyInvokeCount += 1
            yield &_value
        }
    }
    
    @inlinable
    public var projectedValue: T {
        get {
            getterInvokeCount += 1
            return _value
        }
        _modify {
            modifyInvokeCount += 1
            yield &_value
        }
    }

    @inlinable
    public init(wrappedValue: T) {
        self._value = wrappedValue
    }
}

class Test {
    @MyWrapper var wrapped: Set<Int> = []
    var nonWrapped: MyWrapper<Set<Int>> = MyWrapper(wrappedValue: [])
    
    func run() {
        for _ in 0..<100 {
            wrapped.insert(1)
            nonWrapped.wrappedValue.insert(1)
        }
        
        // false :(
        print("_wrapped.getterInvokeCount == 0:", _wrapped.getterInvokeCount == 0)
        // true
        print("_wrapped.modifyInvokeCount == 100:", _wrapped.modifyInvokeCount == 100)
        // true
        print("nonWrapped.getterInvokeCount == 0:", nonWrapped.getterInvokeCount == 0)
        // true
        print("nonWrapped.modifyInvokeCount == 100:", nonWrapped.modifyInvokeCount == 100)
        
        _wrapped.getterInvokeCount = 0
        _wrapped.modifyInvokeCount = 0
        
        for _ in 0..<100 {
            $wrapped.insert(1)
        }
        
        // false :(
        print("_wrapped.getterInvokeCount == 0:", _wrapped.getterInvokeCount == 0)
        // true
        print("_wrapped.modifyInvokeCount == 100:", _wrapped.modifyInvokeCount == 100)
        
        _wrapped.getterInvokeCount = 0
        _wrapped.modifyInvokeCount = 0
        
        for _ in 0..<100 {
            _wrapped.wrappedValue.insert(1)
        }
        
        // true :)
        print("_wrapped.getterInvokeCount == 0:", _wrapped.getterInvokeCount == 0)
        // true
        print("_wrapped.modifyInvokeCount == 100:", _wrapped.modifyInvokeCount == 100)
    }
}

Test().run()

Metadata

Metadata

Assignees

Labels

bugA deviation from expected or documented behavior. Also: expected but undesirable behavior.compilerThe Swift compiler itselfproperty wrappersFeature: property wrappers

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions