SE-0267 — `where` clauses on contextually generic declarations

Ah, right, of course it's "or older". As far as I understand, ABIs talk to each other through the runtime. If we emit two declarations with different mangled names, older frameworks will pick the old one and new ones, obviously, - the new canonicalized one. But is it absolutely necessary to emit a second declaration, that is, would it be viable to have two mangled names for one thing, and have the runtime decide which one to look for in-place?

Sometimes a client talks to a library through the runtime and sometimes a client talks to a library directly. "ABI" ("application binary interface") covers both aspects of this, and unfortunately both need to be able to uniquely identify declarations (which is what mangled names do).

I see, thanks! Sorry if this is another dumb question: why can we not "point" the old mangled name to the new one, similar to how we use type aliases for obsoleted types in the Standard Library. The declaration won't have a unique name, but we can still uniquely identify it.

1 Like

Again, that would work for functions, initializers, and subscripts, but types appear inside other mangled names. Mangled names for types are also used to communicate with the runtime, and I'm pretty sure the old runtime wouldn't be able to find types under the new names even if you emitted symbols for them.

1 Like

Yes, I understand now that types are out of question, I was thinking if we can avoid emitting a second declaration just to have that old mangled name around. So it was "emitting a copy under the old mangling vs emitting the original mangled name and somehow pointing it to the canonical one".

The review period for this proposal ended last Thursday, October 31, 2019. The core team has decided to accept this proposal with one modification. The proposal addresses the issue of conditional protocol requirements by disallowing constraints involving Self from being applied to protocol requirements, but it should also do so for non-final class methods, to avoid the same problem with conditional dynamically-dispatched methods in classes.

Brent Royal-Gordon raised the valid concern that this change exposes more opportunities for surprising ABI breaks:

However, as Jordan Rose noted, this problem already exists in the mangling implementation today:

So this is not a new problem, and the Core Team does not think that SE-267 makes it substantially worse. The ABI concern should not prevent implementation of this proposal.

Thanks to everyone who participated in the review!

7 Likes

Are you referring to something like this (this actually compiles)?

class Class<U> {
  func foo<T>(arg: T) where U: Equatable {
    print("Class")
  }
}

class Sub<U>: Class<U> {
  override func foo<T>(arg: T) where U: Sequence {
    print("Sub")
  }
}

let bar: Class = Sub<Int>()
bar.foo(arg: 0) // "Sub"

Edit

Oops, it gets diagnosed correctly on master.

Yep, this won't compile on master anymore, I fixed a long standing bug with generic signatures being ignored while matching overrides.

/Users/suyashsrijan/Desktop/test.swift:2:8: error: unexpected note produced: overridden declaration is here
  func foo<T>(arg: T) where U: Equatable {
       ^
/Users/suyashsrijan/Desktop/test.swift:8:17: error: unexpected error produced: overridden method 'foo' has generic signature <U, T where U : Sequence> which is incompatible with base method's generic signature <U, T where U : Equatable>; expected generic signature to be <U, T where τ_0_0 : Equatable>

If that's accepted by the compiler, it's a bug, because the override wouldn't apply for all subclass instances, and as your example shows, it would get unsoundly invoked on objects that don't match the subclass method constraints.

1 Like

Nice! I will make sure it does so for non-generic members too.

1 Like

Does the implementation ban overloading methods that only differ in one generic constraint?

struct SomeWrapper<Wrapped> {
  let wrapped: Wrapped
}

protocol HasIdentity {
  static func ===(lhs: Self, rhs: Self) -> Bool
}

extension SomeWrapper: Equatable {
  static func ==(lhs: SomeWrapper<Wrapped>, rhs: SomeWrapper<Wrapped>) -> Bool where Wrapped: Equatable {
    return lhs.wrapped == rhs.wrapped
  }

  static func ==(lhs: SomeWrapper<Wrapped>, rhs: SomeWrapper<Wrapped>) -> Bool where Wrapped: HasIdentity {
    return lhs.wrapped === rhs.wrapped
  }
}

Nope, you can overload just like you would via constrained extensions.

But then this is a bug right? As via @Douglas_Gregor proposal this would create a workaround for banned overlapping implementation of protocol requirements, or am I wrong here?

Original example:

https://github.com/apple/swift-evolution/blob/master/proposals/0143-conditional-conformances.md#overlapping-conformances

1 Like

I will have to test how this compiles before jumping into further conclusions, but what I expect is a conformance error, since the conformance is not actually stated as conditional*, and the == signatures do not match the requirement signatures.

* conformance when <constraints> { potential witnesses }
vs conformance { pot. witnesses }

1 Like

I just tested this and luckily the conformance error is raised as expected. Conformances certainly deserve some test coverage though, so thanks a lot for calling this out!

One thing worth noting: the following simplified version will compile due to the synthesized conformance. The explicit == are just overloads, and they cannot be dispatched to dynamically.

struct SomeWrapper<Wrapped> {}

extension SomeWrapper: Equatable {
  static func ==(
    lhs: SomeWrapper<Wrapped>, rhs: SomeWrapper<Wrapped>
  ) -> Bool where Wrapped: Equatable {
    print("overload")
    return true
  }
}
let a = SomeWrapper<Int>()
let b = SomeWrapper<Int>()

func areEqual<T: Equatable>(_ lhs: T, _ rhs: T) -> Bool {
   return lhs == rhs
}

print(b == a) // overload, true
print(areEqual(a, b)) // true
1 Like

We should probably then warn in this circumstance (to silence the warning, separate the extension declaring conformance from the extension implementing the non-conforming overload).

3 Likes

Since synthesized conformances can only be declared internally, we could suggest to declare the conformance directly on the type instead. Stating these conformances on extensions is generally a source of confusion anyway.

I actually don’t know if that’s as clear of a signal of intent (not that anything is foolproof). I see many examples where people declare conformances on the type and then implement their requirements elsewhere.

Do you propose for the silencing fixit to move the conformance to an empty extension when the overload and synthesized conformance are both declared directly on the type?

Mu.

Generally, a fix-it offers to address the inferred underlying problem. It is usually a manual operation to silence the warning without fixing the inferred problem in situations where there’s a false positive. I don’t think a “silencing fix-it” is in general a wise idea and I don’t think it’s usually offered in Swift (the “it” to be fixed is the code, not the presence of the warning).

We now have near-miss diagnostics for protocol conformances—i.e., warnings about a member resembling but not matching a protocol requirement in an extension stating conformance. The way to silence that warning is to separate the conformance from the member. I don’t think we offer a fix-it there; and if we did, it should offer to correct the typo, not to separate the member from the conformance.

I surmise here that in the vast majority of cases the implementation will be unwanted (because it doesn’t do what the author thought it would) and the conformance is wanted. Hence the warning to begin with. We shouldn’t just delete code that can’t be trivially recovered as part of a fix-it though.

A workable fix-it might propose that the user delete any generic constraints so that the custom logic is used under all circumstances. (That is, if an automated fix-it is desired at all.)

But I would not offer a “fix-it” that silences the warning without actually fixing any potential problems in the code.

Terms of Service

Privacy Policy

Cookie Policy