go/test/typeparam/nested.go

151 lines
3.8 KiB
Go
Raw Normal View History

// run
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This test case stress tests a number of subtle cases involving
// nested type-parameterized declarations. At a high-level, it
// declares a generic function that contains a generic type
// declaration:
//
// func F[A intish]() {
// type T[B intish] struct{}
//
// // store reflect.Type tuple (A, B, F[A].T[B]) in tests
// }
//
// It then instantiates this function with a variety of type arguments
// for A and B. Particularly tricky things like shadowed types.
//
// From this data it tests two things:
//
// 1. Given tuples (A, B, F[A].T[B]) and (A', B', F[A'].T[B']),
// F[A].T[B] should be identical to F[A'].T[B'] iff (A, B) is
// identical to (A', B').
//
// 2. A few of the instantiations are constructed to be identical, and
// it tests that exactly these pairs are duplicated (by golden
// output comparison to nested.out).
//
// In both cases, we're effectively using the compiler's existing
// runtime.Type handling (which is well tested) of type identity of A
// and B as a way to help bootstrap testing and validate its new
// runtime.Type handling of F[A].T[B].
//
// This isn't perfect, but it smoked out a handful of issues in
// gotypes2 and unified IR.
package main
import (
"fmt"
"reflect"
)
type test struct {
TArgs [2]reflect.Type
Instance reflect.Type
}
var tests []test
type intish interface{ ~int }
type Int int
type GlobalInt = Int // allow access to global Int, even when shadowed
func F[A intish]() {
add := func(B, T interface{}) {
tests = append(tests, test{
TArgs: [2]reflect.Type{
reflect.TypeOf(A(0)),
reflect.TypeOf(B),
},
Instance: reflect.TypeOf(T),
})
}
type Int int
type T[B intish] struct{}
add(int(0), T[int]{})
add(Int(0), T[Int]{})
add(GlobalInt(0), T[GlobalInt]{})
add(A(0), T[A]{}) // NOTE: intentionally dups with int and GlobalInt
type U[_ any] int
type V U[int]
type W V
add(U[int](0), T[U[int]]{})
add(U[Int](0), T[U[Int]]{})
add(U[GlobalInt](0), T[U[GlobalInt]]{})
add(U[A](0), T[U[A]]{}) // NOTE: intentionally dups with U[int] and U[GlobalInt]
add(V(0), T[V]{})
add(W(0), T[W]{})
}
func main() {
type Int int
F[int]()
F[Int]()
F[GlobalInt]()
type U[_ any] int
type V U[int]
type W V
F[U[int]]()
F[U[Int]]()
F[U[GlobalInt]]()
F[V]()
F[W]()
cmd/compile/internal/noder: shape-based stenciling for unified IR This CL switches unified IR to use shape-based stenciling with runtime dictionaries, like the existing non-unified frontend. Specifically, when instantiating generic functions and types `X[T]`, we now also instantiated shaped variants `X[shapify(T)]` that can be shared by `T`'s with common underlying types. For example, for generic function `F`, `F[int](args...)` will be rewritten to `F[go.shape.int](&.dict.F[int], args...)`. For generic type `T` with method `M` and value `t` of type `T[int]`, `t.M(args...)` will be rewritten to `T[go.shape.int].M(t, &.dict.T[int], args...)`. Two notable distinctions from the non-unified frontend: 1. For simplicity, currently shaping is limited to simply converting type arguments to their underlying type. Subsequent CLs will implement more aggressive shaping. 2. For generic types, a single dictionary is generated to be shared by all methods, rather than separate dictionaries for each method. I originally went with this design because I have an idea of changing interface calls to pass the itab pointer via the closure register (which should have zero overhead), and then the interface wrappers for generic methods could use the *runtime.itab to find the runtime dictionary that corresponds to the dynamic type. This would allow emitting fewer method wrappers. However, this choice does have the consequence that currently even if a method is unused and its code is pruned by the linker, it may have produced runtime dictionary entries that need to be kept alive anyway. I'm open to changing this to generate per-method dictionaries, though this would require changing the unified IR export data format; so it would be best to make this decision before Go 1.20. The other option is making the linker smarter about pruning unneeded dictionary entries, like how it already prunes itab entries. For example, the runtime dictionary for `T[int]` could have a `R_DICTTYPE` meta-relocation against symbol `.dicttype.T[go.shape.int]` that declares it's a dictionary associated with that type; and then each method on `T[go.shape.T]` could have `R_DICTUSE` meta-relocations against `.dicttype.T[go.shape.T]+offset` indicating which fields within dictionaries of that type need to be preserved. Change-Id: I369580b1d93d19640a4b5ecada4f6231adcce3fd Reviewed-on: https://go-review.googlesource.com/c/go/+/421821 Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Keith Randall <khr@golang.org> Run-TryBot: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com> TryBot-Result: Gopher Robot <gobot@golang.org>
2022-08-06 23:40:56 +00:00
// TODO(go.dev/issue/54512): Restore these tests. They currently
// cause problems for shaping with unified IR.
//
// For example, instantiating X[int] requires instantiating shape
// type X[shapify(int)] == X[go.shape.int]. In turn, this requires
// instantiating U[shapify(X[go.shape.int])]. But we're still in the
// process of constructing X[go.shape.int], so we don't yet know its
// underlying type.
//
// Notably, this is a consequence of unified IR writing out type
// declarations with a reference to the full RHS expression (i.e.,
// U[X[A]]) rather than its underlying type (i.e., int), which is
// necessary to support //go:notinheap. Once go.dev/issue/46731 is
// implemented and unified IR is updated, I expect this will just
// work.
//
// type X[A any] U[X[A]]
//
// F[X[int]]()
// F[X[Int]]()
// F[X[GlobalInt]]()
for j, tj := range tests {
for i, ti := range tests[:j+1] {
if (ti.TArgs == tj.TArgs) != (ti.Instance == tj.Instance) {
fmt.Printf("FAIL: %d,%d: %s, but %s\n", i, j, eq(ti.TArgs, tj.TArgs), eq(ti.Instance, tj.Instance))
}
// The test is constructed so we should see a few identical types.
// See "NOTE" comments above.
if i != j && ti.Instance == tj.Instance {
fmt.Printf("%d,%d: %v\n", i, j, ti.Instance)
}
}
}
}
func eq(a, b interface{}) string {
op := "=="
if a != b {
op = "!="
}
return fmt.Sprintf("%v %s %v", a, op, b)
}