mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
compiler: disambiguate function-local named types (#5336)
* compiler: make getTypeCodeName a method on compilerContext * testdata: add regression tests for function-local named types * compiler: disambiguate function-local named types * Some PR feedback
This commit is contained in:
@@ -92,6 +92,7 @@ type compilerContext struct {
|
||||
pkg *types.Package
|
||||
packageDir string // directory for this package
|
||||
runtimePkg *types.Package
|
||||
localTypeNames typeutil.Map // *types.Named (synthetic local from generic instantiation) -> string
|
||||
}
|
||||
|
||||
// newCompilerContext returns a new compiler context ready for use, most
|
||||
@@ -313,6 +314,11 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
|
||||
// Convert AST to SSA.
|
||||
ssaPkg.Build()
|
||||
|
||||
// Assign names to function-local named types before compiling the
|
||||
// package, so that types declared in different functions (or in
|
||||
// different instantiations of a generic function) do not collide.
|
||||
c.scanLocalTypes(ssaPkg)
|
||||
|
||||
// Initialize debug information.
|
||||
if c.Debug {
|
||||
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
||||
|
||||
+276
-19
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -165,7 +166,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
}
|
||||
}
|
||||
|
||||
typeCodeName, isLocal := getTypeCodeName(typ)
|
||||
typeCodeName, isLocal := c.getTypeCodeName(typ)
|
||||
globalName := "reflect/types.type:" + typeCodeName
|
||||
var global llvm.Value
|
||||
if isLocal {
|
||||
@@ -580,20 +581,50 @@ var basicTypeNames = [...]string{
|
||||
// getTypeCodeName returns a name for this type that can be used in the
|
||||
// interface lowering pass to assign type codes as expected by the reflect
|
||||
// package. See getTypeCodeNum.
|
||||
func getTypeCodeName(t types.Type) (string, bool) {
|
||||
//
|
||||
// isLocal is true when the type is declared inside a function body.
|
||||
// Such types need a per-declaration (or per instantiation) suffix
|
||||
// because their printed names are not unique.
|
||||
//
|
||||
// Ordinary function-local types (TypeName.Parent() != nil) are
|
||||
// disambiguated lazily from their declaration position: every
|
||||
// declaration in the package has a distinct (file, line, column)
|
||||
// triple, and the position is taken un-//line-adjusted so it is
|
||||
// stable across builds. Such types are only nameable inside their
|
||||
// declaring package, so the name does not need to agree with anything
|
||||
// computed in another package.
|
||||
//
|
||||
// Synthetic locals (TypeName.Parent() == nil), produced by generic
|
||||
// instantiation, are pre-registered by scanLocalTypes because their
|
||||
// names must agree across packages that materialize the same instance.
|
||||
func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bool) {
|
||||
switch t := types.Unalias(t).(type) {
|
||||
case *types.Named:
|
||||
if t.Obj().Parent() != t.Obj().Pkg().Scope() {
|
||||
return "named:" + t.String() + "$local", true
|
||||
tn := t.Obj()
|
||||
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() {
|
||||
// Package-scope or builtin: the printed name is unique.
|
||||
return "named:" + t.String(), false
|
||||
}
|
||||
return "named:" + t.String(), false
|
||||
if tn.Parent() != nil {
|
||||
// Ordinary function-local type. Use the un-//line-adjusted
|
||||
// declaration position as the disambiguator.
|
||||
pos := c.program.Fset.PositionFor(tn.Pos(), false)
|
||||
return fmt.Sprintf("named:%s$%s:%d:%d", t.String(), filepath.Base(pos.Filename), pos.Line, pos.Column), true
|
||||
}
|
||||
// Synthetic local from generic instantiation: must have been
|
||||
// pre-registered by scanLocalTypes.
|
||||
v := c.localTypeNames.At(t)
|
||||
if v == nil {
|
||||
panic("compiler: synthetic local type " + tn.Name() + " was not registered by scanLocalTypes")
|
||||
}
|
||||
return "named:" + v.(string), true
|
||||
case *types.Array:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal
|
||||
case *types.Basic:
|
||||
return "basic:" + basicTypeNames[t.Kind()], false
|
||||
case *types.Chan:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
var dir string
|
||||
switch t.Dir() {
|
||||
case types.SendOnly:
|
||||
@@ -613,7 +644,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
if !token.IsExported(name) {
|
||||
name = t.Method(i).Pkg().Path() + "." + name
|
||||
}
|
||||
s, local := getTypeCodeName(t.Method(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Method(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -621,17 +652,17 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal
|
||||
case *types.Map:
|
||||
keyType, keyLocal := getTypeCodeName(t.Key())
|
||||
elemType, elemLocal := getTypeCodeName(t.Elem())
|
||||
keyType, keyLocal := c.getTypeCodeName(t.Key())
|
||||
elemType, elemLocal := c.getTypeCodeName(t.Elem())
|
||||
return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal
|
||||
case *types.Pointer:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
return "pointer:" + s, isLocal
|
||||
case *types.Signature:
|
||||
isLocal := false
|
||||
params := make([]string, t.Params().Len())
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
s, local := getTypeCodeName(t.Params().At(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Params().At(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -639,7 +670,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
results := make([]string, t.Results().Len())
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
s, local := getTypeCodeName(t.Results().At(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Results().At(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -647,7 +678,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal
|
||||
case *types.Slice:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
return "slice:" + s, isLocal
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
@@ -657,7 +688,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
if t.Field(i).Embedded() {
|
||||
embedded = "#"
|
||||
}
|
||||
s, local := getTypeCodeName(t.Field(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Field(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -672,6 +703,232 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// scanLocalTypes assigns names to every synthetic *types.TypeName
|
||||
// (TypeName.Parent() == nil) reachable from this package and stores
|
||||
// them in c.localTypeNames.
|
||||
//
|
||||
// Synthetic TypeNames are produced by generic instantiation: two
|
||||
// instantiations of the same generic function (e.g. F[int] and
|
||||
// F[string]) produce TypeNames with the same printed name and the
|
||||
// same source position, so each is named with the enclosing
|
||||
// instance's RelString as prefix. RelString encodes the type
|
||||
// arguments, matching Go's runtime behavior, where F[int].Inner and
|
||||
// F[string].Inner are distinct types even when Inner does not mention
|
||||
// the type parameter.
|
||||
//
|
||||
// A given instance may be materialized by several packages (the body
|
||||
// of F[int] is compiled in every package that calls F[int]); its
|
||||
// reflect/types.type:* global has LinkOnceODRLinkage and is merged by
|
||||
// name at link time. The chosen name therefore depends only on
|
||||
// intrinsic SSA properties (RelString and the raw token.Pos used as a
|
||||
// sort key), so any package compiling the same instance produces the
|
||||
// same identifier.
|
||||
//
|
||||
// Ordinary function-local TypeNames (TypeName.Parent() != nil) are
|
||||
// not handled here: they are nameable only inside their declaring
|
||||
// package, and getTypeCodeName derives a stable per-declaration name
|
||||
// for them directly from their source position.
|
||||
func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
|
||||
// Locate every generic instance reachable from this package
|
||||
// (including instances declared in imported packages and any
|
||||
// function reached through an instance subtree).
|
||||
var instances []*ssa.Function
|
||||
seen := map[*ssa.Function]struct{}{}
|
||||
var walk func(fn *ssa.Function, inInstance bool)
|
||||
walk = func(fn *ssa.Function, inInstance bool) {
|
||||
if fn == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[fn]; ok {
|
||||
return
|
||||
}
|
||||
// fn belongs to an instance subtree if it is itself an
|
||||
// instantiation or if we reached it from one.
|
||||
//
|
||||
// len(TypeArgs()) is used instead of fn.Origin() because
|
||||
// Origin() may call Build() on fn's declaring package, which
|
||||
// would defeat per-package compilation.
|
||||
isInstanceRoot := len(fn.TypeArgs()) > 0
|
||||
if !isInstanceRoot && !inInstance && fn.Pkg != ssaPkg {
|
||||
return
|
||||
}
|
||||
if fn.Blocks == nil && fn.AnonFuncs == nil {
|
||||
return
|
||||
}
|
||||
seen[fn] = struct{}{}
|
||||
isInInstance := inInstance || isInstanceRoot
|
||||
if isInInstance {
|
||||
instances = append(instances, fn)
|
||||
}
|
||||
for _, anon := range fn.AnonFuncs {
|
||||
walk(anon, isInInstance)
|
||||
}
|
||||
var ops [10]*ssa.Value
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
for _, op := range instr.Operands(ops[:0]) {
|
||||
if op == nil || *op == nil {
|
||||
continue
|
||||
}
|
||||
if callee, ok := (*op).(*ssa.Function); ok {
|
||||
walk(callee, isInInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, member := range ssaPkg.Members {
|
||||
switch m := member.(type) {
|
||||
case *ssa.Function:
|
||||
walk(m, false)
|
||||
case *ssa.Type:
|
||||
mset := c.program.MethodSets.MethodSet(m.Type())
|
||||
for i := 0; i < mset.Len(); i++ {
|
||||
walk(c.program.MethodValue(mset.At(i)), false)
|
||||
}
|
||||
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
|
||||
for i := 0; i < pmset.Len(); i++ {
|
||||
walk(c.program.MethodValue(pmset.At(i)), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Registration is first-writer-wins (a synthetic TypeName may be
|
||||
// reachable from several instances), so visit instances in a
|
||||
// deterministic order. Pos() is a defensive tiebreaker.
|
||||
sort.Slice(instances, func(i, j int) bool {
|
||||
ri, rj := instances[i].RelString(nil), instances[j].RelString(nil)
|
||||
if ri != rj {
|
||||
return ri < rj
|
||||
}
|
||||
return instances[i].Pos() < instances[j].Pos()
|
||||
})
|
||||
for _, fn := range instances {
|
||||
c.registerSyntheticLocalTypes(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// registerSyntheticLocalTypes walks every type reachable from fn's
|
||||
// body and records each synthetic *types.Named (TypeName.Parent() ==
|
||||
// nil) in c.localTypeNames. Each is named with fn.RelString as the
|
||||
// owning function plus a per-function counter assigned in source
|
||||
// order.
|
||||
//
|
||||
// First-writer-wins: a *types.Named already present in
|
||||
// c.localTypeNames is left alone, so a synthetic type reachable from
|
||||
// several instances keeps the name assigned by the first (in
|
||||
// scanLocalTypes' deterministic order).
|
||||
func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
|
||||
var found []*types.Named
|
||||
seen := map[types.Type]struct{}{}
|
||||
var visit func(t types.Type)
|
||||
visit = func(t types.Type) {
|
||||
if t == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := seen[t]; ok {
|
||||
return
|
||||
}
|
||||
seen[t] = struct{}{}
|
||||
switch t := t.(type) {
|
||||
case *types.Alias:
|
||||
visit(types.Unalias(t))
|
||||
case *types.Named:
|
||||
tn := t.Obj()
|
||||
if tn.Pkg() != nil && tn.Parent() == nil {
|
||||
if c.localTypeNames.At(t) == nil {
|
||||
// Reserve the slot so later calls within this
|
||||
// scanLocalTypes invocation skip it; the final
|
||||
// name is filled in after sorting, before any
|
||||
// getTypeCodeName lookups happen.
|
||||
c.localTypeNames.Set(t, "")
|
||||
found = append(found, t)
|
||||
}
|
||||
}
|
||||
targs := t.TypeArgs()
|
||||
for i := 0; i < targs.Len(); i++ {
|
||||
visit(targs.At(i))
|
||||
}
|
||||
visit(t.Underlying())
|
||||
case *types.Pointer:
|
||||
visit(t.Elem())
|
||||
case *types.Slice:
|
||||
visit(t.Elem())
|
||||
case *types.Array:
|
||||
visit(t.Elem())
|
||||
case *types.Chan:
|
||||
visit(t.Elem())
|
||||
case *types.Map:
|
||||
visit(t.Key())
|
||||
visit(t.Elem())
|
||||
case *types.Struct:
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
visit(t.Field(i).Type())
|
||||
}
|
||||
case *types.Signature:
|
||||
if p := t.Params(); p != nil {
|
||||
for i := 0; i < p.Len(); i++ {
|
||||
visit(p.At(i).Type())
|
||||
}
|
||||
}
|
||||
if r := t.Results(); r != nil {
|
||||
for i := 0; i < r.Len(); i++ {
|
||||
visit(r.At(i).Type())
|
||||
}
|
||||
}
|
||||
case *types.Tuple:
|
||||
for i := 0; i < t.Len(); i++ {
|
||||
visit(t.At(i).Type())
|
||||
}
|
||||
case *types.Interface:
|
||||
// A synthetic local type can be reachable only through a
|
||||
// local interface's method signature, so descend into
|
||||
// them. getTypeCodeName encodes those signatures into
|
||||
// the interface's identifier, and the seen map breaks
|
||||
// cycles formed by methods that mention the interface
|
||||
// itself.
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
visit(t.Method(i).Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, p := range fn.Params {
|
||||
visit(p.Type())
|
||||
}
|
||||
for _, fv := range fn.FreeVars {
|
||||
visit(fv.Type())
|
||||
}
|
||||
for _, l := range fn.Locals {
|
||||
visit(l.Type())
|
||||
}
|
||||
var ops [10]*ssa.Value
|
||||
for _, b := range fn.Blocks {
|
||||
for _, instr := range b.Instrs {
|
||||
if v, ok := instr.(ssa.Value); ok {
|
||||
visit(v.Type())
|
||||
}
|
||||
for _, op := range instr.Operands(ops[:0]) {
|
||||
if op != nil && *op != nil {
|
||||
visit((*op).Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(found) == 0 {
|
||||
return
|
||||
}
|
||||
// Sort by raw token.Pos: this gives a total order on declarations
|
||||
// that is stable across builds and unaffected by //line directives
|
||||
// (which only adjust the human-facing position from Fset.Position).
|
||||
sort.Slice(found, func(i, j int) bool {
|
||||
return found[i].Obj().Pos() < found[j].Obj().Pos()
|
||||
})
|
||||
enclosing := fn.RelString(nil)
|
||||
for i, named := range found {
|
||||
c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i))
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||
// method set should be unreferenced after the interface lowering pass.
|
||||
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
@@ -769,7 +1026,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum)
|
||||
}
|
||||
} else {
|
||||
name, _ := getTypeCodeName(expr.AssertedType)
|
||||
name, _ := b.getTypeCodeName(expr.AssertedType)
|
||||
globalName := "reflect/types.typeid:" + name
|
||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
||||
if assertedTypeCodeGlobal.IsNil() {
|
||||
@@ -882,7 +1139,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
|
||||
if !token.IsExported(name) {
|
||||
name = method.Pkg().Path() + "." + name
|
||||
}
|
||||
s, _ := getTypeCodeName(method.Type())
|
||||
s, _ := c.getTypeCodeName(method.Type())
|
||||
globalName := "reflect/types.signature:" + name + ":" + s
|
||||
value := c.mod.NamedGlobal(globalName)
|
||||
if value.IsNil() {
|
||||
@@ -926,7 +1183,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
|
||||
// thunk is declared, not defined: it will be defined by the interface lowering
|
||||
// pass.
|
||||
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
s, _ := getTypeCodeName(instr.Value.Type().Underlying())
|
||||
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
|
||||
fnName := s + "." + instr.Method.Name() + "$invoke"
|
||||
llvmFn := c.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
@@ -951,7 +1208,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
// by the interface lowering pass as a type-ID comparison chain, avoiding the
|
||||
// need for runtime.typeImplementsMethodSet at compile time.
|
||||
func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value {
|
||||
s, _ := getTypeCodeName(intf)
|
||||
s, _ := b.getTypeCodeName(intf)
|
||||
fnName := s + ".$typeassert"
|
||||
llvmFn := b.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
|
||||
@@ -76,6 +76,8 @@ func TestBuild(t *testing.T) {
|
||||
"init_multi.go",
|
||||
"interface.go",
|
||||
"json.go",
|
||||
"localtypes/",
|
||||
"localtypes.go",
|
||||
"map.go",
|
||||
"map_bigkey.go",
|
||||
"math.go",
|
||||
|
||||
Vendored
+229
@@ -0,0 +1,229 @@
|
||||
package main
|
||||
|
||||
import "reflect"
|
||||
|
||||
type checker = func(any) bool
|
||||
|
||||
func siblingA() (any, checker) {
|
||||
type Foo struct{ V int }
|
||||
return Foo{V: 1}, func(x any) bool { _, ok := x.(Foo); return ok }
|
||||
}
|
||||
|
||||
func siblingB() (any, checker) {
|
||||
type Foo struct{ V int }
|
||||
return Foo{V: 2}, func(x any) bool { _, ok := x.(Foo); return ok }
|
||||
}
|
||||
|
||||
func nestedScopes() (any, any, any, checker, checker, checker) {
|
||||
var av, bv, cv any
|
||||
var ac, bc, cc checker
|
||||
{
|
||||
type Bar struct{ X int }
|
||||
av = Bar{X: 10}
|
||||
ac = func(x any) bool { _, ok := x.(Bar); return ok }
|
||||
}
|
||||
{
|
||||
type Bar struct{ X int }
|
||||
bv = Bar{X: 20}
|
||||
bc = func(x any) bool { _, ok := x.(Bar); return ok }
|
||||
}
|
||||
{
|
||||
type Bar struct{ X int }
|
||||
cv = Bar{X: 30}
|
||||
cc = func(x any) bool { _, ok := x.(Bar); return ok }
|
||||
}
|
||||
return av, bv, cv, ac, bc, cc
|
||||
}
|
||||
|
||||
func genericWithLocals[T any]() (any, any, checker, checker) {
|
||||
type UsesT struct{ V T }
|
||||
type NoT struct{ V int }
|
||||
var z T
|
||||
return UsesT{V: z}, NoT{V: 42},
|
||||
func(x any) bool { _, ok := x.(UsesT); return ok },
|
||||
func(x any) bool { _, ok := x.(NoT); return ok }
|
||||
}
|
||||
|
||||
func siblingClosures() (any, any, checker, checker) {
|
||||
var av, bv any
|
||||
var ac, bc checker
|
||||
func() {
|
||||
type C struct{ V int }
|
||||
av = C{V: 1}
|
||||
ac = func(x any) bool { _, ok := x.(C); return ok }
|
||||
}()
|
||||
func() {
|
||||
type C struct{ V int }
|
||||
bv = C{V: 2}
|
||||
bc = func(x any) bool { _, ok := x.(C); return ok }
|
||||
}()
|
||||
return av, bv, ac, bc
|
||||
}
|
||||
|
||||
func closureInGenericUsesT[T any]() (any, checker) {
|
||||
var v any
|
||||
var c checker
|
||||
func() {
|
||||
type X struct{ V T }
|
||||
var z T
|
||||
v = X{V: z}
|
||||
c = func(x any) bool { _, ok := x.(X); return ok }
|
||||
}()
|
||||
return v, c
|
||||
}
|
||||
|
||||
func closureInGenericNoT[T any]() (any, checker) {
|
||||
var v any
|
||||
var c checker
|
||||
func() {
|
||||
type X struct{ V int }
|
||||
v = X{V: 7}
|
||||
c = func(x any) bool { _, ok := x.(X); return ok }
|
||||
}()
|
||||
return v, c
|
||||
}
|
||||
|
||||
func siblingClosuresInGeneric[T any]() (any, any, checker, checker) {
|
||||
var av, bv any
|
||||
var ac, bc checker
|
||||
func() {
|
||||
type C struct{ V T }
|
||||
var z T
|
||||
av = C{V: z}
|
||||
ac = func(x any) bool { _, ok := x.(C); return ok }
|
||||
}()
|
||||
func() {
|
||||
type C struct{ V T }
|
||||
var z T
|
||||
bv = C{V: z}
|
||||
bc = func(x any) bool { _, ok := x.(C); return ok }
|
||||
}()
|
||||
return av, bv, ac, bc
|
||||
}
|
||||
|
||||
func doublyNestedInGeneric[T any]() (any, checker) {
|
||||
var v any
|
||||
var c checker
|
||||
func() {
|
||||
func() {
|
||||
type Y struct{ V T }
|
||||
var z T
|
||||
v = Y{V: z}
|
||||
c = func(x any) bool { _, ok := x.(Y); return ok }
|
||||
}()
|
||||
}()
|
||||
return v, c
|
||||
}
|
||||
|
||||
func expect(name string, ok bool) {
|
||||
if ok {
|
||||
println("ok:", name)
|
||||
} else {
|
||||
println("BUG:", name)
|
||||
}
|
||||
}
|
||||
|
||||
// issue5180Copy1 and issue5180CopyIgnoreNilMembers are the original
|
||||
// repro from https://github.com/tinygo-org/tinygo/issues/5180. Each
|
||||
// declares its own local Foo with a different field type, then uses
|
||||
// reflect.New to construct a value via the runtime type and asserts it
|
||||
// back. Without distinct local-type names the two Foos collide and the
|
||||
// second assertion panics.
|
||||
func issue5180Copy1() bool {
|
||||
type Foo struct{ A int }
|
||||
f1 := &Foo{}
|
||||
dst := reflect.New(reflect.TypeOf(f1).Elem()).Interface()
|
||||
_, ok := dst.(*Foo)
|
||||
return ok
|
||||
}
|
||||
|
||||
func issue5180CopyIgnoreNilMembers() (ok bool) {
|
||||
type Foo struct{ A *int }
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ok = false
|
||||
}
|
||||
}()
|
||||
f1 := &Foo{}
|
||||
dst := reflect.New(reflect.TypeOf(f1).Elem()).Interface()
|
||||
_, ok = dst.(*Foo)
|
||||
return ok
|
||||
}
|
||||
|
||||
func main() {
|
||||
expect("issue5180 TestCopy1", issue5180Copy1())
|
||||
expect("issue5180 TestCopyIgnoreNilMembers", issue5180CopyIgnoreNilMembers())
|
||||
|
||||
// Two siblings with same-named local types are distinct.
|
||||
aV, aC := siblingA()
|
||||
bV, bC := siblingB()
|
||||
expect("siblingA.Foo accepts own", aC(aV))
|
||||
expect("siblingB.Foo accepts own", bC(bV))
|
||||
expect("siblingA.Foo rejects siblingB.Foo", !aC(bV))
|
||||
expect("siblingB.Foo rejects siblingA.Foo", !bC(aV))
|
||||
|
||||
// Three sibling-scope locals in one function are mutually distinct.
|
||||
n1V, n2V, n3V, n1C, n2C, n3C := nestedScopes()
|
||||
expect("nestedScopes.Bar#1 accepts own", n1C(n1V))
|
||||
expect("nestedScopes.Bar#2 accepts own", n2C(n2V))
|
||||
expect("nestedScopes.Bar#3 accepts own", n3C(n3V))
|
||||
expect("nestedScopes.Bar#1 rejects #2", !n1C(n2V))
|
||||
expect("nestedScopes.Bar#2 rejects #3", !n2C(n3V))
|
||||
expect("nestedScopes.Bar#1 rejects #3", !n1C(n3V))
|
||||
|
||||
// Generic instantiations are distinct from each other but match
|
||||
// themselves across calls.
|
||||
iU, iN, iUC, iNC := genericWithLocals[int]()
|
||||
sU, sN, sUC, sNC := genericWithLocals[string]()
|
||||
iU2, iN2, _, _ := genericWithLocals[int]()
|
||||
expect("genericWithLocals[int].UsesT accepts own", iUC(iU))
|
||||
expect("genericWithLocals[int].NoT accepts own", iNC(iN))
|
||||
expect("genericWithLocals[string].UsesT accepts own", sUC(sU))
|
||||
expect("genericWithLocals[string].NoT accepts own", sNC(sN))
|
||||
expect("genericWithLocals[int].UsesT rejects [string].UsesT", !iUC(sU))
|
||||
expect("genericWithLocals[int].NoT rejects [string].NoT", !iNC(sN))
|
||||
expect("genericWithLocals[string].UsesT rejects [int].UsesT", !sUC(iU))
|
||||
expect("genericWithLocals[string].NoT rejects [int].NoT", !sNC(iN))
|
||||
expect("genericWithLocals[int].UsesT matches across calls", iUC(iU2))
|
||||
expect("genericWithLocals[int].NoT matches across calls", iNC(iN2))
|
||||
|
||||
// Sibling closures in a non-generic function.
|
||||
scA, scB, scAC, scBC := siblingClosures()
|
||||
expect("siblingClosures.C#1 accepts own", scAC(scA))
|
||||
expect("siblingClosures.C#2 accepts own", scBC(scB))
|
||||
expect("siblingClosures.C#1 rejects C#2", !scAC(scB))
|
||||
expect("siblingClosures.C#2 rejects C#1", !scBC(scA))
|
||||
|
||||
// Closure inside generic function, type uses T.
|
||||
cgi, cgiC := closureInGenericUsesT[int]()
|
||||
cgs, cgsC := closureInGenericUsesT[string]()
|
||||
expect("closureInGenericUsesT[int].X accepts own", cgiC(cgi))
|
||||
expect("closureInGenericUsesT[string].X accepts own", cgsC(cgs))
|
||||
expect("closureInGenericUsesT[int].X rejects [string].X", !cgiC(cgs))
|
||||
expect("closureInGenericUsesT[string].X rejects [int].X", !cgsC(cgi))
|
||||
|
||||
// Closure inside generic function, type does not use T.
|
||||
cni, cniC := closureInGenericNoT[int]()
|
||||
cns, cnsC := closureInGenericNoT[string]()
|
||||
expect("closureInGenericNoT[int].X accepts own", cniC(cni))
|
||||
expect("closureInGenericNoT[string].X accepts own", cnsC(cns))
|
||||
expect("closureInGenericNoT[int].X rejects [string].X", !cniC(cns))
|
||||
expect("closureInGenericNoT[string].X rejects [int].X", !cnsC(cni))
|
||||
|
||||
// Sibling closures inside a generic function.
|
||||
sgIA, sgIB, sgIAC, sgIBC := siblingClosuresInGeneric[int]()
|
||||
sgSA, _, sgSAC, _ := siblingClosuresInGeneric[string]()
|
||||
expect("siblingClosuresInGeneric[int].C#1 accepts own", sgIAC(sgIA))
|
||||
expect("siblingClosuresInGeneric[int].C#2 accepts own", sgIBC(sgIB))
|
||||
expect("siblingClosuresInGeneric[int].C#1 rejects C#2", !sgIAC(sgIB))
|
||||
expect("siblingClosuresInGeneric[int].C#1 rejects [string].C#1", !sgIAC(sgSA))
|
||||
expect("siblingClosuresInGeneric[string].C#1 rejects [int].C#1", !sgSAC(sgIA))
|
||||
|
||||
// Doubly-nested closure inside a generic function.
|
||||
dni, dniC := doublyNestedInGeneric[int]()
|
||||
dns, dnsC := doublyNestedInGeneric[string]()
|
||||
expect("doublyNestedInGeneric[int].Y accepts own", dniC(dni))
|
||||
expect("doublyNestedInGeneric[string].Y accepts own", dnsC(dns))
|
||||
expect("doublyNestedInGeneric[int].Y rejects [string].Y", !dniC(dns))
|
||||
expect("doublyNestedInGeneric[string].Y rejects [int].Y", !dnsC(dni))
|
||||
}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
ok: issue5180 TestCopy1
|
||||
ok: issue5180 TestCopyIgnoreNilMembers
|
||||
ok: siblingA.Foo accepts own
|
||||
ok: siblingB.Foo accepts own
|
||||
ok: siblingA.Foo rejects siblingB.Foo
|
||||
ok: siblingB.Foo rejects siblingA.Foo
|
||||
ok: nestedScopes.Bar#1 accepts own
|
||||
ok: nestedScopes.Bar#2 accepts own
|
||||
ok: nestedScopes.Bar#3 accepts own
|
||||
ok: nestedScopes.Bar#1 rejects #2
|
||||
ok: nestedScopes.Bar#2 rejects #3
|
||||
ok: nestedScopes.Bar#1 rejects #3
|
||||
ok: genericWithLocals[int].UsesT accepts own
|
||||
ok: genericWithLocals[int].NoT accepts own
|
||||
ok: genericWithLocals[string].UsesT accepts own
|
||||
ok: genericWithLocals[string].NoT accepts own
|
||||
ok: genericWithLocals[int].UsesT rejects [string].UsesT
|
||||
ok: genericWithLocals[int].NoT rejects [string].NoT
|
||||
ok: genericWithLocals[string].UsesT rejects [int].UsesT
|
||||
ok: genericWithLocals[string].NoT rejects [int].NoT
|
||||
ok: genericWithLocals[int].UsesT matches across calls
|
||||
ok: genericWithLocals[int].NoT matches across calls
|
||||
ok: siblingClosures.C#1 accepts own
|
||||
ok: siblingClosures.C#2 accepts own
|
||||
ok: siblingClosures.C#1 rejects C#2
|
||||
ok: siblingClosures.C#2 rejects C#1
|
||||
ok: closureInGenericUsesT[int].X accepts own
|
||||
ok: closureInGenericUsesT[string].X accepts own
|
||||
ok: closureInGenericUsesT[int].X rejects [string].X
|
||||
ok: closureInGenericUsesT[string].X rejects [int].X
|
||||
ok: closureInGenericNoT[int].X accepts own
|
||||
ok: closureInGenericNoT[string].X accepts own
|
||||
ok: closureInGenericNoT[int].X rejects [string].X
|
||||
ok: closureInGenericNoT[string].X rejects [int].X
|
||||
ok: siblingClosuresInGeneric[int].C#1 accepts own
|
||||
ok: siblingClosuresInGeneric[int].C#2 accepts own
|
||||
ok: siblingClosuresInGeneric[int].C#1 rejects C#2
|
||||
ok: siblingClosuresInGeneric[int].C#1 rejects [string].C#1
|
||||
ok: siblingClosuresInGeneric[string].C#1 rejects [int].C#1
|
||||
ok: doublyNestedInGeneric[int].Y accepts own
|
||||
ok: doublyNestedInGeneric[string].Y accepts own
|
||||
ok: doublyNestedInGeneric[int].Y rejects [string].Y
|
||||
ok: doublyNestedInGeneric[string].Y rejects [int].Y
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
module github.com/tinygo-org/tinygo/testdata/localtypes
|
||||
|
||||
go 1.21
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
package lib
|
||||
|
||||
type Checker = func(any) bool
|
||||
|
||||
func GenericWithLocals[T any]() (any, any, Checker, Checker) {
|
||||
type UsesT struct{ V T }
|
||||
type NoT struct{ V int }
|
||||
var z T
|
||||
return UsesT{V: z}, NoT{V: 42},
|
||||
func(x any) bool { _, ok := x.(UsesT); return ok },
|
||||
func(x any) bool { _, ok := x.(NoT); return ok }
|
||||
}
|
||||
|
||||
func SiblingClosures() (any, any, Checker, Checker) {
|
||||
var av, bv any
|
||||
var ac, bc Checker
|
||||
func() {
|
||||
type Foo struct{ V int }
|
||||
av = Foo{V: 1}
|
||||
ac = func(x any) bool { _, ok := x.(Foo); return ok }
|
||||
}()
|
||||
func() {
|
||||
type Foo struct{ V int }
|
||||
bv = Foo{V: 2}
|
||||
bc = func(x any) bool { _, ok := x.(Foo); return ok }
|
||||
}()
|
||||
return av, bv, ac, bc
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
package lib2
|
||||
|
||||
import "github.com/tinygo-org/tinygo/testdata/localtypes/lib"
|
||||
|
||||
func IntPair() (any, any, lib.Checker, lib.Checker) { return lib.GenericWithLocals[int]() }
|
||||
Vendored
+40
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/tinygo-org/tinygo/testdata/localtypes/lib"
|
||||
"github.com/tinygo-org/tinygo/testdata/localtypes/lib2"
|
||||
)
|
||||
|
||||
func expect(name string, ok bool) {
|
||||
if ok {
|
||||
println("ok:", name)
|
||||
} else {
|
||||
println("BUG:", name)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
mainU, mainN, mainUC, mainNC := lib.GenericWithLocals[int]()
|
||||
libU, libN, libUC, libNC := lib2.IntPair()
|
||||
|
||||
// Same instance compiled in two packages: each package's checker
|
||||
// must accept the other package's value.
|
||||
expect("main GenericWithLocals[int].UsesT accepts lib2's value", mainUC(libU))
|
||||
expect("main GenericWithLocals[int].NoT accepts lib2's value", mainNC(libN))
|
||||
expect("lib2 GenericWithLocals[int].UsesT accepts main's value", libUC(mainU))
|
||||
expect("lib2 GenericWithLocals[int].NoT accepts main's value", libNC(mainN))
|
||||
|
||||
// Different instantiations: distinct types.
|
||||
stringU, stringN, stringUC, stringNC := lib.GenericWithLocals[string]()
|
||||
expect("GenericWithLocals[int].UsesT rejects [string].UsesT", !mainUC(stringU))
|
||||
expect("GenericWithLocals[int].NoT rejects [string].NoT", !mainNC(stringN))
|
||||
expect("GenericWithLocals[string].UsesT rejects [int].UsesT", !stringUC(mainU))
|
||||
expect("GenericWithLocals[string].NoT rejects [int].NoT", !stringNC(mainN))
|
||||
|
||||
// Sibling closures inside one function (declared in lib).
|
||||
scA, scB, scAC, scBC := lib.SiblingClosures()
|
||||
expect("lib.SiblingClosures.Foo#1 accepts own", scAC(scA))
|
||||
expect("lib.SiblingClosures.Foo#2 accepts own", scBC(scB))
|
||||
expect("lib.SiblingClosures.Foo#1 rejects Foo#2", !scAC(scB))
|
||||
expect("lib.SiblingClosures.Foo#2 rejects Foo#1", !scBC(scA))
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
ok: main GenericWithLocals[int].UsesT accepts lib2's value
|
||||
ok: main GenericWithLocals[int].NoT accepts lib2's value
|
||||
ok: lib2 GenericWithLocals[int].UsesT accepts main's value
|
||||
ok: lib2 GenericWithLocals[int].NoT accepts main's value
|
||||
ok: GenericWithLocals[int].UsesT rejects [string].UsesT
|
||||
ok: GenericWithLocals[int].NoT rejects [string].NoT
|
||||
ok: GenericWithLocals[string].UsesT rejects [int].UsesT
|
||||
ok: GenericWithLocals[string].NoT rejects [int].NoT
|
||||
ok: lib.SiblingClosures.Foo#1 accepts own
|
||||
ok: lib.SiblingClosures.Foo#2 accepts own
|
||||
ok: lib.SiblingClosures.Foo#1 rejects Foo#2
|
||||
ok: lib.SiblingClosures.Foo#2 rejects Foo#1
|
||||
Reference in New Issue
Block a user