mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
73db9776b1
Add cases where same-named local aliases instantiate generic functions, local types, and methods with float32 and float64. Record the current collisions and incorrect results.
409 lines
12 KiB
Go
409 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"reflect"
|
|
"unsafe"
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
// issue4931Item's layout depends on T, so sharing generic function
|
|
// bodies across different local T declarations is observable.
|
|
type issue4931Item[T any] struct {
|
|
_ T
|
|
Labels []int
|
|
}
|
|
|
|
func issue4931Run[T any](input <-chan issue4931Item[T]) int {
|
|
item := <-input
|
|
return len(item.Labels)
|
|
}
|
|
|
|
func issue4931A() int {
|
|
type T struct{ _ [1]any }
|
|
items := make(chan issue4931Item[T], 1)
|
|
items <- issue4931Item[T]{Labels: nil}
|
|
return issue4931Run(items)
|
|
}
|
|
|
|
func issue4931B() int {
|
|
type T struct{ _ [0]any }
|
|
items := make(chan issue4931Item[T], 1)
|
|
items <- issue4931Item[T]{Labels: nil}
|
|
return issue4931Run(items)
|
|
}
|
|
|
|
func issue4931Pair[A, B any](input <-chan issue4931Item[A]) int {
|
|
item := <-input
|
|
return len(item.Labels)
|
|
}
|
|
|
|
func issue4931PairA() int {
|
|
type T struct{ _ [1]any }
|
|
items := make(chan issue4931Item[T], 1)
|
|
items <- issue4931Item[T]{Labels: nil}
|
|
return issue4931Pair[T, int](items)
|
|
}
|
|
|
|
func issue4931PairB() int {
|
|
type T struct{ _ [0]any }
|
|
items := make(chan issue4931Item[T], 1)
|
|
items <- issue4931Item[T]{Labels: nil}
|
|
return issue4931Pair[T, int](items)
|
|
}
|
|
|
|
type issue4931Runner[T any] struct{}
|
|
|
|
func (issue4931Runner[T]) run(input <-chan issue4931Item[T]) int {
|
|
item := <-input
|
|
return len(item.Labels)
|
|
}
|
|
|
|
func issue4931MethodA() int {
|
|
type T struct{ _ [1]any }
|
|
items := make(chan issue4931Item[T], 1)
|
|
items <- issue4931Item[T]{Labels: nil}
|
|
return issue4931Runner[T]{}.run(items)
|
|
}
|
|
|
|
func issue4931MethodB() int {
|
|
type T struct{ _ [0]any }
|
|
items := make(chan issue4931Item[T], 1)
|
|
items <- issue4931Item[T]{Labels: nil}
|
|
return issue4931Runner[T]{}.run(items)
|
|
}
|
|
|
|
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 aliasSize[F float32 | float64]() int {
|
|
return 8 * int(unsafe.Sizeof(F(0)))
|
|
}
|
|
|
|
func aliasSizeCaller32() int {
|
|
type F = float32
|
|
return aliasSize[F]()
|
|
}
|
|
|
|
func aliasSizeCaller64() int {
|
|
type F = float64
|
|
return aliasSize[F]()
|
|
}
|
|
|
|
func aliasLocal[F float32 | float64]() (any, checker) {
|
|
type Local struct{ Value F }
|
|
return Local{}, func(x any) bool {
|
|
_, ok := x.(Local)
|
|
return ok
|
|
}
|
|
}
|
|
|
|
func aliasLocalCaller32() (any, checker) {
|
|
type F = float32
|
|
return aliasLocal[F]()
|
|
}
|
|
|
|
func aliasLocalCaller64() (any, checker) {
|
|
type F = float64
|
|
return aliasLocal[F]()
|
|
}
|
|
|
|
type aliasBox[F float32 | float64] struct {
|
|
Value F
|
|
}
|
|
|
|
func (b aliasBox[F]) Get() F {
|
|
return b.Value
|
|
}
|
|
|
|
func aliasBoxCaller32() bool {
|
|
type F = float32
|
|
var box any = aliasBox[F]{Value: 32}
|
|
_, ok := box.(interface{ Get() float32 })
|
|
return ok
|
|
}
|
|
|
|
func aliasBoxCaller64() bool {
|
|
type F = float64
|
|
var box any = aliasBox[F]{Value: 64}
|
|
_, ok := box.(interface{ Get() float64 })
|
|
return ok
|
|
}
|
|
|
|
type aliasMethodResult[F float32 | float64] struct {
|
|
Value F
|
|
}
|
|
|
|
type aliasMethodBox[F float32 | float64] struct {
|
|
Value F
|
|
}
|
|
|
|
func (b aliasMethodBox[F]) Get() aliasMethodResult[F] {
|
|
return aliasMethodResult[F]{Value: b.Value}
|
|
}
|
|
|
|
func aliasMethodCaller32() (any, checker) {
|
|
type F = float32
|
|
return aliasMethodBox[F]{Value: 32}, func(x any) bool {
|
|
_, ok := x.(interface {
|
|
Get() aliasMethodResult[F]
|
|
})
|
|
return ok
|
|
}
|
|
}
|
|
|
|
func aliasMethodCaller64() (any, checker) {
|
|
type F = float64
|
|
return aliasMethodBox[F]{Value: 64}, func(x any) bool {
|
|
_, ok := x.(interface {
|
|
Get() aliasMethodResult[F]
|
|
})
|
|
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))
|
|
|
|
// Issue 4931: generic instances with local type args must not share bodies.
|
|
println("issue4931A labels:", issue4931A())
|
|
println("issue4931B labels:", issue4931B())
|
|
println("issue4931PairA labels:", issue4931PairA())
|
|
println("issue4931PairB labels:", issue4931PairB())
|
|
println("issue4931MethodA labels:", issue4931MethodA())
|
|
println("issue4931MethodB labels:", issue4931MethodB())
|
|
expect("aliasSize[float32]==32", aliasSizeCaller32() == 32)
|
|
expect("aliasSize[float64]==64", aliasSizeCaller64() == 64)
|
|
aliasLocal32, aliasLocalCheck32 := aliasLocalCaller32()
|
|
aliasLocal64, aliasLocalCheck64 := aliasLocalCaller64()
|
|
expect("aliasLocal[float32] accepts own", aliasLocalCheck32(aliasLocal32))
|
|
expect("aliasLocal[float64] accepts own", aliasLocalCheck64(aliasLocal64))
|
|
expect("aliasLocal[float32] rejects [float64]", !aliasLocalCheck32(aliasLocal64))
|
|
expect("aliasLocal[float64] rejects [float32]", !aliasLocalCheck64(aliasLocal32))
|
|
expect("aliasBox[float32] implements Get() float32", aliasBoxCaller32())
|
|
expect("aliasBox[float64] implements Get() float64", aliasBoxCaller64())
|
|
aliasMethod32, aliasMethodCheck32 := aliasMethodCaller32()
|
|
aliasMethod64, aliasMethodCheck64 := aliasMethodCaller64()
|
|
expect("aliasMethod[float32] accepts own", aliasMethodCheck32(aliasMethod32))
|
|
expect("aliasMethod[float64] accepts own", aliasMethodCheck64(aliasMethod64))
|
|
expect("aliasMethod[float32] rejects [float64]", !aliasMethodCheck32(aliasMethod64))
|
|
expect("aliasMethod[float64] rejects [float32]", !aliasMethodCheck64(aliasMethod32))
|
|
}
|