aboutsummaryrefslogtreecommitdiff
path: root/resolve/resolve.go
diff options
context:
space:
mode:
Diffstat (limited to 'resolve/resolve.go')
-rw-r--r--resolve/resolve.go969
1 files changed, 969 insertions, 0 deletions
diff --git a/resolve/resolve.go b/resolve/resolve.go
new file mode 100644
index 0000000..56e33ba
--- /dev/null
+++ b/resolve/resolve.go
@@ -0,0 +1,969 @@
+// 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
+}