package typeutil import ( "go/types" ) // Identical reports whether x and y are identical types. // Unlike types.Identical, receivers of Signature types are not ignored. // Unlike types.Identical, interfaces are compared via pointer equality (except for the empty interface, which gets deduplicated). // Unlike types.Identical, structs are compared via pointer equality. func Identical(x, y types.Type) (ret bool) { if !types.Identical(x, y) { return false } switch x := x.(type) { case *types.Struct: y, ok := y.(*types.Struct) if !ok { // should be impossible return true } return x == y case *types.Interface: // The issue with interfaces, typeutil.Map and types.Identical // // types.Identical, when comparing two interfaces, only looks at the set // of all methods, not differentiating between implicit (embedded) and // explicit methods. // // When we see the following two types, in source order // // type I1 interface { foo() } // type I2 interface { I1 } // // then we will first correctly process I1 and its underlying type. When // we get to I2, we will see that its underlying type is identical to // that of I1 and not process it again. This, however, means that we will // not record the fact that I2 embeds I1. If only I2 is reachable via the // graph root, then I1 will not be considered used. // // We choose to be lazy and compare interfaces by their // pointers. This will obviously miss identical interfaces, // but this only has a runtime cost, it doesn't affect // correctness. y, ok := y.(*types.Interface) if !ok { // should be impossible return true } if x.NumEmbeddeds() == 0 && y.NumEmbeddeds() == 0 && x.NumMethods() == 0 && y.NumMethods() == 0 { // all truly empty interfaces are the same return true } return x == y case *types.Signature: y, ok := y.(*types.Signature) if !ok { // should be impossible return true } if x.Recv() == y.Recv() { return true } if x.Recv() == nil || y.Recv() == nil { return false } return Identical(x.Recv().Type(), y.Recv().Type()) default: return true } }