I think you have correctly identified Swift limitations relating to your problem as stated. However it may be worth exploring some "other" solutions. This will require making some assumptions about what you're trying to do though.
We are developing a distributable package (framework)
If this Status type is a type to represent a situation in your framework, and you are the one constructing it, you could put your framework type in the .failed(FrameworkError) case. That type can conform to Error so applications can throw it, and if it needs to wrap an underlying system error it can do so, and deal with comparing them in some way appropriate for your framework.
An underlying issue here is that there's not really a reasonable way to compare errors that is universally appropriate for every context. In Cocoa for example, we might do this by domain and code, but even then the same code can have different causes, which is maybe important, or maybe not. In Swift we're more likely to pack random information about what was happening into the error, which maybe makes error distinct or maybe not.
As a result, one way to look at your problem is that it requires "taking a position" on what it means to equate your type, or the underlying Error, because there is no "natural" meaning.
We definitely want the enum to be Equatable, we've found that usage of it is much too tedious if not.
One position is to say "this type ought to conform to Equatable to get some convenient behavior/usage, but we don't especially what kind of implementation it is to compare the errors". Behind this door you could implement something like lhs.failed(_) == rhs.failed(_) to consider all failed statuses equal (or unequal, if that's more expected) regardless of payload.
Maybe a middle-ground would be to extend your UnconstrainedEquatable with an arbitrary implementation for non-Equatables, and use your other conformance for Equatable
extension UnconstrainedEquatable {
func isEqual<O>(to other: O) -> Bool {
if let _ = self as? O {
return true
}
return false
}
}
Again a key issue here is taking a position on what it "means" for your Status type to be equal.
Another thing worth bringing into this discussion is the PAT problem, which you would encounter if you tried to use Swift.Equatable in your field
protocol EquatableError: Error,Equatable {}
enum Status {
case failed(EquatableError) //Protocol 'EquatableError' can only be used as a generic constraint because it has Self or associated type requirements
}
This is a well-known problem in Swift's typesystem, related to lack of existentials for PATs, which Equatable is one example. There are a bunch of workarounds for this problem, and we regularly discuss if the pattern should be supported.
But your BasicError and UnconstrainedEquatable are more-or-less a version of Equatable without the PAT, which is one of the workarounds to the PAT issue. So in terms of exploring other solutions to your problem, the space looks a lot like other workarounds to the PAT problem.
Other workarounds would include, generics
enum Status<E: Error, Equatable> {
case failed(E)
}
Declaring your own PAT
enum ExampleError: Error,Equatable {}
protocol Status {
associatedtype Error: Equatable, Swift.Error
static func failed(_ arg: Error) -> Self
}
enum StatusImplementation: Status {
typealias Error = ExampleError
case failed(Error)
}