Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/compile: compiler error for calling function, which is a field reference of a typeparam (which has a structural type) #50690

Closed
danscales opened this issue Jan 19, 2022 · 7 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Milestone

Comments

@danscales
Copy link
Contributor

New issue from @akutz for Go 1.18 tip:

This example fails to run with the following error (Golang playground):

package main

import (
	"fmt"
)

// Numeric expresses a type constraint satisfied by any numeric type.
type Numeric interface {
	~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
		~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~float32 | ~float64 |
		~complex64 | ~complex128
}

// Sum returns the sum of the provided arguments.
func Sum[T Numeric](args ...T) T {
	var sum T
	for i := 0; i < len(args); i++ {
		sum += args[i]
	}
	return sum
}

// Ledger is an identifiable, financial record.
type Ledger[T ~string, K Numeric] struct {

	// ID identifies the ledger.
	ID T

	// Amounts is a list of monies associated with this ledger.
	Amounts []K

	// SumFn is a function that can be used to sum the amounts
	// in this ledger.
	SumFn func(...K) K
}

func PrintLedger[
	T ~string,
	K Numeric,
	L ~struct {
		ID      T
		Amounts []K
		SumFn   func(...K) K
	},
](l L) {
	fmt.Printf("%s has a sum of %v\n", l.ID, l.SumFn(l.Amounts...))
}

func main() {
	PrintLedger(Ledger[string, int]{
		ID:      "fake",
		Amounts: []int{1, 2, 3},
		SumFn:   Sum[int],
	})
}
./prog.go:47:52: internal compiler error: panic: runtime error: invalid memory address or nil pointer dereference

goroutine 1 [running]:
runtime/debug.Stack()
	/usr/local/go/src/runtime/debug/stack.go:24 +0x65
cmd/compile/internal/base.FatalfAt({0x61e784?, 0x0?}, {0xd1f694, 0x9}, {0xc0003f0ed0, 0x1, 0x1})
	/usr/local/go/src/cmd/compile/internal/base/print.go:227 +0x1ca
cmd/compile/internal/base.Fatalf(...)
	/usr/local/go/src/cmd/compile/internal/base/print.go:196
cmd/compile/internal/gc.handlePanic()
	/usr/local/go/src/cmd/compile/internal/gc/main.go:49 +0x85
panic({0xca9440, 0x130cdf0})
	/usr/local/go/src/runtime/panic.go:838 +0x207
cmd/compile/internal/noder.(*genInst).getDictionarySym(0x1395e00, 0xc0003cf930, {0xc0003daea0, 0x3, 0x3}, 0x0)
	/usr/local/go/src/cmd/compile/internal/noder/stencil.go:1615 +0xf29
cmd/compile/internal/noder.(*genInst).getDictionaryValue(0x2?, {0x3daac8?, 0xc0?}, 0xc0003daeb8?, {0xc0003daea0?, 0xc0003bf4a0?, 0xc0003daee8?}, 0x50?)
	/usr/local/go/src/cmd/compile/internal/noder/stencil.go:1763 +0x39
cmd/compile/internal/noder.(*genInst).getDictOrSubdict(0x1395e00?, 0xc0003cf930?, {0xe970f8?, 0xc000310a20?}, 0xc0003f13a8?, {0xc0003daea0, 0x3, 0x3}, 0x0?)
	/usr/local/go/src/cmd/compile/internal/noder/stencil.go:601 +0xdc
cmd/compile/internal/noder.(*genInst).scanForGenCalls.func1({0xe970f8, 0xc000310a20?})
	/usr/local/go/src/cmd/compile/internal/noder/stencil.go:152 +0x32f
cmd/compile/internal/ir.Visit.func1({0xe970f8, 0xc000310a20})
	/usr/local/go/src/cmd/compile/internal/ir/visit.go:105 +0x30
cmd/compile/internal/ir.doNodes(...)
	/usr/local/go/src/cmd/compile/internal/ir/node_gen.go:1512
cmd/compile/internal/ir.(*Func).doChildren(0xe97a58?, 0xc0003dae88?)
	/usr/local/go/src/cmd/compile/internal/ir/func.go:152 +0x6e
cmd/compile/internal/ir.DoChildren(...)
	/usr/local/go/src/cmd/compile/internal/ir/visit.go:94
cmd/compile/internal/ir.Visit.func1({0xe97a58, 0xc0003bf1e0})
	/usr/local/go/src/cmd/compile/internal/ir/visit.go:106 +0x57
cmd/compile/internal/ir.Visit({0xe97a58, 0xc0003bf1e0}, 0xc0003e6d80)
	/usr/local/go/src/cmd/compile/internal/ir/visit.go:108 +0xb8
cmd/compile/internal/noder.(*genInst).scanForGenCalls(0x1395e00, {0xe97a58, 0xc0003bf1e0})
	/usr/local/go/src/cmd/compile/internal/noder/stencil.go:133 +0x1f0
cmd/compile/internal/noder.(*genInst).buildInstantiations(0x1395e00)
	/usr/local/go/src/cmd/compile/internal/noder/stencil.go:60 +0x69
cmd/compile/internal/noder.BuildInstantiations(...)
	/usr/local/go/src/cmd/compile/internal/noder/stencil.go:44
cmd/compile/internal/noder.(*irgen).generate(0xc0000fe240, {0xc0000ac440, 0x1, 0x203000?})
	/usr/local/go/src/cmd/compile/internal/noder/irgen.go:331 +0x3d1
cmd/compile/internal/noder.check2({0xc0000ac440, 0x1, 0x1})
	/usr/local/go/src/cmd/compile/internal/noder/irgen.go:92 +0x16d
cmd/compile/internal/noder.LoadPackage({0xc0000b20f0, 0x1, 0x0?})
	/usr/local/go/src/cmd/compile/internal/noder/noder.go:90 +0x335
cmd/compile/internal/gc.Main(0xd4f6d0)
	/usr/local/go/src/cmd/compile/internal/gc/main.go:191 +0xb13
main.main()
	/usr/local/go/src/cmd/compile/main.go:55 +0xdd

The above example compiles as long as I do not call l.SumFn, even though it is passed into the struct (Golang playground):

func PrintLedger[
	T ~string,
	K Numeric,
	L ~struct {
		ID      T
		Amounts []K
		SumFn   func(...K) K
	},
](l L) {
	fmt.Printf("%s has a sum of %v\n", l.ID, l.Amounts)
}
fake has a sum of [1 2 3]

Originally posted by @akutz in #50417 (comment)

@akutz
Copy link

akutz commented Jan 19, 2022

Thanks @danscales. Why does this example work though?

package main

import (
	"fmt"
)

type Printer[T ~string] struct {
	PrintFn func(T)
}

func Print[T ~string](s T) {
	fmt.Println(s)
}

func PrintWithPrinter[T ~string, S struct{ PrintFn func(T) }](message T, obj S) {
	obj.PrintFn(message)
}

func main() {
	PrintWithPrinter(
		"Hello, world.",
		struct{ PrintFn func(string) }{PrintFn: Print[string]},
	)
}

@griesemer griesemer added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jan 19, 2022
@griesemer griesemer added this to the Go1.18 milestone Jan 19, 2022
@danscales
Copy link
Contributor Author

This issue is happening because, given structural types (i.e. you can now have field references on type params), we now have to do more work to distinguish a method call on a type param (what we call a bound call, calling a method that is allow by the constraint) from a call to a function-valued field of a type param (that has a single structural type). I don't know yet why the second case works and the first does not, but we definitely have to put in more checks to distinguish the cases.

@akutz
Copy link

akutz commented Jan 19, 2022

@danscales Thanks, makes sense. If I had to guess, the case that works only has a func type as a field. I wonder if adding a non-func field would cause the error. I will try it.

@akutz
Copy link

akutz commented Jan 19, 2022

Huh, this still works as well (Golang playground):

package main

import (
	"fmt"
)

type Printer[T ~string] struct {
	PrintFn func(T)
}

func Print[T ~string](s T) {
	fmt.Println(s)
}

func PrintWithPrinter[T ~string, S struct {
	ID      T
	PrintFn func(T)
}](message T, obj S) {
	obj.PrintFn(message)
}

func main() {
	PrintWithPrinter(
		"Hello, world.",
		struct {
			ID      string
			PrintFn func(string)
		}{ID: "fake", PrintFn: Print[string]},
	)
}

I am going to try it when using a non-anon struct since that is what was used in the original example. If that does not work I will introduce a second generic type since that was also in the original use case.

@akutz
Copy link

akutz commented Jan 19, 2022

Okay, this fails:

package main

import (
	"fmt"
)

type Printer[T ~string] struct {
	PrintFn func(T)
}

func Print[T ~string](s T) {
	fmt.Println(s)
}

func PrintWithPrinter[T ~string, S ~struct {
	ID      T
	PrintFn func(T)
}](message T, obj S) {
	obj.PrintFn(message)
}

type PrintShop[T ~string] struct {
	ID      T
	PrintFn func(T)
}

func main() {
	PrintWithPrinter(
		"Hello, world.",
		PrintShop[string]{
			ID:      "fake",
			PrintFn: Print[string],
		},
	)
}

It was the use of ~struct with a non-anonymous struct. I tried ~struct with an anon struct, but that worked fine.

@danscales danscales self-assigned this Jan 19, 2022
@gopherbot
Copy link

Change https://golang.org/cl/379674 mentions this issue: cmd/compile: distinguish bound calls/field access in getInstInfo

@griesemer
Copy link
Contributor

For reasons discussed in #51576, field accesses through type parameters will be disabled for Go 1.18.

jproberts pushed a commit to jproberts/go that referenced this issue Jun 21, 2022
Given we have support for field access to type params with a single
structural type, we need to distinguish between methods calls and field
access when we have an OXDOT node on an expression which is a typeparam
(or correspondingly a shape). We were missing checks in getInstInfo,
which figures out the dictionary format, which then caused problems when
we generate the dictionaries. We don't need/want dictionary entries for
field access, only for bound method calls. Added a new function
isBoundMethod() to distinguish OXDOT nodes which are bound calls vs.
field accesses on a shape.

Removed isShapeDeref() - we can't have field access or method call on a
pointer to variable of type param type.

Fixes golang#50690

Change-Id: Id692f65e6f427f28cd2cfe474dd30e53c71877a7
Reviewed-on: https://go-review.googlesource.com/c/go/+/379674
Trust: Dan Scales <danscales@google.com>
Reviewed-by: Keith Randall <khr@golang.org>
@golang golang locked and limited conversation to collaborators Jun 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one.
Projects
None yet
Development

No branches or pull requests

4 participants