Then again that would mean that the nil identifier would be coerced into a typed nil and we would check for the nilness of what is inside an interface in any(somepointer) == nil.
wrt the current behavior, it also makes sense to have a nil value that remains untyped. But in many other cases we do have that automatic inference/coercion, for instance when we set a pointer to nil.(p = nil)
That's quite subtle and that ship has sailed though.
But any(nil) == nil returns true like you'd expect.
The reason that any((*int)(nil)) == nil is false is the same reason that any(uint(2)) == 2 is false: interfaces compare values and types.
any(uint(2)) == int(2) should return false indeed however.
Importantly, untyped constants don't exist at runtime, and non-primitive types like interfaces aren't constants, so any(uint(2)) == 2 can't behave the way you want without some pretty significant changes to the language's semantics. Either untyped constants would have to get a runtime representation--and equality comparisons would have to introduce some heavyweight reflection--or else interfaces would have to be hoisted into the constant part of the language--which is quite tricky to get right--and then you just end up in a situation where any(uint(2)) == 2 works but x == 2 doesn't when x turns out to be any(uint(2)) at runtime.
That means following the type pointer of LHS, switching on its underlying type (with 15 valid possibilities [1]) or similar, and then casting either RHS to LHS's type, or LHS to the untyped representation, and finally doing the equality check. Something like this (modulo choice of representation and possible optimizations):
import ("math/big"; "reflect")
type untypedInt struct { i *big.Int }
func (x untypedInt) equals(y any) bool {
val := reflect.ValueOf(y)
if val.Type() == reflect.TypeOf(x) {
return x.i.Cmp(val.Interface().(untypedInt).i) == 0
} else if val.CanInt() {
if !x.i.IsInt64() { return false }
return x.i.Int64() == val.Int()
} else if val.CanUint() {
if !x.i.IsUint64() { return false }
return x.i.Uint64() == val.Uint()
} else {
var yf float64
if val.CanFloat() {
yf = val.Float()
} else if val.CanComplex() {
yc := val.Complex()
if imag(yc) != 0 { return false }
yf = real(yc)
} else { return false }
xf, acc := x.i.Float64()
if acc != big.Exact { return false }
return xf == yf
}
}
[1]: Untyped integer constants can be compared with any of uint8..uint64, int8..int64, int, uint, uintptr, float32, float64, complex64, or complex128 type intType interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 }
func equals[I intType](x I, y any) bool {
switch val := y.(type) {
case I: return x == val
case untypedInt: return val.i.IsInt64() && val.i.Int64() == int64(x)
default: return false
}
}
And this would need a separate specialization for unsigned integers, floats, and complex numbers. This approach saves us from having to introspect the underlying type at runtime, but the example is incomplete. We also have float and complex untyped constants, so now each concrete type has to switch on all of the untyped constant forms it compares with. Still, it might be faster, though I'm not sure how much it reduces code bloat in practice (it's nice to not need the reflect package though).[edit: side note, I was trying to actually write out all the code that would be needed, and I discovered that you can't call real or imag on generic types: https://github.com/golang/go/issues/50937]