Go version

go version go1.21.6 linux/amd64

Output of go env in your module/workspace:

GO111MODULE=''
GOARCH='amd64'
GOBIN=''
GOCACHE='/home/jcarmichael/.cache/go-build'
GOENV='/home/jcarmichael/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFLAGS=''
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMODCACHE='/home/jcarmichael/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/jcarmichael/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.21.6'
GCCGO='gccgo'
GOAMD64='v1'
AR='ar'
CC='gcc'
CXX='g++'
CGO_ENABLED='1'
GOMOD='/mnt/c/Users/Lenovo/Documents/Programming/util/go.mod'
GOWORK=''
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
PKG_CONFIG='pkg-config'
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build1952531264=/tmp/go-build -gno-record-gcc-switches'

What did you do?

I wrote the code shown below and in the following playground link: https://go.dev/play/p/d8XxoEklO6D

package main

import "fmt"

type Comparisons[OI any, OIR any, K any, V any] interface {
    Difference(other OIR) OI
}

// An interface that only allows read operations on a vector.
type ReadVector[V any] interface {
    Comparisons[Vector[V], ReadVector[V], int, V]
}

// An interface that only allows write operations on a vector.
type WriteVector[V any] interface {
    otherMethods()
}

// An interface that represents a vector with no restrictions on reading or
// writing.
type Vector[V any] interface {
    ReadVector[V]
    WriteVector[V]
}

type VectorImpl[T any] struct{}

func (v *VectorImpl[T]) otherMethods() {}
func (v *VectorImpl[T]) Difference(other ReadVector[T]) Vector[T] {
    rv := VectorImpl[T]{}
    return &rv
}

func main() {
    v := VectorImpl[int]{}
    v2 := VectorImpl[int]{}
    var v2Interface Vector[int] = &v2
    fmt.Println(v.Difference(v2Interface))
}

What did you see happen?

The compiler produced the following error:

./prog.go:38:27: cannot use v2Interface (type ReadVector[int]) as type ReadVector[go.shape.int] in argument to (*VectorImpl[go.shape.int]).Difference:
    ReadVector[int] does not implement ReadVector[go.shape.int] (wrong type for Difference method)
        have Difference(ReadVector[int]) Vector[int]
        want Difference(ReadVector[go.shape.int]) go.shape.interface { main.otherMethods() }

Go build failed.

What did you expect to see?

I expected the program to compile and print the empty Vector[int] structure value.

Of note is if you change the second generic parameter on line 11 to Vector[int] as well as the corresponding type on the Difference method on line then the compiler successfully finishes, and the program works as intended.

I am not sure if this is relevant, but the compiler error does not list the difference method on the Vector return type of the Difference method in the error it produces. It prints this:

want Difference(ReadVector[go.shape.int]) go.shape.interface { main.otherMethods() }

when I think it should print something like this (formatting added for readability):

want Difference(ReadVector[go.shape.int]) go.shape.interface { 
    main.otherMethods() 
    main.Difference(
        ReadVector[go.shape.int]
    ) go.shape.interface{ main.otherMethods(); ReadVector[go.shape.int]} 
}

I am guessing on the syntax of the output shown above, I am just saying what would make sense to see based on what I am already seeing, I am sure the correct output is different.

I am not sure what the rules are regarding recursive interface type definitions as shown in the example, and the issue is only compounded by the added generics.

Comment From: mknyszek

CC @golang/compiler, and perhaps @griesemer specifically?

Comment From: griesemer

The type checker is happy with this code. Looks like a problem in the backend (backend type checker). Here's a simplified repro (playground), at tip:

package main

type Vector[V any] interface {
    ReadVector[V]
}

type ReadVector[V any] interface {
    Comparisons[ReadVector[V], Vector[V]]
}

type Comparisons[RV, V any] interface {
    Diff(RV) V
}

type VectorImpl[V any] struct{}

func (*VectorImpl[V]) Diff(ReadVector[V]) (_ Vector[V]) {
    return
}

func main() {
    var v1 VectorImpl[int]
    var v2 Vector[int]
    _ = v1.Diff(v2)
}

resulting in:

./prog.go:24:14: cannot use v2 (type ReadVector[int]) as type ReadVector[go.shape.int] in argument to (*VectorImpl[go.shape.int]).Diff:
    ReadVector[int] does not implement ReadVector[go.shape.int] (wrong type for Diff method)
        have Diff(ReadVector[int]) Vector[int]
        want Diff(ReadVector[go.shape.int]) go.shape.interface {}

@mdempsky, @randall77 any insights?

Comment From: mdempsky

The code looks reasonable, and I don't immediately see why it would be failing. I'd guess it's an error during runtime dictionary construction. (Cc @cuonglm because he's good at debugging these issues too.)

Comment From: cuonglm

@mdempsky I think this is a bad interaction when shapifying recursive instantiated type argument ReadVector[go.shape.int] vs instantiated type argument Vector[go.shape.int].

Because ReadVector is recursive interface type, we end up shapifying the type argument as ReadVector[go.shape.int], while Vector[go.shape.int] is shapifying to its underlying type go.shape.interface {}, causing the mismatch in interface method signature.

Probably we should shapifying an interface type as-is if it is fully instantiated and already has shape?


types2 does not have this problem, because there's no shapifying there.

Comment From: gopherbot

Change https://go.dev/cl/559656 mentions this issue: cmd/compile: fix recursive generic interface instantiation