diff --git a/src/pkg/go/types/builtins.go b/src/pkg/go/types/builtins.go index ad9259118e..220be08b5d 100644 --- a/src/pkg/go/types/builtins.go +++ b/src/pkg/go/types/builtins.go @@ -11,6 +11,9 @@ import ( "go/token" ) +// TODO(gri): Several built-ins are missing assignment checks. As a result, +// non-constant shift arguments may not be properly type-checked. + // builtin typechecks a built-in call. The built-in type is bin, and iota is the current // value of iota or -1 if iota doesn't have a value in the current context. The result // of the call is returned via x. If the call has type errors, the returned x is marked @@ -170,6 +173,10 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota goto Error } + // arguments have final type + check.updateExprType(args[0], typ, true) + check.updateExprType(args[1], typ, true) + case _Copy: var y operand check.expr(&y, args[1], nil, iota) @@ -269,24 +276,13 @@ func (check *checker) builtin(x *operand, call *ast.CallExpr, bin *builtin, iota check.errorf(call.Pos(), "%s expects %d or %d arguments; found %d", call, min, min+1, n) goto Error } - var sizes []interface{} // constant integer arguments, if any + var sizes []int64 // constant integer arguments, if any for _, arg := range args[1:] { - check.expr(x, arg, nil, iota) - if x.isInteger(check.ctxt) { - if x.mode == constant { - if isNegConst(x.val) { - check.invalidArg(x.pos(), "%s must not be negative", x) - // safe to continue - } else { - sizes = append(sizes, x.val) // x.val >= 0 - } - } - } else { - check.invalidArg(x.pos(), "%s must be an integer", x) - // safe to continue + if s, ok := check.index(arg, -1, iota); ok && s >= 0 { + sizes = append(sizes, s) } } - if len(sizes) == 2 && compareConst(sizes[0], sizes[1], token.GTR) { + if len(sizes) == 2 && sizes[0] > sizes[1] { check.invalidArg(args[1].Pos(), "length and capacity swapped") // safe to continue } diff --git a/src/pkg/go/types/check.go b/src/pkg/go/types/check.go index 8d45d2ea81..e8ee9bc336 100644 --- a/src/pkg/go/types/check.go +++ b/src/pkg/go/types/check.go @@ -18,6 +18,15 @@ const ( trace = false // turn on for detailed type resolution traces ) +// exprInfo stores type and constant value for an untyped expression. +type exprInfo struct { + isConst bool // expression has a, possibly unknown, constant value + isLhs bool // expression is lhs operand of a shift with delayed type check + typ *Basic + val interface{} // constant value (may be nil if unknown); valid if isConst +} + +// A checker is an instance of the type checker. type checker struct { ctxt *Context fset *token.FileSet @@ -31,14 +40,7 @@ type checker struct { initspecs map[*ast.ValueSpec]*ast.ValueSpec // "inherited" type and initialization expressions for constant declarations methods map[*TypeName]*Scope // maps type names to associated methods conversions map[*ast.CallExpr]bool // set of type-checked conversions (to distinguish from calls) - - // untyped expressions - // TODO(gri): Consider merging the untyped and constants map. Should measure - // the ratio between untyped non-constant and untyped constant expressions - // to make an informed decision. - untyped map[ast.Expr]*Basic // map of expressions of untyped type - constants map[ast.Expr]interface{} // map of untyped constant expressions; each key also appears in untyped - shiftOps map[ast.Expr]bool // map of lhs shift operands with delayed type-checking + untyped map[ast.Expr]exprInfo // map of expressions without final type // functions funclist []function // list of functions/methods with correct signatures and non-empty bodies @@ -234,18 +236,14 @@ func (check *checker) object(obj Object, cycleOk bool) { obj.Type = Typ[Invalid] return } + obj.visited = true switch d := obj.decl.(type) { case *ast.Field: unreachable() // function parameters are always typed when collected case *ast.ValueSpec: - obj.visited = true check.valueSpec(d.Pos(), obj, d.Names, d, 0) case *ast.AssignStmt: - // If we reach here, we have a short variable declaration - // where the rhs didn't typecheck and thus the lhs has no - // types. - obj.visited = true - obj.Type = Typ[Invalid] + unreachable() // assign1to1 sets the type for failing short var decls default: unreachable() // see also function newObj } @@ -428,9 +426,7 @@ func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package, initspecs: make(map[*ast.ValueSpec]*ast.ValueSpec), methods: make(map[*TypeName]*Scope), conversions: make(map[*ast.CallExpr]bool), - untyped: make(map[ast.Expr]*Basic), - constants: make(map[ast.Expr]interface{}), - shiftOps: make(map[ast.Expr]bool), + untyped: make(map[ast.Expr]exprInfo), } // set results and handle panics @@ -490,9 +486,9 @@ func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package, // remaining untyped expressions must indeed be untyped if debug { - for x, typ := range check.untyped { - if !isUntyped(typ) { - check.dump("%s: %s (type %s) is not untyped", x.Pos(), x, typ) + for x, info := range check.untyped { + if !isUntyped(info.typ) { + check.dump("%s: %s (type %s) is not untyped", x.Pos(), x, info.typ) panic(0) } } @@ -503,8 +499,12 @@ func check(ctxt *Context, fset *token.FileSet, files []*ast.File) (pkg *Package, // after function body checking for smaller // map size and more immediate feedback. if ctxt.Expr != nil { - for x, typ := range check.untyped { - ctxt.Expr(x, typ, check.constants[x]) + for x, info := range check.untyped { + var val interface{} + if info.isConst { + val = info.val + } + ctxt.Expr(x, info.typ, val) } } diff --git a/src/pkg/go/types/check_test.go b/src/pkg/go/types/check_test.go index 28308a579a..2ee7f6eef8 100644 --- a/src/pkg/go/types/check_test.go +++ b/src/pkg/go/types/check_test.go @@ -54,6 +54,7 @@ var tests = []struct { {"expr1", []string{"testdata/expr1.src"}}, {"expr2", []string{"testdata/expr2.src"}}, {"expr3", []string{"testdata/expr3.src"}}, + {"shifts", []string{"testdata/shifts.src"}}, {"builtins", []string{"testdata/builtins.src"}}, {"conversions", []string{"testdata/conversions.src"}}, {"stmt0", []string{"testdata/stmt0.src"}}, @@ -62,17 +63,6 @@ var tests = []struct { var fset = token.NewFileSet() -func getFile(filename string) (file *token.File) { - fset.Iterate(func(f *token.File) bool { - if f.Name() == filename { - file = f - return false // end iteration - } - return true - }) - return file -} - // Positioned errors are of the form filename:line:column: message . var posMsgRx = regexp.MustCompile(`^(.*:[0-9]+:[0-9]+): *(.*)`) @@ -120,6 +110,7 @@ var errRx = regexp.MustCompile(`^/\* *ERROR *"([^"]*)" *\*/$`) // in files and returns them as a map of error positions to error messages. // func errMap(t *testing.T, testname string, files []*ast.File) map[string][]string { + // map of position strings to lists of error message patterns errmap := make(map[string][]string) for _, file := range files { @@ -130,10 +121,7 @@ func errMap(t *testing.T, testname string, files []*ast.File) map[string][]strin } var s scanner.Scanner - // file was parsed already - do not add it again to the file - // set otherwise the position information returned here will - // not match the position information collected by the parser - s.Init(getFile(filename), src, nil, scanner.ScanComments) + s.Init(fset.AddFile(filename, fset.Base(), len(src)), src, nil, scanner.ScanComments) var prev string // position string of last non-comment, non-semicolon token scanFile: @@ -143,9 +131,8 @@ func errMap(t *testing.T, testname string, files []*ast.File) map[string][]strin case token.EOF: break scanFile case token.COMMENT: - s := errRx.FindStringSubmatch(lit) - if len(s) == 2 { - errmap[prev] = append(errmap[prev], string(s[1])) + if s := errRx.FindStringSubmatch(lit); len(s) == 2 { + errmap[prev] = append(errmap[prev], s[1]) } case token.SEMICOLON: // ignore automatically inserted semicolon @@ -164,17 +151,17 @@ func errMap(t *testing.T, testname string, files []*ast.File) map[string][]strin func eliminate(t *testing.T, errmap map[string][]string, errlist []error) { for _, err := range errlist { - pos, msg := splitError(err) + pos, gotMsg := splitError(err) list := errmap[pos] index := -1 // list index of matching message, if any // we expect one of the messages in list to match the error at pos - for i, msg := range list { - rx, err := regexp.Compile(msg) + for i, wantRx := range list { + rx, err := regexp.Compile(wantRx) if err != nil { t.Errorf("%s: %v", pos, err) continue } - if rx.MatchString(msg) { + if rx.MatchString(gotMsg) { index = i break } @@ -190,9 +177,8 @@ func eliminate(t *testing.T, errmap map[string][]string, errlist []error) { delete(errmap, pos) } } else { - t.Errorf("%s: no error expected: %q", pos, msg) + t.Errorf("%s: no error expected: %q", pos, gotMsg) } - } } @@ -213,10 +199,8 @@ func checkFiles(t *testing.T, testname string, testfiles []string) { return } - // match and eliminate errors + // match and eliminate errors; // we are expecting the following errors - // (collect these after parsing the files so that - // they are found in the file set) errmap := errMap(t, testname, files) eliminate(t, errmap, errlist) diff --git a/src/pkg/go/types/const.go b/src/pkg/go/types/const.go index e8e86e4fb8..7df9038bbe 100644 --- a/src/pkg/go/types/const.go +++ b/src/pkg/go/types/const.go @@ -182,24 +182,6 @@ func isZeroConst(x interface{}) bool { return ok && i == 0 } -// isNegConst reports whether the value of constant x is < 0. -// x must be a non-complex numeric value. -// -func isNegConst(x interface{}) bool { - switch x := x.(type) { - case nil: - return false - case int64: - return x < 0 - case *big.Int: - return x.Sign() < 0 - case *big.Rat: - return x.Sign() < 0 - } - unreachable() - return false -} - // isRepresentableConst reports whether the value of constant x can // be represented as a value of the basic type Typ[as] without loss // of precision. diff --git a/src/pkg/go/types/conversions.go b/src/pkg/go/types/conversions.go index ea629b961c..f75568e27c 100644 --- a/src/pkg/go/types/conversions.go +++ b/src/pkg/go/types/conversions.go @@ -36,7 +36,7 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota // common issue. if typ.Kind == String { switch { - case x.isInteger(check.ctxt): + case x.isInteger(): codepoint, ok := x.val.(int64) if !ok { // absolute value too large (or unknown) for conversion; @@ -60,6 +60,9 @@ func (check *checker) conversion(x *operand, conv *ast.CallExpr, typ Type, iota x.mode = value } + // the conversion argument types are final + check.updateExprType(x.expr, x.typ, true) + check.conversions[conv] = true // for cap/len checking x.expr = conv x.typ = typ diff --git a/src/pkg/go/types/expr.go b/src/pkg/go/types/expr.go index 3b0625239f..5ccd75f99b 100644 --- a/src/pkg/go/types/expr.go +++ b/src/pkg/go/types/expr.go @@ -24,6 +24,51 @@ import ( // - clients need access to builtins type information // - API tests are missing (e.g., identifiers should be handled as expressions in callbacks) +/* +Basic algorithm: + +Expressions are checked recursively, top down. Expression checker functions +are generally of the form: + + func f(x *operand, e *ast.Expr, ...) + +where e is the expression to be checked, and x is the result of the check. +The check performed by f may fail in which case x.mode == invalid, and +related error messages will have been issued by f. + +If a hint argument is present, it is the composite literal element type +of an outer composite literal; it is used to type-check composite literal +elements that have no explicit type specification in the source +(e.g.: []T{{...}, {...}}, the hint is the type T in this case). + +If an iota argument >= 0 is present, it is the value of iota for the +specific expression. + +All expressions are checked via rawExpr, which dispatches according +to expression kind. Upon returning, rawExpr is recording the types and +constant values for all expressions that have an untyped type (those types +may change on the way up in the expression tree). Usually these are constants, +but the results of comparisons or non-constant shifts of untyped constants +may also be untyped, but not constant. + +Untyped expressions may eventually become fully typed (i.e., not untyped), +typically when the value is assigned to a variable, or is used otherwise. +The updateExprType method is used to record this final type and update +the recorded types: the type-checked expression tree is again traversed down, +and the new type is propagated as needed. Untyped constant expression values +that become fully typed must now be representable by the full type (constant +sub-expression trees are left alone except for their roots). This mechanism +ensures that a client sees the actual (run-time) type an untyped value would +have. It also permits type-checking of lhs shift operands "as if the shift +were not present": when updateExprType visits an untyped lhs shift operand +and assigns it it's final type, that type must be an integer type, and a +constant lhs must be representable as an integer. + +When an expression gets its final type, either on the way out from rawExpr, +on the way down in updateExprType, or at the end of the type checker run, +if present the Context.Expr method is invoked to notify a go/types client. +*/ + func (check *checker) collectParams(list *ast.FieldList, variadicOk bool) (params []*Var, isVariadic bool) { if list == nil { return @@ -260,7 +305,7 @@ func (check *checker) isRepresentable(x *operand, typ *Basic) { if !isRepresentableConst(x.val, check.ctxt, typ.Kind) { var msg string if isNumeric(x.typ) && isNumeric(typ) { - msg = "%s overflows %s" + msg = "%s overflows (or cannot be accurately represented as) %s" } else { msg = "cannot convert %s to %s" } @@ -269,24 +314,30 @@ func (check *checker) isRepresentable(x *operand, typ *Basic) { } } -// updateExprType updates the type of all untyped nodes in the -// expression tree of x to typ. If shiftOp is set, x is the lhs -// of a shift expression. In that case, and if x is in the set -// of shift operands with delayed type checking, and typ is not -// an untyped type, updateExprType will check if typ is an -// integer type. -// If Context.Expr != nil, it is called for all nodes that are -// now assigned their final (not untyped) type. -func (check *checker) updateExprType(x ast.Expr, typ Type, shiftOp bool) { +// updateExprType updates the type of x to typ and invokes itself +// recursively for the operands of x, depending on expression kind. +// If typ is still an untyped and not the final type, updateExprType +// only updates the recorded untyped type for x and possibly its +// operands. Otherwise (i.e., typ is not an untyped type anymore, +// or it is the final type for x), Context.Expr is invoked, if present. +// Also, if x is a constant, it must be representable as a value of typ, +// and if x is the (formerly untyped) lhs operand of a non-constant +// shift, it must be an integer value. +// +func (check *checker) updateExprType(x ast.Expr, typ Type, final bool) { + old, found := check.untyped[x] + if !found { + return // nothing to do + } + + // update operands of x if necessary switch x := x.(type) { case *ast.BadExpr, *ast.FuncLit, *ast.CompositeLit, - *ast.SelectorExpr, *ast.IndexExpr, *ast.SliceExpr, *ast.TypeAssertExpr, - *ast.CallExpr, *ast.StarExpr, *ast.KeyValueExpr, *ast.ArrayType, @@ -295,58 +346,86 @@ func (check *checker) updateExprType(x ast.Expr, typ Type, shiftOp bool) { *ast.InterfaceType, *ast.MapType, *ast.ChanType: - // these expression are never untyped - nothing to do + // These expression are never untyped - nothing to do. + // The respective sub-expressions got their final types + // upon assignment or use. + if debug { + check.dump("%s: found old type(%s): %s (new: %s)", x.Pos(), x, old.typ, typ) + unreachable() + } return - case *ast.Ident, *ast.BasicLit: - // update type + case *ast.CallExpr: + // Resulting in an untyped constant (e.g., built-in complex). + // The respective calls take care of calling updateExprType + // for the arguments if necessary. + + case *ast.Ident, *ast.BasicLit, *ast.SelectorExpr: + // An identifier denoting a constant, a constant literal, + // or a qualified identifier (imported untyped constant). + // No operands to take care of. case *ast.ParenExpr: - check.updateExprType(x.X, typ, false) + check.updateExprType(x.X, typ, final) case *ast.UnaryExpr: - check.updateExprType(x.X, typ, false) + // If x is a constant, the operands were constants. + // They don't need to be updated since they never + // get "materialized" into a typed value; and they + // will be processed at the end of the type check. + if old.isConst { + break + } + check.updateExprType(x.X, typ, final) case *ast.BinaryExpr: + if old.isConst { + break // see comment for unary expressions + } if isComparison(x.Op) { - // result type is independent of operand types + // The result type is independent of operand types + // and the operand types must have final types. } else if isShift(x.Op) { - // result type depends only on lhs operand - check.updateExprType(x.X, typ, true) + // The result type depends only on lhs operand. + // The rhs type was updated when checking the shift. + check.updateExprType(x.X, typ, final) } else { - // operand types match result type - check.updateExprType(x.X, typ, false) - check.updateExprType(x.Y, typ, false) + // The operand types match the result type. + check.updateExprType(x.X, typ, final) + check.updateExprType(x.Y, typ, final) } - case *ast.Ellipsis: - unreachable() default: unreachable() } - // TODO(gri) t should always exist, shouldn't it? - if t := check.untyped[x]; t != nil { - if isUntyped(typ) { - check.untyped[x] = typ.(*Basic) - } else { - // notify clients of final type for x - if f := check.ctxt.Expr; f != nil { - f(x, typ, check.constants[x]) - } - delete(check.untyped, x) - delete(check.constants, x) - // check delayed shift - // Note: Using shiftOp is an optimization: it prevents - // map lookups when we know x is not a shiftOp in the - // first place. - if shiftOp && check.shiftOps[x] { - if !isInteger(typ) { - check.invalidOp(x.Pos(), "shifted operand %s (type %s) must be integer", x, typ) - } - delete(check.shiftOps, x) - } + // If the new type is not final and still untyped, just + // update the recorded type. + if !final && isUntyped(typ) { + old.typ = underlying(typ).(*Basic) + check.untyped[x] = old + return + } + + // Otherwise we have the final (typed or untyped type). + // Remove it from the map. + delete(check.untyped, x) + + // If x is the lhs of a shift, its final type must be integer. + // We already know from the shift check that it is representable + // as an integer if it is a constant. + if old.isLhs && !isInteger(typ) { + check.invalidOp(x.Pos(), "shifted operand %s (type %s) must be integer", x, typ) + return + } + + // Everything's fine, notify client of final type for x. + if f := check.ctxt.Expr; f != nil { + var val interface{} + if old.isConst { + val = old.val } + f(x, typ, val) } } @@ -419,7 +498,7 @@ func (check *checker) convertUntyped(x *operand, target Type) { } x.typ = target - check.updateExprType(x.expr, target, false) + check.updateExprType(x.expr, target, true) // UntypedNils are final return Error: @@ -456,18 +535,39 @@ func (check *checker) comparison(x, y *operand, op token.Token) { x.mode = value } + // The result type of a comparison is always boolean and + // independent of the argument types. They have now their + // final types (untyped or typed): update the respective + // expression trees. + check.updateExprType(x.expr, x.typ, true) + check.updateExprType(y.expr, y.typ, true) + x.typ = Typ[UntypedBool] } func (check *checker) shift(x, y *operand, op token.Token) { + untypedx := isUntyped(x.typ) + + // The lhs must be of integer type or be representable + // as an integer; otherwise the shift has no chance. + if !isInteger(x.typ) && (!untypedx || !isRepresentableConst(x.val, nil, UntypedInt)) { + check.invalidOp(x.pos(), "shifted operand %s must be integer", x) + x.mode = invalid + return + } + // spec: "The right operand in a shift expression must have unsigned // integer type or be an untyped constant that can be converted to // unsigned integer type." switch { case isInteger(y.typ) && isUnsigned(y.typ): // nothing to do - case y.mode == constant && isUntyped(y.typ): - check.convertUntyped(x, Typ[UntypedInt]) + case isUntyped(y.typ): + check.convertUntyped(y, Typ[UntypedInt]) + if y.mode == invalid { + x.mode = invalid + return + } default: check.invalidOp(y.pos(), "shift count %s must be unsigned integer", y) x.mode = invalid @@ -476,33 +576,28 @@ func (check *checker) shift(x, y *operand, op token.Token) { if x.mode == constant { if y.mode == constant { - // constant shift - lhs must be (representable as) an integer - if isUntyped(x.typ) { - if !isRepresentableConst(x.val, check.ctxt, UntypedInt) { - check.invalidOp(x.pos(), "shifted operand %s must be integer", x) + if untypedx { + x.typ = Typ[UntypedInt] + } + if x.val != nil && y.val != nil { + // rhs must be within reasonable bounds + const stupidShift = 1024 + s, ok := y.val.(int64) + if !ok || s < 0 || s >= stupidShift { + check.invalidOp(y.pos(), "%s: stupid shift", y) x.mode = invalid return } - x.typ = Typ[UntypedInt] + // everything's ok + x.val = shiftConst(x.val, uint(s), op) + } else { + x.val = nil } - assert(x.isInteger(check.ctxt)) - - // rhs must be within reasonable bounds - const stupidShift = 1024 - s, ok := y.val.(int64) - if !ok || s < 0 || s >= stupidShift { - check.invalidOp(y.pos(), "%s: stupid shift", y) - x.mode = invalid - return - } - - // everything's ok - x.val = shiftConst(x.val, uint(s), op) return } // non-constant shift with constant lhs - if isUntyped(x.typ) { + if untypedx { // spec: "If the left operand of a non-constant shift expression is // an untyped constant, the type of the constant is what it would be // if the shift expression were replaced by its left operand alone; @@ -510,8 +605,16 @@ func (check *checker) shift(x, y *operand, op token.Token) { // instance, if the shift expression is an operand in a comparison // against an untyped constant)". - // delay operand checking until we know the type - check.shiftOps[x.expr] = true + // Delay operand checking until we know the final type: + // The lhs expression must be in the untyped map, mark + // the entry as lhs shift operand. + if info, ok := check.untyped[x.expr]; ok { + info.isLhs = true + check.untyped[x.expr] = info + } else { + unreachable() + } + // keep x's type x.mode = value return } @@ -613,36 +716,41 @@ func (check *checker) binary(x *operand, lhs, rhs ast.Expr, op token.Token, iota // x.typ is unchanged } -// index checks an index expression for validity. If length >= 0, it is the upper -// bound for the index. The result is a valid index >= 0, or a negative value. -// -func (check *checker) index(index ast.Expr, length int64, iota int) int64 { +// index checks an index/size expression arg for validity. +// If length >= 0, it is the upper bound for arg. +// TODO(gri): Do we need iota? +func (check *checker) index(arg ast.Expr, length int64, iota int) (i int64, ok bool) { var x operand + check.expr(&x, arg, nil, iota) - check.expr(&x, index, nil, iota) - if !x.isInteger(check.ctxt) { - check.errorf(x.pos(), "index %s must be integer", &x) - return -1 - } - if x.mode != constant { - return -1 // we cannot check more - } - // The spec doesn't require int64 indices, but perhaps it should. - i, ok := x.val.(int64) - if !ok { - check.errorf(x.pos(), "stupid index %s", &x) - return -1 - } - if i < 0 { - check.errorf(x.pos(), "index %s must not be negative", &x) - return -1 - } - if length >= 0 && i >= length { - check.errorf(x.pos(), "index %s is out of bounds (>= %d)", &x, length) - return -1 + // an untyped constant must be representable as Int + check.convertUntyped(&x, Typ[Int]) + if x.mode == invalid { + return } - return i + // the index/size must be of integer type + if !isInteger(x.typ) { + check.invalidArg(x.pos(), "%s must be integer", &x) + return + } + + // a constant index/size i must be 0 <= i < length + if x.mode == constant && x.val != nil { + i = x.val.(int64) + if i < 0 { + check.invalidArg(x.pos(), "%s must not be negative", &x) + return + } + if length >= 0 && i >= length { + check.errorf(x.pos(), "index %s is out of bounds (>= %d)", &x, length) + return + } + // 0 <= i [ && i < length ] + return i, true + } + + return -1, true } // compositeLitKey resolves unresolved composite literal keys. @@ -673,8 +781,10 @@ func (check *checker) indexedElts(elts []ast.Expr, typ Type, length int64, iota eval := e if kv, _ := e.(*ast.KeyValueExpr); kv != nil { check.compositeLitKey(kv.Key) - if i := check.index(kv.Key, length, iota); i >= 0 { - index = i + if i, ok := check.index(kv.Key, length, iota); ok { + if i >= 0 { + index = i + } validIndex = true } eval = kv.Value @@ -756,6 +866,7 @@ func (check *checker) argument(sig *Signature, i int, arg ast.Expr, x *operand, var emptyResult Result func (check *checker) callExpr(x *operand) { + // convert x into a user-friendly set of values var typ Type var val interface{} switch x.mode { @@ -774,10 +885,7 @@ func (check *checker) callExpr(x *operand) { // until it becomes typed or until the end of // type checking if isUntyped(typ) { - check.untyped[x.expr] = typ.(*Basic) - if val != nil { - check.constants[x.expr] = val - } + check.untyped[x.expr] = exprInfo{x.mode == constant, false, typ.(*Basic), val} return } @@ -806,6 +914,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle defer check.untrace("=> %s", x) } + // record final type of x if untyped, notify clients of type otherwise defer check.callExpr(x) switch e := e.(type) { @@ -998,7 +1107,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle } continue } - if x.mode == constant { + if x.mode == constant && x.val != nil { if visited[x.val] { check.errorf(x.pos(), "duplicate key %s in map literal", x.val) continue @@ -1112,7 +1221,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *Basic: if isString(typ) { valid = true - if x.mode == constant { + if x.mode == constant && x.val != nil { length = int64(len(x.val.(string))) } // an indexed string always yields a byte value @@ -1183,7 +1292,7 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle case *Basic: if isString(typ) { valid = true - if x.mode == constant { + if x.mode == constant && x.val != nil { length = int64(len(x.val.(string))) + 1 // +1 for slice } // a sliced string always yields a string value @@ -1228,12 +1337,16 @@ func (check *checker) rawExpr(x *operand, e ast.Expr, hint Type, iota int, cycle lo := int64(0) if e.Low != nil { - lo = check.index(e.Low, length, iota) + if i, ok := check.index(e.Low, length, iota); ok && i >= 0 { + lo = i + } } hi := int64(-1) if e.High != nil { - hi = check.index(e.High, length, iota) + if i, ok := check.index(e.High, length, iota); ok && i >= 0 { + hi = i + } } else if length >= 0 { hi = length } diff --git a/src/pkg/go/types/operand.go b/src/pkg/go/types/operand.go index 0e4ee2506a..6f6e058a85 100644 --- a/src/pkg/go/types/operand.go +++ b/src/pkg/go/types/operand.go @@ -205,11 +205,10 @@ func (x *operand) isAssignable(ctxt *Context, T Type) bool { } // isInteger reports whether x is a (typed or untyped) integer value. -// TODO(gri) remove ctxt argument - it is not required for UntypedInt. -func (x *operand) isInteger(ctxt *Context) bool { +func (x *operand) isInteger() bool { return x.mode == invalid || isInteger(x.typ) || - x.mode == constant && isRepresentableConst(x.val, ctxt, UntypedInt) + x.mode == constant && isRepresentableConst(x.val, nil, UntypedInt) // no context required for UntypedInt } // lookupResult represents the result of a struct field/method lookup. diff --git a/src/pkg/go/types/stmt.go b/src/pkg/go/types/stmt.go index ae0d422527..198cc439a6 100644 --- a/src/pkg/go/types/stmt.go +++ b/src/pkg/go/types/stmt.go @@ -59,6 +59,8 @@ func (check *checker) assign1to1(lhs, rhs ast.Expr, x *operand, decl bool, iota if !decl { // anything can be assigned to the blank identifier if ident != nil && ident.Name == "_" { + // the rhs has its final type + check.updateExprType(rhs, x.typ, true) return } diff --git a/src/pkg/go/types/testdata/builtins.src b/src/pkg/go/types/testdata/builtins.src index 6fe4655089..e8b5da149b 100644 --- a/src/pkg/go/types/testdata/builtins.src +++ b/src/pkg/go/types/testdata/builtins.src @@ -54,6 +54,7 @@ func _complex() { var f32 float32 var f64 float64 var c64 complex64 + var c128 complex128 _ = complex /* ERROR "argument" */ () _ = complex /* ERROR "argument" */ (1) _ = complex(true /* ERROR "invalid argument" */ , 0) @@ -78,6 +79,21 @@ func _complex() { _ = complex(1, 1.1) _ = complex(1, 'a') complex /* ERROR "not used" */ (1, 2) + + var _ complex64 = complex(f32, f32) + var _ complex64 = complex /* ERROR "cannot initialize" */ (f64, f64) + + var _ complex128 = complex /* ERROR "cannot initialize" */ (f32, f32) + var _ complex128 = complex(f64, f64) + + // untyped constants + const _ int = complex(1, 0) + const _ float32 = complex(1, 0) + const _ complex64 = complex(1, 0) + const _ complex128 = complex(1, 0) + + const _ int = complex /* ERROR "int" */ (1.1, 0) + const _ float32 = complex /* ERROR "float32" */ (1, 2) } func _copy() { @@ -161,7 +177,9 @@ func _len() { } func _make() { - n := 0 + var n int + var m float32 + var s uint _ = make /* ERROR "argument" */ () _ = make(1 /* ERROR "not a type" */) @@ -172,32 +190,40 @@ func _make() { _ = make/* ERROR "arguments" */ ([]int, 2, 3, 4) _ = make([]int, int /* ERROR "not an expression" */) _ = make([]int, 10, float32 /* ERROR "not an expression" */) - _ = make([]int, "foo" /* ERROR "must be an integer" */) - _ = make([]int, 10, 2.3 /* ERROR "must be an integer" */) + _ = make([]int, "foo" /* ERROR "cannot convert" */) + _ = make([]int, 10, 2.3 /* ERROR "overflows" */) _ = make([]int, 5, 10.0) _ = make([]int, 0i) + _ = make([]int, 1.0) + _ = make([]int, 1.0<> s) - // TODO(gri) add more tests (systematically) -} - -func shifts4() { - // from src/pkg/compress/lzw/reader.go:90 - { - var d struct { - bits uint32 - width uint - } - _ = uint16(d.bits & (1<> s) +} + +func shifts4() { + // shifts in comparisons w/ untyped operands + var s uint + + _ = 1<