Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 1 | // Copyright 2017 The Bazel Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Alan Donovan | e3deafe | 2018-10-23 11:05:09 -0400 | [diff] [blame] | 5 | // Package resolve defines a name-resolution pass for Starlark abstract |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 6 | // syntax trees. |
| 7 | // |
| 8 | // The resolver sets the Locals and FreeVars arrays of each DefStmt and |
| 9 | // the LocalIndex field of each syntax.Ident that refers to a local or |
| 10 | // free variable. It also sets the Locals array of a File for locals |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 11 | // bound by top-level comprehensions and load statements. |
| 12 | // Identifiers for global variables do not get an index. |
Alan Donovan | 551f300 | 2018-11-01 09:44:00 -0400 | [diff] [blame] | 13 | package resolve // import "go.starlark.net/resolve" |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 14 | |
| 15 | // All references to names are statically resolved. Names may be |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 16 | // predeclared, global, or local to a function or file. |
| 17 | // File-local variables include those bound by top-level comprehensions |
| 18 | // and by load statements. ("Top-level" means "outside of any function".) |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 19 | // The resolver maps each global name to a small integer and each local |
| 20 | // name to a small integer; these integers enable a fast and compact |
| 21 | // representation of globals and locals in the evaluator. |
| 22 | // |
| 23 | // As an optimization, the resolver classifies each predeclared name as |
| 24 | // either universal (e.g. None, len) or per-module (e.g. glob in Bazel's |
| 25 | // build language), enabling the evaluator to share the representation |
| 26 | // of the universal environment across all modules. |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 27 | // |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 28 | // The lexical environment is a tree of blocks with the file block at |
| 29 | // its root. The file's child blocks may be of two kinds: functions |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 30 | // and comprehensions, and these may have further children of either |
| 31 | // kind. |
| 32 | // |
| 33 | // Python-style resolution requires multiple passes because a name is |
| 34 | // determined to be local to a function only if the function contains a |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 35 | // "binding" use of it; similarly, a name is determined to be global (as |
| 36 | // opposed to predeclared) if the module contains a top-level binding use. |
| 37 | // Unlike ordinary top-level assignments, the bindings created by load |
| 38 | // statements are local to the file block. |
| 39 | // A non-binding use may lexically precede the binding to which it is resolved. |
alandonovan | 4956ce9 | 2018-08-22 17:44:47 -0400 | [diff] [blame] | 40 | // In the first pass, we inspect each function, recording in |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 41 | // 'uses' each identifier and the environment block in which it occurs. |
| 42 | // If a use of a name is binding, such as a function parameter or |
| 43 | // assignment, we add the name to the block's bindings mapping and add a |
| 44 | // local variable to the enclosing function. |
| 45 | // |
| 46 | // As we finish resolving each function, we inspect all the uses within |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 47 | // that function and discard ones that were found to be function-local. The |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 48 | // remaining ones must be either free (local to some lexically enclosing |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 49 | // function), or top-level (global, predeclared, or file-local), but we cannot tell |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 50 | // which until we have finished inspecting the outermost enclosing |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 51 | // function. At that point, we can distinguish local from top-level names |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 52 | // (and this is when Python would compute free variables). |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 53 | // |
Alan Donovan | e3deafe | 2018-10-23 11:05:09 -0400 | [diff] [blame] | 54 | // However, Starlark additionally requires that all references to global |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 55 | // names are satisfied by some declaration in the current module; |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 56 | // Starlark permits a function to forward-reference a global or file-local |
| 57 | // that has not |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 58 | // been declared yet so long as it is declared before the end of the |
| 59 | // module. So, instead of re-resolving the unresolved references after |
| 60 | // each top-level function, we defer this until the end of the module |
| 61 | // and ensure that all such references are satisfied by some definition. |
| 62 | // |
| 63 | // At the end of the module, we visit each of the nested function blocks |
| 64 | // in bottom-up order, doing a recursive lexical lookup for each |
| 65 | // unresolved name. If the name is found to be local to some enclosing |
| 66 | // function, we must create a DefStmt.FreeVar (capture) parameter for |
| 67 | // each intervening function. We enter these synthetic bindings into |
| 68 | // the bindings map so that we create at most one freevar per name. If |
| 69 | // the name was not local, we check that it was defined at module level. |
| 70 | // |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 71 | // We resolve all uses of locals in the module (due to load statements |
| 72 | // and comprehensions) in a similar way and compute the file's set of |
| 73 | // local variables. |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 74 | // |
Alan Donovan | e3deafe | 2018-10-23 11:05:09 -0400 | [diff] [blame] | 75 | // Starlark enforces that all global names are assigned at most once on |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 76 | // all control flow paths by forbidding if/else statements and loops at |
alandonovan | 4956ce9 | 2018-08-22 17:44:47 -0400 | [diff] [blame] | 77 | // top level. A global may be used before it is defined, leading to a |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 78 | // dynamic error. However, the AllowGlobalReassign flag (really: allow |
| 79 | // top-level reassign) makes the resolver allow multiple to a variable |
| 80 | // at top-level. It also allows if-, for-, and while-loops at top-level, |
| 81 | // which in turn may make the evaluator dynamically assign multiple |
| 82 | // values to a variable at top-level. (These two roles should be separated.) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 83 | |
| 84 | import ( |
| 85 | "fmt" |
| 86 | "log" |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 87 | "sort" |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 88 | "strings" |
| 89 | |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 90 | "go.starlark.net/internal/spell" |
Alan Donovan | 6beab7e | 2018-10-31 17:53:09 -0400 | [diff] [blame] | 91 | "go.starlark.net/syntax" |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 92 | ) |
| 93 | |
| 94 | const debug = false |
Alan Donovan | e3deafe | 2018-10-23 11:05:09 -0400 | [diff] [blame] | 95 | const doesnt = "this Starlark dialect does not " |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 96 | |
| 97 | // global options |
Alan Donovan | e3deafe | 2018-10-23 11:05:09 -0400 | [diff] [blame] | 98 | // These features are either not standard Starlark (yet), or deprecated |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 99 | // features of the BUILD language, so we put them behind flags. |
| 100 | var ( |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 101 | AllowSet = false // allow the 'set' built-in |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 102 | AllowGlobalReassign = false // allow reassignment to top-level names; also, allow if/for/while at top-level |
Alessandro Arzilli | 678bafe | 2018-12-07 17:28:35 +0100 | [diff] [blame] | 103 | AllowRecursion = false // allow while statements and recursive functions |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 104 | LoadBindsGlobally = false // load creates global not file-local bindings (deprecated) |
alandonovan | cea917a | 2021-01-21 17:58:09 -0500 | [diff] [blame] | 105 | |
| 106 | // obsolete flags for features that are now standard. No effect. |
| 107 | AllowNestedDef = true |
| 108 | AllowLambda = true |
| 109 | AllowFloat = true |
| 110 | AllowBitwise = true |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 111 | ) |
| 112 | |
alandonovan | 6ddc71c | 2019-06-04 09:08:55 -0400 | [diff] [blame] | 113 | // File resolves the specified file and records information about the |
| 114 | // module in file.Module. |
alandonovan | 631f7b5 | 2017-10-11 13:04:47 -0400 | [diff] [blame] | 115 | // |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 116 | // The isPredeclared and isUniversal predicates report whether a name is |
| 117 | // a pre-declared identifier (visible in the current module) or a |
| 118 | // universal identifier (visible in every module). |
| 119 | // Clients should typically pass predeclared.Has for the first and |
Alan Donovan | e3deafe | 2018-10-23 11:05:09 -0400 | [diff] [blame] | 120 | // starlark.Universe.Has for the second, where predeclared is the |
| 121 | // module's StringDict of predeclared names and starlark.Universe is the |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 122 | // standard set of built-ins. |
| 123 | // The isUniverse predicate is supplied a parameter to avoid a cyclic |
Alan Donovan | e3deafe | 2018-10-23 11:05:09 -0400 | [diff] [blame] | 124 | // dependency upon starlark.Universe, not because users should ever need |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 125 | // to redefine it. |
| 126 | func File(file *syntax.File, isPredeclared, isUniversal func(name string) bool) error { |
alandonovan | 28350e6 | 2019-10-21 14:58:36 -0400 | [diff] [blame] | 127 | return REPLChunk(file, nil, isPredeclared, isUniversal) |
| 128 | } |
| 129 | |
| 130 | // REPLChunk is a generalization of the File function that supports a |
| 131 | // non-empty initial global block, as occurs in a REPL. |
| 132 | func REPLChunk(file *syntax.File, isGlobal, isPredeclared, isUniversal func(name string) bool) error { |
| 133 | r := newResolver(isGlobal, isPredeclared, isUniversal) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 134 | r.stmts(file.Stmts) |
| 135 | |
| 136 | r.env.resolveLocalUses() |
| 137 | |
| 138 | // At the end of the module, resolve all non-local variable references, |
| 139 | // computing closures. |
| 140 | // Function bodies may contain forward references to later global declarations. |
| 141 | r.resolveNonLocalUses(r.env) |
| 142 | |
alandonovan | 6ddc71c | 2019-06-04 09:08:55 -0400 | [diff] [blame] | 143 | file.Module = &Module{ |
| 144 | Locals: r.moduleLocals, |
| 145 | Globals: r.moduleGlobals, |
| 146 | } |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 147 | |
| 148 | if len(r.errors) > 0 { |
| 149 | return r.errors |
| 150 | } |
| 151 | return nil |
| 152 | } |
| 153 | |
| 154 | // Expr resolves the specified expression. |
| 155 | // It returns the local variables bound within the expression. |
alandonovan | 631f7b5 | 2017-10-11 13:04:47 -0400 | [diff] [blame] | 156 | // |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 157 | // The isPredeclared and isUniversal predicates behave as for the File function. |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 158 | func Expr(expr syntax.Expr, isPredeclared, isUniversal func(name string) bool) ([]*Binding, error) { |
alandonovan | 28350e6 | 2019-10-21 14:58:36 -0400 | [diff] [blame] | 159 | r := newResolver(nil, isPredeclared, isUniversal) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 160 | r.expr(expr) |
| 161 | r.env.resolveLocalUses() |
alandonovan | 631f7b5 | 2017-10-11 13:04:47 -0400 | [diff] [blame] | 162 | r.resolveNonLocalUses(r.env) // globals & universals |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 163 | if len(r.errors) > 0 { |
| 164 | return nil, r.errors |
| 165 | } |
| 166 | return r.moduleLocals, nil |
| 167 | } |
| 168 | |
| 169 | // An ErrorList is a non-empty list of resolver error messages. |
| 170 | type ErrorList []Error // len > 0 |
| 171 | |
| 172 | func (e ErrorList) Error() string { return e[0].Error() } |
| 173 | |
| 174 | // An Error describes the nature and position of a resolver error. |
| 175 | type Error struct { |
| 176 | Pos syntax.Position |
| 177 | Msg string |
| 178 | } |
| 179 | |
| 180 | func (e Error) Error() string { return e.Pos.String() + ": " + e.Msg } |
| 181 | |
alandonovan | 28350e6 | 2019-10-21 14:58:36 -0400 | [diff] [blame] | 182 | func newResolver(isGlobal, isPredeclared, isUniversal func(name string) bool) *resolver { |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 183 | file := new(block) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 184 | return &resolver{ |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 185 | file: file, |
| 186 | env: file, |
alandonovan | 28350e6 | 2019-10-21 14:58:36 -0400 | [diff] [blame] | 187 | isGlobal: isGlobal, |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 188 | isPredeclared: isPredeclared, |
| 189 | isUniversal: isUniversal, |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 190 | globals: make(map[string]*Binding), |
| 191 | predeclared: make(map[string]*Binding), |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 192 | } |
| 193 | } |
| 194 | |
| 195 | type resolver struct { |
| 196 | // env is the current local environment: |
| 197 | // a linked list of blocks, innermost first. |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 198 | // The tail of the list is the file block. |
| 199 | env *block |
| 200 | file *block // file block (contains load bindings) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 201 | |
| 202 | // moduleLocals contains the local variables of the module |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 203 | // (due to load statements and comprehensions outside any function). |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 204 | // moduleGlobals contains the global variables of the module. |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 205 | moduleLocals []*Binding |
| 206 | moduleGlobals []*Binding |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 207 | |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 208 | // globals maps each global name in the module to its binding. |
| 209 | // predeclared does the same for predeclared and universal names. |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 210 | globals map[string]*Binding |
| 211 | predeclared map[string]*Binding |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 212 | |
| 213 | // These predicates report whether a name is |
alandonovan | 28350e6 | 2019-10-21 14:58:36 -0400 | [diff] [blame] | 214 | // pre-declared, either in this module or universally, |
| 215 | // or already declared in the module globals (as in a REPL). |
| 216 | // isGlobal may be nil. |
| 217 | isGlobal, isPredeclared, isUniversal func(name string) bool |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 218 | |
alandonovan | cd131d1 | 2020-06-09 17:58:44 -0400 | [diff] [blame] | 219 | loops int // number of enclosing for/while loops |
| 220 | ifstmts int // number of enclosing if statements loops |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 221 | |
| 222 | errors ErrorList |
| 223 | } |
| 224 | |
| 225 | // container returns the innermost enclosing "container" block: |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 226 | // a function (function != nil) or file (function == nil). |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 227 | // Container blocks accumulate local variable bindings. |
| 228 | func (r *resolver) container() *block { |
| 229 | for b := r.env; ; b = b.parent { |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 230 | if b.function != nil || b == r.file { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 231 | return b |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | |
| 236 | func (r *resolver) push(b *block) { |
| 237 | r.env.children = append(r.env.children, b) |
| 238 | b.parent = r.env |
| 239 | r.env = b |
| 240 | } |
| 241 | |
| 242 | func (r *resolver) pop() { r.env = r.env.parent } |
| 243 | |
| 244 | type block struct { |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 245 | parent *block // nil for file block |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 246 | |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 247 | // In the file (root) block, both these fields are nil. |
alandonovan | 6ddc71c | 2019-06-04 09:08:55 -0400 | [diff] [blame] | 248 | function *Function // only for function blocks |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 249 | comp *syntax.Comprehension // only for comprehension blocks |
| 250 | |
| 251 | // bindings maps a name to its binding. |
| 252 | // A local binding has an index into its innermost enclosing container's locals array. |
| 253 | // A free binding has an index into its innermost enclosing function's freevars array. |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 254 | bindings map[string]*Binding |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 255 | |
| 256 | // children records the child blocks of the current one. |
| 257 | children []*block |
| 258 | |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 259 | // uses records all identifiers seen in this container (function or file), |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 260 | // and a reference to the environment in which they appear. |
| 261 | // As we leave each container block, we resolve them, |
| 262 | // so that only free and global ones remain. |
| 263 | // At the end of each top-level function we compute closures. |
| 264 | uses []use |
| 265 | } |
| 266 | |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 267 | func (b *block) bind(name string, bind *Binding) { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 268 | if b.bindings == nil { |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 269 | b.bindings = make(map[string]*Binding) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 270 | } |
| 271 | b.bindings[name] = bind |
| 272 | } |
| 273 | |
| 274 | func (b *block) String() string { |
| 275 | if b.function != nil { |
alandonovan | 6ddc71c | 2019-06-04 09:08:55 -0400 | [diff] [blame] | 276 | return "function block at " + fmt.Sprint(b.function.Pos) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 277 | } |
| 278 | if b.comp != nil { |
alandonovan | 5b0e788 | 2018-08-10 13:44:17 -0400 | [diff] [blame] | 279 | return "comprehension block at " + fmt.Sprint(b.comp.Span()) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 280 | } |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 281 | return "file block" |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 282 | } |
| 283 | |
| 284 | func (r *resolver) errorf(posn syntax.Position, format string, args ...interface{}) { |
| 285 | r.errors = append(r.errors, Error{posn, fmt.Sprintf(format, args...)}) |
| 286 | } |
| 287 | |
| 288 | // A use records an identifier and the environment in which it appears. |
| 289 | type use struct { |
| 290 | id *syntax.Ident |
| 291 | env *block |
| 292 | } |
| 293 | |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 294 | // bind creates a binding for id: a global (not file-local) |
| 295 | // binding at top-level, a local binding otherwise. |
| 296 | // At top-level, it reports an error if a global or file-local |
| 297 | // binding already exists, unless AllowGlobalReassign. |
| 298 | // It sets id.Binding to the binding (whether old or new), |
| 299 | // and returns whether a binding already existed. |
alandonovan | 572ecca | 2019-01-23 18:41:07 -0500 | [diff] [blame] | 300 | func (r *resolver) bind(id *syntax.Ident) bool { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 301 | // Binding outside any local (comprehension/function) block? |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 302 | if r.env == r.file { |
| 303 | bind, ok := r.file.bindings[id.Name] |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 304 | if !ok { |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 305 | bind, ok = r.globals[id.Name] |
| 306 | if !ok { |
| 307 | // first global binding of this name |
| 308 | bind = &Binding{ |
| 309 | First: id, |
| 310 | Scope: Global, |
| 311 | Index: len(r.moduleGlobals), |
| 312 | } |
| 313 | r.globals[id.Name] = bind |
| 314 | r.moduleGlobals = append(r.moduleGlobals, bind) |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 315 | } |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 316 | } |
| 317 | if ok && !AllowGlobalReassign { |
| 318 | r.errorf(id.NamePos, "cannot reassign %s %s declared at %s", |
| 319 | bind.Scope, id.Name, bind.First.NamePos) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 320 | } |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 321 | id.Binding = bind |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 322 | return ok |
| 323 | } |
| 324 | |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 325 | return r.bindLocal(id) |
| 326 | } |
| 327 | |
| 328 | func (r *resolver) bindLocal(id *syntax.Ident) bool { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 329 | // Mark this name as local to current block. |
| 330 | // Assign it a new local (positive) index in the current container. |
| 331 | _, ok := r.env.bindings[id.Name] |
| 332 | if !ok { |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 333 | var locals *[]*Binding |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 334 | if fn := r.container().function; fn != nil { |
| 335 | locals = &fn.Locals |
| 336 | } else { |
| 337 | locals = &r.moduleLocals |
| 338 | } |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 339 | bind := &Binding{ |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 340 | First: id, |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 341 | Scope: Local, |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 342 | Index: len(*locals), |
| 343 | } |
| 344 | r.env.bind(id.Name, bind) |
| 345 | *locals = append(*locals, bind) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 346 | } |
| 347 | |
| 348 | r.use(id) |
| 349 | return ok |
| 350 | } |
| 351 | |
| 352 | func (r *resolver) use(id *syntax.Ident) { |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 353 | use := use{id, r.env} |
| 354 | |
alandonovan | 84a935b | 2018-11-26 14:42:21 -0500 | [diff] [blame] | 355 | // The spec says that if there is a global binding of a name |
| 356 | // then all references to that name in that block refer to the |
| 357 | // global, even if the use precedes the def---just as for locals. |
| 358 | // For example, in this code, |
| 359 | // |
| 360 | // print(len); len=1; print(len) |
| 361 | // |
| 362 | // both occurrences of len refer to the len=1 binding, which |
| 363 | // completely shadows the predeclared len function. |
| 364 | // |
| 365 | // The rationale for these semantics, which differ from Python, |
| 366 | // is that the static meaning of len (a reference to a global) |
| 367 | // does not change depending on where it appears in the file. |
| 368 | // Of course, its dynamic meaning does change, from an error |
| 369 | // into a valid reference, so it's not clear these semantics |
| 370 | // have any practical advantage. |
| 371 | // |
| 372 | // In any case, the Bazel implementation lags behind the spec |
| 373 | // and follows Python behavior, so the first use of len refers |
| 374 | // to the predeclared function. This typically used in a BUILD |
| 375 | // file that redefines a predeclared name half way through, |
| 376 | // for example: |
| 377 | // |
| 378 | // proto_library(...) # built-in rule |
| 379 | // load("myproto.bzl", "proto_library") |
| 380 | // proto_library(...) # user-defined rule |
| 381 | // |
| 382 | // We will piggyback support for the legacy semantics on the |
| 383 | // AllowGlobalReassign flag, which is loosely related and also |
| 384 | // required for Bazel. |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 385 | if AllowGlobalReassign && r.env == r.file { |
| 386 | r.useToplevel(use) |
alandonovan | 84a935b | 2018-11-26 14:42:21 -0500 | [diff] [blame] | 387 | return |
| 388 | } |
| 389 | |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 390 | b := r.container() |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 391 | b.uses = append(b.uses, use) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 392 | } |
| 393 | |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 394 | // useToplevel resolves use.id as a reference to a name visible at top-level. |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 395 | // The use.env field captures the original environment for error reporting. |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 396 | func (r *resolver) useToplevel(use use) (bind *Binding) { |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 397 | id := use.id |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 398 | |
| 399 | if prev, ok := r.file.bindings[id.Name]; ok { |
| 400 | // use of load-defined name in file block |
| 401 | bind = prev |
| 402 | } else if prev, ok := r.globals[id.Name]; ok { |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 403 | // use of global declared by module |
| 404 | bind = prev |
alandonovan | 28350e6 | 2019-10-21 14:58:36 -0400 | [diff] [blame] | 405 | } else if r.isGlobal != nil && r.isGlobal(id.Name) { |
| 406 | // use of global defined in a previous REPL chunk |
| 407 | bind = &Binding{ |
| 408 | First: id, // wrong: this is not even a binding use |
| 409 | Scope: Global, |
| 410 | Index: len(r.moduleGlobals), |
| 411 | } |
| 412 | r.globals[id.Name] = bind |
| 413 | r.moduleGlobals = append(r.moduleGlobals, bind) |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 414 | } else if prev, ok := r.predeclared[id.Name]; ok { |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 415 | // repeated use of predeclared or universal |
| 416 | bind = prev |
alandonovan | a1b28d8 | 2018-03-13 10:59:24 -0400 | [diff] [blame] | 417 | } else if r.isPredeclared(id.Name) { |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 418 | // use of pre-declared name |
| 419 | bind = &Binding{Scope: Predeclared} |
| 420 | r.predeclared[id.Name] = bind // save it |
alandonovan | 631f7b5 | 2017-10-11 13:04:47 -0400 | [diff] [blame] | 421 | } else if r.isUniversal(id.Name) { |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 422 | // use of universal name |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 423 | if !AllowSet && id.Name == "set" { |
| 424 | r.errorf(id.NamePos, doesnt+"support sets") |
| 425 | } |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 426 | bind = &Binding{Scope: Universal} |
| 427 | r.predeclared[id.Name] = bind // save it |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 428 | } else { |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 429 | bind = &Binding{Scope: Undefined} |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 430 | var hint string |
| 431 | if n := r.spellcheck(use); n != "" { |
| 432 | hint = fmt.Sprintf(" (did you mean %s?)", n) |
| 433 | } |
| 434 | r.errorf(id.NamePos, "undefined: %s%s", id.Name, hint) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 435 | } |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 436 | id.Binding = bind |
| 437 | return bind |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 438 | } |
| 439 | |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 440 | // spellcheck returns the most likely misspelling of |
| 441 | // the name use.id in the environment use.env. |
| 442 | func (r *resolver) spellcheck(use use) string { |
| 443 | var names []string |
| 444 | |
| 445 | // locals |
| 446 | for b := use.env; b != nil; b = b.parent { |
| 447 | for name := range b.bindings { |
| 448 | names = append(names, name) |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | // globals |
| 453 | // |
alandonovan | 28350e6 | 2019-10-21 14:58:36 -0400 | [diff] [blame] | 454 | // We have no way to enumerate the sets whose membership |
| 455 | // tests are isPredeclared, isUniverse, and isGlobal, |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 456 | // which includes prior names in the REPL session. |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 457 | for _, bind := range r.moduleGlobals { |
| 458 | names = append(names, bind.First.Name) |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 459 | } |
| 460 | |
| 461 | sort.Strings(names) |
| 462 | return spell.Nearest(use.id.Name, names) |
| 463 | } |
| 464 | |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 465 | // resolveLocalUses is called when leaving a container (function/module) |
alandonovan | 3d5a061 | 2019-03-08 16:16:44 -0500 | [diff] [blame] | 466 | // block. It resolves all uses of locals/cells within that block. |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 467 | func (b *block) resolveLocalUses() { |
| 468 | unresolved := b.uses[:0] |
| 469 | for _, use := range b.uses { |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 470 | if bind := lookupLocal(use); bind != nil && (bind.Scope == Local || bind.Scope == Cell) { |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 471 | use.id.Binding = bind |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 472 | } else { |
| 473 | unresolved = append(unresolved, use) |
| 474 | } |
| 475 | } |
| 476 | b.uses = unresolved |
| 477 | } |
| 478 | |
| 479 | func (r *resolver) stmts(stmts []syntax.Stmt) { |
| 480 | for _, stmt := range stmts { |
| 481 | r.stmt(stmt) |
| 482 | } |
| 483 | } |
| 484 | |
| 485 | func (r *resolver) stmt(stmt syntax.Stmt) { |
| 486 | switch stmt := stmt.(type) { |
| 487 | case *syntax.ExprStmt: |
| 488 | r.expr(stmt.X) |
| 489 | |
| 490 | case *syntax.BranchStmt: |
| 491 | if r.loops == 0 && (stmt.Token == syntax.BREAK || stmt.Token == syntax.CONTINUE) { |
| 492 | r.errorf(stmt.TokenPos, "%s not in a loop", stmt.Token) |
| 493 | } |
| 494 | |
| 495 | case *syntax.IfStmt: |
Alan Donovan | c0b6b76 | 2018-12-18 13:09:56 -0500 | [diff] [blame] | 496 | if !AllowGlobalReassign && r.container().function == nil { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 497 | r.errorf(stmt.If, "if statement not within a function") |
| 498 | } |
| 499 | r.expr(stmt.Cond) |
alandonovan | cd131d1 | 2020-06-09 17:58:44 -0400 | [diff] [blame] | 500 | r.ifstmts++ |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 501 | r.stmts(stmt.True) |
| 502 | r.stmts(stmt.False) |
alandonovan | cd131d1 | 2020-06-09 17:58:44 -0400 | [diff] [blame] | 503 | r.ifstmts-- |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 504 | |
| 505 | case *syntax.AssignStmt: |
| 506 | r.expr(stmt.RHS) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 507 | isAugmented := stmt.Op != syntax.EQ |
| 508 | r.assign(stmt.LHS, isAugmented) |
| 509 | |
| 510 | case *syntax.DefStmt: |
alandonovan | 572ecca | 2019-01-23 18:41:07 -0500 | [diff] [blame] | 511 | r.bind(stmt.Name) |
alandonovan | 6ddc71c | 2019-06-04 09:08:55 -0400 | [diff] [blame] | 512 | fn := &Function{ |
| 513 | Name: stmt.Name.Name, |
| 514 | Pos: stmt.Def, |
| 515 | Params: stmt.Params, |
| 516 | Body: stmt.Body, |
| 517 | } |
| 518 | stmt.Function = fn |
| 519 | r.function(fn, stmt.Def) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 520 | |
| 521 | case *syntax.ForStmt: |
Alan Donovan | c0b6b76 | 2018-12-18 13:09:56 -0500 | [diff] [blame] | 522 | if !AllowGlobalReassign && r.container().function == nil { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 523 | r.errorf(stmt.For, "for loop not within a function") |
| 524 | } |
| 525 | r.expr(stmt.X) |
alandonovan | 572ecca | 2019-01-23 18:41:07 -0500 | [diff] [blame] | 526 | const isAugmented = false |
| 527 | r.assign(stmt.Vars, isAugmented) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 528 | r.loops++ |
| 529 | r.stmts(stmt.Body) |
| 530 | r.loops-- |
| 531 | |
Alessandro Arzilli | 678bafe | 2018-12-07 17:28:35 +0100 | [diff] [blame] | 532 | case *syntax.WhileStmt: |
| 533 | if !AllowRecursion { |
Alan Donovan | c0b6b76 | 2018-12-18 13:09:56 -0500 | [diff] [blame] | 534 | r.errorf(stmt.While, doesnt+"support while loops") |
Alessandro Arzilli | 678bafe | 2018-12-07 17:28:35 +0100 | [diff] [blame] | 535 | } |
Alan Donovan | c0b6b76 | 2018-12-18 13:09:56 -0500 | [diff] [blame] | 536 | if !AllowGlobalReassign && r.container().function == nil { |
Alessandro Arzilli | 678bafe | 2018-12-07 17:28:35 +0100 | [diff] [blame] | 537 | r.errorf(stmt.While, "while loop not within a function") |
| 538 | } |
| 539 | r.expr(stmt.Cond) |
| 540 | r.loops++ |
| 541 | r.stmts(stmt.Body) |
| 542 | r.loops-- |
| 543 | |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 544 | case *syntax.ReturnStmt: |
| 545 | if r.container().function == nil { |
| 546 | r.errorf(stmt.Return, "return statement not within a function") |
| 547 | } |
| 548 | if stmt.Result != nil { |
| 549 | r.expr(stmt.Result) |
| 550 | } |
| 551 | |
| 552 | case *syntax.LoadStmt: |
alandonovan | cd131d1 | 2020-06-09 17:58:44 -0400 | [diff] [blame] | 553 | // A load statement may not be nested in any other statement. |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 554 | if r.container().function != nil { |
| 555 | r.errorf(stmt.Load, "load statement within a function") |
alandonovan | cd131d1 | 2020-06-09 17:58:44 -0400 | [diff] [blame] | 556 | } else if r.loops > 0 { |
| 557 | r.errorf(stmt.Load, "load statement within a loop") |
| 558 | } else if r.ifstmts > 0 { |
| 559 | r.errorf(stmt.Load, "load statement within a conditional") |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 560 | } |
| 561 | |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 562 | for i, from := range stmt.From { |
| 563 | if from.Name == "" { |
| 564 | r.errorf(from.NamePos, "load: empty identifier") |
| 565 | continue |
| 566 | } |
| 567 | if from.Name[0] == '_' { |
| 568 | r.errorf(from.NamePos, "load: names with leading underscores are not exported: %s", from.Name) |
| 569 | } |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 570 | |
| 571 | id := stmt.To[i] |
| 572 | if LoadBindsGlobally { |
| 573 | r.bind(id) |
| 574 | } else if r.bindLocal(id) && !AllowGlobalReassign { |
| 575 | // "Global" in AllowGlobalReassign is a misnomer for "toplevel". |
| 576 | // Sadly we can't report the previous declaration |
| 577 | // as id.Binding may not be set yet. |
| 578 | r.errorf(id.NamePos, "cannot reassign top-level %s", id.Name) |
| 579 | } |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 580 | } |
| 581 | |
| 582 | default: |
Alessandro Arzilli | 688506e | 2019-08-19 16:15:39 +0200 | [diff] [blame] | 583 | log.Panicf("unexpected stmt %T", stmt) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 584 | } |
| 585 | } |
| 586 | |
| 587 | func (r *resolver) assign(lhs syntax.Expr, isAugmented bool) { |
| 588 | switch lhs := lhs.(type) { |
| 589 | case *syntax.Ident: |
| 590 | // x = ... |
alandonovan | 572ecca | 2019-01-23 18:41:07 -0500 | [diff] [blame] | 591 | r.bind(lhs) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 592 | |
| 593 | case *syntax.IndexExpr: |
| 594 | // x[i] = ... |
| 595 | r.expr(lhs.X) |
| 596 | r.expr(lhs.Y) |
| 597 | |
| 598 | case *syntax.DotExpr: |
| 599 | // x.f = ... |
| 600 | r.expr(lhs.X) |
| 601 | |
| 602 | case *syntax.TupleExpr: |
| 603 | // (x, y) = ... |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 604 | if isAugmented { |
| 605 | r.errorf(syntax.Start(lhs), "can't use tuple expression in augmented assignment") |
| 606 | } |
| 607 | for _, elem := range lhs.List { |
| 608 | r.assign(elem, isAugmented) |
| 609 | } |
| 610 | |
| 611 | case *syntax.ListExpr: |
| 612 | // [x, y, z] = ... |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 613 | if isAugmented { |
| 614 | r.errorf(syntax.Start(lhs), "can't use list expression in augmented assignment") |
| 615 | } |
| 616 | for _, elem := range lhs.List { |
| 617 | r.assign(elem, isAugmented) |
| 618 | } |
| 619 | |
Laurent Le Brun | 28ceca7 | 2018-02-26 15:01:53 +0100 | [diff] [blame] | 620 | case *syntax.ParenExpr: |
| 621 | r.assign(lhs.X, isAugmented) |
| 622 | |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 623 | default: |
| 624 | name := strings.ToLower(strings.TrimPrefix(fmt.Sprintf("%T", lhs), "*syntax.")) |
| 625 | r.errorf(syntax.Start(lhs), "can't assign to %s", name) |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | func (r *resolver) expr(e syntax.Expr) { |
| 630 | switch e := e.(type) { |
| 631 | case *syntax.Ident: |
| 632 | r.use(e) |
| 633 | |
| 634 | case *syntax.Literal: |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 635 | |
| 636 | case *syntax.ListExpr: |
| 637 | for _, x := range e.List { |
| 638 | r.expr(x) |
| 639 | } |
| 640 | |
| 641 | case *syntax.CondExpr: |
| 642 | r.expr(e.Cond) |
| 643 | r.expr(e.True) |
| 644 | r.expr(e.False) |
| 645 | |
| 646 | case *syntax.IndexExpr: |
| 647 | r.expr(e.X) |
| 648 | r.expr(e.Y) |
| 649 | |
| 650 | case *syntax.DictEntry: |
| 651 | r.expr(e.Key) |
| 652 | r.expr(e.Value) |
| 653 | |
| 654 | case *syntax.SliceExpr: |
| 655 | r.expr(e.X) |
| 656 | if e.Lo != nil { |
| 657 | r.expr(e.Lo) |
| 658 | } |
| 659 | if e.Hi != nil { |
| 660 | r.expr(e.Hi) |
| 661 | } |
| 662 | if e.Step != nil { |
| 663 | r.expr(e.Step) |
| 664 | } |
| 665 | |
| 666 | case *syntax.Comprehension: |
alandonovan | 0d5491b | 2018-03-30 10:42:13 -0400 | [diff] [blame] | 667 | // The 'in' operand of the first clause (always a ForClause) |
| 668 | // is resolved in the outer block; consider: [x for x in x]. |
| 669 | clause := e.Clauses[0].(*syntax.ForClause) |
| 670 | r.expr(clause.X) |
| 671 | |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 672 | // A list/dict comprehension defines a new lexical block. |
| 673 | // Locals defined within the block will be allotted |
| 674 | // distinct slots in the locals array of the innermost |
| 675 | // enclosing container (function/module) block. |
| 676 | r.push(&block{comp: e}) |
alandonovan | 0d5491b | 2018-03-30 10:42:13 -0400 | [diff] [blame] | 677 | |
alandonovan | 572ecca | 2019-01-23 18:41:07 -0500 | [diff] [blame] | 678 | const isAugmented = false |
| 679 | r.assign(clause.Vars, isAugmented) |
alandonovan | 0d5491b | 2018-03-30 10:42:13 -0400 | [diff] [blame] | 680 | |
| 681 | for _, clause := range e.Clauses[1:] { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 682 | switch clause := clause.(type) { |
| 683 | case *syntax.IfClause: |
| 684 | r.expr(clause.Cond) |
| 685 | case *syntax.ForClause: |
alandonovan | 572ecca | 2019-01-23 18:41:07 -0500 | [diff] [blame] | 686 | r.assign(clause.Vars, isAugmented) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 687 | r.expr(clause.X) |
| 688 | } |
| 689 | } |
| 690 | r.expr(e.Body) // body may be *DictEntry |
| 691 | r.pop() |
| 692 | |
| 693 | case *syntax.TupleExpr: |
| 694 | for _, x := range e.List { |
| 695 | r.expr(x) |
| 696 | } |
| 697 | |
| 698 | case *syntax.DictExpr: |
| 699 | for _, entry := range e.List { |
| 700 | entry := entry.(*syntax.DictEntry) |
| 701 | r.expr(entry.Key) |
| 702 | r.expr(entry.Value) |
| 703 | } |
| 704 | |
| 705 | case *syntax.UnaryExpr: |
| 706 | r.expr(e.X) |
| 707 | |
| 708 | case *syntax.BinaryExpr: |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 709 | r.expr(e.X) |
| 710 | r.expr(e.Y) |
| 711 | |
| 712 | case *syntax.DotExpr: |
| 713 | r.expr(e.X) |
| 714 | // ignore e.Name |
| 715 | |
| 716 | case *syntax.CallExpr: |
| 717 | r.expr(e.Fn) |
alandonovan | 2c65f9e | 2018-12-14 13:46:48 -0500 | [diff] [blame] | 718 | var seenVarargs, seenKwargs bool |
| 719 | var seenName map[string]bool |
Josh Bleecher Snyder | 70a4f43 | 2019-01-01 12:22:42 -1000 | [diff] [blame] | 720 | var n, p int |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 721 | for _, arg := range e.Args { |
| 722 | pos, _ := arg.Span() |
| 723 | if unop, ok := arg.(*syntax.UnaryExpr); ok && unop.Op == syntax.STARSTAR { |
| 724 | // **kwargs |
| 725 | if seenKwargs { |
| 726 | r.errorf(pos, "multiple **kwargs not allowed") |
| 727 | } |
| 728 | seenKwargs = true |
| 729 | r.expr(arg) |
| 730 | } else if ok && unop.Op == syntax.STAR { |
| 731 | // *args |
| 732 | if seenKwargs { |
| 733 | r.errorf(pos, "*args may not follow **kwargs") |
| 734 | } else if seenVarargs { |
| 735 | r.errorf(pos, "multiple *args not allowed") |
| 736 | } |
| 737 | seenVarargs = true |
| 738 | r.expr(arg) |
| 739 | } else if binop, ok := arg.(*syntax.BinaryExpr); ok && binop.Op == syntax.EQ { |
| 740 | // k=v |
Josh Bleecher Snyder | 70a4f43 | 2019-01-01 12:22:42 -1000 | [diff] [blame] | 741 | n++ |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 742 | if seenKwargs { |
alandonovan | 501b6c7 | 2020-11-13 16:43:45 -0500 | [diff] [blame] | 743 | r.errorf(pos, "keyword argument may not follow **kwargs") |
| 744 | } else if seenVarargs { |
| 745 | r.errorf(pos, "keyword argument may not follow *args") |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 746 | } |
alandonovan | 2c65f9e | 2018-12-14 13:46:48 -0500 | [diff] [blame] | 747 | x := binop.X.(*syntax.Ident) |
| 748 | if seenName[x.Name] { |
| 749 | r.errorf(x.NamePos, "keyword argument %s repeated", x.Name) |
| 750 | } else { |
| 751 | if seenName == nil { |
| 752 | seenName = make(map[string]bool) |
| 753 | } |
| 754 | seenName[x.Name] = true |
| 755 | } |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 756 | r.expr(binop.Y) |
| 757 | } else { |
| 758 | // positional argument |
Josh Bleecher Snyder | 70a4f43 | 2019-01-01 12:22:42 -1000 | [diff] [blame] | 759 | p++ |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 760 | if seenVarargs { |
alandonovan | 501b6c7 | 2020-11-13 16:43:45 -0500 | [diff] [blame] | 761 | r.errorf(pos, "positional argument may not follow *args") |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 762 | } else if seenKwargs { |
alandonovan | 501b6c7 | 2020-11-13 16:43:45 -0500 | [diff] [blame] | 763 | r.errorf(pos, "positional argument may not follow **kwargs") |
alandonovan | 2c65f9e | 2018-12-14 13:46:48 -0500 | [diff] [blame] | 764 | } else if len(seenName) > 0 { |
Alan Donovan | 1b9d0e7 | 2017-11-28 14:19:10 -0500 | [diff] [blame] | 765 | r.errorf(pos, "positional argument may not follow named") |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 766 | } |
| 767 | r.expr(arg) |
| 768 | } |
| 769 | } |
| 770 | |
Josh Bleecher Snyder | 70a4f43 | 2019-01-01 12:22:42 -1000 | [diff] [blame] | 771 | // Fail gracefully if compiler-imposed limit is exceeded. |
| 772 | if p >= 256 { |
| 773 | pos, _ := e.Span() |
| 774 | r.errorf(pos, "%v positional arguments in call, limit is 255", p) |
| 775 | } |
| 776 | if n >= 256 { |
| 777 | pos, _ := e.Span() |
| 778 | r.errorf(pos, "%v keyword arguments in call, limit is 255", n) |
| 779 | } |
| 780 | |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 781 | case *syntax.LambdaExpr: |
alandonovan | 6ddc71c | 2019-06-04 09:08:55 -0400 | [diff] [blame] | 782 | fn := &Function{ |
| 783 | Name: "lambda", |
| 784 | Pos: e.Lambda, |
| 785 | Params: e.Params, |
| 786 | Body: []syntax.Stmt{&syntax.ReturnStmt{Result: e.Body}}, |
| 787 | } |
| 788 | e.Function = fn |
| 789 | r.function(fn, e.Lambda) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 790 | |
Laurent Le Brun | 28ceca7 | 2018-02-26 15:01:53 +0100 | [diff] [blame] | 791 | case *syntax.ParenExpr: |
| 792 | r.expr(e.X) |
| 793 | |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 794 | default: |
Alessandro Arzilli | 688506e | 2019-08-19 16:15:39 +0200 | [diff] [blame] | 795 | log.Panicf("unexpected expr %T", e) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 796 | } |
| 797 | } |
| 798 | |
alandonovan | 6ddc71c | 2019-06-04 09:08:55 -0400 | [diff] [blame] | 799 | func (r *resolver) function(function *Function, pos syntax.Position) { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 800 | // Resolve defaults in enclosing environment. |
| 801 | for _, param := range function.Params { |
| 802 | if binary, ok := param.(*syntax.BinaryExpr); ok { |
| 803 | r.expr(binary.Y) |
| 804 | } |
| 805 | } |
| 806 | |
| 807 | // Enter function block. |
| 808 | b := &block{function: function} |
| 809 | r.push(b) |
| 810 | |
alandonovan | b7e3b1f | 2018-12-12 17:14:58 -0500 | [diff] [blame] | 811 | var seenOptional bool |
Alan Donovan | 5215385 | 2019-02-13 19:18:15 -0500 | [diff] [blame] | 812 | var star *syntax.UnaryExpr // * or *args param |
| 813 | var starStar *syntax.Ident // **kwargs ident |
| 814 | var numKwonlyParams int |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 815 | for _, param := range function.Params { |
| 816 | switch param := param.(type) { |
| 817 | case *syntax.Ident: |
| 818 | // e.g. x |
alandonovan | b7e3b1f | 2018-12-12 17:14:58 -0500 | [diff] [blame] | 819 | if starStar != nil { |
| 820 | r.errorf(param.NamePos, "required parameter may not follow **%s", starStar.Name) |
| 821 | } else if star != nil { |
alandonovan | 8313b54 | 2019-02-15 13:17:43 -0500 | [diff] [blame] | 822 | numKwonlyParams++ |
Alan Donovan | 1b9d0e7 | 2017-11-28 14:19:10 -0500 | [diff] [blame] | 823 | } else if seenOptional { |
alandonovan | b7e3b1f | 2018-12-12 17:14:58 -0500 | [diff] [blame] | 824 | r.errorf(param.NamePos, "required parameter may not follow optional") |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 825 | } |
alandonovan | 572ecca | 2019-01-23 18:41:07 -0500 | [diff] [blame] | 826 | if r.bind(param) { |
alandonovan | b7e3b1f | 2018-12-12 17:14:58 -0500 | [diff] [blame] | 827 | r.errorf(param.NamePos, "duplicate parameter: %s", param.Name) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 828 | } |
| 829 | |
| 830 | case *syntax.BinaryExpr: |
| 831 | // e.g. y=dflt |
alandonovan | b7e3b1f | 2018-12-12 17:14:58 -0500 | [diff] [blame] | 832 | if starStar != nil { |
| 833 | r.errorf(param.OpPos, "optional parameter may not follow **%s", starStar.Name) |
| 834 | } else if star != nil { |
Alan Donovan | 5215385 | 2019-02-13 19:18:15 -0500 | [diff] [blame] | 835 | numKwonlyParams++ |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 836 | } |
alandonovan | 572ecca | 2019-01-23 18:41:07 -0500 | [diff] [blame] | 837 | if id := param.X.(*syntax.Ident); r.bind(id) { |
alandonovan | b7e3b1f | 2018-12-12 17:14:58 -0500 | [diff] [blame] | 838 | r.errorf(param.OpPos, "duplicate parameter: %s", id.Name) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 839 | } |
Alan Donovan | 1b9d0e7 | 2017-11-28 14:19:10 -0500 | [diff] [blame] | 840 | seenOptional = true |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 841 | |
| 842 | case *syntax.UnaryExpr: |
Alan Donovan | 5215385 | 2019-02-13 19:18:15 -0500 | [diff] [blame] | 843 | // * or *args or **kwargs |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 844 | if param.Op == syntax.STAR { |
alandonovan | b7e3b1f | 2018-12-12 17:14:58 -0500 | [diff] [blame] | 845 | if starStar != nil { |
Alan Donovan | 5215385 | 2019-02-13 19:18:15 -0500 | [diff] [blame] | 846 | r.errorf(param.OpPos, "* parameter may not follow **%s", starStar.Name) |
alandonovan | b7e3b1f | 2018-12-12 17:14:58 -0500 | [diff] [blame] | 847 | } else if star != nil { |
| 848 | r.errorf(param.OpPos, "multiple * parameters not allowed") |
| 849 | } else { |
Alan Donovan | 5215385 | 2019-02-13 19:18:15 -0500 | [diff] [blame] | 850 | star = param |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 851 | } |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 852 | } else { |
alandonovan | b7e3b1f | 2018-12-12 17:14:58 -0500 | [diff] [blame] | 853 | if starStar != nil { |
| 854 | r.errorf(param.OpPos, "multiple ** parameters not allowed") |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 855 | } |
Alan Donovan | 5215385 | 2019-02-13 19:18:15 -0500 | [diff] [blame] | 856 | starStar = param.X.(*syntax.Ident) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 857 | } |
| 858 | } |
| 859 | } |
Alan Donovan | 5215385 | 2019-02-13 19:18:15 -0500 | [diff] [blame] | 860 | |
| 861 | // Bind the *args and **kwargs parameters at the end, |
| 862 | // so that regular parameters a/b/c are contiguous and |
| 863 | // there is no hole for the "*": |
| 864 | // def f(a, b, *args, c=0, **kwargs) |
| 865 | // def f(a, b, *, c=0, **kwargs) |
| 866 | if star != nil { |
| 867 | if id, _ := star.X.(*syntax.Ident); id != nil { |
| 868 | // *args |
| 869 | if r.bind(id) { |
| 870 | r.errorf(id.NamePos, "duplicate parameter: %s", id.Name) |
| 871 | } |
| 872 | function.HasVarargs = true |
| 873 | } else if numKwonlyParams == 0 { |
alandonovan | 8313b54 | 2019-02-15 13:17:43 -0500 | [diff] [blame] | 874 | r.errorf(star.OpPos, "bare * must be followed by keyword-only parameters") |
Alan Donovan | 5215385 | 2019-02-13 19:18:15 -0500 | [diff] [blame] | 875 | } |
| 876 | } |
| 877 | if starStar != nil { |
| 878 | if r.bind(starStar) { |
| 879 | r.errorf(starStar.NamePos, "duplicate parameter: %s", starStar.Name) |
| 880 | } |
| 881 | function.HasKwargs = true |
| 882 | } |
| 883 | |
| 884 | function.NumKwonlyParams = numKwonlyParams |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 885 | r.stmts(function.Body) |
| 886 | |
| 887 | // Resolve all uses of this function's local vars, |
| 888 | // and keep just the remaining uses of free/global vars. |
| 889 | b.resolveLocalUses() |
| 890 | |
| 891 | // Leave function block. |
| 892 | r.pop() |
| 893 | |
| 894 | // References within the function body to globals are not |
| 895 | // resolved until the end of the module. |
| 896 | } |
| 897 | |
| 898 | func (r *resolver) resolveNonLocalUses(b *block) { |
| 899 | // First resolve inner blocks. |
| 900 | for _, child := range b.children { |
| 901 | r.resolveNonLocalUses(child) |
| 902 | } |
| 903 | for _, use := range b.uses { |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 904 | use.id.Binding = r.lookupLexical(use, use.env) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 905 | } |
| 906 | } |
| 907 | |
| 908 | // lookupLocal looks up an identifier within its immediately enclosing function. |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 909 | func lookupLocal(use use) *Binding { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 910 | for env := use.env; env != nil; env = env.parent { |
| 911 | if bind, ok := env.bindings[use.id.Name]; ok { |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 912 | if bind.Scope == Free { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 913 | // shouldn't exist till later |
Alessandro Arzilli | 688506e | 2019-08-19 16:15:39 +0200 | [diff] [blame] | 914 | log.Panicf("%s: internal error: %s, %v", use.id.NamePos, use.id.Name, bind) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 915 | } |
| 916 | return bind // found |
| 917 | } |
| 918 | if env.function != nil { |
| 919 | break |
| 920 | } |
| 921 | } |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 922 | return nil // not found in this function |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 923 | } |
| 924 | |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 925 | // lookupLexical looks up an identifier use.id within its lexically enclosing environment. |
| 926 | // The use.env field captures the original environment for error reporting. |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 927 | func (r *resolver) lookupLexical(use use, env *block) (bind *Binding) { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 928 | if debug { |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 929 | fmt.Printf("lookupLexical %s in %s = ...\n", use.id.Name, env) |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 930 | defer func() { fmt.Printf("= %v\n", bind) }() |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 931 | } |
| 932 | |
alandonovan | 754257e | 2019-04-03 16:43:05 -0400 | [diff] [blame] | 933 | // Is this the file block? |
| 934 | if env == r.file { |
| 935 | return r.useToplevel(use) // file-local, global, predeclared, or not found |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 936 | } |
| 937 | |
| 938 | // Defined in this block? |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 939 | bind, ok := env.bindings[use.id.Name] |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 940 | if !ok { |
| 941 | // Defined in parent block? |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 942 | bind = r.lookupLexical(use, env.parent) |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 943 | if env.function != nil && (bind.Scope == Local || bind.Scope == Free || bind.Scope == Cell) { |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 944 | // Found in parent block, which belongs to enclosing function. |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 945 | // Add the parent's binding to the function's freevars, |
alandonovan | 3d5a061 | 2019-03-08 16:16:44 -0500 | [diff] [blame] | 946 | // and add a new 'free' binding to the inner function's block, |
| 947 | // and turn the parent's local into cell. |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 948 | if bind.Scope == Local { |
| 949 | bind.Scope = Cell |
alandonovan | 3d5a061 | 2019-03-08 16:16:44 -0500 | [diff] [blame] | 950 | } |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 951 | index := len(env.function.FreeVars) |
| 952 | env.function.FreeVars = append(env.function.FreeVars, bind) |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 953 | bind = &Binding{ |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 954 | First: bind.First, |
alandonovan | 6db5e16 | 2019-03-11 12:31:47 -0400 | [diff] [blame] | 955 | Scope: Free, |
alandonovan | f763f8b | 2019-03-08 15:33:54 -0500 | [diff] [blame] | 956 | Index: index, |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 957 | } |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 958 | if debug { |
| 959 | fmt.Printf("creating freevar %v in function at %s: %s\n", |
alandonovan | 6ddc71c | 2019-06-04 09:08:55 -0400 | [diff] [blame] | 960 | len(env.function.FreeVars), env.function.Pos, use.id.Name) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 961 | } |
| 962 | } |
| 963 | |
| 964 | // Memoize, to avoid duplicate free vars |
| 965 | // and redundant global (failing) lookups. |
alandonovan | 6afa1bb | 2019-02-06 17:49:05 -0500 | [diff] [blame] | 966 | env.bind(use.id.Name, bind) |
Alan Donovan | 312d1a5 | 2017-10-02 10:10:28 -0400 | [diff] [blame] | 967 | } |
| 968 | return bind |
| 969 | } |