]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: factor out single commonUnder function
authorRobert Griesemer <gri@golang.org>
Mon, 3 Mar 2025 23:11:47 +0000 (15:11 -0800)
committerGopher Robot <gobot@golang.org>
Thu, 6 Mar 2025 21:35:46 +0000 (13:35 -0800)
Combine commonUnder and commonUnderOrChan:
- Provide an optional cond(ition) function argument to commonUnder
  to establish additional type set conditions.
- Instead of a *Checker and *string argument for error reporting,
  return an error cause that is only allocated in the presence of
  an error.
- Streamline some error messages.

Replace all calls to coreType with calls to commonUnder.

Change-Id: I81ac86d0d532cddc09164309acced61d90718b44
Reviewed-on: https://go-review.googlesource.com/c/go/+/654455
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
23 files changed:
src/cmd/compile/internal/types2/builtins.go
src/cmd/compile/internal/types2/call.go
src/cmd/compile/internal/types2/compilersupport.go
src/cmd/compile/internal/types2/expr.go
src/cmd/compile/internal/types2/infer.go
src/cmd/compile/internal/types2/literals.go
src/cmd/compile/internal/types2/lookup.go
src/cmd/compile/internal/types2/predicates.go
src/cmd/compile/internal/types2/stmt.go
src/cmd/compile/internal/types2/under.go
src/cmd/compile/internal/types2/unify.go
src/go/types/builtins.go
src/go/types/call.go
src/go/types/expr.go
src/go/types/infer.go
src/go/types/literals.go
src/go/types/lookup.go
src/go/types/predicates.go
src/go/types/stmt.go
src/go/types/under.go
src/go/types/unify.go
src/internal/types/testdata/fixedbugs/issue43671.go
src/internal/types/testdata/fixedbugs/issue47115.go

index 1e2a9a28f88dca0da75bd3e17b0636567a5b96f5..e9f8fc570a20a421362c9a94e59b058f1e0c4ee6 100644 (file)
@@ -377,7 +377,8 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
 
        case _Copy:
                // copy(x, y []T) int
-               dst, _ := commonUnder(check, x.typ, nil).(*Slice)
+               u, _ := commonUnder(x.typ, nil)
+               dst, _ := u.(*Slice)
 
                y := args[1]
                src0 := coreString(y.typ)
@@ -514,7 +515,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
                }
 
                var min int // minimum number of arguments
-               switch coreType(T).(type) {
+               switch u, _ := commonUnder(T, nil); u.(type) {
                case *Slice:
                        min = 2
                case *Map, *Chan:
@@ -818,7 +819,8 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
                // unsafe.Slice(ptr *T, len IntegerType) []T
                check.verifyVersionf(call.Fun, go1_17, "unsafe.Slice")
 
-               ptr, _ := commonUnder(check, x.typ, nil).(*Pointer)
+               u, _ := commonUnder(x.typ, nil)
+               ptr, _ := u.(*Pointer)
                if ptr == nil {
                        check.errorf(x, InvalidUnsafeSlice, invalidArg+"%s is not a pointer", x)
                        return
@@ -839,7 +841,8 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
                // unsafe.SliceData(slice []T) *T
                check.verifyVersionf(call.Fun, go1_20, "unsafe.SliceData")
 
-               slice, _ := commonUnder(check, x.typ, nil).(*Slice)
+               u, _ := commonUnder(x.typ, nil)
+               slice, _ := u.(*Slice)
                if slice == nil {
                        check.errorf(x, InvalidUnsafeSliceData, invalidArg+"%s is not a slice", x)
                        return
index c4e6ad895c95f1a3a597e60f1289ed5e761f7a45..bfce23655567efdbaf7ef28de5f7d84849d9d3b4 100644 (file)
@@ -244,11 +244,12 @@ func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind {
 
        // If the operand type is a type parameter, all types in its type set
        // must have a common underlying type, which must be a signature.
-       var cause string
-       sig, _ := commonUnder(check, x.typ, &cause).(*Signature)
+       // TODO(gri) use commonUnder condition for better error message
+       u, err := commonUnder(x.typ, nil)
+       sig, _ := u.(*Signature)
        if sig == nil {
-               if cause != "" {
-                       check.errorf(x, InvalidCall, invalidOp+"cannot call %s: %s", x, cause)
+               if err != nil {
+                       check.errorf(x, InvalidCall, invalidOp+"cannot call %s: %s", x, err.format(check))
                } else {
                        check.errorf(x, InvalidCall, invalidOp+"cannot call non-function %s", x)
                }
index 5a8b3b9498eade16d0743b30ec16ab31ac715e1a..20a13642887a7f13917c8e2de7c5e9891de59ce2 100644 (file)
@@ -26,7 +26,8 @@ func AsSignature(t Type) *Signature {
 // is the restricted channel type if the restrictions are always the same.
 // If typ is not a type parameter, CoreType returns the underlying type.
 func CoreType(t Type) Type {
-       return coreType(t)
+       u, _ := commonUnder(t, nil)
+       return u
 }
 
 // RangeKeyVal returns the key and value types for a range over typ.
index a73e073ac3eeb19f679e16be8a7387e9b82a6097..f4938a2d8edcab79aac8005fc195f358b8d9c299 100644 (file)
@@ -196,50 +196,38 @@ func (check *Checker) unary(x *operand, e *syntax.Operation) {
 // or send to x (recv == false) operation. If the operation is not valid, chanElem
 // reports an error and returns nil.
 func (check *Checker) chanElem(pos poser, x *operand, recv bool) Type {
-       var elem Type
-       var cause string
-       typeset(x.typ, func(t, u Type) bool {
+       u, err := commonUnder(x.typ, func(t, u Type) *errorCause {
                if u == nil {
-                       // Type set contains no explicit terms.
-                       // It is either empty or contains all types (any)
-                       cause = "no specific channel type"
-                       return false
+                       return newErrorCause("no specific channel type")
                }
                ch, _ := u.(*Chan)
                if ch == nil {
-                       cause = check.sprintf("non-channel %s", t)
-                       return false
+                       return newErrorCause("non-channel %s", t)
                }
                if recv && ch.dir == SendOnly {
-                       cause = check.sprintf("send-only channel %s", t)
-                       return false
+                       return newErrorCause("send-only channel %s", t)
                }
                if !recv && ch.dir == RecvOnly {
-                       cause = check.sprintf("receive-only channel %s", t)
-                       return false
+                       return newErrorCause("receive-only channel %s", t)
                }
-               if elem != nil && !Identical(elem, ch.elem) {
-                       cause = check.sprintf("channels with different element types %s and %s", elem, ch.elem)
-                       return false
-               }
-               elem = ch.elem
-               return true
+               return nil
        })
 
-       if cause == "" {
-               return elem
+       if u != nil {
+               return u.(*Chan).elem
        }
 
+       cause := err.format(check)
        if recv {
                if isTypeParam(x.typ) {
-                       check.errorf(pos, InvalidReceive, invalidOp+"cannot receive from %s: type set contains %s", x, cause)
+                       check.errorf(pos, InvalidReceive, invalidOp+"cannot receive from %s: %s", x, cause)
                } else {
                        // In this case, only the non-channel and send-only channel error are possible.
                        check.errorf(pos, InvalidReceive, invalidOp+"cannot receive from %s %s", cause, x)
                }
        } else {
                if isTypeParam(x.typ) {
-                       check.errorf(pos, InvalidSend, invalidOp+"cannot send to %s: type set contains %s", x, cause)
+                       check.errorf(pos, InvalidSend, invalidOp+"cannot send to %s: %s", x, cause)
                } else {
                        // In this case, only the non-channel and receive-only channel error are possible.
                        check.errorf(pos, InvalidSend, invalidOp+"cannot send to %s %s", cause, x)
index 865cabe31cbf7247a0205e6e2267215ca5a86a5c..08d422969059f234f9d76dd125e34cf36f29da11 100644 (file)
@@ -667,11 +667,12 @@ func coreTerm(tpar *TypeParam) (*term, bool) {
        })
        if n == 1 {
                if debug {
-                       assert(debug && under(single.typ) == coreType(tpar))
+                       u, _ := commonUnder(tpar, nil)
+                       assert(under(single.typ) == u)
                }
                return single, true
        }
-       if typ := coreType(tpar); typ != nil {
+       if typ, _ := commonUnder(tpar, nil); typ != nil {
                // A core type is always an underlying type.
                // If any term of tpar has a tilde, we don't
                // have a precise core type and we must return
index da5b03d8eab39c3b6b7bd50a658a98b9b9cb60dc..5b2dae9b13a72635f33e91c0e83f3ddb3fd21945 100644 (file)
@@ -129,7 +129,8 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type
                typ = hint
                base = typ
                // *T implies &T{}
-               if b, ok := deref(commonUnder(check, base, nil)); ok {
+               u, _ := commonUnder(base, nil)
+               if b, ok := deref(u); ok {
                        base = b
                }
                isElem = true
@@ -142,7 +143,7 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type
                base = typ
        }
 
-       switch utyp := commonUnder(check, base, nil).(type) {
+       switch u, _ := commonUnder(base, nil); utyp := u.(type) {
        case *Struct:
                // Prevent crash if the struct referred to is not yet set up.
                // See analogous comment for *Array.
index 8dd01918e3f37320ee10404872eec843d24a0018..0a47ec08df05adfae3d025cfd8b5da2b819a7823 100644 (file)
@@ -73,7 +73,7 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string, fo
        // we are ok here because only fields are accepted as results.
        const enableTParamFieldLookup = false // see go.dev/issue/51576
        if enableTParamFieldLookup && obj == nil && isTypeParam(T) {
-               if t := commonUnder(nil, T, nil); t != nil {
+               if t, _ := commonUnder(T, nil); t != nil {
                        obj, index, indirect = lookupFieldOrMethodImpl(t, addressable, pkg, name, foldCase)
                        if _, ok := obj.(*Var); !ok {
                                obj, index, indirect = nil, nil, false // accept fields (variables) only
index 86b7e3dccf928d4325d7c1b63423f0b32e650300..c293f30b61464d94c817250729f4dd1671548037 100644 (file)
@@ -39,8 +39,6 @@ func isBasic(t Type, info BasicInfo) bool {
 // The allX predicates below report whether t is an X.
 // If t is a type parameter the result is true if isX is true
 // for all specified types of the type parameter's type set.
-// allX is an optimized version of isX(coreType(t)) (which
-// is the same as underIs(t, isX)).
 
 func allBoolean(t Type) bool         { return allBasic(t, IsBoolean) }
 func allInteger(t Type) bool         { return allBasic(t, IsInteger) }
@@ -53,7 +51,6 @@ func allNumericOrString(t Type) bool { return allBasic(t, IsNumeric|IsString) }
 // allBasic reports whether under(t) is a basic type with the specified info.
 // If t is a type parameter, the result is true if isBasic(t, info) is true
 // for all specific types of the type parameter's type set.
-// allBasic(t, info) is an optimized version of isBasic(coreType(t), info).
 func allBasic(t Type, info BasicInfo) bool {
        if tpar, _ := Unalias(t).(*TypeParam); tpar != nil {
                return tpar.is(func(t *term) bool { return t != nil && isBasic(t.typ, info) })
index 6eb6b2ac17a281022db488bf33cc742305c9dc89..4f5021f07bcaa1e73fcdb13cabf563aa839ae00f 100644 (file)
@@ -1004,10 +1004,15 @@ func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (
                return Typ[Invalid], Typ[Invalid], cause, false
        }
 
-       var cause1 string
-       rtyp := commonUnderOrChan(check, orig, &cause1)
+       rtyp, err := commonUnder(orig, func(t, u Type) *errorCause {
+               // A channel must permit receive operations.
+               if ch, _ := u.(*Chan); ch != nil && ch.dir == SendOnly {
+                       return newErrorCause("receive from send-only channel %s", t)
+               }
+               return nil
+       })
        if rtyp == nil {
-               return bad(cause1)
+               return bad(err.format(check))
        }
 
        switch typ := arrayPtrDeref(rtyp).(type) {
@@ -1043,12 +1048,12 @@ func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (
                }
                assert(typ.Recv() == nil)
                // check iterator argument type
-               var cause2 string
-               cb, _ := commonUnder(check, typ.Params().At(0).Type(), &cause2).(*Signature)
+               u, err := commonUnder(typ.Params().At(0).Type(), nil)
+               cb, _ := u.(*Signature)
                switch {
                case cb == nil:
-                       if cause2 != "" {
-                               return bad(check.sprintf("func must be func(yield func(...) bool): in yield type, %s", cause2))
+                       if err != nil {
+                               return bad(check.sprintf("func must be func(yield func(...) bool): in yield type, %s", err.format(check)))
                        } else {
                                return bad("func must be func(yield func(...) bool): argument is not func")
                        }
index 7dc4f7dd7489c0db2957fd65a2f2cae3530b1fd9..c0c0658c7794aef575b18a7fa394a6a3a7fb3bd4 100644 (file)
@@ -40,117 +40,94 @@ func typeset(t Type, yield func(t, u Type) bool) {
        yield(t, under(t))
 }
 
-// TODO(gri) commonUnder, commonUnderOrChan, and Checker.chanElem (expr.go)
-//           have a lot of similarities. Maybe we can find common ground
-//           between them and distill a better factorization.
-
-// If t is not a type parameter, commonUnder returns the underlying type.
-// If t is a type parameter, commonUnder returns the common underlying
-// type of all types in its type set if it exists.
-// Otherwise the result is nil, and *cause reports the error if a non-nil
-// cause is provided.
-// The check parameter is only used if *cause reports an error; it may be nil.
-func commonUnder(check *Checker, t Type, cause *string) Type {
-       var s, su Type
-
-       bad := func(s string) bool {
-               if cause != nil {
-                       *cause = s
-               }
-               su = nil
-               return false
-       }
+// A errorCause describes an error cause.
+type errorCause struct {
+       format_ string
+       args    []any
+}
 
-       typeset(t, func(t, u Type) bool {
-               if u == nil {
-                       return bad("no specific type")
-               }
-               if su != nil && !Identical(su, u) {
-                       return bad(check.sprintf("%s and %s have different underlying types", s, t))
-               }
-               // su == nil || Identical(su, u)
-               s, su = t, u
-               return true
-       })
+func newErrorCause(format string, args ...any) *errorCause {
+       return &errorCause{format, args}
+}
 
-       return su
+// format formats a cause as a string.
+// check may be nil.
+func (err *errorCause) format(check *Checker) string {
+       return check.sprintf(err.format_, err.args...)
 }
 
-// If t is not a type parameter, commonUnderOrChan returns the underlying type;
-// if that type is a channel type it must permit receive operations.
-// If t is a type parameter, commonUnderOrChan returns the common underlying
-// type of all types in its type set if it exists, or, if the type set contains
-// only channel types permitting receive operations and with identical element
-// types, commonUnderOrChan returns one of those channel types.
-// Otherwise the result is nil, and *cause reports the error if a non-nil cause
-// is provided.
-// The check parameter is only used if *cause reports an error; it may be nil.
-func commonUnderOrChan(check *Checker, t Type, cause *string) Type {
-       var s, su Type
-       var sc *Chan
-
-       bad := func(s string) bool {
-               if cause != nil {
-                       *cause = s
-               }
-               su = nil
+// If t is a type parameter, cond is nil, and t's type set contains no channel types,
+// commonUnder returns the common underlying type of all types in t's type set if
+// it exists, or nil and an error cause otherwise.
+//
+// If t is a type parameter, cond is nil, and there are channel types, t's type set
+// must only contain channel types, they must all have the same element types,
+// channel directions must not conflict, and commonUnder returns one of the most
+// restricted channels. Otherwise, the function returns nil and an error cause.
+//
+// If cond != nil, each pair (t, u) of type and underlying type in t's type set
+// must satisfy the condition expressed by cond. If the result of cond is != nil,
+// commonUnder returns nil and the error cause reported by cond.
+// Note that cond is called before any other conditions are checked; specifically
+// cond may be called with (nil, nil) if the type set contains no specific types.
+//
+// If t is not a type parameter, commonUnder behaves as if t was a type parameter
+// with the single type t in its type set.
+func commonUnder(t Type, cond func(t, u Type) *errorCause) (Type, *errorCause) {
+       var ct, cu Type // type and respective common underlying type
+       var err *errorCause
+
+       bad := func(format string, args ...any) bool {
+               cu = nil
+               err = newErrorCause(format, args...)
                return false
        }
 
        typeset(t, func(t, u Type) bool {
+               if cond != nil {
+                       if err = cond(t, u); err != nil {
+                               cu = nil
+                               return false
+                       }
+               }
+
                if u == nil {
                        return bad("no specific type")
                }
-               c, _ := u.(*Chan)
-               if c != nil && c.dir == SendOnly {
-                       return bad(check.sprintf("receive from send-only channel %s", t))
-               }
-               if su == nil {
-                       s, su = t, u
-                       sc = c // possibly nil
+
+               // If this is the first type we're seeing, we're done.
+               if cu == nil {
+                       ct, cu = t, u
                        return true
                }
-               // su != nil
-               if sc != nil && c != nil {
-                       if !Identical(sc.elem, c.elem) {
-                               return bad(check.sprintf("channels with different element types %s and %s", sc.elem, c.elem))
+
+               // If we've seen a channel before, and we have a channel now, they must be compatible.
+               if chu, _ := cu.(*Chan); chu != nil {
+                       if ch, _ := u.(*Chan); ch != nil {
+                               if !Identical(chu.elem, ch.elem) {
+                                       return bad("channels %s and %s have different element types", ct, t)
+                               }
+                               // If we have different channel directions, keep the restricted one
+                               // and complain if they conflict.
+                               if chu.dir == SendRecv {
+                                       ct, cu = t, u // switch to current, possibly restricted channel
+                               } else if chu.dir != ch.dir {
+                                       return bad("channels %s and %s have conflicting directions", ct, t)
+
+                               }
+                               return true
                        }
-                       return true
                }
-               // sc == nil
-               if !Identical(su, u) {
-                       return bad(check.sprintf("%s and %s have different underlying types", s, t))
-               }
-               return true
-       })
-
-       return su
-}
 
-// If t is not a type parameter, coreType returns the underlying type.
-// If t is a type parameter, coreType returns the single underlying
-// type of all types in its type set if it exists, or nil otherwise. If the
-// type set contains only unrestricted and restricted channel types (with
-// identical element types), the single underlying type is the restricted
-// channel type if the restrictions are always the same, or nil otherwise.
-func coreType(t Type) Type {
-       var su Type
-       typeset(t, func(_, u Type) bool {
-               if u == nil {
-                       return false
-               }
-               if su != nil {
-                       u = match(su, u)
-                       if u == nil {
-                               su = nil
-                               return false
-                       }
+               // Otherwise, the current type must have the same underlying type as all previous types.
+               if !Identical(cu, u) {
+                       return bad("%s and %s have different underlying types", ct, t)
                }
-               // su == nil || match(su, u) != nil
-               su = u
+
                return true
        })
-       return su
+
+       return cu, err
 }
 
 // coreString is like coreType but also considers []byte
index 1c611a3e2a4c948d3e03b92d6398f379329fd1e5..9cd3af8607181e14eb80c53576bebfb03db3c6c5 100644 (file)
@@ -773,7 +773,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
                        // If y is also an unbound type parameter, we will end
                        // up here again with x and y swapped, so we don't
                        // need to take care of that case separately.
-                       if cx := coreType(x); cx != nil {
+                       if cx, _ := commonUnder(x, nil); cx != nil {
                                if traceInference {
                                        u.tracef("core %s ≡ %s", xorig, yorig)
                                }
index c714c1f8b08410ed3703664b0af1e01885a5248b..a0dcddf30a839d66a69d70ab6c22f331806f173e 100644 (file)
@@ -380,7 +380,8 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
 
        case _Copy:
                // copy(x, y []T) int
-               dst, _ := commonUnder(check, x.typ, nil).(*Slice)
+               u, _ := commonUnder(x.typ, nil)
+               dst, _ := u.(*Slice)
 
                y := args[1]
                src0 := coreString(y.typ)
@@ -517,7 +518,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
                }
 
                var min int // minimum number of arguments
-               switch coreType(T).(type) {
+               switch u, _ := commonUnder(T, nil); u.(type) {
                case *Slice:
                        min = 2
                case *Map, *Chan:
@@ -821,7 +822,8 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
                // unsafe.Slice(ptr *T, len IntegerType) []T
                check.verifyVersionf(call.Fun, go1_17, "unsafe.Slice")
 
-               ptr, _ := commonUnder(check, x.typ, nil).(*Pointer)
+               u, _ := commonUnder(x.typ, nil)
+               ptr, _ := u.(*Pointer)
                if ptr == nil {
                        check.errorf(x, InvalidUnsafeSlice, invalidArg+"%s is not a pointer", x)
                        return
@@ -842,7 +844,8 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
                // unsafe.SliceData(slice []T) *T
                check.verifyVersionf(call.Fun, go1_20, "unsafe.SliceData")
 
-               slice, _ := commonUnder(check, x.typ, nil).(*Slice)
+               u, _ := commonUnder(x.typ, nil)
+               slice, _ := u.(*Slice)
                if slice == nil {
                        check.errorf(x, InvalidUnsafeSliceData, invalidArg+"%s is not a slice", x)
                        return
index 17d9152be5304fbc51aa8e5f29138f5efd44cd57..0d84a8dc677021b64bb7dd676ad1b0820dbcb767 100644 (file)
@@ -246,11 +246,12 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
 
        // If the operand type is a type parameter, all types in its type set
        // must have a common underlying type, which must be a signature.
-       var cause string
-       sig, _ := commonUnder(check, x.typ, &cause).(*Signature)
+       // TODO(gri) use commonUnder condition for better error message
+       u, err := commonUnder(x.typ, nil)
+       sig, _ := u.(*Signature)
        if sig == nil {
-               if cause != "" {
-                       check.errorf(x, InvalidCall, invalidOp+"cannot call %s: %s", x, cause)
+               if err != nil {
+                       check.errorf(x, InvalidCall, invalidOp+"cannot call %s: %s", x, err.format(check))
                } else {
                        check.errorf(x, InvalidCall, invalidOp+"cannot call non-function %s", x)
                }
index aaafe95eba0afbcf8ce61c0c4c55bcf352410860..bd81679a2e92a5dd28b47d9b0ff8a8f7f1cfe7b3 100644 (file)
@@ -195,50 +195,38 @@ func (check *Checker) unary(x *operand, e *ast.UnaryExpr) {
 // or send to x (recv == false) operation. If the operation is not valid, chanElem
 // reports an error and returns nil.
 func (check *Checker) chanElem(pos positioner, x *operand, recv bool) Type {
-       var elem Type
-       var cause string
-       typeset(x.typ, func(t, u Type) bool {
+       u, err := commonUnder(x.typ, func(t, u Type) *errorCause {
                if u == nil {
-                       // Type set contains no explicit terms.
-                       // It is either empty or contains all types (any)
-                       cause = "no specific channel type"
-                       return false
+                       return newErrorCause("no specific channel type")
                }
                ch, _ := u.(*Chan)
                if ch == nil {
-                       cause = check.sprintf("non-channel %s", t)
-                       return false
+                       return newErrorCause("non-channel %s", t)
                }
                if recv && ch.dir == SendOnly {
-                       cause = check.sprintf("send-only channel %s", t)
-                       return false
+                       return newErrorCause("send-only channel %s", t)
                }
                if !recv && ch.dir == RecvOnly {
-                       cause = check.sprintf("receive-only channel %s", t)
-                       return false
+                       return newErrorCause("receive-only channel %s", t)
                }
-               if elem != nil && !Identical(elem, ch.elem) {
-                       cause = check.sprintf("channels with different element types %s and %s", elem, ch.elem)
-                       return false
-               }
-               elem = ch.elem
-               return true
+               return nil
        })
 
-       if cause == "" {
-               return elem
+       if u != nil {
+               return u.(*Chan).elem
        }
 
+       cause := err.format(check)
        if recv {
                if isTypeParam(x.typ) {
-                       check.errorf(pos, InvalidReceive, invalidOp+"cannot receive from %s: type set contains %s", x, cause)
+                       check.errorf(pos, InvalidReceive, invalidOp+"cannot receive from %s: %s", x, cause)
                } else {
                        // In this case, only the non-channel and send-only channel error are possible.
                        check.errorf(pos, InvalidReceive, invalidOp+"cannot receive from %s %s", cause, x)
                }
        } else {
                if isTypeParam(x.typ) {
-                       check.errorf(pos, InvalidSend, invalidOp+"cannot send to %s: type set contains %s", x, cause)
+                       check.errorf(pos, InvalidSend, invalidOp+"cannot send to %s: %s", x, cause)
                } else {
                        // In this case, only the non-channel and receive-only channel error are possible.
                        check.errorf(pos, InvalidSend, invalidOp+"cannot send to %s %s", cause, x)
index c04ca98fb5b863287319b75cfaf0a7fdf6fdfad5..e955880674c1360d8b2addc895e04935016c6f67 100644 (file)
@@ -670,11 +670,12 @@ func coreTerm(tpar *TypeParam) (*term, bool) {
        })
        if n == 1 {
                if debug {
-                       assert(debug && under(single.typ) == coreType(tpar))
+                       u, _ := commonUnder(tpar, nil)
+                       assert(under(single.typ) == u)
                }
                return single, true
        }
-       if typ := coreType(tpar); typ != nil {
+       if typ, _ := commonUnder(tpar, nil); typ != nil {
                // A core type is always an underlying type.
                // If any term of tpar has a tilde, we don't
                // have a precise core type and we must return
index ebc25957ed87cfa1eae5fdba0a719bdc7157a32d..df02b7703642e6992e519f36c2cfaf6e9f2adcf3 100644 (file)
@@ -133,7 +133,8 @@ func (check *Checker) compositeLit(x *operand, e *ast.CompositeLit, hint Type) {
                typ = hint
                base = typ
                // *T implies &T{}
-               if b, ok := deref(commonUnder(check, base, nil)); ok {
+               u, _ := commonUnder(base, nil)
+               if b, ok := deref(u); ok {
                        base = b
                }
                isElem = true
@@ -146,7 +147,7 @@ func (check *Checker) compositeLit(x *operand, e *ast.CompositeLit, hint Type) {
                base = typ
        }
 
-       switch utyp := commonUnder(check, base, nil).(type) {
+       switch u, _ := commonUnder(base, nil); utyp := u.(type) {
        case *Struct:
                // Prevent crash if the struct referred to is not yet set up.
                // See analogous comment for *Array.
index 3779fa7e283b7aa1ba7f99ec311005052f503a80..755abc7dbdfdc6a315be93a41912fa4b707c7d20 100644 (file)
@@ -76,7 +76,7 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string, fo
        // we are ok here because only fields are accepted as results.
        const enableTParamFieldLookup = false // see go.dev/issue/51576
        if enableTParamFieldLookup && obj == nil && isTypeParam(T) {
-               if t := commonUnder(nil, T, nil); t != nil {
+               if t, _ := commonUnder(T, nil); t != nil {
                        obj, index, indirect = lookupFieldOrMethodImpl(t, addressable, pkg, name, foldCase)
                        if _, ok := obj.(*Var); !ok {
                                obj, index, indirect = nil, nil, false // accept fields (variables) only
index 240c022848bf8a4a8c272e688496f9af096c9f92..f5a960898e32dcc4c4f3e8bb047a4600551e0779 100644 (file)
@@ -42,8 +42,6 @@ func isBasic(t Type, info BasicInfo) bool {
 // The allX predicates below report whether t is an X.
 // If t is a type parameter the result is true if isX is true
 // for all specified types of the type parameter's type set.
-// allX is an optimized version of isX(coreType(t)) (which
-// is the same as underIs(t, isX)).
 
 func allBoolean(t Type) bool         { return allBasic(t, IsBoolean) }
 func allInteger(t Type) bool         { return allBasic(t, IsInteger) }
@@ -56,7 +54,6 @@ func allNumericOrString(t Type) bool { return allBasic(t, IsNumeric|IsString) }
 // allBasic reports whether under(t) is a basic type with the specified info.
 // If t is a type parameter, the result is true if isBasic(t, info) is true
 // for all specific types of the type parameter's type set.
-// allBasic(t, info) is an optimized version of isBasic(coreType(t), info).
 func allBasic(t Type, info BasicInfo) bool {
        if tpar, _ := Unalias(t).(*TypeParam); tpar != nil {
                return tpar.is(func(t *term) bool { return t != nil && isBasic(t.typ, info) })
index 6615f0d8effc8186eecf56c6d2e17d4de21e2144..7cf11b403cac565b552975453a27064f5bdac607 100644 (file)
@@ -1025,10 +1025,15 @@ func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (
                return Typ[Invalid], Typ[Invalid], cause, false
        }
 
-       var cause1 string
-       rtyp := commonUnderOrChan(check, orig, &cause1)
+       rtyp, err := commonUnder(orig, func(t, u Type) *errorCause {
+               // A channel must permit receive operations.
+               if ch, _ := u.(*Chan); ch != nil && ch.dir == SendOnly {
+                       return newErrorCause("receive from send-only channel %s", t)
+               }
+               return nil
+       })
        if rtyp == nil {
-               return bad(cause1)
+               return bad(err.format(check))
        }
 
        switch typ := arrayPtrDeref(rtyp).(type) {
@@ -1064,12 +1069,12 @@ func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (
                }
                assert(typ.Recv() == nil)
                // check iterator argument type
-               var cause2 string
-               cb, _ := commonUnder(check, typ.Params().At(0).Type(), &cause2).(*Signature)
+               u, err := commonUnder(typ.Params().At(0).Type(), nil)
+               cb, _ := u.(*Signature)
                switch {
                case cb == nil:
-                       if cause2 != "" {
-                               return bad(check.sprintf("func must be func(yield func(...) bool): in yield type, %s", cause2))
+                       if err != nil {
+                               return bad(check.sprintf("func must be func(yield func(...) bool): in yield type, %s", err.format(check)))
                        } else {
                                return bad("func must be func(yield func(...) bool): argument is not func")
                        }
index 6dd744c3c221a01fa905d833e7f900eb3f3c9269..e6d3754e309e4a9746b1cb8afd9ac12e1980a144 100644 (file)
@@ -43,117 +43,94 @@ func typeset(t Type, yield func(t, u Type) bool) {
        yield(t, under(t))
 }
 
-// TODO(gri) commonUnder, commonUnderOrChan, and Checker.chanElem (expr.go)
-//           have a lot of similarities. Maybe we can find common ground
-//           between them and distill a better factorization.
-
-// If t is not a type parameter, commonUnder returns the underlying type.
-// If t is a type parameter, commonUnder returns the common underlying
-// type of all types in its type set if it exists.
-// Otherwise the result is nil, and *cause reports the error if a non-nil
-// cause is provided.
-// The check parameter is only used if *cause reports an error; it may be nil.
-func commonUnder(check *Checker, t Type, cause *string) Type {
-       var s, su Type
-
-       bad := func(s string) bool {
-               if cause != nil {
-                       *cause = s
-               }
-               su = nil
-               return false
-       }
+// A errorCause describes an error cause.
+type errorCause struct {
+       format_ string
+       args    []any
+}
 
-       typeset(t, func(t, u Type) bool {
-               if u == nil {
-                       return bad("no specific type")
-               }
-               if su != nil && !Identical(su, u) {
-                       return bad(check.sprintf("%s and %s have different underlying types", s, t))
-               }
-               // su == nil || Identical(su, u)
-               s, su = t, u
-               return true
-       })
+func newErrorCause(format string, args ...any) *errorCause {
+       return &errorCause{format, args}
+}
 
-       return su
+// format formats a cause as a string.
+// check may be nil.
+func (err *errorCause) format(check *Checker) string {
+       return check.sprintf(err.format_, err.args...)
 }
 
-// If t is not a type parameter, commonUnderOrChan returns the underlying type;
-// if that type is a channel type it must permit receive operations.
-// If t is a type parameter, commonUnderOrChan returns the common underlying
-// type of all types in its type set if it exists, or, if the type set contains
-// only channel types permitting receive operations and with identical element
-// types, commonUnderOrChan returns one of those channel types.
-// Otherwise the result is nil, and *cause reports the error if a non-nil cause
-// is provided.
-// The check parameter is only used if *cause reports an error; it may be nil.
-func commonUnderOrChan(check *Checker, t Type, cause *string) Type {
-       var s, su Type
-       var sc *Chan
-
-       bad := func(s string) bool {
-               if cause != nil {
-                       *cause = s
-               }
-               su = nil
+// If t is a type parameter, cond is nil, and t's type set contains no channel types,
+// commonUnder returns the common underlying type of all types in t's type set if
+// it exists, or nil and an error cause otherwise.
+//
+// If t is a type parameter, cond is nil, and there are channel types, t's type set
+// must only contain channel types, they must all have the same element types,
+// channel directions must not conflict, and commonUnder returns one of the most
+// restricted channels. Otherwise, the function returns nil and an error cause.
+//
+// If cond != nil, each pair (t, u) of type and underlying type in t's type set
+// must satisfy the condition expressed by cond. If the result of cond is != nil,
+// commonUnder returns nil and the error cause reported by cond.
+// Note that cond is called before any other conditions are checked; specifically
+// cond may be called with (nil, nil) if the type set contains no specific types.
+//
+// If t is not a type parameter, commonUnder behaves as if t was a type parameter
+// with the single type t in its type set.
+func commonUnder(t Type, cond func(t, u Type) *errorCause) (Type, *errorCause) {
+       var ct, cu Type // type and respective common underlying type
+       var err *errorCause
+
+       bad := func(format string, args ...any) bool {
+               cu = nil
+               err = newErrorCause(format, args...)
                return false
        }
 
        typeset(t, func(t, u Type) bool {
+               if cond != nil {
+                       if err = cond(t, u); err != nil {
+                               cu = nil
+                               return false
+                       }
+               }
+
                if u == nil {
                        return bad("no specific type")
                }
-               c, _ := u.(*Chan)
-               if c != nil && c.dir == SendOnly {
-                       return bad(check.sprintf("receive from send-only channel %s", t))
-               }
-               if su == nil {
-                       s, su = t, u
-                       sc = c // possibly nil
+
+               // If this is the first type we're seeing, we're done.
+               if cu == nil {
+                       ct, cu = t, u
                        return true
                }
-               // su != nil
-               if sc != nil && c != nil {
-                       if !Identical(sc.elem, c.elem) {
-                               return bad(check.sprintf("channels with different element types %s and %s", sc.elem, c.elem))
+
+               // If we've seen a channel before, and we have a channel now, they must be compatible.
+               if chu, _ := cu.(*Chan); chu != nil {
+                       if ch, _ := u.(*Chan); ch != nil {
+                               if !Identical(chu.elem, ch.elem) {
+                                       return bad("channels %s and %s have different element types", ct, t)
+                               }
+                               // If we have different channel directions, keep the restricted one
+                               // and complain if they conflict.
+                               if chu.dir == SendRecv {
+                                       ct, cu = t, u // switch to current, possibly restricted channel
+                               } else if chu.dir != ch.dir {
+                                       return bad("channels %s and %s have conflicting directions", ct, t)
+
+                               }
+                               return true
                        }
-                       return true
                }
-               // sc == nil
-               if !Identical(su, u) {
-                       return bad(check.sprintf("%s and %s have different underlying types", s, t))
-               }
-               return true
-       })
-
-       return su
-}
 
-// If t is not a type parameter, coreType returns the underlying type.
-// If t is a type parameter, coreType returns the single underlying
-// type of all types in its type set if it exists, or nil otherwise. If the
-// type set contains only unrestricted and restricted channel types (with
-// identical element types), the single underlying type is the restricted
-// channel type if the restrictions are always the same, or nil otherwise.
-func coreType(t Type) Type {
-       var su Type
-       typeset(t, func(_, u Type) bool {
-               if u == nil {
-                       return false
-               }
-               if su != nil {
-                       u = match(su, u)
-                       if u == nil {
-                               su = nil
-                               return false
-                       }
+               // Otherwise, the current type must have the same underlying type as all previous types.
+               if !Identical(cu, u) {
+                       return bad("%s and %s have different underlying types", ct, t)
                }
-               // su == nil || match(su, u) != nil
-               su = u
+
                return true
        })
-       return su
+
+       return cu, err
 }
 
 // coreString is like coreType but also considers []byte
index e4b50d7d4fecab10060782861e5363a8883653dd..abcbab433a1bd1e225f479d09f0ac17c92016d4c 100644 (file)
@@ -776,7 +776,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
                        // If y is also an unbound type parameter, we will end
                        // up here again with x and y swapped, so we don't
                        // need to take care of that case separately.
-                       if cx := coreType(x); cx != nil {
+                       if cx, _ := commonUnder(x, nil); cx != nil {
                                if traceInference {
                                        u.tracef("core %s ≡ %s", xorig, yorig)
                                }
index 19da7e0ccca20768aa1b94c17fe7a47cbb67a145..5b44682a7accd70390969c80ba60c9540eab245e 100644 (file)
@@ -12,11 +12,11 @@ type C4 interface{ chan int | chan<- int }
 type C5[T any] interface{ ~chan T | <-chan T }
 
 func _[T any](ch T) {
-       <-ch // ERRORx `cannot receive from ch .*: type set contains no specific channel type`
+       <-ch // ERRORx `cannot receive from ch .*: no specific channel type`
 }
 
 func _[T C0](ch T) {
-       <-ch // ERRORx `cannot receive from ch .*: type set contains non-channel int`
+       <-ch // ERRORx `cannot receive from ch .*: non-channel int`
 }
 
 func _[T C1](ch T) {
@@ -28,11 +28,11 @@ func _[T C2](ch T) {
 }
 
 func _[T C3](ch T) {
-       <-ch // ERRORx `cannot receive from ch .*: type set contains channels with different element types int and float32`
+       <-ch // ERRORx `cannot receive from ch .*: channels chan int and chan float32 have different element types`
 }
 
 func _[T C4](ch T) {
-       <-ch // ERRORx `cannot receive from ch .*: type set contains send-only channel chan<- int`
+       <-ch // ERRORx `cannot receive from ch .*: send-only channel chan<- int`
 }
 
 func _[T C5[X], X any](ch T, x X) {
index 1de85b3791a2d07d7a006b9d426a0dae7f4715bc..58b668ce4f1cdb4e616278196ded8d12fa8fbfb7 100644 (file)
@@ -12,11 +12,11 @@ type C4 interface{ chan int | chan<- int }
 type C5[T any] interface{ ~chan T | chan<- T }
 
 func _[T any](ch T) {
-       ch <- /* ERRORx `cannot send to ch .*: type set contains no specific channel type` */ 0
+       ch <- /* ERRORx `cannot send to ch .*: no specific channel type` */ 0
 }
 
 func _[T C0](ch T) {
-       ch <- /* ERRORx `cannot send to ch .*: type set contains non-channel int` */ 0
+       ch <- /* ERRORx `cannot send to ch .*: non-channel int` */ 0
 }
 
 func _[T C1](ch T) {
@@ -24,11 +24,11 @@ func _[T C1](ch T) {
 }
 
 func _[T C2](ch T) {
-       ch <- /* ERRORx `cannot send to ch .*: type set contains receive-only channel <-chan int` */ 0
+       ch <- /* ERRORx `cannot send to ch .*: receive-only channel <-chan int` */ 0
 }
 
 func _[T C3](ch T) {
-       ch <- /* ERRORx `cannot send to ch .*: type set contains channels with different element types` */ 0
+       ch <- /* ERRORx `cannot send to ch .*: channels chan int and chan float32 have different element types` */ 0
 }
 
 func _[T C4](ch T) {