go/types, types2: remove most remaining references to coreType in builtin.go

For now, use commonUnder (formerly called sharedUnder) and update
error messages and comments. We can provide better error messages
in individual cases eventually.

Kepp using coreType for make built-in for now because it must accept
different channel types with non-conflicting directions and identical
element types. Added extra test cases.

While at it, rename sharedUnder, sharedUnderOrChan to commonUnder
and commonUnderOrChan, respectively (per suggestion from rfindley).

For #70128.

Change-Id: I11f3d5ce858746574f4302271d8cb763c2cdcf98
Reviewed-on: https://go-review.googlesource.com/c/go/+/653139
Reviewed-by: Robert Findley <rfindley@google.com>
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>
This commit is contained in:
Robert Griesemer 2025-02-26 15:14:30 -08:00 committed by Gopher Robot
parent 19d0b3e81f
commit 26ba61dfad
11 changed files with 50 additions and 38 deletions

View File

@ -377,7 +377,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
case _Copy:
// copy(x, y []T) int
dst, _ := coreType(x.typ).(*Slice)
dst, _ := commonUnder(check, x.typ, nil).(*Slice)
y := args[1]
src0 := coreString(y.typ)
@ -520,7 +520,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
case *Map, *Chan:
min = 1
case nil:
check.errorf(arg0, InvalidMake, invalidArg+"cannot make %s: no core type", arg0)
check.errorf(arg0, InvalidMake, invalidArg+"cannot make %s: no common underlying type", arg0)
return
default:
check.errorf(arg0, InvalidMake, invalidArg+"cannot make %s; type must be slice, map, or channel", arg0)
@ -818,7 +818,7 @@ 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, _ := coreType(x.typ).(*Pointer)
ptr, _ := commonUnder(check, x.typ, nil).(*Pointer)
if ptr == nil {
check.errorf(x, InvalidUnsafeSlice, invalidArg+"%s is not a pointer", x)
return
@ -839,7 +839,7 @@ 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, _ := coreType(x.typ).(*Slice)
slice, _ := commonUnder(check, x.typ, nil).(*Slice)
if slice == nil {
check.errorf(x, InvalidUnsafeSliceData, invalidArg+"%s is not a slice", x)
return

View File

@ -243,9 +243,9 @@ func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind {
cgocall := x.mode == cgofunc
// If the operand type is a type parameter, all types in its type set
// must have a shared underlying type, which must be a signature.
// must have a common underlying type, which must be a signature.
var cause string
sig, _ := sharedUnder(check, x.typ, &cause).(*Signature)
sig, _ := commonUnder(check, x.typ, &cause).(*Signature)
if sig == nil {
if cause != "" {
check.errorf(x, InvalidCall, invalidOp+"cannot call %s: %s", x, cause)

View File

@ -67,13 +67,13 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string, fo
obj, index, indirect = lookupFieldOrMethodImpl(T, addressable, pkg, name, foldCase)
// If we didn't find anything and if we have a type parameter with a shared underlying
// If we didn't find anything and if we have a type parameter with a common underlying
// type, see if there is a matching field (but not a method, those need to be declared
// explicitly in the constraint). If the constraint is a named pointer type (see above),
// 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 := sharedUnder(nil, T, nil); t != nil {
if t := commonUnder(nil, 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

View File

@ -1002,7 +1002,7 @@ func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (
}
var cause1 string
rtyp := sharedUnderOrChan(check, orig, &cause1)
rtyp := commonUnderOrChan(check, orig, &cause1)
if rtyp == nil {
return bad(cause1)
}
@ -1041,7 +1041,7 @@ func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (
assert(typ.Recv() == nil)
// check iterator argument type
var cause2 string
cb, _ := sharedUnder(check, typ.Params().At(0).Type(), &cause2).(*Signature)
cb, _ := commonUnder(check, typ.Params().At(0).Type(), &cause2).(*Signature)
switch {
case cb == nil:
if cause2 != "" {

View File

@ -40,13 +40,17 @@ func typeset(t Type, yield func(t, u Type) bool) {
yield(t, under(t))
}
// If t is not a type parameter, sharedUnder returns the underlying type.
// If t is a type parameter, sharedUnder returns the single underlying
// 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 sharedUnder(check *Checker, t Type, cause *string) Type {
func commonUnder(check *Checker, t Type, cause *string) Type {
var s, su Type
bad := func(s string) bool {
@ -72,16 +76,16 @@ func sharedUnder(check *Checker, t Type, cause *string) Type {
return su
}
// If t is not a type parameter, sharedUnderOrChan returns the underlying type;
// 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, sharedUnderOrChan returns the single underlying
// 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, sharedUnderOrChan returns one of those channel types.
// 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 sharedUnderOrChan(check *Checker, t Type, cause *string) Type {
func commonUnderOrChan(check *Checker, t Type, cause *string) Type {
var s, su Type
var sc *Chan

View File

@ -380,7 +380,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
case _Copy:
// copy(x, y []T) int
dst, _ := coreType(x.typ).(*Slice)
dst, _ := commonUnder(check, x.typ, nil).(*Slice)
y := args[1]
src0 := coreString(y.typ)
@ -523,7 +523,7 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
case *Map, *Chan:
min = 1
case nil:
check.errorf(arg0, InvalidMake, invalidArg+"cannot make %s: no core type", arg0)
check.errorf(arg0, InvalidMake, invalidArg+"cannot make %s: no common underlying type", arg0)
return
default:
check.errorf(arg0, InvalidMake, invalidArg+"cannot make %s; type must be slice, map, or channel", arg0)
@ -821,7 +821,7 @@ 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, _ := coreType(x.typ).(*Pointer)
ptr, _ := commonUnder(check, x.typ, nil).(*Pointer)
if ptr == nil {
check.errorf(x, InvalidUnsafeSlice, invalidArg+"%s is not a pointer", x)
return
@ -842,7 +842,7 @@ 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, _ := coreType(x.typ).(*Slice)
slice, _ := commonUnder(check, x.typ, nil).(*Slice)
if slice == nil {
check.errorf(x, InvalidUnsafeSliceData, invalidArg+"%s is not a slice", x)
return

View File

@ -245,9 +245,9 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
cgocall := x.mode == cgofunc
// If the operand type is a type parameter, all types in its type set
// must have a shared underlying type, which must be a signature.
// must have a common underlying type, which must be a signature.
var cause string
sig, _ := sharedUnder(check, x.typ, &cause).(*Signature)
sig, _ := commonUnder(check, x.typ, &cause).(*Signature)
if sig == nil {
if cause != "" {
check.errorf(x, InvalidCall, invalidOp+"cannot call %s: %s", x, cause)

View File

@ -70,13 +70,13 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string, fo
obj, index, indirect = lookupFieldOrMethodImpl(T, addressable, pkg, name, foldCase)
// If we didn't find anything and if we have a type parameter with a shared underlying
// If we didn't find anything and if we have a type parameter with a common underlying
// type, see if there is a matching field (but not a method, those need to be declared
// explicitly in the constraint). If the constraint is a named pointer type (see above),
// 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 := sharedUnder(nil, T, nil); t != nil {
if t := commonUnder(nil, 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

View File

@ -1020,7 +1020,7 @@ func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (
}
var cause1 string
rtyp := sharedUnderOrChan(check, orig, &cause1)
rtyp := commonUnderOrChan(check, orig, &cause1)
if rtyp == nil {
return bad(cause1)
}
@ -1059,7 +1059,7 @@ func rangeKeyVal(check *Checker, orig Type, allowVersion func(goVersion) bool) (
assert(typ.Recv() == nil)
// check iterator argument type
var cause2 string
cb, _ := sharedUnder(check, typ.Params().At(0).Type(), &cause2).(*Signature)
cb, _ := commonUnder(check, typ.Params().At(0).Type(), &cause2).(*Signature)
switch {
case cb == nil:
if cause2 != "" {

View File

@ -43,13 +43,17 @@ func typeset(t Type, yield func(t, u Type) bool) {
yield(t, under(t))
}
// If t is not a type parameter, sharedUnder returns the underlying type.
// If t is a type parameter, sharedUnder returns the single underlying
// 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 sharedUnder(check *Checker, t Type, cause *string) Type {
func commonUnder(check *Checker, t Type, cause *string) Type {
var s, su Type
bad := func(s string) bool {
@ -75,16 +79,16 @@ func sharedUnder(check *Checker, t Type, cause *string) Type {
return su
}
// If t is not a type parameter, sharedUnderOrChan returns the underlying type;
// 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, sharedUnderOrChan returns the single underlying
// 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, sharedUnderOrChan returns one of those channel types.
// 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 sharedUnderOrChan(check *Checker, t Type, cause *string) Type {
func commonUnderOrChan(check *Checker, t Type, cause *string) Type {
var s, su Type
var sc *Chan

View File

@ -152,7 +152,9 @@ func _[
C1 ~chan int,
C2 ~chan int | ~chan string,
C3 chan int | myChan, // single underlying type
C3 chan int | myChan, // single underlying type
C4 chan int | chan<- int, // channels may have different (non-conflicting) directions
C5 <-chan int | chan<- int,
]() {
type S0 []int
_ = make([]int, 10)
@ -162,7 +164,7 @@ func _[
_ = make /* ERROR "expects 2 or 3 arguments" */ (S1)
_ = make(S1, 10, 20)
_ = make /* ERROR "expects 2 or 3 arguments" */ (S1, 10, 20, 30)
_ = make(S2 /* ERROR "cannot make S2: no core type" */ , 10)
_ = make(S2 /* ERROR "cannot make S2: no common underlying type" */ , 10)
type M0 map[string]int
_ = make(map[string]int)
@ -170,7 +172,7 @@ func _[
_ = make(M1)
_ = make(M1, 10)
_ = make/* ERROR "expects 1 or 2 arguments" */(M1, 10, 20)
_ = make(M2 /* ERROR "cannot make M2: no core type" */ )
_ = make(M2 /* ERROR "cannot make M2: no common underlying type" */ )
type C0 chan int
_ = make(chan int)
@ -178,8 +180,10 @@ func _[
_ = make(C1)
_ = make(C1, 10)
_ = make/* ERROR "expects 1 or 2 arguments" */(C1, 10, 20)
_ = make(C2 /* ERROR "cannot make C2: no core type" */ )
_ = make(C2 /* ERROR "cannot make C2: no common underlying type" */ )
_ = make(C3)
_ = make(C4)
_ = make(C5 /* ERROR "cannot make C5: no common underlying type" */ )
}
// max