| // 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 |
| } |