diff --git a/src/cmd/compile/internal/noder/helpers.go b/src/cmd/compile/internal/noder/helpers.go index 2b084ff311..cf7a3e22b3 100644 --- a/src/cmd/compile/internal/noder/helpers.go +++ b/src/cmd/compile/internal/noder/helpers.go @@ -138,30 +138,19 @@ func Call(pos src.XPos, typ *types.Type, fun ir.Node, args []ir.Node, dots bool) return n } if fun.Op() != ir.OFUNCINST { - // If no type params, still do normal typechecking, since we're - // still missing some things done by tcCall below (mainly - // typecheckargs and typecheckaste). + // If no type params, do normal typechecking, since we're + // still missing some things done by tcCall (mainly + // typecheckaste/assignconvfn - implementing assignability of args + // to params). This will convert OCALL to OCALLFUNC. typecheck.Call(n) return n } + // Leave the op as OCALL, which indicates the call still needs typechecking. n.Use = ir.CallUseExpr if fun.Type().NumResults() == 0 { n.Use = ir.CallUseStmt } - - // Rewrite call node depending on use. - switch fun.Op() { - case ir.ODOTINTER: - n.SetOp(ir.OCALLINTER) - - case ir.ODOTMETH: - n.SetOp(ir.OCALLMETH) - - default: - n.SetOp(ir.OCALLFUNC) - } - typed(typ, n) return n } diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index 071a2f44c2..d8e1eaab65 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -73,7 +73,7 @@ func (g *irgen) stencil() { // immediately called. foundFuncInst = true } - if n.Op() != ir.OCALLFUNC || n.(*ir.CallExpr).X.Op() != ir.OFUNCINST { + if n.Op() != ir.OCALL || n.(*ir.CallExpr).X.Op() != ir.OFUNCINST { return } // We have found a function call using a generic function @@ -95,6 +95,10 @@ func (g *irgen) stencil() { copy(withRecv[1:], call.Args) call.Args = withRecv } + // Do the typechecking of the Call now, which changes OCALL + // to OCALLFUNC and does typecheckaste/assignconvfn. + call.SetTypecheck(0) + typecheck.Call(call) modified = true }) @@ -348,7 +352,8 @@ func (subst *subster) node(n ir.Node) ir.Node { // an error. _, isCallExpr := m.(*ir.CallExpr) _, isStructKeyExpr := m.(*ir.StructKeyExpr) - if !isCallExpr && !isStructKeyExpr { + if !isCallExpr && !isStructKeyExpr && x.Op() != ir.OPANIC && + x.Op() != ir.OCLOSE { base.Fatalf(fmt.Sprintf("Nil type for %v", x)) } } else if x.Op() != ir.OCLOSURE { @@ -382,10 +387,13 @@ func (subst *subster) node(n ir.Node) ir.Node { call.X.SetTypecheck(0) call.X.SetType(nil) typecheck.Callee(call.X) - m.SetTypecheck(0) - typecheck.Call(m.(*ir.CallExpr)) - } else { - base.FatalfAt(call.Pos(), "Expecting OCALLPART or OTYPE with CALL") + call.SetTypecheck(0) + typecheck.Call(call) + } else if call.X.Op() != ir.OFUNCINST { + // A call with an OFUNCINST will get typechecked + // in stencil() once we have created & attached the + // instantiation to be called. + base.FatalfAt(call.Pos(), "Expecting OCALLPART or OTYPE or OFUNCINST with CALL") } } @@ -420,6 +428,7 @@ func (subst *subster) node(n ir.Node) ir.Node { // Set Ntype for now to be compatible with later parts of compiler newfn.Nname.Ntype = subst.node(oldfn.Nname.Ntype).(ir.Ntype) typed(subst.typ(oldfn.Nname.Type()), newfn.Nname) + typed(newfn.Nname.Type(), m) newfn.SetTypecheck(1) subst.g.target.Decls = append(subst.g.target.Decls, newfn) } @@ -664,7 +673,18 @@ func (subst *subster) typ(t *types.Type) *types.Type { newt = nil } - // TODO: case TCHAN + case types.TCHAN: + elem := t.Elem() + newelem := subst.typ(elem) + if newelem != elem { + newt = types.NewChan(newelem, t.ChanDir()) + if !newt.HasTParam() { + // TODO(danscales): not sure why I have to do this + // only for channels..... + types.CheckSize(newt) + } + } + // TODO: case TMAP } if newt == nil { diff --git a/src/cmd/compile/internal/noder/types.go b/src/cmd/compile/internal/noder/types.go index 58b7262455..c5506ab472 100644 --- a/src/cmd/compile/internal/noder/types.go +++ b/src/cmd/compile/internal/noder/types.go @@ -68,8 +68,15 @@ func instTypeName2(name string, targs []types2.Type) string { if i > 0 { b.WriteByte(',') } - b.WriteString(types2.TypeString(targ, - func(*types2.Package) string { return "" })) + tname := types2.TypeString(targ, + func(*types2.Package) string { return "" }) + if strings.Index(tname, ", ") >= 0 { + // types2.TypeString puts spaces after a comma in a type + // list, but we don't want spaces in our actual type names + // and method/function names derived from them. + tname = strings.Replace(tname, ", ", ",", -1) + } + b.WriteString(tname) } b.WriteByte(']') return b.String() diff --git a/test/typeparam/chans.go b/test/typeparam/chans.go new file mode 100644 index 0000000000..2fcd4af75e --- /dev/null +++ b/test/typeparam/chans.go @@ -0,0 +1,413 @@ +// run -gcflags=-G=3 + +// 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. + +// Package chans provides utility functions for working with channels. +package main + +import ( + "context" + "fmt" + "runtime" + "sort" + "sync" + "time" +) + +// _Equal reports whether two slices are equal: the same length and all +// elements equal. All floating point NaNs are considered equal. +func _SliceEqual[Elem comparable](s1, s2 []Elem) bool { + if len(s1) != len(s2) { + return false + } + for i, v1 := range s1 { + v2 := s2[i] + if v1 != v2 { + isNaN := func(f Elem) bool { return f != f } + if !isNaN(v1) || !isNaN(v2) { + return false + } + } + } + return true +} + +// _ReadAll reads from c until the channel is closed or the context is +// canceled, returning all the values read. +func _ReadAll[Elem any](ctx context.Context, c <-chan Elem) []Elem { + var r []Elem + for { + select { + case <-ctx.Done(): + return r + case v, ok := <-c: + if !ok { + return r + } + r = append(r, v) + } + } +} + +// _Merge merges two channels into a single channel. +// This will leave a goroutine running until either both channels are closed +// or the context is canceled, at which point the returned channel is closed. +func _Merge[Elem any](ctx context.Context, c1, c2 <-chan Elem) <-chan Elem { + r := make(chan Elem) + go func(ctx context.Context, c1, c2 <-chan Elem, r chan<- Elem) { + defer close(r) + for c1 != nil || c2 != nil { + select { + case <-ctx.Done(): + return + case v1, ok := <-c1: + if ok { + r <- v1 + } else { + c1 = nil + } + case v2, ok := <-c2: + if ok { + r <- v2 + } else { + c2 = nil + } + } + } + }(ctx, c1, c2, r) + return r +} + +// _Filter calls f on each value read from c. If f returns true the value +// is sent on the returned channel. This will leave a goroutine running +// until c is closed or the context is canceled, at which point the +// returned channel is closed. +func _Filter[Elem any](ctx context.Context, c <-chan Elem, f func(Elem) bool) <-chan Elem { + r := make(chan Elem) + go func(ctx context.Context, c <-chan Elem, f func(Elem) bool, r chan<- Elem) { + defer close(r) + for { + select { + case <-ctx.Done(): + return + case v, ok := <-c: + if !ok { + return + } + if f(v) { + r <- v + } + } + } + }(ctx, c, f, r) + return r +} + +// _Sink returns a channel that discards all values sent to it. +// This will leave a goroutine running until the context is canceled +// or the returned channel is closed. +func _Sink[Elem any](ctx context.Context) chan<- Elem { + r := make(chan Elem) + go func(ctx context.Context, r <-chan Elem) { + for { + select { + case <-ctx.Done(): + return + case _, ok := <-r: + if !ok { + return + } + } + } + }(ctx, r) + return r +} + +// An Exclusive is a value that may only be used by a single goroutine +// at a time. This is implemented using channels rather than a mutex. +type _Exclusive[Val any] struct { + c chan Val +} + +// _MakeExclusive makes an initialized exclusive value. +func _MakeExclusive[Val any](initial Val) *_Exclusive[Val] { + r := &_Exclusive[Val]{ + c: make(chan Val, 1), + } + r.c <- initial + return r +} + +// _Acquire acquires the exclusive value for private use. +// It must be released using the Release method. +func (e *_Exclusive[Val]) Acquire() Val { + return <-e.c +} + +// TryAcquire attempts to acquire the value. The ok result reports whether +// the value was acquired. If the value is acquired, it must be released +// using the Release method. +func (e *_Exclusive[Val]) TryAcquire() (v Val, ok bool) { + select { + case r := <-e.c: + return r, true + default: + return v, false + } +} + +// Release updates and releases the value. +// This method panics if the value has not been acquired. +func (e *_Exclusive[Val]) Release(v Val) { + select { + case e.c <- v: + default: + panic("_Exclusive Release without Acquire") + } +} + +// Ranger returns a Sender and a Receiver. The Receiver provides a +// Next method to retrieve values. The Sender provides a Send method +// to send values and a Close method to stop sending values. The Next +// method indicates when the Sender has been closed, and the Send +// method indicates when the Receiver has been freed. +// +// This is a convenient way to exit a goroutine sending values when +// the receiver stops reading them. +func _Ranger[Elem any]() (*_Sender[Elem], *_Receiver[Elem]) { + c := make(chan Elem) + d := make(chan struct{}) + s := &_Sender[Elem]{ + values: c, + done: d, + } + r := &_Receiver[Elem] { + values: c, + done: d, + } + runtime.SetFinalizer(r, (*_Receiver[Elem]).finalize) + return s, r +} + +// A _Sender is used to send values to a Receiver. +type _Sender[Elem any] struct { + values chan<- Elem + done <-chan struct{} +} + +// Send sends a value to the receiver. It reports whether the value was sent. +// The value will not be sent if the context is closed or the receiver +// is freed. +func (s *_Sender[Elem]) Send(ctx context.Context, v Elem) bool { + select { + case <-ctx.Done(): + return false + case s.values <- v: + return true + case <-s.done: + return false + } +} + +// Close tells the receiver that no more values will arrive. +// After Close is called, the _Sender may no longer be used. +func (s *_Sender[Elem]) Close() { + close(s.values) +} + +// A _Receiver receives values from a _Sender. +type _Receiver[Elem any] struct { + values <-chan Elem + done chan<- struct{} +} + +// Next returns the next value from the channel. The bool result indicates +// whether the value is valid. +func (r *_Receiver[Elem]) Next(ctx context.Context) (v Elem, ok bool) { + select { + case <-ctx.Done(): + case v, ok = <-r.values: + } + return v, ok +} + +// finalize is a finalizer for the receiver. +func (r *_Receiver[Elem]) finalize() { + close(r.done) +} + +func TestReadAll() { + c := make(chan int) + go func() { + c <- 4 + c <- 2 + c <- 5 + close(c) + }() + got := _ReadAll(context.Background(), c) + want := []int{4, 2, 5} + if !_SliceEqual(got, want) { + panic(fmt.Sprintf("_ReadAll returned %v, want %v", got, want)) + } +} + +func TestMerge() { + c1 := make(chan int) + c2 := make(chan int) + go func() { + c1 <- 1 + c1 <- 3 + c1 <- 5 + close(c1) + }() + go func() { + c2 <- 2 + c2 <- 4 + c2 <- 6 + close(c2) + }() + ctx := context.Background() + got := _ReadAll(ctx, _Merge(ctx, c1, c2)) + sort.Ints(got) + want := []int{1, 2, 3, 4, 5, 6} + if !_SliceEqual(got, want) { + panic(fmt.Sprintf("_Merge returned %v, want %v", got, want)) + } +} + +func TestFilter() { + c := make(chan int) + go func() { + c <- 1 + c <- 2 + c <- 3 + close(c) + }() + even := func(i int) bool { return i%2 == 0 } + ctx := context.Background() + got := _ReadAll(ctx, _Filter(ctx, c, even)) + want := []int{2} + if !_SliceEqual(got, want) { + panic(fmt.Sprintf("_Filter returned %v, want %v", got, want)) + } +} + +func TestSink() { + c := _Sink[int](context.Background()) + after := time.NewTimer(time.Minute) + defer after.Stop() + send := func(v int) { + select { + case c <- v: + case <-after.C: + panic("timed out sending to _Sink") + } + } + send(1) + send(2) + send(3) + close(c) +} + +func TestExclusive() { + val := 0 + ex := _MakeExclusive(&val) + + var wg sync.WaitGroup + f := func() { + defer wg.Done() + for i := 0; i < 10; i++ { + p := ex.Acquire() + (*p)++ + ex.Release(p) + } + } + + wg.Add(2) + go f() + go f() + + wg.Wait() + if val != 20 { + panic(fmt.Sprintf("after Acquire/Release loop got %d, want 20", val)) + } +} + +func TestExclusiveTry() { + s := "" + ex := _MakeExclusive(&s) + p, ok := ex.TryAcquire() + if !ok { + panic("TryAcquire failed") + } + *p = "a" + + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + _, ok := ex.TryAcquire() + if ok { + panic(fmt.Sprintf("TryAcquire succeeded unexpectedly")) + } + }() + wg.Wait() + + ex.Release(p) + + p, ok = ex.TryAcquire() + if !ok { + panic(fmt.Sprintf("TryAcquire failed")) + } +} + +func TestRanger() { + s, r := _Ranger[int]() + + ctx := context.Background() + go func() { + // Receive one value then exit. + v, ok := r.Next(ctx) + if !ok { + panic(fmt.Sprintf("did not receive any values")) + } else if v != 1 { + panic(fmt.Sprintf("received %d, want 1", v)) + } + }() + + c1 := make(chan bool) + c2 := make(chan bool) + go func() { + defer close(c2) + if !s.Send(ctx, 1) { + panic(fmt.Sprintf("Send failed unexpectedly")) + } + close(c1) + if s.Send(ctx, 2) { + panic(fmt.Sprintf("Send succeeded unexpectedly")) + } + }() + + <-c1 + + // Force a garbage collection to try to get the finalizers to run. + runtime.GC() + + select { + case <-c2: + case <-time.After(time.Minute): + panic("_Ranger Send should have failed, but timed out") + } +} + +func main() { + TestReadAll() + TestMerge() + TestFilter() + TestSink() + TestExclusive() + TestExclusiveTry() + TestRanger() +} diff --git a/test/typeparam/cons.go b/test/typeparam/cons.go index 08a825f59f..8d255ebdb8 100644 --- a/test/typeparam/cons.go +++ b/test/typeparam/cons.go @@ -88,9 +88,8 @@ func _Map[a, b any](f _Function[a, b], xs _List[a]) _List[b] { func main() { var xs _List[int] = _Cons[int]{3, _Cons[int]{6, _Nil[int]{}}} - // TODO(danscales): Remove conversion calls in next two, needed for now. - var ys _List[int] = _Map[int, int](_Function[int, int](incr{-5}), xs) - var xz _List[bool] = _Map[int, bool](_Function[int, bool](pos{}), ys) + var ys _List[int] = _Map[int, int](incr{-5}, xs) + var xz _List[bool] = _Map[int, bool](pos{}, ys) cs1 := xz.(_Cons[bool]) cs2 := cs1.Tail.(_Cons[bool]) _, ok := cs2.Tail.(_Nil[bool])