swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.28k stars 10.33k forks source link

[SR-11209] Conditional projected property #53608

Open 7156ccc1-d3b8-4460-882a-3f5802920f8f opened 5 years ago

7156ccc1-d3b8-4460-882a-3f5802920f8f commented 5 years ago
Previous ID SR-11209
Radar None
Original Reporter @DevAndArtist
Type Bug
Environment Apple Swift version 5.1 (swiftlang-1100.0.257.2 clang-1100.0.31.3) Target: x86_64-apple-darwin19.0.0
Additional Detail from JIRA | | | |------------------|-----------------| |Votes | 0 | |Component/s | Compiler | |Labels | Bug, PropertyWrappers | |Assignee | None | |Priority | Medium | md5: 0a404a46a1f17b44ccbac70cb9f79037

Issue Description:

Related discussion: https://forums.swift.org/t/conditional-projected-property/27152

This code should compile and behave as expected:

@propertyWrapper
struct Wrapper<Value> {
  var wrappedValue: Value
}

extension Wrapper where Value == Int {
  var projectedValue: String {
    return "\(wrappedValue)"
  }
}

struct S {
  @Wrapper var a = "swift"
  @Wrapper var b = 42

  func foo() {
//    print(self.$a)
    print(self.$b)
  }
}

let s = S()
s.foo()

Because something like this does already work:

struct G<T> {}

extension G where T == Int {
  var p: Int {
    return 42
  }
}

extension G where T == String {
  var p: String {
    return "swift"
  }
}

let g_1 = G<Int>()
let g_2 = G<String>()

print(g_1.p, g_2.p)
belkadan commented 5 years ago

cc @DougGregor

7156ccc1-d3b8-4460-882a-3f5802920f8f commented 5 years ago

Here is a concrete example where I need it. I want to extend `DelayedMutable` property wrapper which by itself does not project anything with a projecting behavior if the generic `Value` is some `Binding`. Please don't get confused with SwiftUI's Binding, this is a custom re-implementation for usage in UIKit on older OS's.

  // FIXME: Change to
  // ```
  // @DelayedMutable
  // public private(projection) var isEnabled: Binding<Bool>
  // ```
  private var _isEnabled = DelayedMutable<Binding<Bool>>()
  public var isEnabled: Binding<Bool> {
    get {
      return _isEnabled.wrappedValue
    }
    set {
      // FIXME: Move the following code to `willSet` when possible.
      do {
        setNeedsLayout()
        _updates[^\.isEnabled] = newValue.transaction.disablesAnimations
      }
      // Update the binding.
      _isEnabled.wrappedValue = newValue
    }
  }
  private var _$isEnabled: Bool {
    return _isEnabled.projectedValue
  }
7156ccc1-d3b8-4460-882a-3f5802920f8f commented 4 years ago

Ping @DougGregor. Is this a natural implication of the the property wrapper design?

DougGregor commented 4 years ago

I think it's reasonable for this to work, yes.