1package compiler
2
3import (
4	"fmt"
5	"go/ast"
6	"go/constant"
7	"go/token"
8	"go/types"
9	"strings"
10
11	"github.com/gopherjs/gopherjs/compiler/analysis"
12	"github.com/gopherjs/gopherjs/compiler/astutil"
13	"github.com/gopherjs/gopherjs/compiler/filter"
14	"github.com/gopherjs/gopherjs/compiler/typesutil"
15)
16
17func (c *funcContext) translateStmtList(stmts []ast.Stmt) {
18	for _, stmt := range stmts {
19		c.translateStmt(stmt, nil)
20	}
21	c.SetPos(token.NoPos)
22}
23
24func (c *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) {
25	c.SetPos(stmt.Pos())
26
27	stmt = filter.IncDecStmt(stmt, c.p.Info.Info)
28	stmt = filter.Assign(stmt, c.p.Info.Info, c.p.Info.Pkg)
29
30	switch s := stmt.(type) {
31	case *ast.BlockStmt:
32		c.translateStmtList(s.List)
33
34	case *ast.IfStmt:
35		var caseClauses []*ast.CaseClause
36		ifStmt := s
37		for {
38			if ifStmt.Init != nil {
39				panic("simplification error")
40			}
41			caseClauses = append(caseClauses, &ast.CaseClause{List: []ast.Expr{ifStmt.Cond}, Body: ifStmt.Body.List})
42			elseStmt, ok := ifStmt.Else.(*ast.IfStmt)
43			if !ok {
44				break
45			}
46			ifStmt = elseStmt
47		}
48		var defaultClause *ast.CaseClause
49		if block, ok := ifStmt.Else.(*ast.BlockStmt); ok {
50			defaultClause = &ast.CaseClause{Body: block.List}
51		}
52		c.translateBranchingStmt(caseClauses, defaultClause, false, c.translateExpr, nil, c.Flattened[s])
53
54	case *ast.SwitchStmt:
55		if s.Init != nil || s.Tag != nil || len(s.Body.List) != 1 {
56			panic("simplification error")
57		}
58		clause := s.Body.List[0].(*ast.CaseClause)
59		if len(clause.List) != 0 {
60			panic("simplification error")
61		}
62
63		prevFlowData := c.flowDatas[nil]
64		data := &flowData{
65			postStmt:  prevFlowData.postStmt,  // for "continue" of outer loop
66			beginCase: prevFlowData.beginCase, // same
67		}
68		c.flowDatas[nil] = data
69		c.flowDatas[label] = data
70		defer func() {
71			delete(c.flowDatas, label)
72			c.flowDatas[nil] = prevFlowData
73		}()
74
75		if c.Flattened[s] {
76			data.endCase = c.caseCounter
77			c.caseCounter++
78
79			c.Indent(func() {
80				c.translateStmtList(clause.Body)
81			})
82			c.Printf("case %d:", data.endCase)
83			return
84		}
85
86		if label != nil || analysis.HasBreak(clause) {
87			if label != nil {
88				c.Printf("%s:", label.Name())
89			}
90			c.Printf("switch (0) { default:")
91			c.Indent(func() {
92				c.translateStmtList(clause.Body)
93			})
94			c.Printf("}")
95			return
96		}
97
98		c.translateStmtList(clause.Body)
99
100	case *ast.TypeSwitchStmt:
101		if s.Init != nil {
102			c.translateStmt(s.Init, nil)
103		}
104		refVar := c.newVariable("_ref")
105		var expr ast.Expr
106		switch a := s.Assign.(type) {
107		case *ast.AssignStmt:
108			expr = a.Rhs[0].(*ast.TypeAssertExpr).X
109		case *ast.ExprStmt:
110			expr = a.X.(*ast.TypeAssertExpr).X
111		}
112		c.Printf("%s = %s;", refVar, c.translateExpr(expr))
113		translateCond := func(cond ast.Expr) *expression {
114			if types.Identical(c.p.TypeOf(cond), types.Typ[types.UntypedNil]) {
115				return c.formatExpr("%s === $ifaceNil", refVar)
116			}
117			return c.formatExpr("$assertType(%s, %s, true)[1]", refVar, c.typeName(c.p.TypeOf(cond)))
118		}
119		var caseClauses []*ast.CaseClause
120		var defaultClause *ast.CaseClause
121		for _, cc := range s.Body.List {
122			clause := cc.(*ast.CaseClause)
123			var bodyPrefix []ast.Stmt
124			if implicit := c.p.Implicits[clause]; implicit != nil {
125				value := refVar
126				if typesutil.IsJsObject(implicit.Type().Underlying()) {
127					value += ".$val.object"
128				} else if _, ok := implicit.Type().Underlying().(*types.Interface); !ok {
129					value += ".$val"
130				}
131				bodyPrefix = []ast.Stmt{&ast.AssignStmt{
132					Lhs: []ast.Expr{c.newIdent(c.objectName(implicit), implicit.Type())},
133					Tok: token.DEFINE,
134					Rhs: []ast.Expr{c.newIdent(value, implicit.Type())},
135				}}
136			}
137			c := &ast.CaseClause{
138				List: clause.List,
139				Body: append(bodyPrefix, clause.Body...),
140			}
141			if len(c.List) == 0 {
142				defaultClause = c
143				continue
144			}
145			caseClauses = append(caseClauses, c)
146		}
147		c.translateBranchingStmt(caseClauses, defaultClause, true, translateCond, label, c.Flattened[s])
148
149	case *ast.ForStmt:
150		if s.Init != nil {
151			c.translateStmt(s.Init, nil)
152		}
153		cond := func() string {
154			if s.Cond == nil {
155				return "true"
156			}
157			return c.translateExpr(s.Cond).String()
158		}
159		c.translateLoopingStmt(cond, s.Body, nil, func() {
160			if s.Post != nil {
161				c.translateStmt(s.Post, nil)
162			}
163		}, label, c.Flattened[s])
164
165	case *ast.RangeStmt:
166		refVar := c.newVariable("_ref")
167		c.Printf("%s = %s;", refVar, c.translateExpr(s.X))
168
169		switch t := c.p.TypeOf(s.X).Underlying().(type) {
170		case *types.Basic:
171			iVar := c.newVariable("_i")
172			c.Printf("%s = 0;", iVar)
173			runeVar := c.newVariable("_rune")
174			c.translateLoopingStmt(func() string { return iVar + " < " + refVar + ".length" }, s.Body, func() {
175				c.Printf("%s = $decodeRune(%s, %s);", runeVar, refVar, iVar)
176				if !isBlank(s.Key) {
177					c.Printf("%s", c.translateAssign(s.Key, c.newIdent(iVar, types.Typ[types.Int]), s.Tok == token.DEFINE))
178				}
179				if !isBlank(s.Value) {
180					c.Printf("%s", c.translateAssign(s.Value, c.newIdent(runeVar+"[0]", types.Typ[types.Rune]), s.Tok == token.DEFINE))
181				}
182			}, func() {
183				c.Printf("%s += %s[1];", iVar, runeVar)
184			}, label, c.Flattened[s])
185
186		case *types.Map:
187			iVar := c.newVariable("_i")
188			c.Printf("%s = 0;", iVar)
189			keysVar := c.newVariable("_keys")
190			c.Printf("%s = $keys(%s);", keysVar, refVar)
191			c.translateLoopingStmt(func() string { return iVar + " < " + keysVar + ".length" }, s.Body, func() {
192				entryVar := c.newVariable("_entry")
193				c.Printf("%s = %s[%s[%s]];", entryVar, refVar, keysVar, iVar)
194				c.translateStmt(&ast.IfStmt{
195					Cond: c.newIdent(entryVar+" === undefined", types.Typ[types.Bool]),
196					Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.CONTINUE}}},
197				}, nil)
198				if !isBlank(s.Key) {
199					c.Printf("%s", c.translateAssign(s.Key, c.newIdent(entryVar+".k", t.Key()), s.Tok == token.DEFINE))
200				}
201				if !isBlank(s.Value) {
202					c.Printf("%s", c.translateAssign(s.Value, c.newIdent(entryVar+".v", t.Elem()), s.Tok == token.DEFINE))
203				}
204			}, func() {
205				c.Printf("%s++;", iVar)
206			}, label, c.Flattened[s])
207
208		case *types.Array, *types.Pointer, *types.Slice:
209			var length string
210			var elemType types.Type
211			switch t2 := t.(type) {
212			case *types.Array:
213				length = fmt.Sprintf("%d", t2.Len())
214				elemType = t2.Elem()
215			case *types.Pointer:
216				length = fmt.Sprintf("%d", t2.Elem().Underlying().(*types.Array).Len())
217				elemType = t2.Elem().Underlying().(*types.Array).Elem()
218			case *types.Slice:
219				length = refVar + ".$length"
220				elemType = t2.Elem()
221			}
222			iVar := c.newVariable("_i")
223			c.Printf("%s = 0;", iVar)
224			c.translateLoopingStmt(func() string { return iVar + " < " + length }, s.Body, func() {
225				if !isBlank(s.Key) {
226					c.Printf("%s", c.translateAssign(s.Key, c.newIdent(iVar, types.Typ[types.Int]), s.Tok == token.DEFINE))
227				}
228				if !isBlank(s.Value) {
229					c.Printf("%s", c.translateAssign(s.Value, c.setType(&ast.IndexExpr{
230						X:     c.newIdent(refVar, t),
231						Index: c.newIdent(iVar, types.Typ[types.Int]),
232					}, elemType), s.Tok == token.DEFINE))
233				}
234			}, func() {
235				c.Printf("%s++;", iVar)
236			}, label, c.Flattened[s])
237
238		case *types.Chan:
239			okVar := c.newIdent(c.newVariable("_ok"), types.Typ[types.Bool])
240			key := s.Key
241			tok := s.Tok
242			if key == nil {
243				key = ast.NewIdent("_")
244				tok = token.ASSIGN
245			}
246			forStmt := &ast.ForStmt{
247				Body: &ast.BlockStmt{
248					List: []ast.Stmt{
249						&ast.AssignStmt{
250							Lhs: []ast.Expr{
251								key,
252								okVar,
253							},
254							Rhs: []ast.Expr{
255								c.setType(&ast.UnaryExpr{X: c.newIdent(refVar, t), Op: token.ARROW}, types.NewTuple(types.NewVar(0, nil, "", t.Elem()), types.NewVar(0, nil, "", types.Typ[types.Bool]))),
256							},
257							Tok: tok,
258						},
259						&ast.IfStmt{
260							Cond: &ast.UnaryExpr{X: okVar, Op: token.NOT},
261							Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}},
262						},
263						s.Body,
264					},
265				},
266			}
267			c.Flattened[forStmt] = true
268			c.translateStmt(forStmt, label)
269
270		default:
271			panic("")
272		}
273
274	case *ast.BranchStmt:
275		normalLabel := ""
276		blockingLabel := ""
277		data := c.flowDatas[nil]
278		if s.Label != nil {
279			normalLabel = " " + s.Label.Name
280			blockingLabel = " s" // use explicit label "s", because surrounding loop may not be flattened
281			data = c.flowDatas[c.p.Uses[s.Label].(*types.Label)]
282		}
283		switch s.Tok {
284		case token.BREAK:
285			c.PrintCond(data.endCase == 0, fmt.Sprintf("break%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.endCase, blockingLabel))
286		case token.CONTINUE:
287			data.postStmt()
288			c.PrintCond(data.beginCase == 0, fmt.Sprintf("continue%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.beginCase, blockingLabel))
289		case token.GOTO:
290			c.PrintCond(false, "goto "+s.Label.Name, fmt.Sprintf("$s = %d; continue;", c.labelCase(c.p.Uses[s.Label].(*types.Label))))
291		case token.FALLTHROUGH:
292			// handled in CaseClause
293		default:
294			panic("Unhandled branch statment: " + s.Tok.String())
295		}
296
297	case *ast.ReturnStmt:
298		results := s.Results
299		if c.resultNames != nil {
300			if len(s.Results) != 0 {
301				c.translateStmt(&ast.AssignStmt{
302					Lhs: c.resultNames,
303					Tok: token.ASSIGN,
304					Rhs: s.Results,
305				}, nil)
306			}
307			results = c.resultNames
308		}
309		rVal := c.translateResults(results)
310		if len(c.Flattened) != 0 {
311			c.Printf("$s = -1; return%s;", rVal)
312			return
313		}
314		c.Printf("return%s;", rVal)
315
316	case *ast.DeferStmt:
317		isBuiltin := false
318		isJs := false
319		switch fun := s.Call.Fun.(type) {
320		case *ast.Ident:
321			var builtin *types.Builtin
322			builtin, isBuiltin = c.p.Uses[fun].(*types.Builtin)
323			if isBuiltin && builtin.Name() == "recover" {
324				c.Printf("$deferred.push([$recover, []]);")
325				return
326			}
327		case *ast.SelectorExpr:
328			isJs = typesutil.IsJsPackage(c.p.Uses[fun.Sel].Pkg())
329		}
330		sig := c.p.TypeOf(s.Call.Fun).Underlying().(*types.Signature)
331		args := c.translateArgs(sig, s.Call.Args, s.Call.Ellipsis.IsValid())
332		if isBuiltin || isJs {
333			vars := make([]string, len(s.Call.Args))
334			callArgs := make([]ast.Expr, len(s.Call.Args))
335			for i, arg := range s.Call.Args {
336				v := c.newVariable("_arg")
337				vars[i] = v
338				callArgs[i] = c.newIdent(v, c.p.TypeOf(arg))
339			}
340			call := c.translateExpr(&ast.CallExpr{
341				Fun:      s.Call.Fun,
342				Args:     callArgs,
343				Ellipsis: s.Call.Ellipsis,
344			})
345			c.Printf("$deferred.push([function(%s) { %s; }, [%s]]);", strings.Join(vars, ", "), call, strings.Join(args, ", "))
346			return
347		}
348		c.Printf("$deferred.push([%s, [%s]]);", c.translateExpr(s.Call.Fun), strings.Join(args, ", "))
349
350	case *ast.AssignStmt:
351		if s.Tok != token.ASSIGN && s.Tok != token.DEFINE {
352			panic(s.Tok)
353		}
354
355		switch {
356		case len(s.Lhs) == 1 && len(s.Rhs) == 1:
357			lhs := astutil.RemoveParens(s.Lhs[0])
358			if isBlank(lhs) {
359				c.Printf("$unused(%s);", c.translateExpr(s.Rhs[0]))
360				return
361			}
362			c.Printf("%s", c.translateAssign(lhs, s.Rhs[0], s.Tok == token.DEFINE))
363
364		case len(s.Lhs) > 1 && len(s.Rhs) == 1:
365			tupleVar := c.newVariable("_tuple")
366			c.Printf("%s = %s;", tupleVar, c.translateExpr(s.Rhs[0]))
367			tuple := c.p.TypeOf(s.Rhs[0]).(*types.Tuple)
368			for i, lhs := range s.Lhs {
369				lhs = astutil.RemoveParens(lhs)
370				if !isBlank(lhs) {
371					c.Printf("%s", c.translateAssign(lhs, c.newIdent(fmt.Sprintf("%s[%d]", tupleVar, i), tuple.At(i).Type()), s.Tok == token.DEFINE))
372				}
373			}
374		case len(s.Lhs) == len(s.Rhs):
375			tmpVars := make([]string, len(s.Rhs))
376			for i, rhs := range s.Rhs {
377				tmpVars[i] = c.newVariable("_tmp")
378				if isBlank(astutil.RemoveParens(s.Lhs[i])) {
379					c.Printf("$unused(%s);", c.translateExpr(rhs))
380					continue
381				}
382				c.Printf("%s", c.translateAssign(c.newIdent(tmpVars[i], c.p.TypeOf(s.Lhs[i])), rhs, true))
383			}
384			for i, lhs := range s.Lhs {
385				lhs = astutil.RemoveParens(lhs)
386				if !isBlank(lhs) {
387					c.Printf("%s", c.translateAssign(lhs, c.newIdent(tmpVars[i], c.p.TypeOf(lhs)), s.Tok == token.DEFINE))
388				}
389			}
390
391		default:
392			panic("Invalid arity of AssignStmt.")
393
394		}
395
396	case *ast.DeclStmt:
397		decl := s.Decl.(*ast.GenDecl)
398		switch decl.Tok {
399		case token.VAR:
400			for _, spec := range s.Decl.(*ast.GenDecl).Specs {
401				valueSpec := spec.(*ast.ValueSpec)
402				lhs := make([]ast.Expr, len(valueSpec.Names))
403				for i, name := range valueSpec.Names {
404					lhs[i] = name
405				}
406				rhs := valueSpec.Values
407				if len(rhs) == 0 {
408					rhs = make([]ast.Expr, len(lhs))
409					for i, e := range lhs {
410						rhs[i] = c.zeroValue(c.p.TypeOf(e))
411					}
412				}
413				c.translateStmt(&ast.AssignStmt{
414					Lhs: lhs,
415					Tok: token.DEFINE,
416					Rhs: rhs,
417				}, nil)
418			}
419		case token.TYPE:
420			for _, spec := range decl.Specs {
421				o := c.p.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName)
422				c.p.typeNames = append(c.p.typeNames, o)
423				c.p.objectNames[o] = c.newVariableWithLevel(o.Name(), true)
424				c.p.dependencies[o] = true
425			}
426		case token.CONST:
427			// skip, constants are inlined
428		}
429
430	case *ast.ExprStmt:
431		expr := c.translateExpr(s.X)
432		if expr != nil && expr.String() != "" {
433			c.Printf("%s;", expr)
434		}
435
436	case *ast.LabeledStmt:
437		label := c.p.Defs[s.Label].(*types.Label)
438		if c.GotoLabel[label] {
439			c.PrintCond(false, s.Label.Name+":", fmt.Sprintf("case %d:", c.labelCase(label)))
440		}
441		c.translateStmt(s.Stmt, label)
442
443	case *ast.GoStmt:
444		c.Printf("$go(%s, [%s]);", c.translateExpr(s.Call.Fun), strings.Join(c.translateArgs(c.p.TypeOf(s.Call.Fun).Underlying().(*types.Signature), s.Call.Args, s.Call.Ellipsis.IsValid()), ", "))
445
446	case *ast.SendStmt:
447		chanType := c.p.TypeOf(s.Chan).Underlying().(*types.Chan)
448		call := &ast.CallExpr{
449			Fun:  c.newIdent("$send", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)),
450			Args: []ast.Expr{s.Chan, c.newIdent(c.translateImplicitConversionWithCloning(s.Value, chanType.Elem()).String(), chanType.Elem())},
451		}
452		c.Blocking[call] = true
453		c.translateStmt(&ast.ExprStmt{X: call}, label)
454
455	case *ast.SelectStmt:
456		selectionVar := c.newVariable("_selection")
457		var channels []string
458		var caseClauses []*ast.CaseClause
459		flattened := false
460		hasDefault := false
461		for i, cc := range s.Body.List {
462			clause := cc.(*ast.CommClause)
463			switch comm := clause.Comm.(type) {
464			case nil:
465				channels = append(channels, "[]")
466				hasDefault = true
467			case *ast.ExprStmt:
468				channels = append(channels, c.formatExpr("[%e]", astutil.RemoveParens(comm.X).(*ast.UnaryExpr).X).String())
469			case *ast.AssignStmt:
470				channels = append(channels, c.formatExpr("[%e]", astutil.RemoveParens(comm.Rhs[0]).(*ast.UnaryExpr).X).String())
471			case *ast.SendStmt:
472				chanType := c.p.TypeOf(comm.Chan).Underlying().(*types.Chan)
473				channels = append(channels, c.formatExpr("[%e, %s]", comm.Chan, c.translateImplicitConversionWithCloning(comm.Value, chanType.Elem())).String())
474			default:
475				panic(fmt.Sprintf("unhandled: %T", comm))
476			}
477
478			indexLit := &ast.BasicLit{Kind: token.INT}
479			c.p.Types[indexLit] = types.TypeAndValue{Type: types.Typ[types.Int], Value: constant.MakeInt64(int64(i))}
480
481			var bodyPrefix []ast.Stmt
482			if assign, ok := clause.Comm.(*ast.AssignStmt); ok {
483				switch rhsType := c.p.TypeOf(assign.Rhs[0]).(type) {
484				case *types.Tuple:
485					bodyPrefix = []ast.Stmt{&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1]", rhsType)}, Tok: assign.Tok}}
486				default:
487					bodyPrefix = []ast.Stmt{&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1][0]", rhsType)}, Tok: assign.Tok}}
488				}
489			}
490
491			caseClauses = append(caseClauses, &ast.CaseClause{
492				List: []ast.Expr{indexLit},
493				Body: append(bodyPrefix, clause.Body...),
494			})
495
496			flattened = flattened || c.Flattened[clause]
497		}
498
499		selectCall := c.setType(&ast.CallExpr{
500			Fun:  c.newIdent("$select", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", types.NewInterface(nil, nil))), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false)),
501			Args: []ast.Expr{c.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterface(nil, nil))},
502		}, types.Typ[types.Int])
503		c.Blocking[selectCall] = !hasDefault
504		c.Printf("%s = %s;", selectionVar, c.translateExpr(selectCall))
505
506		if len(caseClauses) != 0 {
507			translateCond := func(cond ast.Expr) *expression {
508				return c.formatExpr("%s[0] === %e", selectionVar, cond)
509			}
510			c.translateBranchingStmt(caseClauses, nil, true, translateCond, label, flattened)
511		}
512
513	case *ast.EmptyStmt:
514		// skip
515
516	default:
517		panic(fmt.Sprintf("Unhandled statement: %T\n", s))
518
519	}
520}
521
522func (c *funcContext) translateBranchingStmt(caseClauses []*ast.CaseClause, defaultClause *ast.CaseClause, canBreak bool, translateCond func(ast.Expr) *expression, label *types.Label, flatten bool) {
523	var caseOffset, defaultCase, endCase int
524	if flatten {
525		caseOffset = c.caseCounter
526		defaultCase = caseOffset + len(caseClauses)
527		endCase = defaultCase
528		if defaultClause != nil {
529			endCase++
530		}
531		c.caseCounter = endCase + 1
532	}
533
534	hasBreak := false
535	if canBreak {
536		prevFlowData := c.flowDatas[nil]
537		data := &flowData{
538			postStmt:  prevFlowData.postStmt,  // for "continue" of outer loop
539			beginCase: prevFlowData.beginCase, // same
540			endCase:   endCase,
541		}
542		c.flowDatas[nil] = data
543		c.flowDatas[label] = data
544		defer func() {
545			delete(c.flowDatas, label)
546			c.flowDatas[nil] = prevFlowData
547		}()
548
549		for _, child := range caseClauses {
550			if analysis.HasBreak(child) {
551				hasBreak = true
552				break
553			}
554		}
555		if defaultClause != nil && analysis.HasBreak(defaultClause) {
556			hasBreak = true
557		}
558	}
559
560	if label != nil && !flatten {
561		c.Printf("%s:", label.Name())
562	}
563
564	condStrs := make([]string, len(caseClauses))
565	for i, clause := range caseClauses {
566		conds := make([]string, len(clause.List))
567		for j, cond := range clause.List {
568			conds[j] = translateCond(cond).String()
569		}
570		condStrs[i] = strings.Join(conds, " || ")
571		if flatten {
572			c.Printf("/* */ if (%s) { $s = %d; continue; }", condStrs[i], caseOffset+i)
573		}
574	}
575
576	if flatten {
577		c.Printf("/* */ $s = %d; continue;", defaultCase)
578	}
579
580	prefix := ""
581	suffix := ""
582	if label != nil || hasBreak {
583		prefix = "switch (0) { default: "
584		suffix = " }"
585	}
586
587	for i, clause := range caseClauses {
588		c.SetPos(clause.Pos())
589		c.PrintCond(!flatten, fmt.Sprintf("%sif (%s) {", prefix, condStrs[i]), fmt.Sprintf("case %d:", caseOffset+i))
590		c.Indent(func() {
591			c.translateStmtList(clause.Body)
592			if flatten && (i < len(caseClauses)-1 || defaultClause != nil) && !endsWithReturn(clause.Body) {
593				c.Printf("$s = %d; continue;", endCase)
594			}
595		})
596		prefix = "} else "
597	}
598
599	if defaultClause != nil {
600		c.PrintCond(!flatten, prefix+"{", fmt.Sprintf("case %d:", caseOffset+len(caseClauses)))
601		c.Indent(func() {
602			c.translateStmtList(defaultClause.Body)
603		})
604	}
605
606	c.PrintCond(!flatten, "}"+suffix, fmt.Sprintf("case %d:", endCase))
607}
608
609func (c *funcContext) translateLoopingStmt(cond func() string, body *ast.BlockStmt, bodyPrefix, post func(), label *types.Label, flatten bool) {
610	prevFlowData := c.flowDatas[nil]
611	data := &flowData{
612		postStmt: post,
613	}
614	if flatten {
615		data.beginCase = c.caseCounter
616		data.endCase = c.caseCounter + 1
617		c.caseCounter += 2
618	}
619	c.flowDatas[nil] = data
620	c.flowDatas[label] = data
621	defer func() {
622		delete(c.flowDatas, label)
623		c.flowDatas[nil] = prevFlowData
624	}()
625
626	if !flatten && label != nil {
627		c.Printf("%s:", label.Name())
628	}
629	c.PrintCond(!flatten, "while (true) {", fmt.Sprintf("case %d:", data.beginCase))
630	c.Indent(func() {
631		condStr := cond()
632		if condStr != "true" {
633			c.PrintCond(!flatten, fmt.Sprintf("if (!(%s)) { break; }", condStr), fmt.Sprintf("if(!(%s)) { $s = %d; continue; }", condStr, data.endCase))
634		}
635
636		prevEV := c.p.escapingVars
637		c.handleEscapingVars(body)
638
639		if bodyPrefix != nil {
640			bodyPrefix()
641		}
642		c.translateStmtList(body.List)
643		isTerminated := false
644		if len(body.List) != 0 {
645			switch body.List[len(body.List)-1].(type) {
646			case *ast.ReturnStmt, *ast.BranchStmt:
647				isTerminated = true
648			}
649		}
650		if !isTerminated {
651			post()
652		}
653
654		c.p.escapingVars = prevEV
655	})
656	c.PrintCond(!flatten, "}", fmt.Sprintf("$s = %d; continue; case %d:", data.beginCase, data.endCase))
657}
658
659func (c *funcContext) translateAssign(lhs, rhs ast.Expr, define bool) string {
660	lhs = astutil.RemoveParens(lhs)
661	if isBlank(lhs) {
662		panic("translateAssign with blank lhs")
663	}
664
665	if l, ok := lhs.(*ast.IndexExpr); ok {
666		if t, ok := c.p.TypeOf(l.X).Underlying().(*types.Map); ok {
667			if typesutil.IsJsObject(c.p.TypeOf(l.Index)) {
668				c.p.errList = append(c.p.errList, types.Error{Fset: c.p.fileSet, Pos: l.Index.Pos(), Msg: "cannot use js.Object as map key"})
669			}
670			keyVar := c.newVariable("_key")
671			return fmt.Sprintf(`%s = %s; (%s || $throwRuntimeError("assignment to entry in nil map"))[%s.keyFor(%s)] = { k: %s, v: %s };`, keyVar, c.translateImplicitConversionWithCloning(l.Index, t.Key()), c.translateExpr(l.X), c.typeName(t.Key()), keyVar, keyVar, c.translateImplicitConversionWithCloning(rhs, t.Elem()))
672		}
673	}
674
675	lhsType := c.p.TypeOf(lhs)
676	rhsExpr := c.translateImplicitConversion(rhs, lhsType)
677	if _, ok := rhs.(*ast.CompositeLit); ok && define {
678		return fmt.Sprintf("%s = %s;", c.translateExpr(lhs), rhsExpr) // skip $copy
679	}
680
681	isReflectValue := false
682	if named, ok := lhsType.(*types.Named); ok && named.Obj().Pkg() != nil && named.Obj().Pkg().Path() == "reflect" && named.Obj().Name() == "Value" {
683		isReflectValue = true
684	}
685	if !isReflectValue { // this is a performance hack, but it is safe since reflect.Value has no exported fields and the reflect package does not violate this assumption
686		switch lhsType.Underlying().(type) {
687		case *types.Array, *types.Struct:
688			if define {
689				return fmt.Sprintf("%s = $clone(%s, %s);", c.translateExpr(lhs), rhsExpr, c.typeName(lhsType))
690			}
691			return fmt.Sprintf("%s.copy(%s, %s);", c.typeName(lhsType), c.translateExpr(lhs), rhsExpr)
692		}
693	}
694
695	switch l := lhs.(type) {
696	case *ast.Ident:
697		return fmt.Sprintf("%s = %s;", c.objectName(c.p.ObjectOf(l)), rhsExpr)
698	case *ast.SelectorExpr:
699		sel, ok := c.p.SelectionOf(l)
700		if !ok {
701			// qualified identifier
702			return fmt.Sprintf("%s = %s;", c.objectName(c.p.Uses[l.Sel]), rhsExpr)
703		}
704		fields, jsTag := c.translateSelection(sel, l.Pos())
705		if jsTag != "" {
706			return fmt.Sprintf("%s.%s%s = %s;", c.translateExpr(l.X), strings.Join(fields, "."), formatJSStructTagVal(jsTag), c.externalize(rhsExpr.String(), sel.Type()))
707		}
708		return fmt.Sprintf("%s.%s = %s;", c.translateExpr(l.X), strings.Join(fields, "."), rhsExpr)
709	case *ast.StarExpr:
710		return fmt.Sprintf("%s.$set(%s);", c.translateExpr(l.X), rhsExpr)
711	case *ast.IndexExpr:
712		switch t := c.p.TypeOf(l.X).Underlying().(type) {
713		case *types.Array, *types.Pointer:
714			pattern := rangeCheck("%1e[%2f] = %3s", c.p.Types[l.Index].Value != nil, true)
715			if _, ok := t.(*types.Pointer); ok { // check pointer for nil (attribute getter causes a panic)
716				pattern = `%1e.nilCheck, ` + pattern
717			}
718			return c.formatExpr(pattern, l.X, l.Index, rhsExpr).String() + ";"
719		case *types.Slice:
720			return c.formatExpr(rangeCheck("%1e.$array[%1e.$offset + %2f] = %3s", c.p.Types[l.Index].Value != nil, false), l.X, l.Index, rhsExpr).String() + ";"
721		default:
722			panic(fmt.Sprintf("Unhandled lhs type: %T\n", t))
723		}
724	default:
725		panic(fmt.Sprintf("Unhandled lhs type: %T\n", l))
726	}
727}
728
729func (c *funcContext) translateResults(results []ast.Expr) string {
730	tuple := c.sig.Results()
731	switch tuple.Len() {
732	case 0:
733		return ""
734	case 1:
735		result := c.zeroValue(tuple.At(0).Type())
736		if results != nil {
737			result = results[0]
738		}
739		v := c.translateImplicitConversion(result, tuple.At(0).Type())
740		c.delayedOutput = nil
741		return " " + v.String()
742	default:
743		if len(results) == 1 {
744			resultTuple := c.p.TypeOf(results[0]).(*types.Tuple)
745
746			if resultTuple.Len() != tuple.Len() {
747				panic("invalid tuple return assignment")
748			}
749
750			resultExpr := c.translateExpr(results[0]).String()
751
752			if types.Identical(resultTuple, tuple) {
753				return " " + resultExpr
754			}
755
756			tmpVar := c.newVariable("_returncast")
757			c.Printf("%s = %s;", tmpVar, resultExpr)
758
759			// Not all the return types matched, map everything out for implicit casting
760			results = make([]ast.Expr, resultTuple.Len())
761			for i := range results {
762				results[i] = c.newIdent(fmt.Sprintf("%s[%d]", tmpVar, i), resultTuple.At(i).Type())
763			}
764		}
765		values := make([]string, tuple.Len())
766		for i := range values {
767			result := c.zeroValue(tuple.At(i).Type())
768			if results != nil {
769				result = results[i]
770			}
771			values[i] = c.translateImplicitConversion(result, tuple.At(i).Type()).String()
772		}
773		c.delayedOutput = nil
774		return " [" + strings.Join(values, ", ") + "]"
775	}
776}
777
778func (c *funcContext) labelCase(label *types.Label) int {
779	labelCase, ok := c.labelCases[label]
780	if !ok {
781		labelCase = c.caseCounter
782		c.caseCounter++
783		c.labelCases[label] = labelCase
784	}
785	return labelCase
786}
787