printAST = flag.Bool("ast", false, "print AST")
)
-var exitCode = 0
+var errorCount int
func usage() {
fmt.Fprintf(os.Stderr, "usage: gotype [flags] [path ...]\n")
func report(err error) {
scanner.PrintError(os.Stderr, err)
- exitCode = 2
+ if list, ok := err.(scanner.ErrorList); ok {
+ errorCount += len(list)
+ return
+ }
+ errorCount++
}
// parse returns the AST for the Go source src.
}
func processPackage(fset *token.FileSet, files []*ast.File) {
- _, err := types.Check(fset, files)
- if err != nil {
- report(err)
+ type bailout struct{}
+ ctxt := types.Context{
+ Error: func(err error) {
+ if !*allErrors && errorCount >= 10 {
+ panic(bailout{})
+ }
+ report(err)
+ },
}
+
+ defer func() {
+ switch err := recover().(type) {
+ case nil, bailout:
+ default:
+ panic(err)
+ }
+ }()
+
+ ctxt.Check(fset, files)
}
func main() {
processFiles(flag.Args(), true)
}
- os.Exit(exitCode)
+ if errorCount > 0 {
+ os.Exit(2)
+ }
}
)
func runTest(t *testing.T, path string) {
- exitCode = 0
+ errorCount = 0
*recursive = false
if suffix := ".go"; strings.HasSuffix(path, suffix) {
processFiles(files, true)
}
- if exitCode != 0 {
- t.Errorf("processing %s failed: exitCode = %d", path, exitCode)
+ if errorCount > 0 {
+ t.Errorf("processing %s failed: %d errors", path, errorCount)
}
}
type Importer func(imports map[string]*Package, path string) (pkg *Package, err error)
// Check resolves and typechecks a set of package files within the given
-// context. If there are no errors, Check returns the package, otherwise
-// it returns the first error. If the context's Error handler is nil,
-// Check terminates as soon as the first error is encountered.
+// context. It returns the package and the first error encountered, if
+// any. If the context's Error handler is nil, Check terminates as soon
+// as the first error is encountered; otherwise it continues until the
+// entire package is checked. If there are errors, the package may be
+// only partially type-checked, and the resulting package may be incomplete
+// (missing objects, imports, etc.).
func (ctxt *Context) Check(fset *token.FileSet, files []*ast.File) (*Package, error) {
return check(ctxt, fset, files)
}
obj.Type = Typ[Invalid]
return
}
- spec := obj.decl.(*ast.ValueSpec)
- obj.visited = true
- check.valueSpec(spec.Pos(), obj, spec.Names, spec, 0)
+ 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]
+ default:
+ unreachable() // see also function newObj
+ }
case *TypeName:
if obj.Type != nil {
err = check.firsterr
default:
// unexpected panic: don't crash clients
- panic(p) // enable for debugging
+ const debug = true
+ if debug {
+ check.dump("INTERNAL PANIC: %v", p)
+ panic(p)
+ }
// TODO(gri) add a test case for this scenario
err = fmt.Errorf("types internal error: %v", p)
}
for i, arg := range args {
switch a := arg.(type) {
case token.Pos:
- args[i] = check.fset.Position(a)
+ args[i] = check.fset.Position(a).String()
case ast.Expr:
args[i] = exprString(a)
case Type:
return &TypeName{Pkg: pkg, Name: name, Type: typ, spec: astObj.Decl.(*ast.TypeSpec)}
case ast.Var:
switch astObj.Decl.(type) {
- case *ast.Field, *ast.ValueSpec, *ast.AssignStmt: // these are ok
+ case *ast.Field: // function parameters
+ case *ast.ValueSpec: // proper variable declarations
+ case *ast.AssignStmt: // short variable declarations
default:
- unreachable()
+ unreachable() // everything else is not ok
}
return &Var{Pkg: pkg, Name: name, Type: typ, decl: astObj.Decl}
case ast.Fun:
var x operand
check.expr(&x, rhs[0], nil, iota)
if x.mode == invalid {
+ // If decl is set, this leaves the lhs identifiers
+ // untyped. We catch this when looking up the respective
+ // object.
return
}