diff options
author | Rob Findley <rfindley@google.com> | 2020-10-19 11:51:17 -0400 |
---|---|---|
committer | Robert Findley <rfindley@google.com> | 2020-10-30 14:13:03 +0000 |
commit | 733c4af41d2e89d18cfb039e0107336a4c1ec220 (patch) | |
tree | dd690132c110447b802de7b1b08c89635df192ca /src/go/types/expr.go | |
parent | f7e26467b4e7ee0bb3219c26e71292ff4aac7da9 (diff) | |
download | go-733c4af41d2e89d18cfb039e0107336a4c1ec220.tar.gz go-733c4af41d2e89d18cfb039e0107336a4c1ec220.zip |
go/types: add internal error codes
Tools using go/types sometimes need to implement special handling for
certain errors produced by the type-checker. They can offer suggested
fixes, expand the error position to surrounding syntax, highlight
related syntax (for example in the case of a declaration cycle), link to
additional documentation, group errors by category, or correlate errors
with signals produced by other static analysis tools.
All these require a notion of error identity. Tools need to be able to
reliably determine the nature of an error without re-implementing type
checking logic or parsing error messages. This CL is a first-pass at
adding such an identifier to types.Error: a (for the moment unexported)
field containing one of many declared errorCode constants.
A wide variety of error code constants are defined, and assigned to type
checker errors according to their 'functional equivalence', meaning that
they should be ideally be stable under refactoring.
With few exceptions, each error code is documented with an example that
produces it. This is enforced by tests.
When error codes are exported they will represent quite a large API
surface. For this reason, as well as the likelihood that error codes
will change at the start, both the code field and the codes themselves
are initially unexported. gopls will read these fields using reflection
during this experimental phase. Others can obviously do the same,
provided they accept the lack of forward compatibility.
For #42290
Change-Id: I15e3c2bffd2046c20297b1857057d421f633098a
Reviewed-on: https://go-review.googlesource.com/c/go/+/264179
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
Trust: Robert Griesemer <gri@golang.org>
Trust: Robert Findley <rfindley@google.com>
Diffstat (limited to 'src/go/types/expr.go')
-rw-r--r-- | src/go/types/expr.go | 127 |
1 files changed, 73 insertions, 54 deletions
diff --git a/src/go/types/expr.go b/src/go/types/expr.go index 94d98f0fbb..5f3415a59d 100644 --- a/src/go/types/expr.go +++ b/src/go/types/expr.go @@ -68,7 +68,7 @@ var unaryOpPredicates = opPredicates{ func (check *Checker) op(m opPredicates, x *operand, op token.Token) bool { if pred := m[op]; pred != nil { if !pred(x.typ) { - check.invalidOp(x.pos(), "operator %s not defined for %s", op, x) + check.invalidOp(x.pos(), _UndefinedOp, "operator %s not defined for %s", op, x) return false } } else { @@ -85,7 +85,7 @@ func (check *Checker) unary(x *operand, e *ast.UnaryExpr, op token.Token) { // spec: "As an exception to the addressability // requirement x may also be a composite literal." if _, ok := unparen(x.expr).(*ast.CompositeLit); !ok && x.mode != variable { - check.invalidOp(x.pos(), "cannot take address of %s", x) + check.invalidOp(x.pos(), _UnaddressableOperand, "cannot take address of %s", x) x.mode = invalid return } @@ -96,12 +96,12 @@ func (check *Checker) unary(x *operand, e *ast.UnaryExpr, op token.Token) { case token.ARROW: typ, ok := x.typ.Underlying().(*Chan) if !ok { - check.invalidOp(x.pos(), "cannot receive from non-channel %s", x) + check.invalidOp(x.pos(), _InvalidReceive, "cannot receive from non-channel %s", x) x.mode = invalid return } if typ.dir == SendOnly { - check.invalidOp(x.pos(), "cannot receive from send-only channel %s", x) + check.invalidOp(x.pos(), _InvalidReceive, "cannot receive from send-only channel %s", x) x.mode = invalid return } @@ -342,6 +342,7 @@ func (check *Checker) isRepresentable(x *operand, typ *Basic) error { assert(x.mode == constant_) if !representableConst(x.val, check, typ, &x.val) { var msg string + var code errorCode if isNumeric(x.typ) && isNumeric(typ) { // numeric conversion : error msg // @@ -352,13 +353,16 @@ func (check *Checker) isRepresentable(x *operand, typ *Basic) error { // if !isInteger(x.typ) && isInteger(typ) { msg = "%s truncated to %s" + code = _TruncatedFloat } else { msg = "%s overflows %s" + code = _NumericOverflow } } else { msg = "cannot convert %s to %s" + code = _InvalidConstVal } - return check.newErrorf(x.pos(), msg, x, typ) + return check.newErrorf(x.pos(), code, msg, x, typ) } return nil } @@ -466,7 +470,7 @@ func (check *Checker) updateExprType(x ast.Expr, typ Type, final bool) { // We already know from the shift check that it is representable // as an integer if it is a constant. if !isInteger(typ) { - check.invalidOp(x.Pos(), "shifted operand %s (type %s) must be integer", x, typ) + check.invalidOp(x.Pos(), _InvalidShiftOperand, "shifted operand %s (type %s) must be integer", x, typ) return } // Even if we have an integer, if the value is a constant we @@ -517,7 +521,7 @@ func (check *Checker) canConvertUntyped(x *operand, target Type) error { check.updateExprType(x.expr, target, false) } } else if xkind != tkind { - return check.newErrorf(x.pos(), "cannot convert %s to %s", x, target) + return check.newErrorf(x.pos(), _InvalidUntypedConversion, "cannot convert %s to %s", x, target) } return nil } @@ -531,7 +535,7 @@ func (check *Checker) canConvertUntyped(x *operand, target Type) error { } else { newTarget := check.implicitType(x, target) if newTarget == nil { - return check.newErrorf(x.pos(), "cannot convert %s to %s", x, target) + return check.newErrorf(x.pos(), _InvalidUntypedConversion, "cannot convert %s to %s", x, target) } target = newTarget } @@ -608,7 +612,10 @@ func (check *Checker) comparison(x, y *operand, op token.Token) { // spec: "In any comparison, the first operand must be assignable // to the type of the second operand, or vice versa." err := "" - if x.assignableTo(check, y.typ, nil) || y.assignableTo(check, x.typ, nil) { + var code errorCode + xok, _ := x.assignableTo(check, y.typ, nil) + yok, _ := y.assignableTo(check, x.typ, nil) + if xok || yok { defined := false switch op { case token.EQL, token.NEQ: @@ -626,13 +633,15 @@ func (check *Checker) comparison(x, y *operand, op token.Token) { typ = y.typ } err = check.sprintf("operator %s not defined for %s", op, typ) + code = _UndefinedOp } } else { err = check.sprintf("mismatched types %s and %s", x.typ, y.typ) + code = _MismatchedTypes } if err != "" { - check.errorf(x.pos(), "cannot compare %s %s %s (%s)", x.expr, op, y.expr, err) + check.errorf(x.pos(), code, "cannot compare %s %s %s (%s)", x.expr, op, y.expr, err) x.mode = invalid return } @@ -669,7 +678,7 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { // as an integer. Nothing to do. } else { // shift has no chance - check.invalidOp(x.pos(), "shifted operand %s must be integer", x) + check.invalidOp(x.pos(), _InvalidShiftOperand, "shifted operand %s must be integer", x) x.mode = invalid return } @@ -686,7 +695,7 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { return } default: - check.invalidOp(y.pos(), "shift count %s must be integer", y) + check.invalidOp(y.pos(), _InvalidShiftCount, "shift count %s must be integer", y) x.mode = invalid return } @@ -699,7 +708,7 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { yval = constant.ToInt(y.val) assert(yval.Kind() == constant.Int) if constant.Sign(yval) < 0 { - check.invalidOp(y.pos(), "negative shift count %s", y) + check.invalidOp(y.pos(), _InvalidShiftCount, "negative shift count %s", y) x.mode = invalid return } @@ -711,7 +720,7 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { const shiftBound = 1023 - 1 + 52 // so we can express smallestFloat64 s, ok := constant.Uint64Val(yval) if !ok || s > shiftBound { - check.invalidOp(y.pos(), "invalid shift count %s", y) + check.invalidOp(y.pos(), _InvalidShiftCount, "invalid shift count %s", y) x.mode = invalid return } @@ -768,7 +777,7 @@ func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) { // non-constant shift - lhs must be an integer if !isInteger(x.typ) { - check.invalidOp(x.pos(), "shifted operand %s must be integer", x) + check.invalidOp(x.pos(), _InvalidShiftOperand, "shifted operand %s must be integer", x) x.mode = invalid return } @@ -832,7 +841,7 @@ func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, o // only report an error if we have valid types // (otherwise we had an error reported elsewhere already) if x.typ != Typ[Invalid] && y.typ != Typ[Invalid] { - check.invalidOp(x.pos(), "mismatched types %s and %s", x.typ, y.typ) + check.invalidOp(x.pos(), _MismatchedTypes, "mismatched types %s and %s", x.typ, y.typ) } x.mode = invalid return @@ -846,7 +855,7 @@ func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, o if op == token.QUO || op == token.REM { // check for zero divisor if (x.mode == constant_ || isInteger(x.typ)) && y.mode == constant_ && constant.Sign(y.val) == 0 { - check.invalidOp(y.pos(), "division by zero") + check.invalidOp(y.pos(), _DivByZero, "division by zero") x.mode = invalid return } @@ -856,7 +865,7 @@ func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, o re, im := constant.Real(y.val), constant.Imag(y.val) re2, im2 := constant.BinaryOp(re, token.MUL, re), constant.BinaryOp(im, token.MUL, im) if constant.Sign(re2) == 0 && constant.Sign(im2) == 0 { - check.invalidOp(y.pos(), "division by zero") + check.invalidOp(y.pos(), _DivByZero, "division by zero") x.mode = invalid return } @@ -909,7 +918,7 @@ func (check *Checker) index(index ast.Expr, max int64) (typ Type, val int64) { // the index must be of integer type if !isInteger(x.typ) { - check.invalidArg(x.pos(), "index %s must be integer", &x) + check.invalidArg(x.pos(), _InvalidIndex, "index %s must be integer", &x) return } @@ -919,13 +928,13 @@ func (check *Checker) index(index ast.Expr, max int64) (typ Type, val int64) { // a constant index i must be in bounds if constant.Sign(x.val) < 0 { - check.invalidArg(x.pos(), "index %s must not be negative", &x) + check.invalidArg(x.pos(), _InvalidIndex, "index %s must not be negative", &x) return } v, valid := constant.Int64Val(constant.ToInt(x.val)) if !valid || max >= 0 && v >= max { - check.errorf(x.pos(), "index %s is out of bounds", &x) + check.errorf(x.pos(), _InvalidIndex, "index %s is out of bounds", &x) return } @@ -951,12 +960,12 @@ func (check *Checker) indexedElts(elts []ast.Expr, typ Type, length int64) int64 index = i validIndex = true } else { - check.errorf(e.Pos(), "index %s must be integer constant", kv.Key) + check.errorf(e.Pos(), _InvalidLitIndex, "index %s must be integer constant", kv.Key) } } eval = kv.Value } else if length >= 0 && index >= length { - check.errorf(e.Pos(), "index %d is out of bounds (>= %d)", index, length) + check.errorf(e.Pos(), _OversizeArrayLit, "index %d is out of bounds (>= %d)", index, length) } else { validIndex = true } @@ -964,7 +973,7 @@ func (check *Checker) indexedElts(elts []ast.Expr, typ Type, length int64) int64 // if we have a valid index, check for duplicate entries if validIndex { if visited[index] { - check.errorf(e.Pos(), "duplicate index %d in array or slice literal", index) + check.errorf(e.Pos(), _DuplicateLitKey, "duplicate index %d in array or slice literal", index) } visited[index] = true } @@ -1054,7 +1063,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { case *ast.Ellipsis: // ellipses are handled explicitly where they are legal // (array composite literals and parameter lists) - check.error(e.Pos(), "invalid use of '...'") + check.error(e.Pos(), _BadDotDotDotSyntax, "invalid use of '...'") goto Error case *ast.BasicLit: @@ -1113,7 +1122,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { default: // TODO(gri) provide better error messages depending on context - check.error(e.Pos(), "missing type in composite literal") + check.error(e.Pos(), _UntypedLit, "missing type in composite literal") goto Error } @@ -1129,7 +1138,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { for _, e := range e.Elts { kv, _ := e.(*ast.KeyValueExpr) if kv == nil { - check.error(e.Pos(), "mixture of field:value and value elements in struct literal") + check.error(e.Pos(), _MixedStructLit, "mixture of field:value and value elements in struct literal") continue } key, _ := kv.Key.(*ast.Ident) @@ -1137,12 +1146,12 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { // so we don't drop information on the floor check.expr(x, kv.Value) if key == nil { - check.errorf(kv.Pos(), "invalid field name %s in struct literal", kv.Key) + check.errorf(kv.Pos(), _InvalidLitField, "invalid field name %s in struct literal", kv.Key) continue } i := fieldIndex(utyp.fields, check.pkg, key.Name) if i < 0 { - check.errorf(kv.Pos(), "unknown field %s in struct literal", key.Name) + check.errorf(kv.Pos(), _MissingLitField, "unknown field %s in struct literal", key.Name) continue } fld := fields[i] @@ -1151,7 +1160,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { check.assignment(x, etyp, "struct literal") // 0 <= i < len(fields) if visited[i] { - check.errorf(kv.Pos(), "duplicate field name %s in struct literal", key.Name) + check.errorf(kv.Pos(), _DuplicateLitField, "duplicate field name %s in struct literal", key.Name) continue } visited[i] = true @@ -1160,25 +1169,25 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { // no element must have a key for i, e := range e.Elts { if kv, _ := e.(*ast.KeyValueExpr); kv != nil { - check.error(kv.Pos(), "mixture of field:value and value elements in struct literal") + check.error(kv.Pos(), _MixedStructLit, "mixture of field:value and value elements in struct literal") continue } check.expr(x, e) if i >= len(fields) { - check.error(x.pos(), "too many values in struct literal") + check.error(x.pos(), _InvalidStructLit, "too many values in struct literal") break // cannot continue } // i < len(fields) fld := fields[i] if !fld.Exported() && fld.pkg != check.pkg { - check.errorf(x.pos(), "implicit assignment to unexported field %s in %s literal", fld.name, typ) + check.errorf(x.pos(), _UnexportedLitField, "implicit assignment to unexported field %s in %s literal", fld.name, typ) continue } etyp := fld.typ check.assignment(x, etyp, "struct literal") } if len(e.Elts) < len(fields) { - check.error(e.Rbrace, "too few values in struct literal") + check.error(e.Rbrace, _InvalidStructLit, "too few values in struct literal") // ok to continue } } @@ -1188,7 +1197,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { // This is a stop-gap solution. Should use Checker.objPath to report entire // path starting with earliest declaration in the source. TODO(gri) fix this. if utyp.elem == nil { - check.error(e.Pos(), "illegal cycle in type declaration") + check.error(e.Pos(), _InvalidTypeCycle, "illegal cycle in type declaration") goto Error } n := check.indexedElts(e.Elts, utyp.elem, utyp.len) @@ -1215,7 +1224,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { // Prevent crash if the slice referred to is not yet set up. // See analogous comment for *Array. if utyp.elem == nil { - check.error(e.Pos(), "illegal cycle in type declaration") + check.error(e.Pos(), _InvalidTypeCycle, "illegal cycle in type declaration") goto Error } check.indexedElts(e.Elts, utyp.elem, -1) @@ -1224,14 +1233,14 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { // Prevent crash if the map referred to is not yet set up. // See analogous comment for *Array. if utyp.key == nil || utyp.elem == nil { - check.error(e.Pos(), "illegal cycle in type declaration") + check.error(e.Pos(), _InvalidTypeCycle, "illegal cycle in type declaration") goto Error } visited := make(map[interface{}][]Type, len(e.Elts)) for _, e := range e.Elts { kv, _ := e.(*ast.KeyValueExpr) if kv == nil { - check.error(e.Pos(), "missing key in map literal") + check.error(e.Pos(), _MissingLitKey, "missing key in map literal") continue } check.exprWithHint(x, kv.Key, utyp.key) @@ -1256,7 +1265,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { visited[xkey] = nil } if duplicate { - check.errorf(x.pos(), "duplicate key %s in map literal", x.val) + check.errorf(x.pos(), _DuplicateLitKey, "duplicate key %s in map literal", x.val) continue } } @@ -1278,7 +1287,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { } // if utyp is invalid, an error was reported before if utyp != Typ[Invalid] { - check.errorf(e.Pos(), "invalid composite literal type %s", typ) + check.errorf(e.Pos(), _InvalidLit, "invalid composite literal type %s", typ) goto Error } } @@ -1352,7 +1361,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { } if !valid { - check.invalidOp(x.pos(), "cannot index %s", x) + check.invalidOp(x.pos(), _NonIndexableOperand, "cannot index %s", x) goto Error } @@ -1377,7 +1386,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { case *Basic: if isString(typ) { if e.Slice3 { - check.invalidOp(x.pos(), "3-index slice of string") + check.invalidOp(x.pos(), _InvalidSliceExpr, "3-index slice of string") goto Error } valid = true @@ -1395,7 +1404,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { valid = true length = typ.len if x.mode != variable { - check.invalidOp(x.pos(), "cannot slice %s (value not addressable)", x) + check.invalidOp(x.pos(), _NonSliceableOperand, "cannot slice %s (value not addressable)", x) goto Error } x.typ = &Slice{elem: typ.elem} @@ -1413,7 +1422,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { } if !valid { - check.invalidOp(x.pos(), "cannot slice %s", x) + check.invalidOp(x.pos(), _NonSliceableOperand, "cannot slice %s", x) goto Error } @@ -1421,7 +1430,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { // spec: "Only the first index may be omitted; it defaults to 0." if e.Slice3 && (e.High == nil || e.Max == nil) { - check.error(e.Rbrack, "2nd and 3rd index required in 3-index slice") + check.invalidAST(e.Rbrack, "2nd and 3rd index required in 3-index slice") goto Error } @@ -1458,7 +1467,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { if x > 0 { for _, y := range ind[i+1:] { if y >= 0 && x > y { - check.errorf(e.Rbrack, "invalid slice indices: %d > %d", x, y) + check.errorf(e.Rbrack, _SwappedSliceIndices, "swapped slice indices: %d > %d", x, y) break L // only report one error, ok to continue } } @@ -1472,12 +1481,14 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { } xtyp, _ := x.typ.Underlying().(*Interface) if xtyp == nil { - check.invalidOp(x.pos(), "%s is not an interface", x) + check.invalidOp(x.pos(), _InvalidAssert, "%s is not an interface", x) goto Error } // x.(type) expressions are handled explicitly in type switches if e.Type == nil { - check.invalidAST(e.Pos(), "use of .(type) outside type switch") + // Don't use invalidAST because this can occur in the AST produced by + // go/parser. + check.error(e.Pos(), _BadTypeKeyword, "use of .(type) outside type switch") goto Error } T := check.typ(e.Type) @@ -1503,7 +1514,7 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind { x.mode = variable x.typ = typ.base } else { - check.invalidOp(x.pos(), "cannot indirect %s", x) + check.invalidOp(x.pos(), _InvalidIndirection, "cannot indirect %s", x) goto Error } } @@ -1597,7 +1608,7 @@ func (check *Checker) typeAssertion(pos token.Pos, x *operand, xtyp *Interface, } else { msg = "missing method " + method.name } - check.errorf(pos, "%s cannot have dynamic type %s (%s)", x, T, msg) + check.errorf(pos, _ImpossibleAssert, "%s cannot have dynamic type %s (%s)", x, T, msg) } func (check *Checker) singleValue(x *operand) { @@ -1605,7 +1616,7 @@ func (check *Checker) singleValue(x *operand) { // tuple types are never named - no need for underlying type below if t, ok := x.typ.(*Tuple); ok { assert(t.Len() != 1) - check.errorf(x.pos(), "%d-valued %s where single value is expected", t.Len(), x) + check.errorf(x.pos(), _TooManyValues, "%d-valued %s where single value is expected", t.Len(), x) x.mode = invalid } } @@ -1624,17 +1635,21 @@ func (check *Checker) expr(x *operand, e ast.Expr) { func (check *Checker) multiExpr(x *operand, e ast.Expr) { check.rawExpr(x, e, nil) var msg string + var code errorCode switch x.mode { default: return case novalue: msg = "%s used as value" + code = _TooManyValues case builtin: msg = "%s must be called" + code = _UncalledBuiltin case typexpr: msg = "%s is not an expression" + code = _NotAnExpr } - check.errorf(x.pos(), msg, x) + check.errorf(x.pos(), code, msg, x) x.mode = invalid } @@ -1647,17 +1662,21 @@ func (check *Checker) exprWithHint(x *operand, e ast.Expr, hint Type) { check.rawExpr(x, e, hint) check.singleValue(x) var msg string + var code errorCode switch x.mode { default: return case novalue: msg = "%s used as value" + code = _TooManyValues case builtin: msg = "%s must be called" + code = _UncalledBuiltin case typexpr: msg = "%s is not an expression" + code = _NotAnExpr } - check.errorf(x.pos(), msg, x) + check.errorf(x.pos(), code, msg, x) x.mode = invalid } @@ -1668,7 +1687,7 @@ func (check *Checker) exprOrType(x *operand, e ast.Expr) { check.rawExpr(x, e, nil) check.singleValue(x) if x.mode == novalue { - check.errorf(x.pos(), "%s used as value or type", x) + check.errorf(x.pos(), _NotAnExpr, "%s used as value or type", x) x.mode = invalid } } |