What you miss is that the (equality) comparison operator does not require its operands to be of the same type. The requirement is according to the Spec: Comparison operators:
In any comparison, the first operand must be assignable to the type of the second operand, or vice versa.
Yes, type equality automatically gives the assignability attribute, but there are other cases:
A value x
is assignable to a variable of type T
("x
is assignable to T
") in any of these cases:

x
's type is identical to T
.
x
's type V
and T
have identical underlying types and at least one of V
or T
is not a defined type.

T
is an interface type and x
implements T
.

x
is a bidirectional channel value, T
is a channel type, x
's type V
and T
have identical element types, and at least one of V
or T
is not a defined type.

x
is the predeclared identifier nil
and T
is a pointer, function, slice, map, channel, or interface type.

x
is an untyped constant representable by a value of type T
.
The highlighted rule applies here. v1
, v2
and v3
all have the same underlying type (which is struct { name string }
), and in the v1 == v3
and v2 == v3
comparisons only v1
's type is defined. So they are assignable, and from there, the comparison rule that applies to struct
values you quoted clearly explains why the result is true
:
Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding nonblank fields are equal.
There's nothing "shocking" about this if you think about it. You're not comparing the types, you're comparing the values. What may not be intuitive is when the comparison may be used, but the rules are listed clearly and cleanly at assignability.
Another similar example (try it on the Go Playground):
var i interface{} = 3
var j int = 3
fmt.Println(i == j) // Comparing interface{} with int: true
We're comparing values of interface{}
and int
types (even more "distant" that your struct types), yet this is valid and the result is true
and there's nothing shocking about that either, on the contrary, we'd expect that. int
is assignable to a variable of type interface{}
(everything is assignable to interface{}
). When comparing them, an implicit interface{}
value will be created that wraps j
, and those 2 interface values will be compared according to this rule:
Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil
.
Notes:
Assignability is a requirement but not a satisfactory condition to comparability. Further requirements are outlined in the Spec: Comparison operators that also must be met, based on the types of the operands:
The equality operators ==
and !=
apply to operands that are comparable. The ordering operators <
, <=
, >
, and >=
apply to operands that are ordered. These terms and the result of the comparisons are defined as follows:
 Boolean values are comparable. Two boolean values are equal if they are either both
true
or both false
.
 Integer values are comparable and ordered, in the usual way.
 Floatingpoint values are comparable and ordered, as defined by the IEEE754 standard.
 Complex values are comparable. Two complex values
u
and v
are equal if both real(u) == real(v)
and imag(u) == imag(v)
.
 String values are comparable and ordered, lexically bytewise.
 Pointer values are comparable. Two pointer values are equal if they point to the same variable or if both have value
nil
. Pointers to distinct zerosize variables may or may not be equal.
 Channel values are comparable. Two channel values are equal if they were created by the same call to make or if both have value
nil
.
 Interface values are comparable. Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value
nil
.
 A value
x
of noninterface type X
and a value t
of interface type T
are comparable when values of type X
are comparable and X
implements T
. They are equal if t
's dynamic type is identical to X
and t
's dynamic value is equal to x
.
 Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding nonblank fields are equal.
 Array values are comparable if values of the array element type are comparable. Two array values are equal if their corresponding elements are equal.
A comparison of two interface values with identical dynamic types causes a runtime panic if values of that type are not comparable. This behavior applies not only to direct interface value comparisons but also when comparing arrays of interface values or structs with interfacevalued fields.
Slice, map, and function values are not comparable. However, as a special case, a slice, map, or function value may be compared to the predeclared identifier nil
. Comparison of pointer, channel, and interface values to nil is also allowed and follows from the general rules above.
As a trivial example the spec explicitly states slice values are not comparable:
i1, i2 := []int{1, 2}, []int{1, 2}
fmt.Println(i1 == i2)
// invalid operation: i1 == i2 (slice can only be compared to nil)
Even though i1
and i2
have identical types, and therefore i1
is assignable to i2
(and vice versa), comparison is still not allowed and therefore they are not comparable.
Another less trivial example is that structs may be comparable if all their fields are comparable. So for example values of this struct are not comparable (because it has a field of slice type and slices are not comparable):
type Foo struct { Names []string }