go.tools/go/types: support for "soft" errors

Provide an extra field, "Soft" in types.Error.
A client may want to filter out soft errors and
only abort if there are "hard" errors.

Qualified a first set of errors as "soft".

Fixes golang/go#7360.

LGTM=adonovan
R=adonovan
CC=golang-codereviews
https://golang.org/cl/71800043
This commit is contained in:
Robert Griesemer 2014-03-05 15:19:38 -08:00
parent 5d3ec72759
commit d14a9d31e6
6 changed files with 41 additions and 31 deletions

View File

@ -46,12 +46,15 @@ func Check(path string, fset *token.FileSet, files []*ast.File) (*Package, error
return pkg, nil return pkg, nil
} }
// An Error describes a type-checking error; // An Error describes a type-checking error; it implements the error interface.
// it implements the error interface. // A "soft" error is an error that still permits a valid interpretation of a
// package (such as "unused variable"); "hard" errors may lead to unpredictable
// behavior if ignored.
type Error struct { type Error struct {
Fset *token.FileSet // file set for interpretation of Pos Fset *token.FileSet // file set for interpretation of Pos
Pos token.Pos // error position Pos token.Pos // error position
Msg string // error message Msg string // error message
Soft bool // if set, error is "soft"
} }
// Error returns an error string formatted as follows: // Error returns an error string formatted as follows:

View File

@ -54,8 +54,8 @@ func (check *checker) dump(format string, args ...interface{}) {
fmt.Println(check.sprintf(format, args...)) fmt.Println(check.sprintf(format, args...))
} }
func (check *checker) err(pos token.Pos, msg string) { func (check *checker) err(pos token.Pos, msg string, soft bool) {
err := Error{check.fset, pos, msg} err := Error{check.fset, pos, msg, soft}
if check.firstErr == nil { if check.firstErr == nil {
check.firstErr = err check.firstErr = err
} }
@ -66,8 +66,16 @@ func (check *checker) err(pos token.Pos, msg string) {
f(err) f(err)
} }
func (check *checker) error(pos token.Pos, msg string) {
check.err(pos, msg, false)
}
func (check *checker) errorf(pos token.Pos, format string, args ...interface{}) { func (check *checker) errorf(pos token.Pos, format string, args ...interface{}) {
check.err(pos, check.sprintf(format, args...)) check.err(pos, check.sprintf(format, args...), false)
}
func (check *checker) softErrorf(pos token.Pos, format string, args ...interface{}) {
check.err(pos, check.sprintf(format, args...), true)
} }
func (check *checker) invalidAST(pos token.Pos, format string, args ...interface{}) { func (check *checker) invalidAST(pos token.Pos, format string, args ...interface{}) {

View File

@ -961,7 +961,7 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
case *ast.Ellipsis: case *ast.Ellipsis:
// ellipses are handled explicitly where they are legal // ellipses are handled explicitly where they are legal
// (array composite literals and parameter lists) // (array composite literals and parameter lists)
check.errorf(e.Pos(), "invalid use of '...'") check.error(e.Pos(), "invalid use of '...'")
goto Error goto Error
case *ast.BasicLit: case *ast.BasicLit:
@ -1005,7 +1005,7 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
} }
} }
if typ == nil { if typ == nil {
check.errorf(e.Pos(), "missing type in composite literal") check.error(e.Pos(), "missing type in composite literal")
goto Error goto Error
} }
@ -1021,7 +1021,7 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
for _, e := range e.Elts { for _, e := range e.Elts {
kv, _ := e.(*ast.KeyValueExpr) kv, _ := e.(*ast.KeyValueExpr)
if kv == nil { if kv == nil {
check.errorf(e.Pos(), "mixture of field:value and value elements in struct literal") check.error(e.Pos(), "mixture of field:value and value elements in struct literal")
continue continue
} }
key, _ := kv.Key.(*ast.Ident) key, _ := kv.Key.(*ast.Ident)
@ -1055,12 +1055,12 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
// no element must have a key // no element must have a key
for i, e := range e.Elts { for i, e := range e.Elts {
if kv, _ := e.(*ast.KeyValueExpr); kv != nil { if kv, _ := e.(*ast.KeyValueExpr); kv != nil {
check.errorf(kv.Pos(), "mixture of field:value and value elements in struct literal") check.error(kv.Pos(), "mixture of field:value and value elements in struct literal")
continue continue
} }
check.expr(x, e) check.expr(x, e)
if i >= len(fields) { if i >= len(fields) {
check.errorf(x.pos(), "too many values in struct literal") check.error(x.pos(), "too many values in struct literal")
break // cannot continue break // cannot continue
} }
// i < len(fields) // i < len(fields)
@ -1073,7 +1073,7 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
} }
} }
if len(e.Elts) < len(fields) { if len(e.Elts) < len(fields) {
check.errorf(e.Rbrace, "too few values in struct literal") check.error(e.Rbrace, "too few values in struct literal")
// ok to continue // ok to continue
} }
} }
@ -1093,7 +1093,7 @@ func (check *checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
for _, e := range e.Elts { for _, e := range e.Elts {
kv, _ := e.(*ast.KeyValueExpr) kv, _ := e.(*ast.KeyValueExpr)
if kv == nil { if kv == nil {
check.errorf(e.Pos(), "missing key in map literal") check.error(e.Pos(), "missing key in map literal")
continue continue
} }
check.expr(x, kv.Key) check.expr(x, kv.Key)
@ -1263,7 +1263,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." // spec: "Only the first index may be omitted; it defaults to 0."
if slice3(e) && (e.High == nil || sliceMax(e) == nil) { if slice3(e) && (e.High == nil || sliceMax(e) == nil) {
check.errorf(e.Rbrack, "2nd and 3rd index required in 3-index slice") check.error(e.Rbrack, "2nd and 3rd index required in 3-index slice")
goto Error goto Error
} }

View File

@ -35,7 +35,7 @@ func (check *checker) labels(body *ast.BlockStmt) {
// spec: "It is illegal to define a label that is never used." // spec: "It is illegal to define a label that is never used."
for _, obj := range all.elems { for _, obj := range all.elems {
if lbl := obj.(*Label); !lbl.used { if lbl := obj.(*Label); !lbl.used {
check.errorf(lbl.pos, "label %s declared but not used", lbl.name) check.softErrorf(lbl.pos, "label %s declared but not used", lbl.name)
} }
} }
} }

View File

@ -311,8 +311,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
check.recordDef(d.Name, obj) check.recordDef(d.Name, obj)
// init functions must have a body // init functions must have a body
if d.Body == nil { if d.Body == nil {
check.errorf(obj.pos, "missing function body") check.softErrorf(obj.pos, "missing function body")
// ok to continue
} }
} else { } else {
check.declare(pkg.scope, d.Name, obj) check.declare(pkg.scope, d.Name, obj)
@ -414,7 +413,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
// Unused "blank imports" are automatically ignored // Unused "blank imports" are automatically ignored
// since _ identifiers are not entered into scopes. // since _ identifiers are not entered into scopes.
if !obj.used { if !obj.used {
check.errorf(obj.pos, "%q imported but not used", obj.pkg.path) check.softErrorf(obj.pos, "%q imported but not used", obj.pkg.path)
} }
default: default:
// All other objects in the file scope must be dot- // All other objects in the file scope must be dot-
@ -432,7 +431,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
// check if the corresponding package was used. // check if the corresponding package was used.
for pkg, pos := range dotImports[i] { for pkg, pos := range dotImports[i] {
if !usedDotImports[pkg] { if !usedDotImports[pkg] {
check.errorf(pos, "%q imported but not used", pkg.path) check.softErrorf(pos, "%q imported but not used", pkg.path)
} }
} }
} }

View File

@ -43,7 +43,7 @@ func (check *checker) funcBody(decl *declInfo, name string, sig *Signature, body
} }
if sig.results.Len() > 0 && !check.isTerminating(body, "") { if sig.results.Len() > 0 && !check.isTerminating(body, "") {
check.errorf(body.Rbrace, "missing return") check.error(body.Rbrace, "missing return")
} }
// spec: "Implementation restriction: A compiler may make it illegal to // spec: "Implementation restriction: A compiler may make it illegal to
@ -56,7 +56,7 @@ func (check *checker) funcBody(decl *declInfo, name string, sig *Signature, body
func (check *checker) usage(scope *Scope) { func (check *checker) usage(scope *Scope) {
for _, obj := range scope.elems { for _, obj := range scope.elems {
if v, _ := obj.(*Var); v != nil && !v.used { if v, _ := obj.(*Var); v != nil && !v.used {
check.errorf(v.pos, "%s declared but not used", v.name) check.softErrorf(v.pos, "%s declared but not used", v.name)
} }
} }
for _, scope := range scope.children { for _, scope := range scope.children {
@ -186,7 +186,7 @@ L:
for t, pos := range seen { for t, pos := range seen {
if T == nil && t == nil || T != nil && t != nil && Identical(T, t) { if T == nil && t == nil || T != nil && t != nil && Identical(T, t) {
// talk about "case" rather than "type" because of nil case // talk about "case" rather than "type" because of nil case
check.errorf(e.Pos(), "duplicate case in type switch") check.error(e.Pos(), "duplicate case in type switch")
check.errorf(pos, "\tprevious case %s", T) // secondary error, \t indented check.errorf(pos, "\tprevious case %s", T) // secondary error, \t indented
continue L continue L
} }
@ -340,7 +340,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
check.initVars(res.vars, s.Results, s.Return) check.initVars(res.vars, s.Results, s.Return)
} }
} else if len(s.Results) > 0 { } else if len(s.Results) > 0 {
check.errorf(s.Results[0].Pos(), "no result values expected") check.error(s.Results[0].Pos(), "no result values expected")
} }
case *ast.BranchStmt: case *ast.BranchStmt:
@ -351,15 +351,15 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
switch s.Tok { switch s.Tok {
case token.BREAK: case token.BREAK:
if ctxt&inBreakable == 0 { if ctxt&inBreakable == 0 {
check.errorf(s.Pos(), "break not in for, switch, or select statement") check.error(s.Pos(), "break not in for, switch, or select statement")
} }
case token.CONTINUE: case token.CONTINUE:
if ctxt&inContinuable == 0 { if ctxt&inContinuable == 0 {
check.errorf(s.Pos(), "continue not in for statement") check.error(s.Pos(), "continue not in for statement")
} }
case token.FALLTHROUGH: case token.FALLTHROUGH:
if ctxt&fallthroughOk == 0 { if ctxt&fallthroughOk == 0 {
check.errorf(s.Pos(), "fallthrough statement out of place") check.error(s.Pos(), "fallthrough statement out of place")
} }
default: default:
check.invalidAST(s.Pos(), "branch statement: %s", s.Tok) check.invalidAST(s.Pos(), "branch statement: %s", s.Tok)
@ -379,7 +379,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
var x operand var x operand
check.expr(&x, s.Cond) check.expr(&x, s.Cond)
if x.mode != invalid && !isBoolean(x.typ) { if x.mode != invalid && !isBoolean(x.typ) {
check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") check.error(s.Cond.Pos(), "non-boolean condition in if statement")
} }
check.stmt(inner, s.Body) check.stmt(inner, s.Body)
if s.Else != nil { if s.Else != nil {
@ -526,7 +526,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
v.used = true // avoid usage error when checking entire function v.used = true // avoid usage error when checking entire function
} }
if !used { if !used {
check.errorf(lhs.Pos(), "%s declared but not used", lhs.Name) check.softErrorf(lhs.Pos(), "%s declared but not used", lhs.Name)
} }
} }
@ -563,7 +563,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
} }
if !valid { if !valid {
check.errorf(clause.Comm.Pos(), "select case must be send or receive (possibly with assignment)") check.error(clause.Comm.Pos(), "select case must be send or receive (possibly with assignment)")
continue continue
} }
@ -585,7 +585,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
var x operand var x operand
check.expr(&x, s.Cond) check.expr(&x, s.Cond)
if x.mode != invalid && !isBoolean(x.typ) { if x.mode != invalid && !isBoolean(x.typ) {
check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") check.error(s.Cond.Pos(), "non-boolean condition in for statement")
} }
} }
check.initStmt(s.Post) check.initStmt(s.Post)
@ -703,7 +703,7 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
check.declare(check.scope, nil, obj) // recordObject already called check.declare(check.scope, nil, obj) // recordObject already called
} }
} else { } else {
check.errorf(s.TokPos, "no new variables on left side of :=") check.error(s.TokPos, "no new variables on left side of :=")
} }
} else { } else {
// ordinary assignment // ordinary assignment
@ -721,6 +721,6 @@ func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) {
check.stmt(inner, s.Body) check.stmt(inner, s.Body)
default: default:
check.errorf(s.Pos(), "invalid statement") check.error(s.Pos(), "invalid statement")
} }
} }