// 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 }
// 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 }
// 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) (TypeAndValue, 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 across 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 node, err := parser.ParseExprFrom(fset, "eval", expr, 0) 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 }
// 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)) }
// 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 }
// 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.ExprList, returnPos token.Pos, entangledLhs *Var) { l := len(lhs) rhsIsEntangled := false if rhs.EntangledPos == 0 && len(rhs.List) > 0 { var x operand check.rhsMultiExpr(&x, rhs.List[0]) if t, ok := x.typ.(*Tuple); ok { if t.entangled != nil { // a, b \ c := f() l = len(lhs) + 1 rhsIsEntangled = true } else { // a, b, c := f() l = len(lhs) } } else { // a, b, c := x, y, z l = len(lhs) if entangledLhs != nil { // v \ ok := m[123] l += 1 } } } else if rhs.EntangledPos == 1 { // a, b \ c := \ z rhsIsEntangled = true l = 1 } else if rhs.EntangledPos == len(rhs.List)+1 { // a, b \ c := x, y \ rhsIsEntangled = true l = len(lhs) } else if len(rhs.List) > 0 { rhsIsEntangled = true check.error(rhs.List[0].Pos(), "must have values at either side of \\, not both") } if rhsIsEntangled && entangledLhs == nil { check.error(lhs[0].Pos(), "expected entangled assignment, but left-hand side is not entangled") } allowCommaOk := l == 2 && entangledLhs != nil && !returnPos.IsValid() get, r, commaOk := unpack(func(x *operand, i int) { if allowCommaOk { check.rhsMultiExpr(x, rhs.List[i]) } else { check.multiExpr(x, rhs.List[i]) } if rhsIsEntangled && isBoolean(x.typ) && (!isBooleanConst(*x) || constant.BoolVal(x.val) != false) { check.error(rhs.List[i].Pos(), "entangled bool must be the false constant") } }, len(rhs.List), allowCommaOk) if !commaOk && (!rhsIsEntangled && entangledLhs != nil) { check.error(rhs.List[0].Pos(), "expected entangled assignment, but right-hand side is not entangled") } 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) // TODO: Error reporting for entangled could be better. if returnPos.IsValid() { check.errorf(returnPos, "wrong number of return values (want %d, got %d)", l, r) return } check.errorf(rhs.List[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 lhs := []*Var{lhs[0], entangledLhs} for i := range a { get(&x, i) a[i] = check.initVar(lhs[i], &x, context) } check.recordCommaOkTypes(rhs.List[0], a) return } for i, v := range append(lhs, entangledLhs) { if v == nil { continue } if rhs.EntangledPos == 1 && i != len(lhs) { continue } else if rhs.EntangledPos == len(lhs)+1 && i == len(lhs) { continue } get(&x, i) check.initVar(v, &x, context) } }