Kotlin and Scala solve the problem pretty decently. Equals and hashCode are automatically generated for data/case classes. I've never encountered a messed up equals in Scala and only very rarely felt the need to override the generated equals, and then you make a very conscious choice to do it.
C# does it even better, there is a IEquatable<T> interface that defines the `bool Equals(T other)` method. It partially solves the irritating universal equality.
If I would design Java today Object would have no methods and there would be a stdlib interface defining `equals(other: T)`, and data classes would automatically implement that interface
This polymorphic equality is the default in some functional languages, like OCaml. It's fine for simple cases, but you really do need overloadable equality. Equality as a method in OO is a problem because of the asymmetry though, and often an even bigger problem because such languages typically permit pervasive null.
The problems you mentioned:
Classes could change equals to accept subclasses. Then subclasses could not alter equality themselves.
T implementing `equals(other: U)` for some U solves a lot of other use-cases, but could make equality asymetrical.
Reference equality would still exist, but would not be the default, e.g. Java's `equals` is Scala's `==`, and Java's `==` is Scala's `eq` operator (which you almost never use)
So I completely agree, that having to implement basic attributes like 'equals' or 'comparable' is no good idea. Instead I would favor some logic which assumes some natural situation and lets you implement a custom logic if you want to do it.
Nevertheless, I think those problems just show some of the inconsistencies Go comes with. Don't get me wrong, I love Go and I like the way the Go devs work. It is just that I am worried that we will end up with Go 2.0 adding Generics without solving the issues within the otherwise pretty nice Interface system.
In Java, I can't count the number of times I've debugged either correctness bugs or 'merely' horrible performance bugs based on incorrect implementations of Equals or Hashcode methods. Implementing a non-commutative Equals method is a colossal footgun, and incredibly irritating to debug, and yet somehow it happens time and again.
And there are other, subtler, arguably "correct" and yet but extremely unwise things one can when do given Java-like maps implemented on the Equals and Hashcode interface methods. For example, one can define two types T1 and T2 which both claim to be (commutatively) equal, and define some interface TX which is implemented by T1 and T2. Using a Map<TX,Object> and inserting members by T1 and T2 now has an interesting form of semi-defined behavior: if inserting "equal" keys, whichever type was used first will be persisted. Imagine doing this in some concurrent map, and imagine that T1 is 8 bytes and T2 is 400 bytes; enjoy debugging those occasional OOMs.
I've been very happy not to ever have this particular problem when defining the key types in my Go maps.