Protocol Witness Matching Mini-Manifesto

Thank you, I have fixed it now!

2 Likes

This rule would likely be arbitrary subtyping, which includes optionals, like it is with class overrides.

4 Likes

Moving the discussion from the other thread into here:

So either the default argument feature from above cannot be reduced to zero arguments or I still don‘t understand something here.

protocol P {
  static func foo() -> Self
}

struct S: P {
  static func foo(_ s: S = .init()) -> S { s } // #1 is this okay?
}

enum E: P {
  case foo(Void = ()) // if #1 is okay then this should be okay as well
}

Are you thinking of case foo(Void = ()) to be the same as just a case foo (or do you want it to be the same)? Because they are not the same at the moment:

enum Foo: Equatable {
  case bar1(Void = ())
  case bar2

  // Probably not needed when tuples are equatable
  static func == (lhs: Self, rhs: Self) -> Bool {
	switch (lhs, rhs) {
	  case (.bar1, .bar1): return true
	  case (.bar2, .bar2): return true
	  default: return false
    }
  }
}

let b1 = Foo.bar1()
let b2 = Foo.bar2

b1 == .bar1 // error
b1 == .bar1() // okay
b2 == .bar2 // okay, as usual

Again, I‘m not talking about a payload less enum case at all. My thinking path is that case foo(Int = 42) would satisfy static func foo() -> Self if #1 in the above example would be valid.

Then you can do:

func test<T: P>(_: T.Type) -> T {
  T.foo()
}

let e = test(E.self)
// E is from an example upthread, it‘s payload has Void not Int, 
// but it does not matter, in can be anything
e == .foo() // assuming Void is Equatable

Okay, sorry I misunderstood what you were talking about. I think it might be okay for that to work (i.e. a function with all default arguments can witness a function which takes no arguments).

1 Like

@suyashsrijan I think this would be important to incorporate into the text of the mini-manifesto. There is also a prior PR, never merged and closed for inactivity, to make this work in the case of optionals, which would be good to link to for reference.

2 Likes

Ah yes, I remember that PR. I have updated the text now, thank you!

One thing to keep in mind: as the witness matching rules get more complex, it has a spillover effect on associated type inference rules becoming more complex and possibly less predictable.

4 Likes

Is that still true if we push through the changes to inference that @Douglas_Gregor pitched last year?

Are you referring to this? I do remember associated type inference concerns being raised in the past whenever the topic of covariance has come up. I’m not familiar with the implementation so I’m not sure how exactly one would go about solving it (maybe Doug has some ideas?) but I’ll add Slava’s point as a note in the document so it can be referred to in the future.

Nope, I was referring to this: [RFC] Associated type inference. Looks like it was actually a couple years ago now.

1 Like

I still think we need to nail down associated type inference to make it more predictable and more efficiently implementable. Loosening the witness matching requirements might make the current system a little more complicated, and we should be mindful of that... but we shouldn't let it get in the way of improving on this area.

Doug

4 Likes

I propose to write a pattern below in Introduction section.

protocol P1 {}

struct S1: P1 {}

protocol P2 {
    func foo(_ p1: S1)
}

struct S2: P2 {
    func foo<X: P1>(_ p1: X) {}
}

Generic function can conform to requirements.
This technique is useful sometime.

1 Like

The introduction section already has a few examples (see the doc here: [Docs] Add the Protocol Witness Matching mini-manifesto by theblixguy · Pull Request #29617 · apple/swift · GitHub) but I could add this as well.

I have added it now!

1 Like

Its nice. Thank you.

1 Like

@suyashsrijan one alternative to properties with function type would be to just allow functions and variables interchangably for functions with no parameters. I.e. the following would build with no error.

protocol One {
  var integer: Int { get }
}

class Alpha: One {
  func integer() -> Int {
    return 1
  }
}

protocol Two {
  func integer() -> Int
}

class Bravo: Two {
  var integer: Int {
    2
  }
}
1 Like

I see the PR is still active, so I'm reviving the conversation.

Now with SE-302 introducing @unchecked conformances, would it be feasible to say any @unchecked conformance possibly falls back to a @dynamicMemberLookup method dispatch and be the protocol witness that way? or was it meant for a totally different context?

I can imagine this being useful for a test mocking/stubbing library, where you can conform a Stub class with @unchecked conformace to an existing protocol, so it can adopt any protocol with ease via its subscript

extension Stub: @unchecked SomeExistingProtocol {
    subscript<T>(dynamicMember member: String) -> T { ... }
}

I understand the review of the PR has paused for a while. I just would like to bring up another mismatching scenario that isn't mentioned in the doc and is probably worth considering.

protocol Proto {
    var x: Int? { get }
}

struct Test: Proto {
    var x: Int
}

In my opinion this example meets the following criteria of the doc perfectly:

it should be based on what the protocol requirement semantically requires.

I think supporting the above example helps to remove boilerplate code (though I have no idea how complex it would be to add support for it).