]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: implement min/max builtins
authorRobert Griesemer <gri@golang.org>
Wed, 17 May 2023 22:47:12 +0000 (15:47 -0700)
committerGopher Robot <gobot@golang.org>
Thu, 18 May 2023 21:16:29 +0000 (21:16 +0000)
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>

src/cmd/compile/internal/types2/builtins.go
src/cmd/compile/internal/types2/builtins_test.go
src/cmd/compile/internal/types2/universe.go
src/go/types/builtins.go
src/go/types/builtins_test.go
src/go/types/universe.go
src/internal/types/errors/code_string.go
src/internal/types/errors/codes.go
src/internal/types/testdata/check/builtins0.go
src/internal/types/testdata/check/builtins1.go

index 1d2780966a4cb99ced5d102e1486c2dcca980b84..1a79fc8ebd9fb4bd962ef11370382759b77ef1e2 100644 (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)
index 1066a91c618cf849f20b48cc100af9d660366ef1..875ee5a4d5dde41245e1f8673cb9a8602765b66d 100644 (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`},
 
index 3fe849e737e0528704f1ed1615a3ea6feb08b362..79cd8cbf0ace6ee66c51d85639d6cff5aa6af685 100644 (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},
index 9f22fcf166004f1474ec9b8b3aad606776c207c6..80cfeb3880c767b8fa968c12a67aae3edda09c09 100644 (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)
index 6238464f58a951b734bc94fbd90a047885565b79..4b198ef408bd92431386762e53807301e9c853ee 100644 (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`},
 
index d32a8ed4e8f279b3a87978a9bff25323b1eb0ce5..cc4d42d98c022e43cd619599b578b47ec76e159f 100644 (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},
index d00e62bf1d66e74c42ffc330d59e7d965cdab609..719fc73a5a7672d109fe83bf0b4bfac520a30a16 100644 (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:
index 0982aeb39741cad5b31fdb9b9cb0bdb1c9334e4f..62358c7e8ceae7edec86809793e16f98eab94425 100644 (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
 )
index 913dc5156ef4035bf2d91cf780deef4375bbae99..ed4769ee8c415c8e525b962d47459fc1d9a774df 100644 (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"
index b99114f4d67d1d64732d72329d5e7050effd69ad..f7ac72d4b9ae60b89e9c478997e74757b13065d6 100644 (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]() {