// Copyright 2017 The Bazel Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package resolve defines a name-resolution pass for Starlark abstract // syntax trees. // // The resolver sets the Locals and FreeVars arrays of each DefStmt and // the LocalIndex field of each syntax.Ident that refers to a local or // free variable. It also sets the Locals array of a File for locals // bound by top-level comprehensions and load statements. // Identifiers for global variables do not get an index. package resolve // import "go.starlark.net/resolve" // All references to names are statically resolved. Names may be // predeclared, global, or local to a function or file. // File-local variables include those bound by top-level comprehensions // and by load statements. ("Top-level" means "outside of any function".) // The resolver maps each global name to a small integer and each local // name to a small integer; these integers enable a fast and compact // representation of globals and locals in the evaluator. // // As an optimization, the resolver classifies each predeclared name as // either universal (e.g. None, len) or per-module (e.g. glob in Bazel's // build language), enabling the evaluator to share the representation // of the universal environment across all modules. // // The lexical environment is a tree of blocks with the file block at // its root. The file's child blocks may be of two kinds: functions // and comprehensions, and these may have further children of either // kind. // // Python-style resolution requires multiple passes because a name is // determined to be local to a function only if the function contains a // "binding" use of it; similarly, a name is determined to be global (as // opposed to predeclared) if the module contains a top-level binding use. // Unlike ordinary top-level assignments, the bindings created by load // statements are local to the file block. // A non-binding use may lexically precede the binding to which it is resolved. // In the first pass, we inspect each function, recording in // 'uses' each identifier and the environment block in which it occurs. // If a use of a name is binding, such as a function parameter or // assignment, we add the name to the block's bindings mapping and add a // local variable to the enclosing function. // // As we finish resolving each function, we inspect all the uses within // that function and discard ones that were found to be function-local. The // remaining ones must be either free (local to some lexically enclosing // function), or top-level (global, predeclared, or file-local), but we cannot tell // which until we have finished inspecting the outermost enclosing // function. At that point, we can distinguish local from top-level names // (and this is when Python would compute free variables). // // However, Starlark additionally requires that all references to global // names are satisfied by some declaration in the current module; // Starlark permits a function to forward-reference a global or file-local // that has not // been declared yet so long as it is declared before the end of the // module. So, instead of re-resolving the unresolved references after // each top-level function, we defer this until the end of the module // and ensure that all such references are satisfied by some definition. // // At the end of the module, we visit each of the nested function blocks // in bottom-up order, doing a recursive lexical lookup for each // unresolved name. If the name is found to be local to some enclosing // function, we must create a DefStmt.FreeVar (capture) parameter for // each intervening function. We enter these synthetic bindings into // the bindings map so that we create at most one freevar per name. If // the name was not local, we check that it was defined at module level. // // We resolve all uses of locals in the module (due to load statements // and comprehensions) in a similar way and compute the file's set of // local variables. // // Starlark enforces that all global names are assigned at most once on // all control flow paths by forbidding if/else statements and loops at // top level. A global may be used before it is defined, leading to a // dynamic error. However, the AllowGlobalReassign flag (really: allow // top-level reassign) makes the resolver allow multiple to a variable // at top-level. It also allows if-, for-, and while-loops at top-level, // which in turn may make the evaluator dynamically assign multiple // values to a variable at top-level. (These two roles should be separated.) import ( "fmt" "log" "sort" "strings" "go.starlark.net/internal/spell" "go.starlark.net/syntax" ) const debug = false const doesnt = "this Starlark dialect does not " // global options // These features are either not standard Starlark (yet), or deprecated // features of the BUILD language, so we put them behind flags. var ( AllowSet = false // allow the 'set' built-in AllowGlobalReassign = false // allow reassignment to top-level names; also, allow if/for/while at top-level AllowRecursion = false // allow while statements and recursive functions LoadBindsGlobally = false // load creates global not file-local bindings (deprecated) // obsolete flags for features that are now standard. No effect. AllowNestedDef = true AllowLambda = true AllowFloat = true AllowBitwise = true ) // File resolves the specified file and records information about the // module in file.Module. // // The isPredeclared and isUniversal predicates report whether a name is // a pre-declared identifier (visible in the current module) or a // universal identifier (visible in every module). // Clients should typically pass predeclared.Has for the first and // starlark.Universe.Has for the second, where predeclared is the // module's StringDict of predeclared names and starlark.Universe is the // standard set of built-ins. // The isUniverse predicate is supplied a parameter to avoid a cyclic // dependency upon starlark.Universe, not because users should ever need // to redefine it. func File(file *syntax.File, isPredeclared, isUniversal func(name string) bool) error { return REPLChunk(file, nil, isPredeclared, isUniversal) } // REPLChunk is a generalization of the File function that supports a // non-empty initial global block, as occurs in a REPL. func REPLChunk(file *syntax.File, isGlobal, isPredeclared, isUniversal func(name string) bool) error { r := newResolver(isGlobal, isPredeclared, isUniversal) r.stmts(file.Stmts) r.env.resolveLocalUses() // At the end of the module, resolve all non-local variable references, // computing closures. // Function bodies may contain forward references to later global declarations. r.resolveNonLocalUses(r.env) file.Module = &Module{ Locals: r.moduleLocals, Globals: r.moduleGlobals, } if len(r.errors) > 0 { return r.errors } return nil } // Expr resolves the specified expression. // It returns the local variables bound within the expression. // // The isPredeclared and isUniversal predicates behave as for the File function. func Expr(expr syntax.Expr, isPredeclared, isUniversal func(name string) bool) ([]*Binding, error) { r := newResolver(nil, isPredeclared, isUniversal) r.expr(expr) r.env.resolveLocalUses() r.resolveNonLocalUses(r.env) // globals & universals if len(r.errors) > 0 { return nil, r.errors } return r.moduleLocals, nil } // An ErrorList is a non-empty list of resolver error messages. type ErrorList []Error // len > 0 func (e ErrorList) Error() string { return e[0].Error() } // An Error describes the nature and position of a resolver error. type Error struct { Pos syntax.Position Msg string } func (e Error) Error() string { return e.Pos.String() + ": " + e.Msg } func newResolver(isGlobal, isPredeclared, isUniversal func(name string) bool) *resolver { file := new(block) return &resolver{ file: file, env: file, isGlobal: isGlobal, isPredeclared: isPredeclared, isUniversal: isUniversal, globals: make(map[string]*Binding), predeclared: make(map[string]*Binding), } } type resolver struct { // env is the current local environment: // a linked list of blocks, innermost first. // The tail of the list is the file block. env *block file *block // file block (contains load bindings) // moduleLocals contains the local variables of the module // (due to load statements and comprehensions outside any function). // moduleGlobals contains the global variables of the module. moduleLocals []*Binding moduleGlobals []*Binding // globals maps each global name in the module to its binding. // predeclared does the same for predeclared and universal names. globals map[string]*Binding predeclared map[string]*Binding // These predicates report whether a name is // pre-declared, either in this module or universally, // or already declared in the module globals (as in a REPL). // isGlobal may be nil. isGlobal, isPredeclared, isUniversal func(name string) bool loops int // number of enclosing for/while loops ifstmts int // number of enclosing if statements loops errors ErrorList } // container returns the innermost enclosing "container" block: // a function (function != nil) or file (function == nil). // Container blocks accumulate local variable bindings. func (r *resolver) container() *block { for b := r.env; ; b = b.parent { if b.function != nil || b == r.file { return b } } } func (r *resolver) push(b *block) { r.env.children = append(r.env.children, b) b.parent = r.env r.env = b } func (r *resolver) pop() { r.env = r.env.parent } type block struct { parent *block // nil for file block // In the file (root) block, both these fields are nil. function *Function // only for function blocks comp *syntax.Comprehension // only for comprehension blocks // bindings maps a name to its binding. // A local binding has an index into its innermost enclosing container's locals array. // A free binding has an index into its innermost enclosing function's freevars array. bindings map[string]*Binding // children records the child blocks of the current one. children []*block // uses records all identifiers seen in this container (function or file), // and a reference to the environment in which they appear. // As we leave each container block, we resolve them, // so that only free and global ones remain. // At the end of each top-level function we compute closures. uses []use } func (b *block) bind(name string, bind *Binding) { if b.bindings == nil { b.bindings = make(map[string]*Binding) } b.bindings[name] = bind } func (b *block) String() string { if b.function != nil { return "function block at " + fmt.Sprint(b.function.Pos) } if b.comp != nil { return "comprehension block at " + fmt.Sprint(b.comp.Span()) } return "file block" } func (r *resolver) errorf(posn syntax.Position, format string, args ...interface{}) { r.errors = append(r.errors, Error{posn, fmt.Sprintf(format, args...)}) } // A use records an identifier and the environment in which it appears. type use struct { id *syntax.Ident env *block } // bind creates a binding for id: a global (not file-local) // binding at top-level, a local binding otherwise. // At top-level, it reports an error if a global or file-local // binding already exists, unless AllowGlobalReassign. // It sets id.Binding to the binding (whether old or new), // and returns whether a binding already existed. func (r *resolver) bind(id *syntax.Ident) bool { // Binding outside any local (comprehension/function) block? if r.env == r.file { bind, ok := r.file.bindings[id.Name] if !ok { bind, ok = r.globals[id.Name] if !ok { // first global binding of this name bind = &Binding{ First: id, Scope: Global, Index: len(r.moduleGlobals), } r.globals[id.Name] = bind r.moduleGlobals = append(r.moduleGlobals, bind) } } if ok && !AllowGlobalReassign { r.errorf(id.NamePos, "cannot reassign %s %s declared at %s", bind.Scope, id.Name, bind.First.NamePos) } id.Binding = bind return ok } return r.bindLocal(id) } func (r *resolver) bindLocal(id *syntax.Ident) bool { // Mark this name as local to current block. // Assign it a new local (positive) index in the current container. _, ok := r.env.bindings[id.Name] if !ok { var locals *[]*Binding if fn := r.container().function; fn != nil { locals = &fn.Locals } else { locals = &r.moduleLocals } bind := &Binding{ First: id, Scope: Local, Index: len(*locals), } r.env.bind(id.Name, bind) *locals = append(*locals, bind) } r.use(id) return ok } func (r *resolver) use(id *syntax.Ident) { use := use{id, r.env} // The spec says that if there is a global binding of a name // then all references to that name in that block refer to the // global, even if the use precedes the def---just as for locals. // For example, in this code, // // print(len); len=1; print(len) // // both occurrences of len refer to the len=1 binding, which // completely shadows the predeclared len function. // // The rationale for these semantics, which differ from Python, // is that the static meaning of len (a reference to a global) // does not change depending on where it appears in the file. // Of course, its dynamic meaning does change, from an error // into a valid reference, so it's not clear these semantics // have any practical advantage. // // In any case, the Bazel implementation lags behind the spec // and follows Python behavior, so the first use of len refers // to the predeclared function. This typically used in a BUILD // file that redefines a predeclared name half way through, // for example: // // proto_library(...) # built-in rule // load("myproto.bzl", "proto_library") // proto_library(...) # user-defined rule // // We will piggyback support for the legacy semantics on the // AllowGlobalReassign flag, which is loosely related and also // required for Bazel. if AllowGlobalReassign && r.env == r.file { r.useToplevel(use) return } b := r.container() b.uses = append(b.uses, use) } // useToplevel resolves use.id as a reference to a name visible at top-level. // The use.env field captures the original environment for error reporting. func (r *resolver) useToplevel(use use) (bind *Binding) { id := use.id if prev, ok := r.file.bindings[id.Name]; ok { // use of load-defined name in file block bind = prev } else if prev, ok := r.globals[id.Name]; ok { // use of global declared by module bind = prev } else if r.isGlobal != nil && r.isGlobal(id.Name) { // use of global defined in a previous REPL chunk bind = &Binding{ First: id, // wrong: this is not even a binding use Scope: Global, Index: len(r.moduleGlobals), } r.globals[id.Name] = bind r.moduleGlobals = append(r.moduleGlobals, bind) } else if prev, ok := r.predeclared[id.Name]; ok { // repeated use of predeclared or universal bind = prev } else if r.isPredeclared(id.Name) { // use of pre-declared name bind = &Binding{Scope: Predeclared} r.predeclared[id.Name] = bind // save it } else if r.isUniversal(id.Name) { // use of universal name if !AllowSet && id.Name == "set" { r.errorf(id.NamePos, doesnt+"support sets") } bind = &Binding{Scope: Universal} r.predeclared[id.Name] = bind // save it } else { bind = &Binding{Scope: Undefined} var hint string if n := r.spellcheck(use); n != "" { hint = fmt.Sprintf(" (did you mean %s?)", n) } r.errorf(id.NamePos, "undefined: %s%s", id.Name, hint) } id.Binding = bind return bind } // spellcheck returns the most likely misspelling of // the name use.id in the environment use.env. func (r *resolver) spellcheck(use use) string { var names []string // locals for b := use.env; b != nil; b = b.parent { for name := range b.bindings { names = append(names, name) } } // globals // // We have no way to enumerate the sets whose membership // tests are isPredeclared, isUniverse, and isGlobal, // which includes prior names in the REPL session. for _, bind := range r.moduleGlobals { names = append(names, bind.First.Name) } sort.Strings(names) return spell.Nearest(use.id.Name, names) } // resolveLocalUses is called when leaving a container (function/module) // block. It resolves all uses of locals/cells within that block. func (b *block) resolveLocalUses() { unresolved := b.uses[:0] for _, use := range b.uses { if bind := lookupLocal(use); bind != nil && (bind.Scope == Local || bind.Scope == Cell) { use.id.Binding = bind } else { unresolved = append(unresolved, use) } } b.uses = unresolved } func (r *resolver) stmts(stmts []syntax.Stmt) { for _, stmt := range stmts { r.stmt(stmt) } } func (r *resolver) stmt(stmt syntax.Stmt) { switch stmt := stmt.(type) { case *syntax.ExprStmt: r.expr(stmt.X) case *syntax.BranchStmt: if r.loops == 0 && (stmt.Token == syntax.BREAK || stmt.Token == syntax.CONTINUE) { r.errorf(stmt.TokenPos, "%s not in a loop", stmt.Token) } case *syntax.IfStmt: if !AllowGlobalReassign && r.container().function == nil { r.errorf(stmt.If, "if statement not within a function") } r.expr(stmt.Cond) r.ifstmts++ r.stmts(stmt.True) r.stmts(stmt.False) r.ifstmts-- case *syntax.AssignStmt: r.expr(stmt.RHS) isAugmented := stmt.Op != syntax.EQ r.assign(stmt.LHS, isAugmented) case *syntax.DefStmt: r.bind(stmt.Name) fn := &Function{ Name: stmt.Name.Name, Pos: stmt.Def, Params: stmt.Params, Body: stmt.Body, } stmt.Function = fn r.function(fn, stmt.Def) case *syntax.ForStmt: if !AllowGlobalReassign && r.container().function == nil { r.errorf(stmt.For, "for loop not within a function") } r.expr(stmt.X) const isAugmented = false r.assign(stmt.Vars, isAugmented) r.loops++ r.stmts(stmt.Body) r.loops-- case *syntax.WhileStmt: if !AllowRecursion { r.errorf(stmt.While, doesnt+"support while loops") } if !AllowGlobalReassign && r.container().function == nil { r.errorf(stmt.While, "while loop not within a function") } r.expr(stmt.Cond) r.loops++ r.stmts(stmt.Body) r.loops-- case *syntax.ReturnStmt: if r.container().function == nil { r.errorf(stmt.Return, "return statement not within a function") } if stmt.Result != nil { r.expr(stmt.Result) } case *syntax.LoadStmt: // A load statement may not be nested in any other statement. if r.container().function != nil { r.errorf(stmt.Load, "load statement within a function") } else if r.loops > 0 { r.errorf(stmt.Load, "load statement within a loop") } else if r.ifstmts > 0 { r.errorf(stmt.Load, "load statement within a conditional") } for i, from := range stmt.From { if from.Name == "" { r.errorf(from.NamePos, "load: empty identifier") continue } if from.Name[0] == '_' { r.errorf(from.NamePos, "load: names with leading underscores are not exported: %s", from.Name) } id := stmt.To[i] if LoadBindsGlobally { r.bind(id) } else if r.bindLocal(id) && !AllowGlobalReassign { // "Global" in AllowGlobalReassign is a misnomer for "toplevel". // Sadly we can't report the previous declaration // as id.Binding may not be set yet. r.errorf(id.NamePos, "cannot reassign top-level %s", id.Name) } } default: log.Panicf("unexpected stmt %T", stmt) } } func (r *resolver) assign(lhs syntax.Expr, isAugmented bool) { switch lhs := lhs.(type) { case *syntax.Ident: // x = ... r.bind(lhs) case *syntax.IndexExpr: // x[i] = ... r.expr(lhs.X) r.expr(lhs.Y) case *syntax.DotExpr: // x.f = ... r.expr(lhs.X) case *syntax.TupleExpr: // (x, y) = ... if isAugmented { r.errorf(syntax.Start(lhs), "can't use tuple expression in augmented assignment") } for _, elem := range lhs.List { r.assign(elem, isAugmented) } case *syntax.ListExpr: // [x, y, z] = ... if isAugmented { r.errorf(syntax.Start(lhs), "can't use list expression in augmented assignment") } for _, elem := range lhs.List { r.assign(elem, isAugmented) } case *syntax.ParenExpr: r.assign(lhs.X, isAugmented) default: name := strings.ToLower(strings.TrimPrefix(fmt.Sprintf("%T", lhs), "*syntax.")) r.errorf(syntax.Start(lhs), "can't assign to %s", name) } } func (r *resolver) expr(e syntax.Expr) { switch e := e.(type) { case *syntax.Ident: r.use(e) case *syntax.Literal: case *syntax.ListExpr: for _, x := range e.List { r.expr(x) } case *syntax.CondExpr: r.expr(e.Cond) r.expr(e.True) r.expr(e.False) case *syntax.IndexExpr: r.expr(e.X) r.expr(e.Y) case *syntax.DictEntry: r.expr(e.Key) r.expr(e.Value) case *syntax.SliceExpr: r.expr(e.X) if e.Lo != nil { r.expr(e.Lo) } if e.Hi != nil { r.expr(e.Hi) } if e.Step != nil { r.expr(e.Step) } case *syntax.Comprehension: // The 'in' operand of the first clause (always a ForClause) // is resolved in the outer block; consider: [x for x in x]. clause := e.Clauses[0].(*syntax.ForClause) r.expr(clause.X) // A list/dict comprehension defines a new lexical block. // Locals defined within the block will be allotted // distinct slots in the locals array of the innermost // enclosing container (function/module) block. r.push(&block{comp: e}) const isAugmented = false r.assign(clause.Vars, isAugmented) for _, clause := range e.Clauses[1:] { switch clause := clause.(type) { case *syntax.IfClause: r.expr(clause.Cond) case *syntax.ForClause: r.assign(clause.Vars, isAugmented) r.expr(clause.X) } } r.expr(e.Body) // body may be *DictEntry r.pop() case *syntax.TupleExpr: for _, x := range e.List { r.expr(x) } case *syntax.DictExpr: for _, entry := range e.List { entry := entry.(*syntax.DictEntry) r.expr(entry.Key) r.expr(entry.Value) } case *syntax.UnaryExpr: r.expr(e.X) case *syntax.BinaryExpr: r.expr(e.X) r.expr(e.Y) case *syntax.DotExpr: r.expr(e.X) // ignore e.Name case *syntax.CallExpr: r.expr(e.Fn) var seenVarargs, seenKwargs bool var seenName map[string]bool var n, p int for _, arg := range e.Args { pos, _ := arg.Span() if unop, ok := arg.(*syntax.UnaryExpr); ok && unop.Op == syntax.STARSTAR { // **kwargs if seenKwargs { r.errorf(pos, "multiple **kwargs not allowed") } seenKwargs = true r.expr(arg) } else if ok && unop.Op == syntax.STAR { // *args if seenKwargs { r.errorf(pos, "*args may not follow **kwargs") } else if seenVarargs { r.errorf(pos, "multiple *args not allowed") } seenVarargs = true r.expr(arg) } else if binop, ok := arg.(*syntax.BinaryExpr); ok && binop.Op == syntax.EQ { // k=v n++ if seenKwargs { r.errorf(pos, "keyword argument may not follow **kwargs") } else if seenVarargs { r.errorf(pos, "keyword argument may not follow *args") } x := binop.X.(*syntax.Ident) if seenName[x.Name] { r.errorf(x.NamePos, "keyword argument %s repeated", x.Name) } else { if seenName == nil { seenName = make(map[string]bool) } seenName[x.Name] = true } r.expr(binop.Y) } else { // positional argument p++ if seenVarargs { r.errorf(pos, "positional argument may not follow *args") } else if seenKwargs { r.errorf(pos, "positional argument may not follow **kwargs") } else if len(seenName) > 0 { r.errorf(pos, "positional argument may not follow named") } r.expr(arg) } } // Fail gracefully if compiler-imposed limit is exceeded. if p >= 256 { pos, _ := e.Span() r.errorf(pos, "%v positional arguments in call, limit is 255", p) } if n >= 256 { pos, _ := e.Span() r.errorf(pos, "%v keyword arguments in call, limit is 255", n) } case *syntax.LambdaExpr: fn := &Function{ Name: "lambda", Pos: e.Lambda, Params: e.Params, Body: []syntax.Stmt{&syntax.ReturnStmt{Result: e.Body}}, } e.Function = fn r.function(fn, e.Lambda) case *syntax.ParenExpr: r.expr(e.X) default: log.Panicf("unexpected expr %T", e) } } func (r *resolver) function(function *Function, pos syntax.Position) { // Resolve defaults in enclosing environment. for _, param := range function.Params { if binary, ok := param.(*syntax.BinaryExpr); ok { r.expr(binary.Y) } } // Enter function block. b := &block{function: function} r.push(b) var seenOptional bool var star *syntax.UnaryExpr // * or *args param var starStar *syntax.Ident // **kwargs ident var numKwonlyParams int for _, param := range function.Params { switch param := param.(type) { case *syntax.Ident: // e.g. x if starStar != nil { r.errorf(param.NamePos, "required parameter may not follow **%s", starStar.Name) } else if star != nil { numKwonlyParams++ } else if seenOptional { r.errorf(param.NamePos, "required parameter may not follow optional") } if r.bind(param) { r.errorf(param.NamePos, "duplicate parameter: %s", param.Name) } case *syntax.BinaryExpr: // e.g. y=dflt if starStar != nil { r.errorf(param.OpPos, "optional parameter may not follow **%s", starStar.Name) } else if star != nil { numKwonlyParams++ } if id := param.X.(*syntax.Ident); r.bind(id) { r.errorf(param.OpPos, "duplicate parameter: %s", id.Name) } seenOptional = true case *syntax.UnaryExpr: // * or *args or **kwargs if param.Op == syntax.STAR { if starStar != nil { r.errorf(param.OpPos, "* parameter may not follow **%s", starStar.Name) } else if star != nil { r.errorf(param.OpPos, "multiple * parameters not allowed") } else { star = param } } else { if starStar != nil { r.errorf(param.OpPos, "multiple ** parameters not allowed") } starStar = param.X.(*syntax.Ident) } } } // Bind the *args and **kwargs parameters at the end, // so that regular parameters a/b/c are contiguous and // there is no hole for the "*": // def f(a, b, *args, c=0, **kwargs) // def f(a, b, *, c=0, **kwargs) if star != nil { if id, _ := star.X.(*syntax.Ident); id != nil { // *args if r.bind(id) { r.errorf(id.NamePos, "duplicate parameter: %s", id.Name) } function.HasVarargs = true } else if numKwonlyParams == 0 { r.errorf(star.OpPos, "bare * must be followed by keyword-only parameters") } } if starStar != nil { if r.bind(starStar) { r.errorf(starStar.NamePos, "duplicate parameter: %s", starStar.Name) } function.HasKwargs = true } function.NumKwonlyParams = numKwonlyParams r.stmts(function.Body) // Resolve all uses of this function's local vars, // and keep just the remaining uses of free/global vars. b.resolveLocalUses() // Leave function block. r.pop() // References within the function body to globals are not // resolved until the end of the module. } func (r *resolver) resolveNonLocalUses(b *block) { // First resolve inner blocks. for _, child := range b.children { r.resolveNonLocalUses(child) } for _, use := range b.uses { use.id.Binding = r.lookupLexical(use, use.env) } } // lookupLocal looks up an identifier within its immediately enclosing function. func lookupLocal(use use) *Binding { for env := use.env; env != nil; env = env.parent { if bind, ok := env.bindings[use.id.Name]; ok { if bind.Scope == Free { // shouldn't exist till later log.Panicf("%s: internal error: %s, %v", use.id.NamePos, use.id.Name, bind) } return bind // found } if env.function != nil { break } } return nil // not found in this function } // lookupLexical looks up an identifier use.id within its lexically enclosing environment. // The use.env field captures the original environment for error reporting. func (r *resolver) lookupLexical(use use, env *block) (bind *Binding) { if debug { fmt.Printf("lookupLexical %s in %s = ...\n", use.id.Name, env) defer func() { fmt.Printf("= %v\n", bind) }() } // Is this the file block? if env == r.file { return r.useToplevel(use) // file-local, global, predeclared, or not found } // Defined in this block? bind, ok := env.bindings[use.id.Name] if !ok { // Defined in parent block? bind = r.lookupLexical(use, env.parent) if env.function != nil && (bind.Scope == Local || bind.Scope == Free || bind.Scope == Cell) { // Found in parent block, which belongs to enclosing function. // Add the parent's binding to the function's freevars, // and add a new 'free' binding to the inner function's block, // and turn the parent's local into cell. if bind.Scope == Local { bind.Scope = Cell } index := len(env.function.FreeVars) env.function.FreeVars = append(env.function.FreeVars, bind) bind = &Binding{ First: bind.First, Scope: Free, Index: index, } if debug { fmt.Printf("creating freevar %v in function at %s: %s\n", len(env.function.FreeVars), env.function.Pos, use.id.Name) } } // Memoize, to avoid duplicate free vars // and redundant global (failing) lookups. env.bind(use.id.Name, bind) } return bind }