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:
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