From 5c4a8fa46d44f2db5d37ddbb271918dfa38bfada Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Fri, 28 Jun 2024 09:40:54 +1000 Subject: [PATCH 1/5] Support slices in go.shape strings --- compiler/function.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/compiler/function.go b/compiler/function.go index 08518b9..0c103b6 100644 --- a/compiler/function.go +++ b/compiler/function.go @@ -558,6 +558,10 @@ func (g *genericInstance) gcshapePath() string { func writeGoShape(b *strings.Builder, tt types.Type) { b.WriteString("go.shape.") + writeGoShapeType(b, tt) +} + +func writeGoShapeType(b *strings.Builder, tt types.Type) { switch t := tt.Underlying().(type) { case *types.Basic: b.WriteString(t.Name()) @@ -588,6 +592,9 @@ func writeGoShape(b *strings.Builder, tt types.Type) { b.WriteString(f.Type().String()) } b.WriteString(" }") + case *types.Slice: + b.WriteString("[]") + writeGoShapeType(b, t.Elem()) default: panic(fmt.Sprintf("not implemented: %#v (%T)", tt, t)) } From a6234c3e3b32c7f78e6235ff641906fbef655696 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Fri, 28 Jun 2024 09:41:01 +1000 Subject: [PATCH 2/5] Support struct tags --- compiler/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/types.go b/compiler/types.go index bbdaa63..4ddce2f 100644 --- a/compiler/types.go +++ b/compiler/types.go @@ -42,7 +42,7 @@ func typeExpr(p *packages.Package, typ types.Type, typeArg func(*types.TypeParam fields[i].Names = []*ast.Ident{ast.NewIdent(f.Name())} } if tag := t.Tag(i); tag != "" { - panic("not implemented: struct tags") + fields[i].Tag = &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(tag)} } } return &ast.StructType{Fields: &ast.FieldList{List: fields}} From 02e002b9ffd6187bac5785b12e270a6c449a1185 Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Fri, 28 Jun 2024 09:59:53 +1000 Subject: [PATCH 3/5] Test struct tag support --- compiler/coroutine_test.go | 6 ++ compiler/testdata/coroutine.go | 24 +++++ compiler/testdata/coroutine_durable.go | 116 ++++++++++++++++++++++++- 3 files changed, 145 insertions(+), 1 deletion(-) diff --git a/compiler/coroutine_test.go b/compiler/coroutine_test.go index e515f9d..c8ce3ce 100644 --- a/compiler/coroutine_test.go +++ b/compiler/coroutine_test.go @@ -294,6 +294,12 @@ func TestCoroutineYield(t *testing.T) { coro: func() { GenericStructClosure(3) }, yields: []int{3, 5, 7}, }, + + { + name: "JSON roundtrip", + coro: func() { JSONRoundTrip(3) }, + yields: []int{3, 3}, + }, } // This emulates the installation of function type information by the diff --git a/compiler/testdata/coroutine.go b/compiler/testdata/coroutine.go index e89f427..f85296b 100644 --- a/compiler/testdata/coroutine.go +++ b/compiler/testdata/coroutine.go @@ -3,6 +3,8 @@ package testdata import ( + "encoding/json" + "fmt" "math" "reflect" "time" @@ -777,3 +779,25 @@ type GenericAdder[A adder] struct{ adder A } func (b *GenericAdder[A]) Add(n int) int { return b.adder.Add(n) } + +func JSONRoundTrip(n int) { + b, err := json.Marshal(struct { + N int `json:"n"` + }{n}) + if err != nil { + panic(err) + } + if string(b) != fmt.Sprintf(`{"n":%d}`, n) { + panic(fmt.Errorf("unexpected JSON: %v", b)) + } + + coroutine.Yield[int, any](n) + + var result struct { + N int `json:"n"` + } + if err := json.Unmarshal(b, &result); err != nil { + panic(err) + } + coroutine.Yield[int, any](result.N) +} diff --git a/compiler/testdata/coroutine_durable.go b/compiler/testdata/coroutine_durable.go index 6b15b8a..2aa9d90 100644 --- a/compiler/testdata/coroutine_durable.go +++ b/compiler/testdata/coroutine_durable.go @@ -3,6 +3,8 @@ package testdata import ( + json "encoding/json" + fmt "fmt" coroutine "github.com/dispatchrun/coroutine" subpkg "github.com/dispatchrun/coroutine/compiler/testdata/subpkg" math "math" @@ -3517,7 +3519,7 @@ func IdentityGenericInt(n int) { IdentityGeneric[int](n) } //go:noinline func IdentityGenericClosure[T any](_fn0 T) { - _c := coroutine.LoadContext[T, any]() + _c := coroutine.LoadContext[int, any]() var _f0 *struct { IP int X0 T @@ -4263,6 +4265,117 @@ type GenericAdder[A adder] struct{ adder A } func (b *GenericAdder[A]) Add(n int) int { return b.adder.Add(n) } + +//go:noinline +func JSONRoundTrip(_fn0 int) { + _c := coroutine.LoadContext[int, any]() + var _f0 *struct { + IP int + X0 int + X1 []byte + X2 error + X3 string + X4 bool + X5 error + X6 struct { + N int "json:\"n\"" + } + X7 error + } = coroutine.Push[struct { + IP int + X0 int + X1 []byte + X2 error + X3 string + X4 bool + X5 error + X6 struct { + N int "json:\"n\"" + } + X7 error + }](&_c.Stack) + if _f0.IP == 0 { + *_f0 = struct { + IP int + X0 int + X1 []byte + X2 error + X3 string + X4 bool + X5 error + X6 struct { + N int "json:\"n\"" + } + X7 error + }{X0: _fn0} + } + defer func() { + if !_c.Unwinding() { + coroutine.Pop(&_c.Stack) + } + }() + switch { + case _f0.IP < 2: + _f0.X1, _f0.X2 = json.Marshal(struct { + N int `json:"n"` + }{_f0.X0}) + _f0.IP = 2 + fallthrough + case _f0.IP < 3: + if _f0.X2 != nil { + panic(_f0.X2) + } + _f0.IP = 3 + fallthrough + case _f0.IP < 7: + switch { + case _f0.IP < 4: + _f0.X3 = fmt.Sprintf(`{"n":%d}`, _f0.X0) + _f0.IP = 4 + fallthrough + case _f0.IP < 5: + _f0.X4 = string(_f0.X1) != _f0.X3 + _f0.IP = 5 + fallthrough + case _f0.IP < 7: + if _f0.X4 { + switch { + case _f0.IP < 6: + _f0.X5 = fmt.Errorf("unexpected JSON: %v", _f0.X1) + _f0.IP = 6 + fallthrough + case _f0.IP < 7: + panic(_f0.X5) + } + } + } + _f0.IP = 7 + fallthrough + case _f0.IP < 8: + + coroutine.Yield[int, any](_f0.X0) + _f0.IP = 8 + fallthrough + case _f0.IP < 9: + _f0.IP = 9 + fallthrough + case _f0.IP < 11: + switch { + case _f0.IP < 10: + _f0.X7 = json.Unmarshal(_f0.X1, &_f0.X6) + _f0.IP = 10 + fallthrough + case _f0.IP < 11: + if _f0.X7 != nil { + panic(_f0.X7) + } + } + _f0.IP = 11 + fallthrough + case _f0.IP < 12: + coroutine.Yield[int, any](_f0.X6.N) + } +} func init() { _types.RegisterFunc[func(_fn1 int) (_ func(int))]("github.com/dispatchrun/coroutine/compiler/testdata.(*Box).Closure") _types.RegisterClosure[func(_fn0 int), struct { @@ -4314,6 +4427,7 @@ func init() { _types.RegisterFunc[func(n int)]("github.com/dispatchrun/coroutine/compiler/testdata.IdentityGeneric[go.shape.int]") _types.RegisterFunc[func(_fn0 int)]("github.com/dispatchrun/coroutine/compiler/testdata.IndirectClosure") _types.RegisterFunc[func()]("github.com/dispatchrun/coroutine/compiler/testdata.InterfaceEmbedded") + _types.RegisterFunc[func(_fn0 int)]("github.com/dispatchrun/coroutine/compiler/testdata.JSONRoundTrip") _types.RegisterFunc[func(_ int)]("github.com/dispatchrun/coroutine/compiler/testdata.LoopBreakAndContinue") _types.RegisterFunc[func(_fn0 ...int) (_ func())]("github.com/dispatchrun/coroutine/compiler/testdata.MakeEllipsisClosure") _types.RegisterClosure[func(), struct { From 242f88c52257c11dd78b93ce27ef2c71c9bc12ef Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Fri, 28 Jun 2024 10:05:11 +1000 Subject: [PATCH 4/5] Test generics with slices --- compiler/coroutine_test.go | 6 ++ compiler/testdata/coroutine.go | 29 +++++ compiler/testdata/coroutine_durable.go | 141 +++++++++++++++++++++++++ 3 files changed, 176 insertions(+) diff --git a/compiler/coroutine_test.go b/compiler/coroutine_test.go index c8ce3ce..3d3a217 100644 --- a/compiler/coroutine_test.go +++ b/compiler/coroutine_test.go @@ -300,6 +300,12 @@ func TestCoroutineYield(t *testing.T) { coro: func() { JSONRoundTrip(3) }, yields: []int{3, 3}, }, + + { + name: "generics with slices", + coro: func() { GenericSlice(3) }, + yields: []int{0, 1, 2, 0, 1, 2}, + }, } // This emulates the installation of function type information by the diff --git a/compiler/testdata/coroutine.go b/compiler/testdata/coroutine.go index f85296b..3a587a7 100644 --- a/compiler/testdata/coroutine.go +++ b/compiler/testdata/coroutine.go @@ -801,3 +801,32 @@ func JSONRoundTrip(n int) { } coroutine.Yield[int, any](result.N) } + +type Cloner[S ~[]E, E any] struct { + Slice S +} + +func (c *Cloner[S, E]) Clone() S { + s2 := make(S, len(c.Slice)) + copy(s2, c.Slice) + return s2 +} + +func GenericSlice(n int) { + ints := make([]int, n) + for i := range ints { + ints[i] = i + } + for _, x := range ints { + coroutine.Yield[int, any](x) + } + + cloner := &Cloner[[]int, int]{Slice: ints} + ints2 := cloner.Clone() + + clear(ints) + + for _, x := range ints2 { + coroutine.Yield[int, any](x) + } +} diff --git a/compiler/testdata/coroutine_durable.go b/compiler/testdata/coroutine_durable.go index 2aa9d90..408c17f 100644 --- a/compiler/testdata/coroutine_durable.go +++ b/compiler/testdata/coroutine_durable.go @@ -4376,6 +4376,145 @@ func JSONRoundTrip(_fn0 int) { coroutine.Yield[int, any](_f0.X6.N) } } + +type Cloner[S ~[]E, E any] struct { + Slice S +} + +func (c *Cloner[S, E]) Clone() S { + s2 := make(S, len(c.Slice)) + copy(s2, c.Slice) + return s2 +} + +//go:noinline +func GenericSlice(_fn0 int) { + _c := coroutine.LoadContext[int, any]() + var _f0 *struct { + IP int + X0 int + X1 []int + X2 []int + X3 int + X4 int + X5 *Cloner[[]int, int] + X6 []int + X7 []int + X8 int + X9 int + } = coroutine.Push[struct { + IP int + X0 int + X1 []int + X2 []int + X3 int + X4 int + X5 *Cloner[[]int, int] + X6 []int + X7 []int + X8 int + X9 int + }](&_c.Stack) + if _f0.IP == 0 { + *_f0 = struct { + IP int + X0 int + X1 []int + X2 []int + X3 int + X4 int + X5 *Cloner[[]int, int] + X6 []int + X7 []int + X8 int + X9 int + }{X0: _fn0} + } + defer func() { + if !_c.Unwinding() { + coroutine.Pop(&_c.Stack) + } + }() + switch { + case _f0.IP < 2: + _f0.X1 = make([]int, _f0.X0) + _f0.IP = 2 + fallthrough + case _f0.IP < 3: + for i := range _f0.X1 { + _f0.X1[i] = i + } + _f0.IP = 3 + fallthrough + case _f0.IP < 7: + switch { + case _f0.IP < 4: + _f0.X2 = _f0.X1 + _f0.IP = 4 + fallthrough + case _f0.IP < 7: + switch { + case _f0.IP < 5: + _f0.X3 = 0 + _f0.IP = 5 + fallthrough + case _f0.IP < 7: + for ; _f0.X3 < len(_f0.X2); _f0.X3, _f0.IP = _f0.X3+1, 5 { + switch { + case _f0.IP < 6: + _f0.X4 = _f0.X2[_f0.X3] + _f0.IP = 6 + fallthrough + case _f0.IP < 7: + + coroutine.Yield[int, any](_f0.X4) + } + } + } + } + _f0.IP = 7 + fallthrough + case _f0.IP < 8: + _f0.X5 = &Cloner[[]int, int]{Slice: _f0.X1} + _f0.IP = 8 + fallthrough + case _f0.IP < 9: + _f0.X6 = _f0.X5.Clone() + _f0.IP = 9 + fallthrough + case _f0.IP < 10: + + clear(_f0.X1) + _f0.IP = 10 + fallthrough + case _f0.IP < 14: + switch { + case _f0.IP < 11: + _f0.X7 = _f0.X6 + _f0.IP = 11 + fallthrough + case _f0.IP < 14: + switch { + case _f0.IP < 12: + _f0.X8 = 0 + _f0.IP = 12 + fallthrough + case _f0.IP < 14: + for ; _f0.X8 < len(_f0.X7); _f0.X8, _f0.IP = _f0.X8+1, 12 { + switch { + case _f0.IP < 13: + _f0.X9 = _f0.X7[_f0.X8] + _f0.IP = 13 + fallthrough + case _f0.IP < 14: + + coroutine.Yield[int, any](_f0.X9) + } + } + } + } + } +} func init() { _types.RegisterFunc[func(_fn1 int) (_ func(int))]("github.com/dispatchrun/coroutine/compiler/testdata.(*Box).Closure") _types.RegisterClosure[func(_fn0 int), struct { @@ -4387,6 +4526,7 @@ func init() { } }]("github.com/dispatchrun/coroutine/compiler/testdata.(*Box).Closure.func1") _types.RegisterFunc[func()]("github.com/dispatchrun/coroutine/compiler/testdata.(*Box).YieldAndInc") + _types.RegisterFunc[func() (_ []int)]("github.com/dispatchrun/coroutine/compiler/testdata.(*Cloner[go.shape.[]int,go.shape.int]).Clone") _types.RegisterFunc[func(n int) (_ int)]("github.com/dispatchrun/coroutine/compiler/testdata.(*GenericAdder[go.shape.struct { github.com/dispatchrun/coroutine/compiler/testdata.base int; github.com/dispatchrun/coroutine/compiler/testdata.mul int }]).Add") _types.RegisterFunc[func(_fn1 int) (_ func(int))]("github.com/dispatchrun/coroutine/compiler/testdata.(*GenericBox[go.shape.int]).Closure") _types.RegisterClosure[func(_fn0 int), struct { @@ -4417,6 +4557,7 @@ func init() { _types.RegisterFunc[func(_fn0 int)]("github.com/dispatchrun/coroutine/compiler/testdata.EvenSquareGenerator") _types.RegisterFunc[func(_fn0 int)]("github.com/dispatchrun/coroutine/compiler/testdata.FizzBuzzIfGenerator") _types.RegisterFunc[func(_fn0 int)]("github.com/dispatchrun/coroutine/compiler/testdata.FizzBuzzSwitchGenerator") + _types.RegisterFunc[func(_fn0 int)]("github.com/dispatchrun/coroutine/compiler/testdata.GenericSlice") _types.RegisterFunc[func(_fn0 int)]("github.com/dispatchrun/coroutine/compiler/testdata.GenericStructClosure") _types.RegisterFunc[func(n int)]("github.com/dispatchrun/coroutine/compiler/testdata.Identity") _types.RegisterFunc[func(n int)]("github.com/dispatchrun/coroutine/compiler/testdata.IdentityGenericClosureInt") From c9119d611967f22d5da52ff323698094e90bebdf Mon Sep 17 00:00:00 2001 From: Chris O'Hara Date: Fri, 28 Jun 2024 10:11:11 +1000 Subject: [PATCH 5/5] Fix lint warnings --- types/inspect.go | 54 ++++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/types/inspect.go b/types/inspect.go index b2c90a9..434200b 100644 --- a/types/inspect.go +++ b/types/inspect.go @@ -312,6 +312,10 @@ func (t *Type) Format(s fmt.State, v rune) { name = pkg + "." + name } + writeString := func(str string) { + _, _ = s.Write([]byte(str)) + } + if t.Opaque() { if name == "" { name = fmt.Sprintf("", t.Kind()) @@ -319,13 +323,13 @@ func (t *Type) Format(s fmt.State, v rune) { if t.typ.Kind == coroutinev1.Kind_KIND_POINTER { name = "*" + name } - s.Write([]byte(name)) + writeString(name) return } verbose := s.Flag('+') || s.Flag('#') if name != "" && !verbose { - s.Write([]byte(name)) + writeString(name) return } @@ -386,7 +390,7 @@ func (t *Type) Format(s fmt.State, v rune) { default: result = primitiveKind } - s.Write([]byte(result)) + writeString(result) return } @@ -412,82 +416,82 @@ func (t *Type) Format(s fmt.State, v rune) { if name != "" { elemPrefix = fmt.Sprintf("(%s=%s", name, elemPrefix) } - s.Write([]byte(elemPrefix)) + writeString(elemPrefix) t.Elem().Format(withoutFlags{s}, v) if name != "" { - s.Write([]byte(")")) + writeString(")") } return } if name != "" { - s.Write([]byte(fmt.Sprintf("(%s=", name))) + writeString(fmt.Sprintf("(%s=", name)) } switch t.typ.Kind { case coroutinev1.Kind_KIND_FUNC: - s.Write([]byte("func(")) + writeString("func(") paramCount := t.NumParam() for i := 0; i < paramCount; i++ { if i > 0 { - s.Write([]byte(", ")) + writeString(", ") } if i == paramCount-1 && t.Variadic() { - s.Write([]byte("...")) + writeString("...") } t.Param(i).Format(withoutFlags{s}, v) } - s.Write([]byte(")")) + writeString(")") n := t.NumResult() if n > 0 { - s.Write([]byte(" ")) + writeString(" ") } if n > 1 { - s.Write([]byte("(")) + writeString("(") } for i := 0; i < n; i++ { if i > 0 { - s.Write([]byte(", ")) + writeString(", ") } t.Result(i).Format(withoutFlags{s}, v) } if n > 1 { - s.Write([]byte(")")) + writeString(")") } if name != "" { - s.Write([]byte(")")) + writeString(")") } case coroutinev1.Kind_KIND_MAP: - s.Write([]byte("map[")) + writeString("map[") t.Key().Format(withoutFlags{s}, v) - s.Write([]byte("]")) + writeString("]") t.Elem().Format(withoutFlags{s}, v) case coroutinev1.Kind_KIND_STRUCT: n := t.NumField() if n == 0 { - s.Write([]byte("struct{}")) + writeString("struct{}") } else { - s.Write([]byte("struct{ ")) + writeString("struct{ ") for i := 0; i < n; i++ { if i > 0 { - s.Write([]byte("; ")) + writeString("; ") } f := t.Field(i) if !f.Anonymous() { - s.Write([]byte(f.Name())) - s.Write([]byte(" ")) + writeString(f.Name()) + writeString(" ") } f.Type().Format(withoutFlags{State: s}, v) } - s.Write([]byte(" }")) + writeString(" }") } default: - s.Write([]byte("invalid")) + writeString("invalid") } if name != "" { - s.Write([]byte(")")) + writeString(")") } }