func (p *parser) parseCallOrConversion(fun ast.Expr) *ast.CallExpr { if p.trace { defer un(trace(p, "CallOrConversion")) } lparen := p.expect(token.LPAREN) p.exprLev++ var list []ast.Expr var ellipsis token.Pos for p.tok != token.RPAREN && p.tok != token.EOF && !ellipsis.IsValid() { list = append(list, p.parseExpr()) if p.tok == token.ELLIPSIS { ellipsis = p.pos p.next() } if p.tok != token.COMMA { break } p.next() } p.exprLev-- rparen := p.expect(token.RPAREN) return &ast.CallExpr{fun, lparen, list, ellipsis, rparen} }
func debugCall(fName string, pos token.Pos, args ...string) []byte { vals := make(map[string]string) if len(args) > 0 { vals["args"] = strings.Join(args, ", ") } else { vals["args"] = "" } if timing { vals["timing"] = "true" } vals["fname"] = fName if pos.IsValid() { vals["position"] = fset.Position(pos).String() } if showReturn { vals["return"] = "true" } var b bytes.Buffer err := funcTemplate.Execute(&b, vals) if err != nil { log.Fatal(err) } return b.Bytes() }
// If returnPos is valid, initVars is called to type-check the assignment of // return expressions, and returnPos is the position of the return statement. func (check *checker) initVars(lhs []*Var, rhs []ast.Expr, returnPos token.Pos) { l := len(lhs) get, r, commaOk := unpack(func(x *operand, i int) { check.expr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid()) if l != r { // invalidate lhs for _, obj := range lhs { if obj.Type == nil { obj.Type = Typ[Invalid] } } if returnPos.IsValid() { check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r) return } check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) return } var x operand if commaOk { var a [2]Type for i := range a { get(&x, i) a[i] = check.initVar(lhs[i], &x) } check.recordCommaOkTypes(rhs[0], a) return } for i, lhs := range lhs { get(&x, i) check.initVar(lhs, &x) } }
func (check *checker) declareObj(scope, altScope *Scope, obj Object, dotImport token.Pos) { alt := scope.Insert(obj) if alt == nil && altScope != nil { // see if there is a conflicting declaration in altScope alt = altScope.Lookup(obj.Name()) } if alt != nil { prevDecl := "" // for dot-imports, local declarations are declared first - swap messages if dotImport.IsValid() { if pos := alt.Pos(); pos.IsValid() { check.errorf(pos, fmt.Sprintf("%s redeclared in this block by dot-import at %s", obj.Name(), check.fset.Position(dotImport))) return } // get by w/o other position check.errorf(dotImport, fmt.Sprintf("dot-import redeclares %s", obj.Name())) return } if pos := alt.Pos(); pos.IsValid() { prevDecl = fmt.Sprintf("\n\tother declaration at %s", check.fset.Position(pos)) } check.errorf(obj.Pos(), fmt.Sprintf("%s redeclared in this block%s", obj.Name(), prevDecl)) } }
func newPosLink_urlFunc(srcPosLinkFunc func(s string, line, low, high int) string) func(info *PageInfo, n interface{}) string { // n must be an ast.Node or a *doc.Note return func(info *PageInfo, n interface{}) string { var pos, end token.Pos switch n := n.(type) { case ast.Node: pos = n.Pos() end = n.End() case *doc.Note: pos = n.Pos end = n.End default: panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n)) } var relpath string var line int var low, high int // selection offset range if pos.IsValid() { p := info.FSet.Position(pos) relpath = p.Filename line = p.Line low = p.Offset } if end.IsValid() { high = info.FSet.Position(end).Offset } return srcPosLinkFunc(relpath, line, low, high) } }
// distanceFrom returns the column difference between from and p.pos (the current // estimated position) if both are on the same line; if they are on different lines // (or unknown) the result is infinity. func (p *printer) distanceFrom(from token.Pos) int { if from.IsValid() && p.pos.IsValid() { if f := p.posFor(from); f.Line == p.pos.Line { return p.pos.Column - f.Column } } return infinity }
func error_(pos token.Pos, msg string, args ...interface{}) { nerrors++ if pos.IsValid() { fmt.Fprintf(os.Stderr, "%s: ", fset.Position(pos).String()) } fmt.Fprintf(os.Stderr, msg, args...) fmt.Fprintf(os.Stderr, "\n") }
func warn(pos token.Pos, msg string, args ...interface{}) { if pos.IsValid() { msg = "%s: " + msg arg1 := []interface{}{fset.Position(pos).String()} args = append(arg1, args...) } fmt.Fprintf(os.Stderr, msg+"\n", args...) }
// LookupParent follows the parent chain of scopes starting with s until // it finds a scope where Lookup(name) returns a non-nil object, and then // returns that scope and object. If a valid position pos is provided, // only objects that were declared at or before pos are considered. // If no such scope and object exists, the result is (nil, nil). // // Note that obj.Parent() may be different from the returned scope if the // object was inserted into the scope and already had a parent at that // time (see Insert, below). This can only happen for dot-imported objects // whose scope is the scope of the package that exported them. func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object) { for ; s != nil; s = s.parent { if obj := s.elems[name]; obj != nil && (!pos.IsValid() || obj.scopePos() <= pos) { return s, obj } } return nil, nil }
// SetLocation sets the current debug location. func (d *DIBuilder) SetLocation(b llvm.Builder, pos token.Pos) { if !pos.IsValid() { return } position := d.fset.Position(pos) d.lb = llvm.Metadata{} if position.Filename != d.fnFile && position.Filename != "" { // This can happen rarely, e.g. in init functions. diFile := d.builder.CreateFile(d.remapFilePath(position.Filename), "") d.lb = d.builder.CreateLexicalBlockFile(d.scope(), diFile, 0) } b.SetCurrentDebugLocation(uint(position.Line), uint(position.Column), d.scope(), llvm.Metadata{}) }
// Eval returns the type and, if constant, the value for the // expression expr, evaluated at position pos of package pkg, // which must have been derived from type-checking an AST with // complete position information relative to the provided file // set. // // If the expression contains function literals, their bodies // are ignored (i.e., the bodies are not type-checked). // // If pkg == nil, the Universe scope is used and the provided // position pos is ignored. If pkg != nil, and pos is invalid, // the package scope is used. Otherwise, pos must belong to the // package. // // An error is returned if pos is not within the package or // if the node cannot be evaluated. // // Note: Eval should not be used instead of running Check to compute // types and values, but in addition to Check. Eval will re-evaluate // its argument each time, and it also does not know about the context // in which an expression is used (e.g., an assignment). Thus, top- // level untyped constants will return an untyped type rather then the // respective context-specific type. // func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (tv TypeAndValue, err error) { // determine scope var scope *Scope if pkg == nil { scope = Universe pos = token.NoPos } else if !pos.IsValid() { scope = pkg.scope } else { // The package scope extent (position information) may be // incorrect (files spread accross a wide range of fset // positions) - ignore it and just consider its children // (file scopes). for _, fscope := range pkg.scope.children { if scope = fscope.Innermost(pos); scope != nil { break } } if scope == nil || debug { s := scope for s != nil && s != pkg.scope { s = s.parent } // s == nil || s == pkg.scope if s == nil { return TypeAndValue{}, fmt.Errorf("no position %s found in package %s", fset.Position(pos), pkg.name) } } } // parse expressions // BUG(gri) In case of type-checking errors below, the type checker // doesn't have the correct file set for expr. The correct // solution requires a ParseExpr that uses the incoming // file set fset. node, err := parser.ParseExpr(expr) if err != nil { return TypeAndValue{}, err } // initialize checker check := NewChecker(nil, fset, pkg, nil) check.scope = scope check.pos = pos defer check.handleBailout(&err) // evaluate node var x operand check.rawExpr(&x, node, nil) return TypeAndValue{x.mode, x.typ, x.val}, err }
// n must be an ast.Node or a *doc.Note func posLink_urlFunc(info *PageInfo, n interface{}) string { var pos, end token.Pos switch n := n.(type) { case ast.Node: pos = n.Pos() end = n.End() case *doc.Note: pos = n.Pos end = n.End default: panic(fmt.Sprintf("wrong type for posLink_url template formatter: %T", n)) } var relpath string var line int var low, high int // selection offset range if pos.IsValid() { p := info.FSet.Position(pos) relpath = p.Filename line = p.Line low = p.Offset } if end.IsValid() { high = info.FSet.Position(end).Offset } var buf bytes.Buffer template.HTMLEscape(&buf, []byte(relpath)) // selection ranges are of form "s=low:high" if low < high { fmt.Fprintf(&buf, "?s=%d:%d", low, high) // no need for URL escaping // if we have a selection, position the page // such that the selection is a bit below the top line -= 10 if line < 1 { line = 1 } } // line id's in html-printed source are of the // form "L%d" where %d stands for the line number if line > 0 { fmt.Fprintf(&buf, "#L%d", line) // no need for URL escaping } return buf.String() }
// MakePosHash keeps track of references put into the code for later extraction in a runtime debug function. // It returns the PosHash integer to be used for exception handling that was passed in. func MakePosHash(pos token.Pos) PosHash { if pos.IsValid() { fname := rootProgram.Fset.Position(pos).Filename for f := range PosHashFileList { if PosHashFileList[f].FileName == fname { LatestValidPosHash = PosHash(PosHashFileList[f].BasePosHash + rootProgram.Fset.Position(pos).Line) return LatestValidPosHash } } panic(fmt.Errorf("pogo.MakePosHash() Cant find file: %s", fname)) } else { if LatestValidPosHash == NoPosHash { return NoPosHash } return -LatestValidPosHash // -ve value => nearby reference } }
// argument checks passing of argument x to the i'th parameter of the given signature. // If ellipsis is valid, the argument is followed by ... at that position in the call. func (check *Checker) argument(sig *Signature, i int, x *operand, ellipsis token.Pos) { n := sig.params.Len() // determine parameter type var typ Type switch { case i < n: typ = sig.params.vars[i].typ case sig.variadic: typ = sig.params.vars[n-1].typ if debug { if _, ok := typ.(*Slice); !ok { check.dump("%s: expected unnamed slice type, got %s", sig.params.vars[n-1].Pos(), typ) } } default: check.errorf(x.pos(), "too many arguments") return } if ellipsis.IsValid() { // argument is of the form x... if i != n-1 { check.errorf(ellipsis, "can only use ... with matching parameter") return } switch t := x.typ.Underlying().(type) { case *Slice: // ok case *Tuple: check.errorf(ellipsis, "cannot use ... with %d-valued expression %s", t.Len(), x) return default: check.errorf(x.pos(), "cannot use %s as parameter of type %s", x, typ) return } } else if sig.variadic && i >= n-1 { // use the variadic parameter slice's element type typ = typ.(*Slice).elem } if !check.assignment(x, typ) && x.mode != invalid { check.errorf(x.pos(), "cannot pass argument %s to parameter of type %s", x, typ) } }
// SetLocation sets the current debug location. func (d *DIBuilder) SetLocation(b llvm.Builder, pos token.Pos) { if !pos.IsValid() { return } position := d.fset.Position(pos) d.lb = llvm.Value{nil} if position.Filename != d.fnFile && position.Filename != "" { // This can happen rarely, e.g. in init functions. diFile := d.builder.CreateFile(d.remapFilePath(position.Filename), "") d.lb = d.builder.CreateLexicalBlockFile(d.scope(), diFile, 0) } b.SetCurrentDebugLocation(llvm.MDNode([]llvm.Value{ llvm.ConstInt(llvm.Int32Type(), uint64(position.Line), false), llvm.ConstInt(llvm.Int32Type(), uint64(position.Column), false), d.scope(), llvm.Value{}, })) }
// If returnPos is valid, initVars is called to type-check the assignment of // return expressions, and returnPos is the position of the return statement. func (check *Checker) initVars(lhs []*Var, rhs []ast.Expr, returnPos token.Pos) { l := len(lhs) get, r, commaOk := unpack(func(x *operand, i int) { check.multiExpr(x, rhs[i]) }, len(rhs), l == 2 && !returnPos.IsValid()) if get == nil || l != r { // invalidate lhs and use rhs for _, obj := range lhs { if obj.typ == nil { obj.typ = Typ[Invalid] } } if get == nil { return // error reported by unpack } check.useGetter(get, r) if returnPos.IsValid() { check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r) return } check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) return } context := "assignment" if returnPos.IsValid() { context = "return statement" } var x operand if commaOk { var a [2]Type for i := range a { get(&x, i) a[i] = check.initVar(lhs[i], &x, context) } check.recordCommaOkTypes(rhs[0], a) return } for i, lhs := range lhs { get(&x, i) check.initVar(lhs, &x, context) } }
// argument checks passing of argument x to the i'th parameter of the given signature. // If ellipsis is valid, the argument is followed by ... at that position in the call. func (check *Checker) argument(fun ast.Expr, sig *Signature, i int, x *operand, ellipsis token.Pos) { check.singleValue(x) if x.mode == invalid { return } n := sig.params.Len() // determine parameter type var typ Type switch { case i < n: typ = sig.params.vars[i].typ case sig.variadic: typ = sig.params.vars[n-1].typ if debug { if _, ok := typ.(*Slice); !ok { check.dump("%s: expected unnamed slice type, got %s", sig.params.vars[n-1].Pos(), typ) } } default: check.errorf(x.pos(), "too many arguments") return } if ellipsis.IsValid() { // argument is of the form x... and x is single-valued if i != n-1 { check.errorf(ellipsis, "can only use ... with matching parameter") return } if _, ok := x.typ.Underlying().(*Slice); !ok { check.errorf(x.pos(), "cannot use %s as parameter of type %s", x, typ) return } } else if sig.variadic && i >= n-1 { // use the variadic parameter slice's element type typ = typ.(*Slice).elem } check.assignment(x, typ, check.sprintf("argument to %s", fun)) }
// If returnPos is valid, initVars is called to type-check the assignment of // return expressions, and returnPos is the position of the return statement. func (check *checker) initVars(lhs []*Var, rhs []ast.Expr, returnPos token.Pos) { l := len(lhs) r := len(rhs) assert(l > 0) // If the lhs and rhs have corresponding expressions, // treat each matching pair as an individual pair. if l == r { var x operand for i, e := range rhs { check.expr(&x, e) check.initVar(lhs[i], &x) } return } // Otherwise, the rhs must be a single expression (possibly // a function call returning multiple values, or a comma-ok // expression). if r == 1 { // l > 1 // Start with rhs so we have expression types // for declarations with implicit types. var x operand rhs := rhs[0] check.expr(&x, rhs) if x.mode == invalid { invalidateVars(lhs) return } if t, ok := x.typ.(*Tuple); ok { // function result r = t.Len() if l == r { for i, lhs := range lhs { x.mode = value x.expr = rhs x.typ = t.At(i).typ check.initVar(lhs, &x) } return } } if !returnPos.IsValid() && x.mode == valueok && l == 2 { // comma-ok expression (not permitted with return statements) x.mode = value t1 := check.initVar(lhs[0], &x) x.mode = value x.expr = rhs x.typ = Typ[UntypedBool] t2 := check.initVar(lhs[1], &x) if t1 != nil && t2 != nil { check.recordCommaOkTypes(rhs, t1, t2) } return } } invalidateVars(lhs) // lhs variables may be function result parameters (return statement); // use rhs position for properly located error messages if returnPos.IsValid() { check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r) return } check.errorf(rhs[0].Pos(), "assignment count mismatch (%d vs %d)", l, r) }
// stmt typechecks statement s. func (check *checker) stmt(ctxt stmtContext, s ast.Stmt) { // statements cannot use iota in general // (constant declarations set it explicitly) assert(check.iota == nil) // statements must end with the same top scope as they started with if debug { defer func(scope *Scope) { // don't check if code is panicking if p := recover(); p != nil { panic(p) } assert(scope == check.topScope) }(check.topScope) } inner := ctxt &^ fallthroughOk switch s := s.(type) { case *ast.BadStmt, *ast.EmptyStmt: // ignore case *ast.DeclStmt: check.declStmt(s.Decl) case *ast.LabeledStmt: check.hasLabel = true check.stmt(ctxt, s.Stmt) case *ast.ExprStmt: // spec: "With the exception of specific built-in functions, // function and method calls and receive operations can appear // in statement context. Such statements may be parenthesized." var x operand kind := check.rawExpr(&x, s.X, nil) var msg string switch x.mode { default: if kind == statement { return } msg = "is not used" case builtin: msg = "must be called" case typexpr: msg = "is not an expression" } check.errorf(x.pos(), "%s %s", &x, msg) case *ast.SendStmt: var ch, x operand check.expr(&ch, s.Chan) check.expr(&x, s.Value) if ch.mode == invalid || x.mode == invalid { return } if tch, ok := ch.typ.Underlying().(*Chan); !ok || tch.dir&ast.SEND == 0 || !check.assignment(&x, tch.elt) { if x.mode != invalid { check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) } } case *ast.IncDecStmt: var op token.Token switch s.Tok { case token.INC: op = token.ADD case token.DEC: op = token.SUB default: check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) return } var x operand Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position check.binary(&x, s.X, Y, op, nil) if x.mode == invalid { return } check.assignVar(s.X, &x) case *ast.AssignStmt: switch s.Tok { case token.ASSIGN, token.DEFINE: if len(s.Lhs) == 0 { check.invalidAST(s.Pos(), "missing lhs in assignment") return } if s.Tok == token.DEFINE { check.shortVarDecl(s.Lhs, s.Rhs) } else { // regular assignment check.assignVars(s.Lhs, s.Rhs) } default: // assignment operations if len(s.Lhs) != 1 || len(s.Rhs) != 1 { check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) return } op := assignOp(s.Tok) if op == token.ILLEGAL { check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) return } var x operand check.binary(&x, s.Lhs[0], s.Rhs[0], op, nil) if x.mode == invalid { return } check.assignVar(s.Lhs[0], &x) } case *ast.GoStmt: check.suspendedCall("go", s.Call) case *ast.DeferStmt: check.suspendedCall("defer", s.Call) case *ast.ReturnStmt: sig := check.funcSig if n := sig.results.Len(); n > 0 { // determine if the function has named results named := false lhs := make([]*Var, n) for i, res := range sig.results.vars { if res.name != "" { // a blank (_) result parameter is a named result named = true } lhs[i] = res } if len(s.Results) > 0 || !named { check.initVars(lhs, s.Results, s.Return) return } } else if len(s.Results) > 0 { check.errorf(s.Pos(), "no result values expected") } case *ast.BranchStmt: if s.Label != nil { check.hasLabel = true return // checked in 2nd pass (check.labels) } switch s.Tok { case token.BREAK: if ctxt&inBreakable == 0 { check.errorf(s.Pos(), "break not in for, switch, or select statement") } case token.CONTINUE: if ctxt&inContinuable == 0 { check.errorf(s.Pos(), "continue not in for statement") } case token.FALLTHROUGH: if ctxt&fallthroughOk == 0 { check.errorf(s.Pos(), "fallthrough statement out of place") } default: check.invalidAST(s.Pos(), "branch statement: %s", s.Tok) } case *ast.BlockStmt: check.openScope(s) check.stmtList(inner, s.List) check.closeScope() case *ast.IfStmt: check.openScope(s) check.initStmt(s.Init) var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") } check.stmt(inner, s.Body) if s.Else != nil { check.stmt(inner, s.Else) } check.closeScope() case *ast.SwitchStmt: inner |= inBreakable check.openScope(s) check.initStmt(s.Init) var x operand tag := s.Tag if tag == nil { // use fake true tag value and position it at the opening { of the switch ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} check.recordObject(ident, Universe.Lookup("true")) tag = ident } check.expr(&x, tag) check.multipleDefaults(s.Body.List) // TODO(gri) check also correct use of fallthrough seen := make(map[interface{}]token.Pos) for i, c := range s.Body.List { clause, _ := c.(*ast.CaseClause) if clause == nil { continue // error reported before } if x.mode != invalid { for _, expr := range clause.List { x := x // copy of x (don't modify original) var y operand check.expr(&y, expr) if y.mode == invalid { continue // error reported before } // If we have a constant case value, it must appear only // once in the switch statement. Determine if there is a // duplicate entry, but only report an error if there are // no other errors. var dupl token.Pos var yy operand if y.mode == constant { // TODO(gri) This code doesn't work correctly for // large integer, floating point, or // complex values - the respective struct // comparisons are shallow. Need to use a // hash function to index the map. dupl = seen[y.val] seen[y.val] = y.pos() yy = y // remember y } // TODO(gri) The convertUntyped call pair below appears in other places. Factor! // Order matters: By comparing y against x, error positions are at the case values. check.convertUntyped(&y, x.typ) if y.mode == invalid { continue // error reported before } check.convertUntyped(&x, y.typ) if x.mode == invalid { continue // error reported before } check.comparison(&y, &x, token.EQL) if y.mode != invalid && dupl.IsValid() { check.errorf(yy.pos(), "%s is duplicate case (previous at %s)", &yy, check.fset.Position(dupl)) } } } check.openScope(clause) inner := inner if i+1 < len(s.Body.List) { inner |= fallthroughOk } check.stmtList(inner, clause.Body) check.closeScope() } check.closeScope() case *ast.TypeSwitchStmt: inner |= inBreakable check.openScope(s) defer check.closeScope() check.initStmt(s.Init) // A type switch guard must be of the form: // // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . // // The parser is checking syntactic correctness; // remaining syntactic errors are considered AST errors here. // TODO(gri) better factoring of error handling (invalid ASTs) // var lhs *ast.Ident // lhs identifier or nil var rhs ast.Expr switch guard := s.Assign.(type) { case *ast.ExprStmt: rhs = guard.X case *ast.AssignStmt: if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } lhs, _ = guard.Lhs[0].(*ast.Ident) if lhs == nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } check.recordObject(lhs, nil) // lhs variable is implicitly declared in each cause clause rhs = guard.Rhs[0] default: check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } // rhs must be of the form: expr.(type) and expr must be an interface expr, _ := rhs.(*ast.TypeAssertExpr) if expr == nil || expr.Type != nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } var x operand check.expr(&x, expr.X) if x.mode == invalid { return } xtyp, _ := x.typ.Underlying().(*Interface) if xtyp == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } check.multipleDefaults(s.Body.List) var lhsVars []*Var // set of implicitly declared lhs variables for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { continue // error reported before } // Check each type in this type switch case. var T Type for _, expr := range clause.List { T = check.typOrNil(expr) if T != nil && T != Typ[Invalid] { check.typeAssertion(expr.Pos(), &x, xtyp, T) } } check.openScope(clause) // If lhs exists, declare a corresponding variable in the case-local scope if necessary. if lhs != nil { // spec: "The TypeSwitchGuard may include a short variable declaration. // When that form is used, the variable is declared at the beginning of // the implicit block in each clause. In clauses with a case listing // exactly one type, the variable has that type; otherwise, the variable // has the type of the expression in the TypeSwitchGuard." if len(clause.List) != 1 || T == nil { T = x.typ } obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) // For the "declared but not used" error, all lhs variables act as // one; i.e., if any one of them is 'used', all of them are 'used'. // Collect them for later analysis. lhsVars = append(lhsVars, obj) check.declareObj(check.topScope, nil, obj) check.recordImplicit(clause, obj) } check.stmtList(inner, clause.Body) check.closeScope() } // If a lhs variable was declared but there were no case clauses, make sure // we have at least one (dummy) 'unused' variable to force an error message. if len(lhsVars) == 0 && lhs != nil { lhsVars = []*Var{NewVar(lhs.Pos(), check.pkg, lhs.Name, x.typ)} } // Record lhs variables for this type switch, if any. if len(lhsVars) > 0 { check.lhsVarsList = append(check.lhsVarsList, lhsVars) } case *ast.SelectStmt: inner |= inBreakable check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CommClause) if clause == nil { continue // error reported before } check.openScope(clause) if s := clause.Comm; s != nil { check.stmt(inner, s) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt) } check.stmtList(inner, clause.Body) check.closeScope() } case *ast.ForStmt: inner |= inBreakable | inContinuable check.openScope(s) check.initStmt(s.Init) if s.Cond != nil { var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") } } check.initStmt(s.Post) check.stmt(inner, s.Body) check.closeScope() case *ast.RangeStmt: inner |= inBreakable | inContinuable check.openScope(s) defer check.closeScope() // check expression to iterate over decl := s.Tok == token.DEFINE var x operand check.expr(&x, s.X) if x.mode == invalid { // if we don't have a declaration, we can still check the loop's body // (otherwise we can't because we are missing the declared variables) if !decl { check.stmt(inner, s.Body) } return } // determine key/value types var key, val Type switch typ := x.typ.Underlying().(type) { case *Basic: if isString(typ) { key = Typ[Int] val = Typ[Rune] } case *Array: key = Typ[Int] val = typ.elt case *Slice: key = Typ[Int] val = typ.elt case *Pointer: if typ, _ := typ.base.Underlying().(*Array); typ != nil { key = Typ[Int] val = typ.elt } case *Map: key = typ.key val = typ.elt case *Chan: key = typ.elt val = Typ[Invalid] if typ.dir&ast.RECV == 0 { check.errorf(x.pos(), "cannot range over send-only channel %s", &x) // ok to continue } if s.Value != nil { check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) // ok to continue } } if key == nil { check.errorf(x.pos(), "cannot range over %s", &x) // if we don't have a declaration, we can still check the loop's body if !decl { check.stmt(inner, s.Body) } return } // check assignment to/declaration of iteration variables // (irregular assignment, cannot easily map to existing assignment checks) if s.Key == nil { check.invalidAST(s.Pos(), "range clause requires index iteration variable") // ok to continue } // lhs expressions and initialization value (rhs) types lhs := [2]ast.Expr{s.Key, s.Value} rhs := [2]Type{key, val} if decl { // declaration; variable scope starts after the range clause var idents []*ast.Ident var vars []*Var for i, lhs := range lhs { if lhs == nil { continue } // determine lhs variable name := "_" // dummy, in case lhs is not an identifier ident, _ := lhs.(*ast.Ident) if ident != nil { name = ident.Name } else { check.errorf(lhs.Pos(), "cannot declare %s", lhs) } idents = append(idents, ident) obj := NewVar(lhs.Pos(), check.pkg, name, nil) vars = append(vars, obj) // initialize lhs variable x.mode = value x.expr = lhs // we don't have a better rhs expression to use here x.typ = rhs[i] check.initVar(obj, &x) } // declare variables for i, ident := range idents { check.declareObj(check.topScope, ident, vars[i]) } } else { // ordinary assignment for i, lhs := range lhs { if lhs == nil { continue } x.mode = value x.expr = lhs // we don't have a better rhs expression to use here x.typ = rhs[i] check.assignVar(lhs, &x) } } check.stmt(inner, s.Body) default: check.errorf(s.Pos(), "invalid statement") } }
func (ctxt *context) getErrorInfo(v ssa.Value, member int, enclosingPos token.Pos, seen map[ssa.Value]bool) (result *errorInfo) { if !enclosingPos.IsValid() { panicf("getErrorInfo with invalid pos; %T %s", v, v) } // log.Printf("getErrorInfo[%d] %T %v {", member, v, v) // defer func() { // log.Printf("} -> %+v", result) // }() if seen[v] { return &errorInfo{} } seen[v] = true defer delete(seen, v) if pos := v.Pos(); pos.IsValid() { enclosingPos = pos } terminate := func() []errorTermination { return []errorTermination{{ val: v, pos: enclosingPos, }} } switch v := v.(type) { case *ssa.Call: if member > 0 && member != v.Type().(*types.Tuple).Len()-1 { log.Printf("error from non-final member of function") return &errorInfo{unknown: terminate()} } return &errorInfo{nested: []*ssa.Call{v}} case *ssa.ChangeInterface: return ctxt.getErrorInfo(v.X, 0, enclosingPos, seen) case *ssa.Extract: return ctxt.getErrorInfo(v.Tuple, v.Index, enclosingPos, seen) case *ssa.Field: return &errorInfo{unknown: terminate()} case *ssa.Index: return &errorInfo{unknown: terminate()} case *ssa.Lookup: return &errorInfo{unknown: terminate()} case *ssa.Const: if v.Value != nil { panicf("non-nil constant cannot make error, surely?") } return &errorInfo{} case *ssa.MakeInterface: // TODO look into components of v.X return &errorInfo{nonNil: terminate()} case *ssa.Next: return &errorInfo{unknown: terminate()} case *ssa.Parameter: return &errorInfo{unknown: terminate()} case *ssa.Phi: var info errorInfo for _, edge := range v.Edges { info.add(ctxt.getErrorInfo(edge, member, enclosingPos, seen)) } return &info case *ssa.Select: return &errorInfo{unknown: terminate()} case *ssa.TypeAssert: if v.CommaOk { return &errorInfo{unknown: terminate()} } return ctxt.getErrorInfo(v.X, 0, enclosingPos, seen) case *ssa.UnOp: switch v.Op { case token.ARROW: return &errorInfo{unknown: terminate()} case token.MUL: if _, isGlobal := v.X.(*ssa.Global); isGlobal { // Assume that if we're returning a global variable, it's a // global non-nil error, such as os.ErrInvalid. return &errorInfo{nonNil: terminate()} } return &errorInfo{unknown: terminate()} default: panicf("unexpected unary operator %s at %s", v, ctxt.lprog.Fset.Position(enclosingPos)) } } panicf("unexpected value found for error: %T; %v", v, v) panic("not reached") }
// stmt typechecks statement s. func (check *checker) stmt(s ast.Stmt) { switch s := s.(type) { case *ast.BadStmt, *ast.EmptyStmt: // ignore case *ast.DeclStmt: d, _ := s.Decl.(*ast.GenDecl) if d == nil || (d.Tok != token.CONST && d.Tok != token.TYPE && d.Tok != token.VAR) { check.invalidAST(token.NoPos, "const, type, or var declaration expected") return } if d.Tok == token.CONST { check.assocInitvals(d) } check.decl(d) case *ast.LabeledStmt: // TODO(gri) anything to do with label itself? check.stmt(s.Stmt) case *ast.ExprStmt: var x operand used := false switch e := unparen(s.X).(type) { case *ast.CallExpr: // function calls are permitted used = true // but some builtins are excluded // (Caution: This evaluates e.Fun twice, once here and once // below as part of s.X. This has consequences for // check.register. Perhaps this can be avoided.) check.expr(&x, e.Fun, nil, -1) if x.mode != invalid { if b, ok := x.typ.(*builtin); ok && !b.isStatement { used = false } } case *ast.UnaryExpr: // receive operations are permitted if e.Op == token.ARROW { used = true } } if !used { check.errorf(s.Pos(), "%s not used", s.X) // ok to continue } check.rawExpr(&x, s.X, nil, -1, false) if x.mode == typexpr { check.errorf(x.pos(), "%s is not an expression", &x) } case *ast.SendStmt: var ch, x operand check.expr(&ch, s.Chan, nil, -1) check.expr(&x, s.Value, nil, -1) if ch.mode == invalid || x.mode == invalid { return } if tch, ok := underlying(ch.typ).(*Chan); !ok || tch.Dir&ast.SEND == 0 || !check.assignment(&x, tch.Elt) { if x.mode != invalid { check.invalidOp(ch.pos(), "cannot send %s to channel %s", &x, &ch) } } case *ast.IncDecStmt: var op token.Token switch s.Tok { case token.INC: op = token.ADD case token.DEC: op = token.SUB default: check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) return } var x operand Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position check.binary(&x, s.X, Y, op, -1) if x.mode == invalid { return } check.assign1to1(s.X, nil, &x, false, -1) case *ast.AssignStmt: switch s.Tok { case token.ASSIGN, token.DEFINE: if len(s.Lhs) == 0 { check.invalidAST(s.Pos(), "missing lhs in assignment") return } check.assignNtoM(s.Lhs, s.Rhs, s.Tok == token.DEFINE, -1) default: // assignment operations if len(s.Lhs) != 1 || len(s.Rhs) != 1 { check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) return } // TODO(gri) make this conversion more efficient var op token.Token switch s.Tok { case token.ADD_ASSIGN: op = token.ADD case token.SUB_ASSIGN: op = token.SUB case token.MUL_ASSIGN: op = token.MUL case token.QUO_ASSIGN: op = token.QUO case token.REM_ASSIGN: op = token.REM case token.AND_ASSIGN: op = token.AND case token.OR_ASSIGN: op = token.OR case token.XOR_ASSIGN: op = token.XOR case token.SHL_ASSIGN: op = token.SHL case token.SHR_ASSIGN: op = token.SHR case token.AND_NOT_ASSIGN: op = token.AND_NOT default: check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) return } var x operand check.binary(&x, s.Lhs[0], s.Rhs[0], op, -1) if x.mode == invalid { return } check.assign1to1(s.Lhs[0], nil, &x, false, -1) } case *ast.GoStmt: check.call(s.Call) case *ast.DeferStmt: check.call(s.Call) case *ast.ReturnStmt: sig := check.funcsig if n := len(sig.Results); n > 0 { // TODO(gri) should not have to compute lhs, named every single time - clean this up lhs := make([]ast.Expr, n) named := false // if set, function has named results for i, res := range sig.Results { if len(res.Name) > 0 { // a blank (_) result parameter is a named result named = true } name := ast.NewIdent(res.Name) name.NamePos = s.Pos() check.register(name, &Var{Name: res.Name, Type: res.Type}) // Pkg == nil lhs[i] = name } if len(s.Results) > 0 || !named { // TODO(gri) assignNtoM should perhaps not require len(lhs) > 0 check.assignNtoM(lhs, s.Results, false, -1) } } else if len(s.Results) > 0 { check.errorf(s.Pos(), "no result values expected") } case *ast.BranchStmt: // TODO(gri) implement this case *ast.BlockStmt: check.stmtList(s.List) case *ast.IfStmt: check.optionalStmt(s.Init) var x operand check.expr(&x, s.Cond, nil, -1) if x.mode != invalid && !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in if statement") } check.stmt(s.Body) check.optionalStmt(s.Else) case *ast.SwitchStmt: check.optionalStmt(s.Init) var x operand tag := s.Tag if tag == nil { // use fake true tag value and position it at the opening { of the switch ident := &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} check.register(ident, Universe.Lookup("true")) tag = ident } check.expr(&x, tag, nil, -1) check.multipleDefaults(s.Body.List) // TODO(gri) check also correct use of fallthrough seen := make(map[interface{}]token.Pos) for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { continue // error reported before } if x.mode != invalid { for _, expr := range clause.List { x := x // copy of x (don't modify original) var y operand check.expr(&y, expr, nil, -1) if y.mode == invalid { continue // error reported before } // If we have a constant case value, it must appear only // once in the switch statement. Determine if there is a // duplicate entry, but only report an error if there are // no other errors. var dupl token.Pos var yy operand if y.mode == constant { // TODO(gri) This code doesn't work correctly for // large integer, floating point, or // complex values - the respective struct // comparisons are shallow. Need to use a // hash function to index the map. dupl = seen[y.val] seen[y.val] = y.pos() yy = y // remember y } // TODO(gri) The convertUntyped call pair below appears in other places. Factor! // Order matters: By comparing y against x, error positions are at the case values. check.convertUntyped(&y, x.typ) if y.mode == invalid { continue // error reported before } check.convertUntyped(&x, y.typ) if x.mode == invalid { continue // error reported before } check.comparison(&y, &x, token.EQL) if y.mode != invalid && dupl.IsValid() { check.errorf(yy.pos(), "%s is duplicate case (previous at %s)", &yy, check.fset.Position(dupl)) } } } check.stmtList(clause.Body) } case *ast.TypeSwitchStmt: check.optionalStmt(s.Init) // A type switch guard must be of the form: // // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . // // The parser is checking syntactic correctness; // remaining syntactic errors are considered AST errors here. // TODO(gri) better factoring of error handling (invalid ASTs) // var lhs *Var // lhs variable or nil var rhs ast.Expr switch guard := s.Assign.(type) { case *ast.ExprStmt: rhs = guard.X case *ast.AssignStmt: if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } ident, _ := guard.Lhs[0].(*ast.Ident) if ident == nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } lhs = check.lookup(ident).(*Var) rhs = guard.Rhs[0] default: check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } // rhs must be of the form: expr.(type) and expr must be an interface expr, _ := rhs.(*ast.TypeAssertExpr) if expr == nil || expr.Type != nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } var x operand check.expr(&x, expr.X, nil, -1) if x.mode == invalid { return } var T *Interface if T, _ = underlying(x.typ).(*Interface); T == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { continue // error reported before } // Check each type in this type switch case. var typ Type for _, expr := range clause.List { typ = check.typOrNil(expr, false) if typ != nil && typ != Typ[Invalid] { if method, wrongType := missingMethod(typ, T); method != nil { var msg string if wrongType { msg = "%s cannot have dynamic type %s (wrong type for method %s)" } else { msg = "%s cannot have dynamic type %s (missing method %s)" } check.errorf(expr.Pos(), msg, &x, typ, method.Name) // ok to continue } } } // If lhs exists, set its type for each clause. if lhs != nil { // In clauses with a case listing exactly one type, the variable has that type; // otherwise, the variable has the type of the expression in the TypeSwitchGuard. if len(clause.List) != 1 || typ == nil { typ = x.typ } lhs.Type = typ } check.stmtList(clause.Body) } // There is only one object (lhs) associated with a lhs identifier, but that object // assumes different types for different clauses. Set it back to the type of the // TypeSwitchGuard expression so that that variable always has a valid type. if lhs != nil { lhs.Type = x.typ } case *ast.SelectStmt: check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CommClause) if clause == nil { continue // error reported before } check.optionalStmt(clause.Comm) // TODO(gri) check correctness of c.Comm (must be Send/RecvStmt) check.stmtList(clause.Body) } case *ast.ForStmt: check.optionalStmt(s.Init) if s.Cond != nil { var x operand check.expr(&x, s.Cond, nil, -1) if x.mode != invalid && !isBoolean(x.typ) { check.errorf(s.Cond.Pos(), "non-boolean condition in for statement") } } check.optionalStmt(s.Post) check.stmt(s.Body) case *ast.RangeStmt: // check expression to iterate over decl := s.Tok == token.DEFINE var x operand check.expr(&x, s.X, nil, -1) if x.mode == invalid { // if we don't have a declaration, we can still check the loop's body if !decl { check.stmt(s.Body) } return } // determine key/value types var key, val Type switch typ := underlying(x.typ).(type) { case *Basic: if isString(typ) { key = Typ[UntypedInt] val = Typ[UntypedRune] } case *Array: key = Typ[UntypedInt] val = typ.Elt case *Slice: key = Typ[UntypedInt] val = typ.Elt case *Pointer: if typ, _ := underlying(typ.Base).(*Array); typ != nil { key = Typ[UntypedInt] val = typ.Elt } case *Map: key = typ.Key val = typ.Elt case *Chan: key = typ.Elt if typ.Dir&ast.RECV == 0 { check.errorf(x.pos(), "cannot range over send-only channel %s", &x) // ok to continue } if s.Value != nil { check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) // ok to continue } } if key == nil { check.errorf(x.pos(), "cannot range over %s", &x) // if we don't have a declaration, we can still check the loop's body if !decl { check.stmt(s.Body) } return } // check assignment to/declaration of iteration variables // TODO(gri) The error messages/positions are not great here, // they refer to the expression in the range clause. // Should give better messages w/o too much code // duplication (assignment checking). x.mode = value if s.Key != nil { x.typ = key x.expr = s.Key check.assign1to1(s.Key, nil, &x, decl, -1) } else { check.invalidAST(s.Pos(), "range clause requires index iteration variable") // ok to continue } if s.Value != nil { x.typ = val x.expr = s.Value check.assign1to1(s.Value, nil, &x, decl, -1) } check.stmt(s.Body) default: check.errorf(s.Pos(), "invalid statement") } }
// blockBranches processes a block's statement list and returns the set of outgoing forward jumps. // all is the scope of all declared labels, parent the set of labels declared in the immediately // enclosing block, and lstmt is the labeled statement this block is associated with (or nil). func (check *Checker) blockBranches(all *Scope, parent *block, lstmt *ast.LabeledStmt, list []ast.Stmt) []*ast.BranchStmt { b := &block{parent: parent, lstmt: lstmt} var ( varDeclPos token.Pos fwdJumps, badJumps []*ast.BranchStmt ) // All forward jumps jumping over a variable declaration are possibly // invalid (they may still jump out of the block and be ok). // recordVarDecl records them for the given position. recordVarDecl := func(pos token.Pos) { varDeclPos = pos badJumps = append(badJumps[:0], fwdJumps...) // copy fwdJumps to badJumps } jumpsOverVarDecl := func(jmp *ast.BranchStmt) bool { if varDeclPos.IsValid() { for _, bad := range badJumps { if jmp == bad { return true } } } return false } blockBranches := func(lstmt *ast.LabeledStmt, list []ast.Stmt) { // Unresolved forward jumps inside the nested block // become forward jumps in the current block. fwdJumps = append(fwdJumps, check.blockBranches(all, b, lstmt, list)...) } var stmtBranches func(ast.Stmt) stmtBranches = func(s ast.Stmt) { switch s := s.(type) { case *ast.DeclStmt: if d, _ := s.Decl.(*ast.GenDecl); d != nil && d.Tok == token.VAR { recordVarDecl(d.Pos()) } case *ast.LabeledStmt: // declare non-blank label if name := s.Label.Name; name != "_" { lbl := NewLabel(s.Label.Pos(), check.pkg, name) if alt := all.Insert(lbl); alt != nil { check.softErrorf(lbl.pos, "label %s already declared", name) check.reportAltDecl(alt) // ok to continue } else { b.insert(s) check.recordDef(s.Label, lbl) } // resolve matching forward jumps and remove them from fwdJumps i := 0 for _, jmp := range fwdJumps { if jmp.Label.Name == name { // match lbl.used = true check.recordUse(jmp.Label, lbl) if jumpsOverVarDecl(jmp) { check.softErrorf( jmp.Label.Pos(), "goto %s jumps over variable declaration at line %d", name, check.fset.Position(varDeclPos).Line, ) // ok to continue } } else { // no match - record new forward jump fwdJumps[i] = jmp i++ } } fwdJumps = fwdJumps[:i] lstmt = s } stmtBranches(s.Stmt) case *ast.BranchStmt: if s.Label == nil { return // checked in 1st pass (check.stmt) } // determine and validate target name := s.Label.Name switch s.Tok { case token.BREAK: // spec: "If there is a label, it must be that of an enclosing // "for", "switch", or "select" statement, and that is the one // whose execution terminates." valid := false if t := b.enclosingTarget(name); t != nil { switch t.Stmt.(type) { case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt, *ast.ForStmt, *ast.RangeStmt: valid = true } } if !valid { check.errorf(s.Label.Pos(), "invalid break label %s", name) return } case token.CONTINUE: // spec: "If there is a label, it must be that of an enclosing // "for" statement, and that is the one whose execution advances." valid := false if t := b.enclosingTarget(name); t != nil { switch t.Stmt.(type) { case *ast.ForStmt, *ast.RangeStmt: valid = true } } if !valid { check.errorf(s.Label.Pos(), "invalid continue label %s", name) return } case token.GOTO: if b.gotoTarget(name) == nil { // label may be declared later - add branch to forward jumps fwdJumps = append(fwdJumps, s) return } default: check.invalidAST(s.Pos(), "branch statement: %s %s", s.Tok, name) return } // record label use obj := all.Lookup(name) obj.(*Label).used = true check.recordUse(s.Label, obj) case *ast.AssignStmt: if s.Tok == token.DEFINE { recordVarDecl(s.Pos()) } case *ast.BlockStmt: blockBranches(lstmt, s.List) case *ast.IfStmt: stmtBranches(s.Body) if s.Else != nil { stmtBranches(s.Else) } case *ast.CaseClause: blockBranches(nil, s.Body) case *ast.SwitchStmt: stmtBranches(s.Body) case *ast.TypeSwitchStmt: stmtBranches(s.Body) case *ast.CommClause: blockBranches(nil, s.Body) case *ast.SelectStmt: stmtBranches(s.Body) case *ast.ForStmt: stmtBranches(s.Body) case *ast.RangeStmt: stmtBranches(s.Body) } } for _, s := range list { stmtBranches(s) } return fwdJumps }