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 }