From b5515eef565a7d0fd820009fc8c7b282155340a5 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 17 May 2023 15:47:12 -0700 Subject: [PATCH] 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 Run-TryBot: Robert Griesemer Reviewed-by: Robert Griesemer Reviewed-by: Matthew Dempsky Auto-Submit: Robert Griesemer --- src/cmd/compile/internal/types2/builtins.go | 57 +++++++++ .../compile/internal/types2/builtins_test.go | 12 ++ src/cmd/compile/internal/types2/universe.go | 5 + src/go/types/builtins.go | 57 +++++++++ src/go/types/builtins_test.go | 12 ++ src/go/types/universe.go | 5 + src/internal/types/errors/code_string.go | 7 +- src/internal/types/errors/codes.go | 12 ++ .../types/testdata/check/builtins0.go | 108 ++++++++++++++++++ .../types/testdata/check/builtins1.go | 42 +++++++ 10 files changed, 314 insertions(+), 3 deletions(-) diff --git a/src/cmd/compile/internal/types2/builtins.go b/src/cmd/compile/internal/types2/builtins.go index 1d2780966a..1a79fc8ebd 100644 --- a/src/cmd/compile/internal/types2/builtins.go +++ b/src/cmd/compile/internal/types2/builtins.go @@ -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) diff --git a/src/cmd/compile/internal/types2/builtins_test.go b/src/cmd/compile/internal/types2/builtins_test.go index 1066a91c61..875ee5a4d5 100644 --- a/src/cmd/compile/internal/types2/builtins_test.go +++ b/src/cmd/compile/internal/types2/builtins_test.go @@ -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`}, diff --git a/src/cmd/compile/internal/types2/universe.go b/src/cmd/compile/internal/types2/universe.go index 3fe849e737..79cd8cbf0a 100644 --- a/src/cmd/compile/internal/types2/universe.go +++ b/src/cmd/compile/internal/types2/universe.go @@ -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}, diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go index 9f22fcf166..80cfeb3880 100644 --- a/src/go/types/builtins.go +++ b/src/go/types/builtins.go @@ -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) diff --git a/src/go/types/builtins_test.go b/src/go/types/builtins_test.go index 6238464f58..4b198ef408 100644 --- a/src/go/types/builtins_test.go +++ b/src/go/types/builtins_test.go @@ -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`}, diff --git a/src/go/types/universe.go b/src/go/types/universe.go index d32a8ed4e8..cc4d42d98c 100644 --- a/src/go/types/universe.go +++ b/src/go/types/universe.go @@ -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}, diff --git a/src/internal/types/errors/code_string.go b/src/internal/types/errors/code_string.go index d00e62bf1d..719fc73a5a 100644 --- a/src/internal/types/errors/code_string.go +++ b/src/internal/types/errors/code_string.go @@ -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: diff --git a/src/internal/types/errors/codes.go b/src/internal/types/errors/codes.go index 0982aeb397..62358c7e8c 100644 --- a/src/internal/types/errors/codes.go +++ b/src/internal/types/errors/codes.go @@ -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 ) diff --git a/src/internal/types/testdata/check/builtins0.go b/src/internal/types/testdata/check/builtins0.go index 913dc5156e..ed4769ee8c 100644 --- a/src/internal/types/testdata/check/builtins0.go +++ b/src/internal/types/testdata/check/builtins0.go @@ -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" diff --git a/src/internal/types/testdata/check/builtins1.go b/src/internal/types/testdata/check/builtins1.go index b99114f4d6..f7ac72d4b9 100644 --- a/src/internal/types/testdata/check/builtins1.go +++ b/src/internal/types/testdata/check/builtins1.go @@ -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]() { -- 2.50.0