Skip to content

cmd/compile: type inference fails for generic function in package-level var initializer depending on source position #77905

@pkolakow

Description

@pkolakow

Go version

go version go1.26.0 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE='on'
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/pkolak/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/pkolak/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/9f/3zyyhrh945q002gvclld4bg00000gp/T/go-build2148103633=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMODCACHE='/Users/pkolak/go/pkg/mod'
GONOPROXY=''
GONOSUMDB='github.com/nike-*'
GOOS='darwin'
GOPATH='/Users/pkolak/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/Users/pkolak/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.0.darwin-arm64'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/Users/pkolak/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/Users/pkolak/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.0.darwin-arm64/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.26.0'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

I defined a generic interface Getter[T] and a generic function Evaluate[T] that accepts a Getter[T] parameter. I then created a concrete type SomeInt whose pointer receiver implements Getter[int].

Minimal reproducer:

package main

type Getter[T any] interface {
	Get() T
}

func Evaluate[T any](getter Getter[T]) T {
	return getter.Get()
}

var someInt = Evaluate(&SomeInt{}) // ERROR: cannot infer T

type SomeInt struct{}

func (i *SomeInt) Get() int {
	return 10
}

func main() {
	_ = someInt
}

Playground: https://go.dev/play/p/mp1G_OFCoui

What did you see happen?

When calling Evaluate(&SomeInt{}) in a package-level variable initializer, the compiler fails to infer the type parameter T, even though *SomeInt unambiguously implements Getter[int]. The behavior depends on where in the file the var declaration is placed relative to the type and method declarations:

Case Declaration position Evaluate(&SomeInt{}) Evaluate[int](&SomeInt{})
1 Before type declaration cannot infer T ✅ Succeeds
2 After type declaration, before method cannot infer T ✅ Succeeds
3 Inside init() function ✅ Succeeds ✅ Succeeds
4 After method declaration ✅ Succeeds ✅ Succeeds

What did you expect to see?

I expected the compiler to successfully infer the type parameter T as int in the call Evaluate(&SomeInt{}) when used in a package-level variable initializer, regardless of where in the file the var declaration appears relative to the type and method declarations.

Since *SomeInt unambiguously implements Getter[int] (it has a single method Get() int matching the interface), the compiler has all the information needed to deduce T = int. This inference already works correctly in all of the following contexts:

  • Inside function bodies (main(), init(), or any other function)
  • At package level only if the var is placed after the method declaration in source order
  • When the type parameter is provided explicitly (Evaluate[int](&SomeInt{})) — works everywhere

Therefore, the expected behavior is that all four cases below should compile without error:

// Case 1 — Before type declaration
var someInt = Evaluate(&SomeInt{})  // should infer T = int ✅

type SomeInt struct{}

// Case 2 — After type declaration, before method
var someInt = Evaluate(&SomeInt{})  // should infer T = int ✅

func (i *SomeInt) Get() int { return 10 }

// Case 3 — Inside init()
func init() {
    someInt = Evaluate(&SomeInt{})  // should infer T = int ✅
}

// Case 4 — After method declaration
var someInt = Evaluate(&SomeInt{})  // should infer T = int ✅

According to the Go specification section on Package initialization:

Dependency analysis does not rely on the actual values of the variables, only on lexical references to them in the source, analyzed transitively.

This means the compiler is expected to resolve all package-level references — including type and method declarations — transitively across the entire package, without regard to their position in the source file. Since *SomeInt and its Get() int method are declared in the same package, the compiler should recognize that *SomeInt satisfies Getter[int] and infer T = int at any point in the file. The current behavior — where inference fails in Cases 1 & 2 but succeeds in Case 4 — indicates that the type inference pass is incorrectly sensitive to source ordering, contradicting the specification's guarantee of order-independent dependency analysis.

Metadata

Metadata

Labels

BugReportIssues describing a possible bug in the Go implementation.compiler/runtimeIssues related to the Go compiler and/or runtime.

Type

No type

Projects

Status

Todo

Relationships

None yet

Development

No branches or pull requests

Issue actions