go/types, types2: implement min/max builtins

For #59488.

Change-Id: I4553ab11af9179a4786dede44877f88286c168dc
Reviewed-on: https://go-review.googlesource.com/c/go/+/496038
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2023-05-17 15:47:12 -07:00 committed by Gopher Robot
parent c7d6c6000a
commit b5515eef56
10 changed files with 314 additions and 3 deletions

View File

@ -533,6 +533,63 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
check.recordBuiltinType(call.Fun, makeSig(x.typ, types...))
}
case _Max, _Min:
// max(x, ...)
// min(x, ...)
if !check.verifyVersionf(check.pkg, call.Fun, go1_21, bin.name) {
return
}
op := token.LSS
if id == _Max {
op = token.GTR
}
for i, a := range args {
if a.mode == invalid {
return
}
if !allOrdered(a.typ) {
check.errorf(a, InvalidMinMaxOperand, invalidArg+"%s cannot be ordered", a)
return
}
// The first argument is already in x and there's nothing left to do.
if i > 0 {
check.matchTypes(x, a)
if x.mode == invalid {
return
}
if !Identical(x.typ, a.typ) {
check.errorf(a, MismatchedTypes, invalidArg+"mismatched types %s (previous argument) and %s (type of %s)", x.typ, a.typ, a.expr)
return
}
if x.mode == constant_ && a.mode == constant_ {
if constant.Compare(a.val, op, x.val) {
*x = *a
}
} else {
x.mode = value
}
}
}
// If nargs == 1, make sure x.mode is either a value or a constant.
if x.mode != constant_ {
x.mode = value
}
if check.recordTypes() && x.mode != constant_ {
types := make([]Type, nargs)
for i := range types {
types[i] = x.typ
}
check.recordBuiltinType(call.Fun, makeSig(x.typ, types...))
}
case _New:
// new(T)
// (no argument evaluated yet)

View File

@ -95,6 +95,18 @@ var builtinCalls = []struct {
// go.dev/issue/45667
{"make", `const l uint = 1; _ = make([]int, l)`, `func([]int, uint) []int`},
{"max", ` _ = max(0 )`, `invalid type`}, // constant
{"max", `var x int ; _ = max(x )`, `func(int) int`},
{"max", `var x int ; _ = max(0, x )`, `func(int, int) int`},
{"max", `var x string ; _ = max("a", x )`, `func(string, string) string`},
{"max", `var x float32; _ = max(0, 1.0, x)`, `func(float32, float32, float32) float32`},
{"min", ` _ = min(0 )`, `invalid type`}, // constant
{"min", `var x int ; _ = min(x )`, `func(int) int`},
{"min", `var x int ; _ = min(0, x )`, `func(int, int) int`},
{"min", `var x string ; _ = min("a", x )`, `func(string, string) string`},
{"min", `var x float32; _ = min(0, 1.0, x)`, `func(float32, float32, float32) float32`},
{"new", `_ = new(int)`, `func(int) *int`},
{"new", `type T struct{}; _ = new(T)`, `func(p.T) *p.T`},

View File

@ -153,6 +153,8 @@ const (
_Imag
_Len
_Make
_Max
_Min
_New
_Panic
_Print
@ -191,6 +193,9 @@ var predeclaredFuncs = [...]struct {
_Imag: {"imag", 1, false, expression},
_Len: {"len", 1, false, expression},
_Make: {"make", 1, true, expression},
// To disable max/min, remove the next two lines.
_Max: {"max", 1, true, expression},
_Min: {"min", 1, true, expression},
_New: {"new", 1, false, expression},
_Panic: {"panic", 1, false, statement},
_Print: {"print", 0, true, statement},

View File

@ -532,6 +532,63 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
check.recordBuiltinType(call.Fun, makeSig(x.typ, types...))
}
case _Max, _Min:
// max(x, ...)
// min(x, ...)
if !check.verifyVersionf(check.pkg, call.Fun, go1_21, bin.name) {
return
}
op := token.LSS
if id == _Max {
op = token.GTR
}
for i, a := range args {
if a.mode == invalid {
return
}
if !allOrdered(a.typ) {
check.errorf(a, InvalidMinMaxOperand, invalidArg+"%s cannot be ordered", a)
return
}
// The first argument is already in x and there's nothing left to do.
if i > 0 {
check.matchTypes(x, a)
if x.mode == invalid {
return
}
if !Identical(x.typ, a.typ) {
check.errorf(a, MismatchedTypes, invalidArg+"mismatched types %s (previous argument) and %s (type of %s)", x.typ, a.typ, a.expr)
return
}
if x.mode == constant_ && a.mode == constant_ {
if constant.Compare(a.val, op, x.val) {
*x = *a
}
} else {
x.mode = value
}
}
}
// If nargs == 1, make sure x.mode is either a value or a constant.
if x.mode != constant_ {
x.mode = value
}
if check.recordTypes() && x.mode != constant_ {
types := make([]Type, nargs)
for i := range types {
types[i] = x.typ
}
check.recordBuiltinType(call.Fun, makeSig(x.typ, types...))
}
case _New:
// new(T)
// (no argument evaluated yet)

View File

@ -95,6 +95,18 @@ var builtinCalls = []struct {
// go.dev/issue/45667
{"make", `const l uint = 1; _ = make([]int, l)`, `func([]int, uint) []int`},
{"max", ` _ = max(0 )`, `invalid type`}, // constant
{"max", `var x int ; _ = max(x )`, `func(int) int`},
{"max", `var x int ; _ = max(0, x )`, `func(int, int) int`},
{"max", `var x string ; _ = max("a", x )`, `func(string, string) string`},
{"max", `var x float32; _ = max(0, 1.0, x)`, `func(float32, float32, float32) float32`},
{"min", ` _ = min(0 )`, `invalid type`}, // constant
{"min", `var x int ; _ = min(x )`, `func(int) int`},
{"min", `var x int ; _ = min(0, x )`, `func(int, int) int`},
{"min", `var x string ; _ = min("a", x )`, `func(string, string) string`},
{"min", `var x float32; _ = min(0, 1.0, x)`, `func(float32, float32, float32) float32`},
{"new", `_ = new(int)`, `func(int) *int`},
{"new", `type T struct{}; _ = new(T)`, `func(p.T) *p.T`},

View File

@ -155,6 +155,8 @@ const (
_Imag
_Len
_Make
_Max
_Min
_New
_Panic
_Print
@ -193,6 +195,9 @@ var predeclaredFuncs = [...]struct {
_Imag: {"imag", 1, false, expression},
_Len: {"len", 1, false, expression},
_Make: {"make", 1, true, expression},
// To disable max/min, remove the next two lines.
_Max: {"max", 1, true, expression},
_Min: {"min", 1, true, expression},
_New: {"new", 1, false, expression},
_Panic: {"panic", 1, false, statement},
_Print: {"print", 0, true, statement},

View File

@ -154,6 +154,7 @@ func _() {
_ = x[InvalidUnsafeString-146]
_ = x[InvalidClear-148]
_ = x[TypeTooLarge-149]
_ = x[InvalidMinMaxOperand-150]
}
const (
@ -162,7 +163,7 @@ const (
_Code_name_2 = "InvalidPtrEmbedBadRecvInvalidRecvDuplicateFieldAndMethodDuplicateMethodInvalidBlankInvalidIotaMissingInitBodyInvalidInitSigInvalidInitDeclInvalidMainDeclTooManyValuesNotAnExprTruncatedFloatNumericOverflowUndefinedOpMismatchedTypesDivByZeroNonNumericIncDecUnaddressableOperandInvalidIndirectionNonIndexableOperandInvalidIndexSwappedSliceIndicesNonSliceableOperandInvalidSliceExprInvalidShiftCountInvalidShiftOperandInvalidReceiveInvalidSendDuplicateLitKeyMissingLitKeyInvalidLitIndexOversizeArrayLitMixedStructLitInvalidStructLitMissingLitFieldDuplicateLitFieldUnexportedLitFieldInvalidLitFieldUntypedLitInvalidLitAmbiguousSelectorUndeclaredImportedNameUnexportedNameUndeclaredNameMissingFieldOrMethodBadDotDotDotSyntaxNonVariadicDotDotDotMisplacedDotDotDot"
_Code_name_3 = "InvalidDotDotDotUncalledBuiltinInvalidAppendInvalidCapInvalidCloseInvalidCopyInvalidComplexInvalidDeleteInvalidImagInvalidLenSwappedMakeArgsInvalidMakeInvalidRealInvalidAssertImpossibleAssertInvalidConversionInvalidUntypedConversionBadOffsetofSyntaxInvalidOffsetofUnusedExprUnusedVarMissingReturnWrongResultCountOutOfScopeResultInvalidCondInvalidPostDecl"
_Code_name_4 = "InvalidIterVarInvalidRangeExprMisplacedBreakMisplacedContinueMisplacedFallthroughDuplicateCaseDuplicateDefaultBadTypeKeywordInvalidTypeSwitchInvalidExprSwitchInvalidSelectCaseUndeclaredLabelDuplicateLabelMisplacedLabelUnusedLabelJumpOverDeclJumpIntoBlockInvalidMethodExprWrongArgCountInvalidCallUnusedResultsInvalidDeferInvalidGoBadDeclRepeatedDeclInvalidUnsafeAddInvalidUnsafeSliceUnsupportedFeatureNotAGenericTypeWrongTypeArgCountCannotInferTypeArgsInvalidTypeArgInvalidInstanceCycleInvalidUnionMisplacedConstraintIfaceInvalidMethodTypeParamsMisplacedTypeParamInvalidUnsafeSliceDataInvalidUnsafeString"
_Code_name_5 = "InvalidClearTypeTooLarge"
_Code_name_5 = "InvalidClearTypeTooLargeInvalidMinMaxOperand"
)
var (
@ -170,7 +171,7 @@ var (
_Code_index_2 = [...]uint16{0, 15, 22, 33, 56, 71, 83, 94, 109, 123, 138, 153, 166, 175, 189, 204, 215, 230, 239, 255, 275, 293, 312, 324, 343, 362, 378, 395, 414, 428, 439, 454, 467, 482, 498, 512, 528, 543, 560, 578, 593, 603, 613, 630, 652, 666, 680, 700, 718, 738, 756}
_Code_index_3 = [...]uint16{0, 16, 31, 44, 54, 66, 77, 91, 104, 115, 125, 140, 151, 162, 175, 191, 208, 232, 249, 264, 274, 283, 296, 312, 328, 339, 354}
_Code_index_4 = [...]uint16{0, 14, 30, 44, 61, 81, 94, 110, 124, 141, 158, 175, 190, 204, 218, 229, 241, 254, 271, 284, 295, 308, 320, 329, 336, 348, 364, 382, 400, 415, 432, 451, 465, 485, 497, 521, 544, 562, 584, 603}
_Code_index_5 = [...]uint8{0, 12, 24}
_Code_index_5 = [...]uint8{0, 12, 24, 44}
)
func (i Code) String() string {
@ -189,7 +190,7 @@ func (i Code) String() string {
case 108 <= i && i <= 146:
i -= 108
return _Code_name_4[_Code_index_4[i]:_Code_index_4[i+1]]
case 148 <= i && i <= 149:
case 148 <= i && i <= 150:
i -= 148
return _Code_name_5[_Code_index_5[i]:_Code_index_5[i+1]]
default:

View File

@ -1462,4 +1462,16 @@ const (
// }
// var _ = unsafe.Offsetof(s.x)
TypeTooLarge
// InvalidMinMaxOperand occurs if min or max is called
// with an operand that cannot be ordered because it
// does not support the < operator.
//
// Example:
// const _ = min(true)
//
// Example:
// var s, t []byte
// var _ = max(s, t)
InvalidMinMaxOperand
)

View File

@ -498,6 +498,114 @@ func make2() {
_ = make(f1 /* ERROR "not a type" */ ())
}
func max1() {
var b bool
var c complex128
var x int
var s string
type myint int
var m myint
_ = max() /* ERROR "not enough arguments" */
_ = max(b /* ERROR "cannot be ordered" */ )
_ = max(c /* ERROR "cannot be ordered" */ )
_ = max(x)
_ = max(s)
_ = max(x, x)
_ = max(x, x, x, x, x)
var _ int = max /* ERROR "cannot use max(m) (value of type myint) as int value" */ (m)
_ = max(x, m /* ERROR "invalid argument: mismatched types int (previous argument) and myint (type of m)" */ , x)
_ = max(1, x)
_ = max(1.0, x)
_ = max(1.2 /* ERROR "1.2 (untyped float constant) truncated to int" */ , x)
_ = max(-10, 1.0, c /* ERROR "cannot be ordered" */ )
const (
_ = max /* ERROR "max(x) (value of type int) is not constant" */ (x)
_ = max(true /* ERROR "invalid argument: true (untyped bool constant) cannot be ordered" */ )
_ = max(1)
_ = max(1, 2.3, 'a')
_ = max(1, "foo" /* ERROR "mismatched types" */ )
_ = max(1, 0i /* ERROR "cannot be ordered" */ )
_ = max(1, 2 /* ERROR "cannot be ordered" */ + 3i )
)
}
func max2() {
_ = assert(max(0) == 0)
_ = assert(max(0, 1) == 1)
_ = assert(max(0, -10, 123456789) == 123456789)
_ = assert(max(-12345678901234567890, 0) == 0)
_ = assert(max(1, 2.3) == 2.3)
_ = assert(max(1, 2.3, 'a') == 'a')
_ = assert(max("", "a") == "a")
_ = assert(max("abcde", "xyz", "foo", "bar") == "xyz")
const (
_ int = max(1.0)
_ float32 = max(1, 2)
_ int = max /* ERROR "cannot use max(1, 2.3) (untyped float constant 2.3) as int value" */ (1, 2.3)
_ int = max(1.2, 3) // ok!
_ byte = max(1, 'a')
)
}
func min1() {
var b bool
var c complex128
var x int
var s string
type myint int
var m myint
_ = min() /* ERROR "not enough arguments" */
_ = min(b /* ERROR "cannot be ordered" */ )
_ = min(c /* ERROR "cannot be ordered" */ )
_ = min(x)
_ = min(s)
_ = min(x, x)
_ = min(x, x, x, x, x)
var _ int = min /* ERROR "cannot use min(m) (value of type myint) as int value" */ (m)
_ = min(x, m /* ERROR "invalid argument: mismatched types int (previous argument) and myint (type of m)" */ , x)
_ = min(1, x)
_ = min(1.0, x)
_ = min(1.2 /* ERROR "1.2 (untyped float constant) truncated to int" */ , x)
_ = min(-10, 1.0, c /* ERROR "cannot be ordered" */ )
const (
_ = min /* ERROR "min(x) (value of type int) is not constant" */ (x)
_ = min(true /* ERROR "invalid argument: true (untyped bool constant) cannot be ordered" */ )
_ = min(1)
_ = min(1, 2.3, 'a')
_ = min(1, "foo" /* ERROR "mismatched types" */ )
_ = min(1, 0i /* ERROR "cannot be ordered" */ )
_ = min(1, 2 /* ERROR "cannot be ordered" */ + 3i )
)
}
func min2() {
_ = assert(min(0) == 0)
_ = assert(min(0, 1) == 0)
_ = assert(min(0, -10, 123456789) == -10)
_ = assert(min(-12345678901234567890, 0) == -12345678901234567890)
_ = assert(min(1, 2.3) == 1)
_ = assert(min(1, 2.3, 'a') == 1)
_ = assert(min("", "a") == "")
_ = assert(min("abcde", "xyz", "foo", "bar") == "abcde")
const (
_ int = min(1.0)
_ float32 = min(1, 2)
_ int = min(1, 2.3) // ok!
_ int = min /* ERROR "cannot use min(1.2, 3) (untyped float constant 1.2) as int value" */ (1.2, 3)
_ byte = min(1, 'a')
)
}
func new1() {
_ = new() // ERROR "not enough arguments"
_ = new(1, 2) // ERROR "too many arguments"

View File

@ -182,6 +182,48 @@ func _[
_ = make(C3)
}
// max
func _[
P1 ~int|~float64,
P2 ~int|~string|~uint,
P3 ~int|bool,
]() {
var x1 P1
_ = max(x1)
_ = max(x1, x1)
_ = max(1, x1, 2)
const _ = max /* ERROR "max(1, x1, 2) (value of type P1 constrained by ~int | ~float64) is not constant" */ (1, x1, 2)
var x2 P2
_ = max(x2)
_ = max(x2, x2)
_ = max(1, 2 /* ERROR "cannot convert 2 (untyped int constant) to type P2" */, x2) // error at 2 because max is 2
_ = max(x1, x2 /* ERROR "mismatched types P1 (previous argument) and P2 (type of x2)" */ )
}
// min
func _[
P1 ~int|~float64,
P2 ~int|~string|~uint,
P3 ~int|bool,
]() {
var x1 P1
_ = min(x1)
_ = min(x1, x1)
_ = min(1, x1, 2)
const _ = min /* ERROR "min(1, x1, 2) (value of type P1 constrained by ~int | ~float64) is not constant" */ (1, x1, 2)
var x2 P2
_ = min(x2)
_ = min(x2, x2)
_ = min(1 /* ERROR "cannot convert 1 (untyped int constant) to type P2" */ , 2, x2) // error at 1 because min is 1
_ = min(x1, x2 /* ERROR "mismatched types P1 (previous argument) and P2 (type of x2)" */ )
}
// unsafe.Alignof
func _[T comparable]() {