github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/statements.go (about)

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