Skip to content

Commit

Permalink
resolve: move resolver types to resolver package (google#211)
Browse files Browse the repository at this point in the history
The job of the resolver is to compute a binding for
each identifer, the local and free variables for each function,
and the locals and globals for each module. As a space optimization and
for convenience, this information is saved in the syntax tree rather
than in a side table.

We have a choice between declaring these resolver data structures
in the syntax package or the resolve package. Putting them in the syntax
package, as we did prior to this change, allowed each Identifier, File,
DefStmt, and LambdaExpr to depend directly on the resolver types,
even though those types didn't really belong there.
This change moves these types to the resolver package where they belong.

The dependencies on the types are broken by using an interface{} in each case.
(The values are are all pointers, so this does not induce new allocation.)

Also, this change eliminates syntax.Function, which was a false abstraction
of DefStmt and LambdaExpr. The two syntax nodes are now fully concrete;
it is in the resolver that their shared concept of Function belongs.

This is a breaking API change, but it should affect very few clients
and be trivial to fix.
  • Loading branch information
adonovan authored Jun 4, 2019
1 parent 30ae18b commit 6ddc71c
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 140 deletions.
14 changes: 7 additions & 7 deletions internal/compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,7 @@ func (fcomp *fcomp) setPos(pos syntax.Position) {
// set emits code to store the top-of-stack value
// to the specified local, cell, or global variable.
func (fcomp *fcomp) set(id *syntax.Ident) {
bind := id.Binding
bind := id.Binding.(*resolve.Binding)
switch bind.Scope {
case resolve.Local:
fcomp.emit1(SETLOCAL, uint32(bind.Index))
Expand All @@ -1002,7 +1002,7 @@ func (fcomp *fcomp) set(id *syntax.Ident) {

// lookup emits code to push the value of the specified variable.
func (fcomp *fcomp) lookup(id *syntax.Ident) {
bind := id.Binding
bind := id.Binding.(*resolve.Binding)
if bind.Scope != resolve.Universal { // (universal lookup can't fail)
fcomp.setPos(id.NamePos)
}
Expand Down Expand Up @@ -1149,7 +1149,7 @@ func (fcomp *fcomp) stmt(stmt syntax.Stmt) {
}

case *syntax.DefStmt:
fcomp.function(stmt.Def, stmt.Name.Name, &stmt.Function)
fcomp.function(stmt.Function.(*resolve.Function))
fcomp.set(stmt.Name)

case *syntax.ForStmt:
Expand Down Expand Up @@ -1428,7 +1428,7 @@ func (fcomp *fcomp) expr(e syntax.Expr) {
fcomp.call(e)

case *syntax.LambdaExpr:
fcomp.function(e.Lambda, "lambda", &e.Function)
fcomp.function(e.Function.(*resolve.Function))

default:
start, _ := e.Span()
Expand Down Expand Up @@ -1777,9 +1777,9 @@ func (fcomp *fcomp) comprehension(comp *syntax.Comprehension, clauseIndex int) {
log.Fatalf("%s: unexpected comprehension clause %T", start, clause)
}

func (fcomp *fcomp) function(pos syntax.Position, name string, f *syntax.Function) {
func (fcomp *fcomp) function(f *resolve.Function) {
// Evaluation of the defaults may fail, so record the position.
fcomp.setPos(pos)
fcomp.setPos(f.Pos)

// To reduce allocation, we emit a combined tuple
// for the defaults and the freevars.
Expand Down Expand Up @@ -1821,7 +1821,7 @@ func (fcomp *fcomp) function(pos syntax.Position, name string, f *syntax.Functio

fcomp.emit1(MAKETUPLE, uint32(ndefaults+len(f.FreeVars)))

funcode := fcomp.pcomp.function(name, pos, f.Body, f.Locals, f.FreeVars)
funcode := fcomp.pcomp.function(f.Name, f.Pos, f.Body, f.Locals, f.FreeVars)

if debug {
// TODO(adonovan): do compilations sequentially not as a tree,
Expand Down
74 changes: 74 additions & 0 deletions resolve/binding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2019 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

import "go.starlark.net/syntax"

// This file defines resolver data types saved in the syntax tree.
// We cannot guarantee API stability for these types
// as they are closely tied to the implementation.

// A Binding contains resolver information about an identifer.
// The resolver populates the Binding field of each syntax.Identifier.
// The Binding ties together all identifiers that denote the same variable.
type Binding struct {
Scope Scope

// Index records the index into the enclosing
// - {DefStmt,File}.Locals, if Scope==Local
// - DefStmt.FreeVars, if Scope==Free
// - File.Globals, if Scope==Global.
// It is zero if Scope is Predeclared, Universal, or Undefined.
Index int

First *syntax.Ident // first binding use (iff Scope==Local/Free/Global)
}

// The Scope of Binding indicates what kind of scope it has.
type Scope uint8

const (
Undefined Scope = iota // name is not defined
Local // name is local to its function or file
Cell // name is function-local but shared with a nested function
Free // name is cell of some enclosing function
Global // name is global to module
Predeclared // name is predeclared for this module (e.g. glob)
Universal // name is universal (e.g. len)
)

var scopeNames = [...]string{
Undefined: "undefined",
Local: "local",
Cell: "cell",
Free: "free",
Global: "global",
Predeclared: "predeclared",
Universal: "universal",
}

func (scope Scope) String() string { return scopeNames[scope] }

// A Module contains resolver information about a file.
// The resolver populates the Module field of each syntax.File.
type Module struct {
Locals []*Binding // the file's (comprehension-)local variables
Globals []*Binding // the file's global variables
}

// A Function contains resolver information about a named or anonymous function.
// The resolver populates the Function field of each syntax.DefStmt and syntax.LambdaExpr.
type Function struct {
Pos syntax.Position // of DEF or LAMBDA
Name string // name of def, or "lambda"
Params []syntax.Expr // param = ident | ident=expr | * | *ident | **ident
Body []syntax.Stmt // contains synthetic 'return expr' for lambda

HasVarargs bool // whether params includes *args (convenience)
HasKwargs bool // whether params includes **kwargs (convenience)
NumKwonlyParams int // number of keyword-only optional parameters
Locals []*Binding // this function's local/cell variables, parameters first
FreeVars []*Binding // enclosing cells to capture in closure
}
49 changes: 26 additions & 23 deletions resolve/resolve.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ var (
LoadBindsGlobally = false // load creates global not file-local bindings (deprecated)
)

// File resolves the specified file.
// 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
Expand All @@ -131,8 +132,10 @@ func File(file *syntax.File, isPredeclared, isUniversal func(name string) bool)
// Function bodies may contain forward references to later global declarations.
r.resolveNonLocalUses(r.env)

file.Locals = r.moduleLocals
file.Globals = r.moduleGlobals
file.Module = &Module{
Locals: r.moduleLocals,
Globals: r.moduleGlobals,
}

if len(r.errors) > 0 {
return r.errors
Expand Down Expand Up @@ -180,20 +183,6 @@ func newResolver(isPredeclared, isUniversal func(name string) bool) *resolver {
}
}

// Declare aliases for types and constants that logically belong here.

type Binding = syntax.Binding

const (
Undefined = syntax.UndefinedScope
Local = syntax.LocalScope
Cell = syntax.CellScope
Free = syntax.FreeScope
Global = syntax.GlobalScope
Predeclared = syntax.PredeclaredScope
Universal = syntax.UniversalScope
)

type resolver struct {
// env is the current local environment:
// a linked list of blocks, innermost first.
Expand Down Expand Up @@ -244,7 +233,7 @@ type block struct {
parent *block // nil for file block

// In the file (root) block, both these fields are nil.
function *syntax.Function // only for function blocks
function *Function // only for function blocks
comp *syntax.Comprehension // only for comprehension blocks

// bindings maps a name to its binding.
Expand Down Expand Up @@ -272,7 +261,7 @@ func (b *block) bind(name string, bind *Binding) {

func (b *block) String() string {
if b.function != nil {
return "function block at " + fmt.Sprint(b.function.Span())
return "function block at " + fmt.Sprint(b.function.Pos)
}
if b.comp != nil {
return "comprehension block at " + fmt.Sprint(b.comp.Span())
Expand Down Expand Up @@ -502,7 +491,14 @@ func (r *resolver) stmt(stmt syntax.Stmt) {
r.errorf(stmt.Def, doesnt+"support nested def")
}
r.bind(stmt.Name)
r.function(stmt.Def, stmt.Name.Name, &stmt.Function)
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 {
Expand Down Expand Up @@ -773,7 +769,14 @@ func (r *resolver) expr(e syntax.Expr) {
if !AllowLambda {
r.errorf(e.Lambda, doesnt+"support lambda")
}
r.function(e.Lambda, "lambda", &e.Function)
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)
Expand All @@ -783,7 +786,7 @@ func (r *resolver) expr(e syntax.Expr) {
}
}

func (r *resolver) function(pos syntax.Position, name string, function *syntax.Function) {
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 {
Expand Down Expand Up @@ -944,7 +947,7 @@ func (r *resolver) lookupLexical(use use, env *block) (bind *Binding) {
}
if debug {
fmt.Printf("creating freevar %v in function at %s: %s\n",
len(env.function.FreeVars), fmt.Sprint(env.function.Span()), use.id.Name)
len(env.function.FreeVars), env.function.Pos, use.id.Name)
}
}

Expand Down
4 changes: 2 additions & 2 deletions resolve/resolve_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestDefVarargsAndKwargsSet(t *testing.T) {
if err := resolve.File(file, isPredeclared, isUniversal); err != nil {
t.Fatal(err)
}
fn := file.Stmts[0].(*syntax.DefStmt)
fn := file.Stmts[0].(*syntax.DefStmt).Function.(*resolve.Function)
if !fn.HasVarargs {
t.Error("HasVarargs not set")
}
Expand All @@ -78,7 +78,7 @@ func TestLambdaVarargsAndKwargsSet(t *testing.T) {
if err := resolve.File(file, isPredeclared, isUniversal); err != nil {
t.Fatal(err)
}
lam := file.Stmts[0].(*syntax.AssignStmt).RHS.(*syntax.LambdaExpr)
lam := file.Stmts[0].(*syntax.AssignStmt).RHS.(*syntax.LambdaExpr).Function.(*resolve.Function)
if !lam.HasVarargs {
t.Error("HasVarargs not set")
}
Expand Down
3 changes: 2 additions & 1 deletion starlark/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,8 @@ func FileProgram(f *syntax.File, isPredeclared func(string) bool) (*Program, err
pos = syntax.MakePosition(&f.Path, 1, 1)
}

compiled := compile.File(f.Stmts, pos, "<toplevel>", f.Locals, f.Globals)
module := f.Module.(*resolve.Module)
compiled := compile.File(f.Stmts, pos, "<toplevel>", module.Locals, module.Globals)

return &Program{compiled}, nil
}
Expand Down
4 changes: 2 additions & 2 deletions starlarkstruct/struct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"path/filepath"
"testing"

"go.starlark.net/starlark"
"go.starlark.net/resolve"
"go.starlark.net/starlark"
"go.starlark.net/starlarkstruct"
"go.starlark.net/starlarktest"
)
Expand Down Expand Up @@ -67,7 +67,7 @@ func (sym *symbol) Name() string { return sym.name }
func (sym *symbol) String() string { return sym.name }
func (sym *symbol) Type() string { return "symbol" }
func (sym *symbol) Freeze() {} // immutable
func (sym *symbol) Truth() starlark.Bool { return starlark.True }
func (sym *symbol) Truth() starlark.Bool { return starlark.True }
func (sym *symbol) Hash() (uint32, error) { return 0, fmt.Errorf("unhashable: %s", sym.Type()) }

func (sym *symbol) CallInternal(thread *starlark.Thread, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
Expand Down
48 changes: 0 additions & 48 deletions syntax/binding.go

This file was deleted.

18 changes: 6 additions & 12 deletions syntax/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,13 +164,10 @@ func (p *parser) parseDefStmt() Stmt {
p.consume(COLON)
body := p.parseSuite()
return &DefStmt{
Def: defpos,
Name: id,
Function: Function{
StartPos: defpos,
Params: params,
Body: body,
},
Def: defpos,
Name: id,
Params: params,
Body: body,
}
}

Expand Down Expand Up @@ -569,11 +566,8 @@ func (p *parser) parseLambda(allowCond bool) Expr {

return &LambdaExpr{
Lambda: lambda,
Function: Function{
StartPos: lambda,
Params: params,
Body: []Stmt{&ReturnStmt{Result: body}},
},
Params: params,
Body: body,
}
}

Expand Down
Loading

0 comments on commit 6ddc71c

Please sign in to comment.