~mna/snow

56c35e06401756e4525039f117507ab1c79ec4ab — Martin Angers 1 year, 8 months ago 9da316f
pkg/codegen: finalize codegen from list of nodes
3 files changed, 233 insertions(+), 227 deletions(-)

M pkg/codegen/codegen.go
M pkg/codegen/translate.go
M pkg/codegen/treebuild.go
M pkg/codegen/codegen.go => pkg/codegen/codegen.go +1 -1
@@ 84,7 84,7 @@ func ExecUnit(outDir string, unit *semantic.Unit) ([]string, error) {
	// 4. build the Go AST tree from the Snow-associated nodes so that relative
	//    position of nodes respects the snow source
	tb := &treebuilder{
		nodeToGenerated: t.nodeToGenerated,
		filesList: t.filesList,
	}
	semantic.Walk(tb, unit)
	gofiles := tb.gofiles

M pkg/codegen/translate.go => pkg/codegen/translate.go +98 -65
@@ 42,32 42,36 @@ type translator struct {
	// map associating placeholder marks in their container where generic
	// instantiations should be inserted. The associated Element is a container
	// with no associated Go nodes.
	genDeclMarks map[semantic.Decl]*list.Element
	genDeclMarks map[semantic.Decl]*mark
}

type convertFnKey struct {
	from, to semantic.Type
}

type mark struct {
	list *list.List
	elem *list.Element
}

// file, block, if, guard, struct and fn are containers
type container struct {
	snode    semantic.Node
	nodes    []goast.Node
	children *list.List
}

func newContainer(ns ...goast.Node) *container {
func newContainer(snode semantic.Node, ns ...goast.Node) *container {
	return &container{
		snode:    snode,
		nodes:    ns,
		children: list.New(),
	}
}

type topLevelNode struct {
	goast.Node
}

func (t *translator) withContainer(c *container, fn func()) {
	old := t.parentContainer
	t.parentContainer = c.children
	defer func() { t.parentContainer = old }()
	fn()
}


@@ 95,7 99,7 @@ func (t *translator) Visit(n semantic.Node) semantic.Visitor {
	case *semantic.Unit:
		t.convertFns = make(map[convertFnKey]string)
		t.renderedGenInsts = make(map[string]bool)
		t.genDeclMarks = make(map[semantic.Decl]*list.Element)
		t.genDeclMarks = make(map[semantic.Decl]*mark)
		t.filesList = list.New()
		for _, f := range n.Files {
			t.curFile = f


@@ 106,7 110,7 @@ func (t *translator) Visit(n semantic.Node) semantic.Visitor {
		// create package clause, imports if any
		gof := &goast.File{Name: &goast.Ident{Name: "main"}}
		t.addFileImports(n, gof)
		cont := newContainer(gof)
		cont := newContainer(n, gof)
		t.filesList.PushBack(cont)
		t.topLevel = cont.children



@@ 127,26 131,27 @@ func (t *translator) Visit(n semantic.Node) semantic.Visitor {
	case *semantic.Fn:
		if n.IsGeneric() {
			// go nodes will be generated only on instantiation, create an empty container for now
			mark := newContainer()
			t.genDeclMarks[n] = t.parentContainer.PushBack(mark)
			var m *mark
			if n.Scope().IsTopLevel() || n.MethodOf != nil {
				m = &mark{list: t.topLevel, elem: t.topLevel.PushBack(newContainer(n))}
			} else {
				m = &mark{list: t.parentContainer, elem: t.parentContainer.PushBack(newContainer(n))}
			}
			t.genDeclMarks[n] = m
			break
		}
		name := t.resolver.NameForDecl(n)
		// TODO: createFn should happen inside that container as parent...
		cont := newContainer(t.createFn(name, n)...)
		t.parentContainer.PushBack(cont)
		t.createFn(name, n)

	case *semantic.Struct:
		if n.IsGeneric() {
			// go nodes will be generated only on instantiation, create an empty container for now
			mark := newContainer()
			t.genDeclMarks[n] = t.parentContainer.PushBack(mark)
			m := &mark{list: t.topLevel, elem: t.topLevel.PushBack(newContainer(n))}
			t.genDeclMarks[n] = m
			break
		}
		name := t.resolver.NameForDecl(n)
		// TODO: createStruct should happen inside that container as parent...
		cont := newContainer(t.createStruct(name, semantic.AsStructType(n.Type()))...)
		t.parentContainer.PushBack(cont)
		t.createStruct(name, semantic.AsStructType(n.Type()))

	case *semantic.Var:
		name := t.resolver.NameForDecl(n)


@@ 170,7 175,7 @@ func (t *translator) Visit(n semantic.Node) semantic.Visitor {
		bs := &goast.BlockStmt{
			List: make([]goast.Stmt, 0, len(n.Stmts)),
		}
		cont := newContainer(bs)
		cont := newContainer(n, bs)
		t.parentContainer.PushBack(cont)
		t.withContainer(cont, func() {
			for _, stmt := range n.Stmts {


@@ 216,7 221,7 @@ func (t *translator) Visit(n semantic.Node) semantic.Visitor {
			is.Cond = and
		}

		cont := newContainer(is)
		cont := newContainer(n, is)
		t.parentContainer.PushBack(cont)
		t.withContainer(cont, func() {
			semantic.Walk(t, n.Body)


@@ 244,7 249,7 @@ func (t *translator) Visit(n semantic.Node) semantic.Visitor {

		// empty "if" body
		is.Body = &goast.BlockStmt{}
		cont := newContainer(is)
		cont := newContainer(n, is)
		t.parentContainer.PushBack(cont)
		t.withContainer(cont, func() {
			semantic.Walk(t, n.Else)


@@ 389,7 394,7 @@ func (t *translator) Visit(n semantic.Node) semantic.Visitor {
	return nil
}

func (t *translator) createFn(nm string, fn *semantic.Fn) []goast.Node {
func (t *translator) createFn(nm string, fn *semantic.Fn) {
	ft := t.goTypeExprFor(fn.Type()).(*goast.FuncType)
	// function type has no name for parameters, add them in
	for i, p := range ft.Params.List {


@@ 397,12 402,18 @@ func (t *translator) createFn(nm string, fn *semantic.Fn) []goast.Node {
		p.Names = []*goast.Ident{{Name: nm}}
	}

	// create the container but don't set its Go nodes yet, we don't know
	// if it will be a func literal or declaration.
	cont := &container{snode: fn, children: list.New()}

	var body *goast.BlockStmt
	if fn.Body == nil {
		body = createExternalFnBody(ft, t.fnImports[fn])
	} else {
		semantic.Walk(t, fn.Body)
	}
	t.withContainer(cont, func() {
		if fn.Body == nil {
			body = createExternalFnBody(ft, t.fnImports[fn])
		} else {
			semantic.Walk(t, fn.Body)
		}
	})

	// the function declaration is a top-level func statement if it is top-level
	// in snow or is a struct method.


@@ 415,30 426,62 @@ func (t *translator) createFn(nm string, fn *semantic.Fn) []goast.Node {
		if str := fn.MethodOf; str != nil {
			t.convertFuncToMethod(fd, str, fn.IsRef)
		}
		return []goast.Node{topLevelNode{fd}}

		cont.nodes = []goast.Node{fd}
		if mark := t.genDeclMarks[fn]; mark != nil {
			mark.list.InsertBefore(cont, mark.elem)
		} else {
			t.topLevel.PushBack(cont)
		}
		return
	}

	cont.nodes = createFuncLit(nm, ft, body)
	if mark := t.genDeclMarks[fn]; mark != nil {
		mark.list.InsertBefore(cont, mark.elem)
	} else {
		t.parentContainer.PushBack(cont)
	}
	return createFuncLit(nm, ft, body)
}

func (t *translator) createStruct(nm string, strTyp *semantic.StructType) []goast.Node {
func (t *translator) convertFuncToMethod(fn *goast.FuncDecl, str *semantic.Struct, isRef bool) {
	typ := t.goTypeExprFor(str.Type())
	if isRef {
		typ = &goast.StarExpr{X: typ}
	}
	fn.Recv = &goast.FieldList{
		List: []*goast.Field{{
			Names: []*goast.Ident{{Name: semantic.SelfVarName}},
			Type:  typ,
		}},
	}
}

func (t *translator) createStruct(nm string, strTyp *semantic.StructType) {
	// all struct declarations are attached to the top-level scope of the file.
	fields := &goast.FieldList{List: make([]*goast.Field, len(strTyp.Decl.Vars))}

	// create the container but don't set its Go node yet, it will be created
	// only after looping over the vars.
	cont := &container{snode: strTyp.Decl, children: list.New()}

	// keep a map of field values, will be used to generate the struct
	// initializer
	propInit := make(map[*semantic.Var][]goast.Expr)
	for i, prop := range strTyp.Decl.Vars {
		semantic.Walk(t, prop)
		propDecls := t.nodeToGenerated[prop]
		vs := propDecls[len(propDecls)-1].(*goast.GenDecl).Specs[0].(*goast.ValueSpec)
		fields.List[i] = &goast.Field{
			Names: vs.Names,
			Type:  vs.Type,
		}
		if prop.Value != nil {
			propInit[prop] = vs.Values
	t.withContainer(cont, func() {
		for i, prop := range strTyp.Decl.Vars {
			semantic.Walk(t, prop)
			node := t.parentContainer.Back().Value
			vs := node.(*goast.GenDecl).Specs[0].(*goast.ValueSpec)
			fields.List[i] = &goast.Field{
				Names: vs.Names,
				Type:  vs.Type,
			}
			if prop.Value != nil {
				propInit[prop] = vs.Values
			}
		}
	}
	})

	decl := &goast.GenDecl{
		Tok: gotoken.TYPE,


@@ 446,6 489,12 @@ func (t *translator) createStruct(nm string, strTyp *semantic.StructType) []goas
			&goast.TypeSpec{Name: &goast.Ident{Name: nm}, Type: &goast.StructType{Fields: fields}},
		},
	}
	cont.nodes = []goast.Node{decl}
	if mark := t.genDeclMarks[strTyp.Decl]; mark != nil {
		mark.list.InsertBefore(cont, mark.elem)
	} else {
		t.topLevel.PushBack(cont)
	}

	// generate struct initializer function
	strInit := t.createStructInit(nm, strTyp, propInit)


@@ 458,7 507,11 @@ func (t *translator) createStruct(nm string, strTyp *semantic.StructType) []goas
		semantic.Walk(t, str)
	}

	return []goast.Node{topLevelNode{decl}, topLevelNode{strInit}}
	if mark := t.genDeclMarks[strTyp.Decl]; mark != nil {
		mark.list.InsertBefore(strInit, mark.elem)
	} else {
		t.topLevel.PushBack(strInit)
	}
}

func (t *translator) addFileImports(f *semantic.File, gof *goast.File) {


@@ 605,24 658,11 @@ func (t *translator) getOrCreateConvertFn(from, to *semantic.TupleType) string {
		Results: []goast.Expr{cl},
	}
	fd.Body.List[0] = ret
	t.append(t.curFile, fd)
	t.topLevel.PushBack(fd)

	return fnName
}

func (t *translator) convertFuncToMethod(fn *goast.FuncDecl, str *semantic.Struct, isRef bool) {
	typ := t.goTypeExprFor(str.Type())
	if isRef {
		typ = &goast.StarExpr{X: typ}
	}
	fn.Recv = &goast.FieldList{
		List: []*goast.Field{{
			Names: []*goast.Ident{{Name: semantic.SelfVarName}},
			Type:  typ,
		}},
	}
}

func (t *translator) callStructInit(c *semantic.Call) *goast.CallExpr {
	// map of struct labels to field decls
	// TODO: memoize this to optimize codegen a bit


@@ 766,9 806,6 @@ func (t *translator) renderGenInst(gi *semantic.GenericInst, name string) {

	t.renderedGenInsts[name] = true

	parentMap, newMap := t.nodeToGenerated, make(map[semantic.Node][]interface{})
	t.nodeToGenerated = newMap

	decl := gi.GenericDecl.Ref
	gd := decl.(semantic.GenericDecl)
	_, insts := gd.GenInsts()


@@ 779,16 816,12 @@ func (t *translator) renderGenInst(gi *semantic.GenericInst, name string) {

	switch decl := decl.(type) {
	case *semantic.Fn:
		t.append(decl, t.createFn(name, decl)...)
		t.createFn(name, decl)
	case *semantic.Struct:
		t.append(decl, t.createStruct(name, semantic.AsStructType(gi.Type()))...)
		t.createStruct(name, semantic.AsStructType(gi.Type()))
	default:
		panic(fmt.Sprintf("invalid generic type to render: %T", decl))
	}

	// reset the nodeToGenerated map and record the sub-map with the generic declaration
	parentMap[decl] = append(parentMap[decl], t.nodeToGenerated)
	t.nodeToGenerated = parentMap
}

func createFuncLit(name string, ft *goast.FuncType, body *goast.BlockStmt) []goast.Node {

M pkg/codegen/treebuild.go => pkg/codegen/treebuild.go +134 -161
@@ 1,6 1,7 @@
package codegen

import (
	"container/list"
	"fmt"
	goast "go/ast"



@@ 10,57 11,12 @@ import (
// The treebuilder builds the Go AST from the nodes associated with the
// abstract semantic graph in the translator phase.
type treebuilder struct {
	curGoFile       *goast.File
	gofiles         []*goast.File
	nodeToGenerated map[semantic.Node][]interface{}
}

func (tb *treebuilder) appendDecl(list *[]goast.Decl, n semantic.Node, vs []interface{}) {
	for _, v := range vs {
		if tln, ok := v.(topLevelNode); ok {
			v = tln.Node
		}

		switch v := v.(type) {
		case goast.Node:
			*list = append(*list, v.(goast.Decl))

		case map[semantic.Node][]interface{}:
			// inside that nested map, the Go AST node for that node is the one to append
			tb.appendDecl(list, n, v[n])

		default:
			panic(fmt.Sprintf("invalid value type in nodeToGenerated map: %T", v))
		}
	}
}

func (tb *treebuilder) appendStmt(list *[]goast.Stmt, n semantic.Node, vs []interface{}) {
	for _, v := range vs {
		switch v := v.(type) {
		case goast.Node:
			if tln, ok := v.(topLevelNode); ok {
				tb.curGoFile.Decls = append(tb.curGoFile.Decls, tln.Node.(goast.Decl))
			} else {
				if gd, ok := v.(*goast.GenDecl); ok {
					v = &goast.DeclStmt{Decl: gd}
				}
				*list = append(*list, v.(goast.Stmt))
			}
	filesList *list.List

		case map[semantic.Node][]interface{}:
			// inside that nested map, the Go AST node for stmt is the one to append
			tb.appendStmt(list, n, v[n])

		default:
			panic(fmt.Sprintf("invalid value type in nodeToGenerated map: %T", v))
		}
	}
}
	curGoFile *goast.File
	gofiles   []*goast.File

func (tb *treebuilder) generatedStmt(n semantic.Node) goast.Stmt {
	ns := tb.nodeToGenerated[n]
	return ns[0].(goast.Stmt)
	curContainer *container
}

func (tb *treebuilder) Visit(n semantic.Node) semantic.Visitor {


@@ 72,153 28,170 @@ func (tb *treebuilder) Visit(n semantic.Node) semantic.Visitor {
		}

	case *semantic.File:
		// file node should have its goast.File as first node, rest should be added
		// to the file's declarations.
		ns := tb.nodeToGenerated[n]
		tb.curGoFile = ns[0].(*goast.File)
		tb.appendDecl(&tb.curGoFile.Decls, n, ns[1:])
		elem := tb.filesList.Front()
		tb.filesList.Remove(elem)
		cont := elem.Value.(*container)
		if len(cont.nodes) != 1 {
			panic(fmt.Sprintf("file element should have a single node, got %d", len(cont.nodes)))
		}

		tb.curGoFile = cont.nodes[0].(*goast.File)
		for n := cont.children.Front(); n != nil; n = n.Next() {
			switch v := n.Value.(type) {
			case goast.Decl:
				tb.curGoFile.Decls = append(tb.curGoFile.Decls, v)

			case *container:
				if len(v.nodes) > 0 {
					tb.curContainer = v
					semantic.Walk(tb, v.snode)
				}
				for _, node := range v.nodes {
					tb.curGoFile.Decls = append(tb.curGoFile.Decls, node.(goast.Decl))
				}

		decls := make([]semantic.Decl, 0, len(n.Vars)+len(n.Structs)+len(n.Fns))
		for _, v := range n.Vars {
			semantic.Walk(tb, v)
			decls = append(decls, v)
		}
		for _, str := range n.Structs {
			semantic.Walk(tb, str)
			decls = append(decls, str)
		}
		for _, fn := range n.Fns {
			semantic.Walk(tb, fn)
			decls = append(decls, fn)
		}
		for _, decl := range decls {
			tb.appendDecl(&tb.curGoFile.Decls, decl, tb.nodeToGenerated[decl])
			default:
				panic(fmt.Sprintf("invalid element value type: %T", v))
			}
		}

	case *semantic.Fn:
		cont := tb.curContainer
		if n.Body == nil {
			if count := cont.children.Len(); count != 0 {
				panic(fmt.Sprintf("external function should have no child, got %d", count))
			}
			break
		}

		for _, v := range tb.nodeToGenerated[n] {
			if tln, ok := v.(topLevelNode); ok {
				v = tln.Node
		// fn children should be a single container with a single Go node, the body block
		if count := cont.children.Len(); count != 1 {
			panic(fmt.Sprintf("function should have a single child, the body block, got %d", count))
		}
		var body *goast.BlockStmt
		for n := cont.children.Front(); n != nil; n = n.Next() {
			switch v := n.Value.(type) {
			case *container:
				if len(v.nodes) > 0 {
					tb.curContainer = v
					semantic.Walk(tb, v.snode)
				}
				if len(v.nodes) != 1 {
					panic(fmt.Sprintf("function body block should have a single Go node, got %d", len(v.nodes)))
				}
				body = v.nodes[0].(*goast.BlockStmt)

			default:
				panic(fmt.Sprintf("invalid element value type: %T", v))
			}
		}

			switch v := v.(type) {
		// find the body target
		for _, node := range cont.nodes {
			switch node := node.(type) {
			case *goast.FuncDecl:
				semantic.Walk(tb, n.Body)
				v.Body = tb.generatedStmt(n.Body).(*goast.BlockStmt)

				node.Body = body
			case *goast.AssignStmt:
				semantic.Walk(tb, n.Body)
				fnLit := v.Rhs[0].(*goast.FuncLit)
				fnLit.Body = tb.generatedStmt(n.Body).(*goast.BlockStmt)

			case map[semantic.Node][]interface{}:
				prev := tb.nodeToGenerated
				tb.nodeToGenerated = v
				semantic.Walk(tb, n)
				tb.nodeToGenerated = prev

			case *goast.DeclStmt:
				// nothing to do, always paired with an AssignStmt
			default:
				panic(fmt.Sprintf("invalid generated node type for function: %T", v))
				node.Rhs[0].(*goast.FuncLit).Body = body
			}
		}

	case *semantic.Struct:
		for _, v := range tb.nodeToGenerated[n] {
			if tln, ok := v.(topLevelNode); ok {
				v = tln.Node
			}
		// a struct container has a single Go node, the struct decl, and it is fully
		// formed, no need to append children, so nothing to do.

			switch v := v.(type) {
			case map[semantic.Node][]interface{}:
				prev := tb.nodeToGenerated
				tb.nodeToGenerated = v
				for _, fn := range n.Fns {
					semantic.Walk(tb, fn)
					tb.appendDecl(&tb.curGoFile.Decls, fn, tb.nodeToGenerated[fn])
				}
				for _, str := range n.Structs {
					semantic.Walk(tb, str)
					tb.appendDecl(&tb.curGoFile.Decls, str, tb.nodeToGenerated[str])
				}
				tb.nodeToGenerated = prev
	case *semantic.Block:
		cont := tb.curContainer
		if len(cont.nodes) != 1 {
			panic(fmt.Sprintf("block should have a single Go node, got %d", len(cont.nodes)))
		}
		bs := cont.nodes[0].(*goast.BlockStmt)

			case *goast.GenDecl:
				// the struct declaration
				for _, fn := range n.Fns {
					semantic.Walk(tb, fn)
					tb.appendDecl(&tb.curGoFile.Decls, fn, tb.nodeToGenerated[fn])
		for n := cont.children.Front(); n != nil; n = n.Next() {
			switch v := n.Value.(type) {
			case goast.Stmt:
				bs.List = append(bs.List, v)

			case *container:
				if len(v.nodes) > 0 {
					tb.curContainer = v
					semantic.Walk(tb, v.snode)
				}
				for _, str := range n.Structs {
					semantic.Walk(tb, str)
					tb.appendDecl(&tb.curGoFile.Decls, str, tb.nodeToGenerated[str])
				for _, node := range v.nodes {
					bs.List = append(bs.List, node.(goast.Stmt))
				}

			case *goast.FuncDecl:
				// the struct init, always paired with a GenDecl
			case *goast.GenDecl:
				bs.List = append(bs.List, &goast.DeclStmt{Decl: v})

			default:
				panic(fmt.Sprintf("invalid generated node type for struct: %T", v))
				panic(fmt.Sprintf("invalid element value type: %T", v))
			}
		}

	case *semantic.Block:
		// block should have a single associated statement
		ns := tb.nodeToGenerated[n]
		if len(ns) != 1 {
			panic(fmt.Sprintf("block statement should have 1 associated Go node, got %d", len(ns)))
	case *semantic.If:
		cont := tb.curContainer
		if len(cont.nodes) != 1 {
			panic(fmt.Sprintf("if should have a single Go node, got %d", len(cont.nodes)))
		}
		block := ns[0].(*goast.BlockStmt)
		is := cont.nodes[0].(*goast.IfStmt)

		for _, stmt := range n.Stmts {
			semantic.Walk(tb, stmt)
			tb.appendStmt(&block.List, stmt, tb.nodeToGenerated[stmt])
		wantChild := 1
		if n.Else != nil {
			wantChild = 2
		}

	case *semantic.If:
		// if should have a single associated statement
		ns := tb.nodeToGenerated[n]
		if len(ns) != 1 {
			panic(fmt.Sprintf("if statement should have 1 associated Go node, got %d", len(ns)))
		if count := cont.children.Len(); count != wantChild {
			panic(fmt.Sprintf("if should have %d child node(s), got %d", wantChild, count))
		}
		ifs := ns[0].(*goast.IfStmt)

		semantic.Walk(tb, n.Body)
		ifs.Body = tb.generatedStmt(n.Body).(*goast.BlockStmt)
		if n.Else != nil {
			semantic.Walk(tb, n.Else)
			ifs.Else = tb.generatedStmt(n.Else)
		for n := cont.children.Front(); n != nil; n = n.Next() {
			switch v := n.Value.(type) {
			case *container:
				if len(v.nodes) > 0 {
					tb.curContainer = v
					semantic.Walk(tb, v.snode)
				}
				if len(v.nodes) != 1 {
					panic(fmt.Sprintf("if block should have a single Go node, got %d", len(v.nodes)))
				}
				if is.Body == nil {
					is.Body = v.nodes[0].(*goast.BlockStmt)
				} else {
					is.Else = v.nodes[0].(goast.Stmt)
				}

			default:
				panic(fmt.Sprintf("invalid element value type: %T", v))
			}
		}

	case *semantic.Guard:
		// guard should have a single associated statement
		ns := tb.nodeToGenerated[n]
		if len(ns) != 1 {
			panic(fmt.Sprintf("guard statement should have 1 associated Go node, got %d", len(ns)))
		}
		ifs := ns[0].(*goast.IfStmt)
		semantic.Walk(tb, n.Else)
		ifs.Else = tb.generatedStmt(n.Else)

	case *semantic.Var, *semantic.Return, *semantic.Assign, *semantic.ExprStmt:
		ns := tb.nodeToGenerated[n]
		if len(ns) != 1 {
			panic(fmt.Sprintf("statement should have 1 associated Go node, got %d", len(ns)))
		}
		/*
			var stmt goast.Stmt
			if gd, ok := ns[0].(*goast.GenDecl); ok {
				stmt = &goast.DeclStmt{Decl: gd}
			} else {
				stmt = ns[0].(goast.Stmt)
		cont := tb.curContainer
		if len(cont.nodes) != 1 {
			panic(fmt.Sprintf("guard should have a single Go node, got %d", len(cont.nodes)))
		}
		is := cont.nodes[0].(*goast.IfStmt)

		if count := cont.children.Len(); count != 1 {
			panic(fmt.Sprintf("guard should have a single child, got %d", count))
		}

		for n := cont.children.Front(); n != nil; n = n.Next() {
			switch v := n.Value.(type) {
			case *container:
				if len(v.nodes) > 0 {
					tb.curContainer = v
					semantic.Walk(tb, v.snode)
				}
				if len(v.nodes) != 1 {
					panic(fmt.Sprintf("guard block should have a single Go node, got %d", len(v.nodes)))
				}
				is.Else = v.nodes[0].(*goast.BlockStmt)

			default:
				panic(fmt.Sprintf("invalid element value type: %T", v))
			}
			tb.stmt = stmt
		*/
		}
	}
	return nil
}