Parseable interfaces: Conditional conformances with non-public conditions are problematic

I've run into a bit of a quandary with my part of the parseable interfaces work. Consider the following:

// MyKit.swift
public struct X<T> {}
private protocol Evil {}
extension X: Equatable where T: Evil {}
// main.swift
import MyKit
extension X: Equatable where T == Int {}
// even_worse.swift
import MyKit
extension X: Equatable {}

We don't allow overlapping conformances, but we don't allow multiple non-overlapping conditional conformances either. I think we have to print the extension in some way to show that the conformance is present even though it's not actually accessible, but I don't have a good way to do that. New attribute? New constraint kind that's not satisfiable? Something else?

("Allow multiple conformances" is not an acceptable answer here, at least not as part of the parseable interfaces feature.)

I'm particularly interested in what @Douglas_Gregor has to say, as well as my parseable interface collaborators @harlanhaskins and @Graydon_Hoare, but if anyone has ideas I'd like to hear 'em.

We could ban conditional conformances when the condition has narrower visibility than both the protocol and the type.

I had not considered that, and I shudder to think about taking it through the evolution process, but it's not a bad idea! It's a little tricky in this case because "visibility" also applies to @usableFromInline-ness here. You may have internal conditions, but unless they're @usableFromInline they may not be printable.

Introducing an interface syntax for "non-visible protocol" seems like the right thing. The ban is still against overlapping conformances—T: Evil and T == Int overlap because Int can be potentially conformed to Evil post-hoc, but different same-type constraints do not overlap, so you could have e.g. T == Evil and T == Int conformances. It seems like what you need to know therefore is whether there is any protocol-constrained conformance in order to disallow same-type-constrained conformances to the same protocol.

It does seem to be a direction opposite where the community would like to go, with retroactive conformances, and conditional conformances in particular. There's been discussion about removing the "conformance must be as visible as the protocol" restriction before, right?

1 Like

The more I think about it, the more an _UnsatisfiableConstraint layout constraint sounds easiest to me. Otherwise I have to track down all the places where we decide if a conformance is okay to use and add another check to them.

extension X: Equatable where T: _UnsatisfiableConstraint {}

Alternately, I could do something ridiculous like this:

extension X: Equatable where T == _NonexistentType {}
@usableFromInline internal enum _NonexistentType {}

I don't like the implication of declaring a type that doesn't actually exist, but it would solve the problem in practice without having to change the rest of the compiler at all.

The == _NonexistentType approach wouldn't have the effect you want, though, which is to prevent overlapping conditional conformances, since that would still allow for other same-type-constrained conformances that do formally overlap with the invisible protocol-constrained conformance. Maybe you could constrain to : _NonexistentProtocol though.

2 Likes

Thanks, Joe, and thanks for the suggestions, Nevin and Jon! Implemented this "simplest possible thing" in [ParseableInterfaces] Handle unsatisfiable conditional conformances by jrose-apple · Pull Request #20433 · apple/swift · GitHub.