// RUN: %target-run-simple-swift // TODO(TF-1254): Support and test forward-mode differentiation. // TODO(TF-1254): %target-run-simple-swift(-Xfrontend -enable-experimental-forward-mode-differentiation) // REQUIRES: executable_test import StdlibUnittest import DifferentiationUnittest var PropertyWrapperTests = TestSuite("PropertyWrapperDifferentiation") @propertyWrapper struct SimpleWrapper { var wrappedValue: Value // stored property } @propertyWrapper struct Wrapper { private var value: Value var wrappedValue: Value { // computed property get { value } set { value = newValue } } init(wrappedValue: Value) { self.value = wrappedValue } } struct Struct: Differentiable { @Wrapper @SimpleWrapper var x: Tracked = 10 @SimpleWrapper @Wrapper var y: Tracked = 20 var z: Tracked = 30 } PropertyWrapperTests.test("SimpleStruct") { func getter(_ s: Struct) -> Tracked { return s.x } expectEqual(.init(x: 1, y: 0, z: 0), gradient(at: Struct(), of: getter)) func setter(_ s: Struct, _ x: Tracked) -> Tracked { var s = s s.x = s.x * x * s.z return s.x } expectEqual((.init(x: 60, y: 0, z: 20), 300), gradient(at: Struct(), 2, of: setter)) // TODO: Support `modify` accessors (https://github.com/apple/swift/issues/55084). /* func modify(_ s: Struct, _ x: Tracked) -> Tracked { var s = s s.x *= x * s.z return s.x } expectEqual((.init(x: 60, y: 0, z: 20), 300), gradient(at: Struct(), 2, of: modify)) */ } struct GenericStruct { @Wrapper var x: Tracked = 10 @Wrapper @Wrapper @Wrapper var y: T var z: Tracked = 30 } extension GenericStruct: Differentiable where T: Differentiable {} PropertyWrapperTests.test("GenericStruct") { func getter(_ s: GenericStruct) -> T { return s.y } expectEqual(.init(x: 0, y: 1, z: 0), gradient(at: GenericStruct>(y: 20), of: getter)) func getter2(_ s: GenericStruct) -> Tracked { return s.x * s.z } expectEqual(.init(x: 30, y: 0, z: 10), gradient(at: GenericStruct>(y: 20), of: getter2)) func setter(_ s: GenericStruct, _ x: Tracked) -> Tracked { var s = s s.x = s.x * x * s.z return s.x } expectEqual((.init(x: 60, y: 0, z: 20), 300), gradient(at: GenericStruct>(y: 20), 2, of: setter)) // TODO: Support `modify` accessors (https://github.com/apple/swift/issues/55084). /* func modify(_ s: GenericStruct, _ x: Tracked) -> Tracked { var s = s s.x *= x * s.z return s.x } expectEqual((.init(x: 60, y: 0, z: 20), 300), gradient(at: GenericStruct>(y: 1), 2, of: modify)) */ } // TF-1149: Test class with loadable type but address-only `TangentVector` type. class Class: Differentiable { @differentiable(reverse) @Wrapper @Wrapper var x: Tracked = 10 @differentiable(reverse) @Wrapper var y: Tracked = 20 @differentiable(reverse) var z: Tracked = 30 } PropertyWrapperTests.test("SimpleClass") { func getter(_ c: Class) -> Tracked { return c.x } expectEqual(.init(x: 1, y: 0, z: 0), gradient(at: Class(), of: getter)) func setter(_ c: Class, _ x: Tracked) -> Tracked { var c = c c.x = c.x * x * c.z return c.x } // FIXME(TF-1175): Class operands should always be marked active. // This is relevant for `Class.x.setter`, which has type // `$@convention(method) (@in Tracked, @guaranteed Class) -> ()`. expectEqual((.init(x: 1, y: 0, z: 0), 0), gradient(at: Class(), 2, of: setter)) /* expectEqual((.init(x: 60, y: 0, z: 20), 300), gradient(at: Class(), 2, of: setter)) */ // TODO: Support `modify` accessors (https://github.com/apple/swift/issues/55084). /* func modify(_ c: Class, _ x: Tracked) -> Tracked { var c = c c.x *= x * c.z return c.x } expectEqual((.init(x: 60, y: 0, z: 20), 300), gradient(at: Class(), 2, of: modify)) */ } // From: https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#proposed-solution // Tests the following functionality: // - Enum property wrapper. @propertyWrapper enum Lazy { case uninitialized(() -> Value) case initialized(Value) init(wrappedValue: @autoclosure @escaping () -> Value) { self = .uninitialized(wrappedValue) } var wrappedValue: Value { // TODO(TF-1250): Replace with actual mutating getter implementation. // Requires differentiation to support functions with multiple results. get { switch self { case .uninitialized(let initializer): let value = initializer() // NOTE: Actual implementation assigns to `self` here. return value case .initialized(let value): return value } } set { self = .initialized(newValue) } } } // From: https://github.com/apple/swift-evolution/blob/master/proposals/0258-property-wrappers.md#clamping-a-value-within-bounds @propertyWrapper struct Clamping { var value: V let min: V let max: V init(wrappedValue: V, min: V, max: V) { value = wrappedValue self.min = min self.max = max assert(value >= min && value <= max) } var wrappedValue: V { get { return value } set { if newValue < min { value = min } else if newValue > max { value = max } else { value = newValue } } } } struct RealPropertyWrappers: Differentiable { @Lazy var x: Float = 3 @Clamping(min: -10, max: 10) var y: Float = 4 } PropertyWrapperTests.test("RealPropertyWrappers") { @differentiable(reverse) func multiply(_ s: RealPropertyWrappers) -> Float { return s.x * s.y } expectEqual(.init(x: 4, y: 3), gradient(at: RealPropertyWrappers(x: 3, y: 4), of: multiply)) } runAllTests()