// Evaluates identifier expressions func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) { switch node.Name { case "true", "false": return newConstant(constant.MakeBool(node.Name == "true"), scope.Thread), nil case "nil": return nilVariable, nil } // try to interpret this as a local variable v, err := scope.extractVarInfo(node.Name) if err != nil { origErr := err // workaround: sometimes go inserts an entry for '&varname' instead of varname v, err = scope.extractVarInfo("&" + node.Name) if err != nil { // if it's not a local variable then it could be a package variable w/o explicit package name _, _, fn := scope.Thread.dbp.PCToLine(scope.PC) if fn != nil { if v, err := scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil { v.Name = node.Name return v, nil } } return nil, origErr } v = v.maybeDereference() v.Name = node.Name } return v, nil }
// zeroConst returns a new "zero" constant of the specified type, // which must not be an array or struct type: the zero values of // aggregates are well-defined but cannot be represented by Const. // func zeroConst(t types.Type) *Const { switch t := t.(type) { case *types.Basic: switch { case t.Info()&types.IsBoolean != 0: return NewConst(exact.MakeBool(false), t) case t.Info()&types.IsNumeric != 0: return NewConst(exact.MakeInt64(0), t) case t.Info()&types.IsString != 0: return NewConst(exact.MakeString(""), t) case t.Kind() == types.UnsafePointer: fallthrough case t.Kind() == types.UntypedNil: return nilConst(t) default: panic(fmt.Sprint("zeroConst for unexpected type:", t)) } case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature: return nilConst(t) case *types.Named: return NewConst(zeroConst(t.Underlying()).Value, t) case *types.Array, *types.Struct, *types.Tuple: panic(fmt.Sprint("zeroConst applied to aggregate:", t)) } panic(fmt.Sprint("zeroConst: unexpected ", t)) }
func (c *funcContext) zeroValue(ty types.Type) ast.Expr { switch t := ty.Underlying().(type) { case *types.Basic: switch { case isBoolean(t): return c.newConst(ty, constant.MakeBool(false)) case isNumeric(t): return c.newConst(ty, constant.MakeInt64(0)) case isString(t): return c.newConst(ty, constant.MakeString("")) case t.Kind() == types.UnsafePointer: // fall through to "nil" case t.Kind() == types.UntypedNil: panic("Zero value for untyped nil.") default: panic(fmt.Sprintf("Unhandled basic type: %v\n", t)) } case *types.Array, *types.Struct: return c.setType(&ast.CompositeLit{}, ty) case *types.Chan, *types.Interface, *types.Map, *types.Signature, *types.Slice, *types.Pointer: // fall through to "nil" default: panic(fmt.Sprintf("Unhandled type: %T\n", t)) } id := c.newIdent("nil", ty) c.p.Uses[id] = nilObj return id }
func (p *importer) value() constant.Value { switch kind := constant.Kind(p.int()); kind { case falseTag: return constant.MakeBool(false) case trueTag: return constant.MakeBool(true) case int64Tag: return constant.MakeInt64(p.int64()) case floatTag: return p.float() case complexTag: re := p.float() im := p.float() return constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) case stringTag: return constant.MakeString(p.string()) default: panic(fmt.Sprintf("unexpected value kind %d", kind)) } }
func (p *importer) value() constant.Value { switch tag := p.tagOrIndex(); tag { case falseTag: return constant.MakeBool(false) case trueTag: return constant.MakeBool(true) case int64Tag: return constant.MakeInt64(p.int64()) case floatTag: return p.float() case complexTag: re := p.float() im := p.float() return constant.BinaryOp(re, token.ADD, constant.MakeImag(im)) case stringTag: return constant.MakeString(p.string()) default: panic(fmt.Sprintf("unexpected value tag %d", tag)) } }
func (c *simplifyContext) makeTag(stmts *[]ast.Stmt, tag ast.Expr, needsTag bool) ast.Expr { if tag == nil { id := ast.NewIdent("true") c.info.Types[id] = types.TypeAndValue{Type: types.Typ[types.Bool], Value: constant.MakeBool(true)} return id } if !needsTag { *stmts = append(*stmts, simpleAssign(ast.NewIdent("_"), token.ASSIGN, tag)) return nil } return c.newVar(stmts, tag) }
func (check *Checker) comparison(x, y *operand, op token.Token) { // spec: "In any comparison, the first operand must be assignable // to the type of the second operand, or vice versa." err := "" if x.assignableTo(check.conf, y.typ, nil) || y.assignableTo(check.conf, x.typ, nil) { defined := false switch op { case token.EQL, token.NEQ: // spec: "The equality operators == and != apply to operands that are comparable." defined = Comparable(x.typ) || x.isNil() && hasNil(y.typ) || y.isNil() && hasNil(x.typ) case token.LSS, token.LEQ, token.GTR, token.GEQ: // spec: The ordering operators <, <=, >, and >= apply to operands that are ordered." defined = isOrdered(x.typ) default: unreachable() } if !defined { typ := x.typ if x.isNil() { typ = y.typ } err = check.sprintf("operator %s not defined for %s", op, typ) } } else { err = check.sprintf("mismatched types %s and %s", x.typ, y.typ) } if err != "" { check.errorf(x.pos(), "cannot compare %s %s %s (%s)", x.expr, op, y.expr, err) x.mode = invalid return } if x.mode == constant_ && y.mode == constant_ { x.val = constant.MakeBool(constant.Compare(x.val, op, y.val)) // The operands are never materialized; no need to update // their types. } else { x.mode = value // The operands have now their final types, which at run- // time will be materialized. Update the expression trees. // If the current types are untyped, the materialized type // is the respective default type. check.updateExprType(x.expr, defaultType(x.typ), true) check.updateExprType(y.expr, defaultType(y.typ), true) } // spec: "Comparison operators compare two operands and yield // an untyped boolean value." x.typ = Typ[UntypedBool] }
// Evaluates identifier expressions func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) { switch node.Name { case "true", "false": return newConstant(constant.MakeBool(node.Name == "true"), scope.Thread), nil case "nil": return nilVariable, nil } // try to interpret this as a local variable v, err := scope.extractVarInfo(node.Name) if err != nil { // if it's not a local variable then it could be a package variable w/o explicit package name origErr := err _, _, fn := scope.Thread.dbp.PCToLine(scope.PC) if fn != nil { if v, err := scope.packageVarAddr(fn.PackageName() + "." + node.Name); err == nil { v.Name = node.Name return v, nil } } return nil, origErr } return v, nil }
func Compile(importPath string, files []*ast.File, fileSet *token.FileSet, importContext *ImportContext, minify bool) (*Archive, error) { typesInfo := &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), Scopes: make(map[ast.Node]*types.Scope), } var importError error var errList ErrorList var previousErr error config := &types.Config{ Importer: packageImporter{ importContext: importContext, importError: &importError, }, Sizes: sizes32, Error: func(err error) { if previousErr != nil && previousErr.Error() == err.Error() { return } errList = append(errList, err) previousErr = err }, } typesPkg, err := config.Check(importPath, fileSet, files, typesInfo) if importError != nil { return nil, importError } if errList != nil { if len(errList) > 10 { pos := token.NoPos if last, ok := errList[9].(types.Error); ok { pos = last.Pos } errList = append(errList[:10], types.Error{Fset: fileSet, Pos: pos, Msg: "too many errors"}) } return nil, errList } if err != nil { return nil, err } importContext.Packages[importPath] = typesPkg exportData := importer.ExportData(typesPkg) encodedFileSet := bytes.NewBuffer(nil) if err := fileSet.Write(json.NewEncoder(encodedFileSet).Encode); err != nil { return nil, err } isBlocking := func(f *types.Func) bool { archive, err := importContext.Import(f.Pkg().Path()) if err != nil { panic(err) } fullName := f.FullName() for _, d := range archive.Declarations { if string(d.FullName) == fullName { return d.Blocking } } panic(fullName) } pkgInfo := analysis.AnalyzePkg(files, fileSet, typesInfo, typesPkg, isBlocking) c := &funcContext{ FuncInfo: pkgInfo.InitFuncInfo, p: &pkgContext{ Info: pkgInfo, additionalSelections: make(map[*ast.SelectorExpr]selection), pkgVars: make(map[string]string), objectNames: make(map[types.Object]string), varPtrNames: make(map[*types.Var]string), escapingVars: make(map[*types.Var]bool), indentation: 1, dependencies: make(map[types.Object]bool), minify: minify, fileSet: fileSet, }, allVars: make(map[string]int), flowDatas: map[*types.Label]*flowData{nil: {}}, caseCounter: 1, labelCases: make(map[*types.Label]int), } for name := range reservedKeywords { c.allVars[name] = 1 } // imports var importDecls []*Decl var importedPaths []string for _, importedPkg := range typesPkg.Imports() { c.p.pkgVars[importedPkg.Path()] = c.newVariableWithLevel(importedPkg.Name(), true) importedPaths = append(importedPaths, importedPkg.Path()) } sort.Strings(importedPaths) for _, impPath := range importedPaths { id := c.newIdent(fmt.Sprintf(`%s.$init`, c.p.pkgVars[impPath]), types.NewSignature(nil, nil, nil, false)) call := &ast.CallExpr{Fun: id} c.Blocking[call] = true c.Flattened[call] = true importDecls = append(importDecls, &Decl{ Vars: []string{c.p.pkgVars[impPath]}, DeclCode: []byte(fmt.Sprintf("\t%s = $packages[\"%s\"];\n", c.p.pkgVars[impPath], impPath)), InitCode: c.CatchOutput(1, func() { c.translateStmt(&ast.ExprStmt{X: call}, nil) }), }) } var functions []*ast.FuncDecl var vars []*types.Var for _, file := range files { for _, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: sig := c.p.Defs[d.Name].(*types.Func).Type().(*types.Signature) var recvType types.Type if sig.Recv() != nil { recvType = sig.Recv().Type() if ptr, isPtr := recvType.(*types.Pointer); isPtr { recvType = ptr.Elem() } } if sig.Recv() == nil { c.objectName(c.p.Defs[d.Name].(*types.Func)) // register toplevel name } if !isBlank(d.Name) { functions = append(functions, d) } case *ast.GenDecl: switch d.Tok { case token.TYPE: for _, spec := range d.Specs { o := c.p.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) c.p.typeNames = append(c.p.typeNames, o) c.objectName(o) // register toplevel name } case token.VAR: for _, spec := range d.Specs { for _, name := range spec.(*ast.ValueSpec).Names { if !isBlank(name) { o := c.p.Defs[name].(*types.Var) vars = append(vars, o) c.objectName(o) // register toplevel name } } } case token.CONST: // skip, constants are inlined } } } } collectDependencies := func(f func()) []string { c.p.dependencies = make(map[types.Object]bool) f() var deps []string for o := range c.p.dependencies { qualifiedName := o.Pkg().Path() + "." + o.Name() if f, ok := o.(*types.Func); ok && f.Type().(*types.Signature).Recv() != nil { deps = append(deps, qualifiedName+"~") continue } deps = append(deps, qualifiedName) } sort.Strings(deps) return deps } // variables var varDecls []*Decl varsWithInit := make(map[*types.Var]bool) for _, init := range c.p.InitOrder { for _, o := range init.Lhs { varsWithInit[o] = true } } for _, o := range vars { var d Decl if !o.Exported() { d.Vars = []string{c.objectName(o)} } if c.p.HasPointer[o] && !o.Exported() { d.Vars = append(d.Vars, c.varPtrName(o)) } if _, ok := varsWithInit[o]; !ok { d.DceDeps = collectDependencies(func() { d.InitCode = []byte(fmt.Sprintf("\t\t%s = %s;\n", c.objectName(o), c.translateExpr(c.zeroValue(o.Type())).String())) }) } d.DceObjectFilter = o.Name() varDecls = append(varDecls, &d) } for _, init := range c.p.InitOrder { lhs := make([]ast.Expr, len(init.Lhs)) for i, o := range init.Lhs { ident := ast.NewIdent(o.Name()) c.p.Defs[ident] = o lhs[i] = c.setType(ident, o.Type()) varsWithInit[o] = true } var d Decl d.DceDeps = collectDependencies(func() { c.localVars = nil d.InitCode = c.CatchOutput(1, func() { c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: []ast.Expr{init.Rhs}, }, nil) }) d.Vars = append(d.Vars, c.localVars...) }) if len(init.Lhs) == 1 { if !analysis.HasSideEffect(init.Rhs, c.p.Info.Info) { d.DceObjectFilter = init.Lhs[0].Name() } } varDecls = append(varDecls, &d) } // functions var funcDecls []*Decl var mainFunc *types.Func for _, fun := range functions { o := c.p.Defs[fun.Name].(*types.Func) funcInfo := c.p.FuncDeclInfos[o] d := Decl{ FullName: o.FullName(), Blocking: len(funcInfo.Blocking) != 0, } if fun.Recv == nil { d.Vars = []string{c.objectName(o)} d.DceObjectFilter = o.Name() switch o.Name() { case "main": mainFunc = o d.DceObjectFilter = "" case "init": d.InitCode = c.CatchOutput(1, func() { id := c.newIdent("", types.NewSignature(nil, nil, nil, false)) c.p.Uses[id] = o call := &ast.CallExpr{Fun: id} if len(c.p.FuncDeclInfos[o].Blocking) != 0 { c.Blocking[call] = true } c.translateStmt(&ast.ExprStmt{X: call}, nil) }) d.DceObjectFilter = "" } } if fun.Recv != nil { recvType := o.Type().(*types.Signature).Recv().Type() ptr, isPointer := recvType.(*types.Pointer) namedRecvType, _ := recvType.(*types.Named) if isPointer { namedRecvType = ptr.Elem().(*types.Named) } d.DceObjectFilter = namedRecvType.Obj().Name() if !fun.Name.IsExported() { d.DceMethodFilter = o.Name() + "~" } } d.DceDeps = collectDependencies(func() { d.DeclCode = c.translateToplevelFunction(fun, funcInfo) }) funcDecls = append(funcDecls, &d) } if typesPkg.Name() == "main" { if mainFunc == nil { return nil, fmt.Errorf("missing main function") } id := c.newIdent("", types.NewSignature(nil, nil, nil, false)) c.p.Uses[id] = mainFunc call := &ast.CallExpr{Fun: id} ifStmt := &ast.IfStmt{ Cond: c.newIdent("$pkg === $mainPkg", types.Typ[types.Bool]), Body: &ast.BlockStmt{ List: []ast.Stmt{ &ast.ExprStmt{X: call}, &ast.AssignStmt{ Lhs: []ast.Expr{c.newIdent("$mainFinished", types.Typ[types.Bool])}, Tok: token.ASSIGN, Rhs: []ast.Expr{c.newConst(types.Typ[types.Bool], constant.MakeBool(true))}, }, }, }, } if len(c.p.FuncDeclInfos[mainFunc].Blocking) != 0 { c.Blocking[call] = true c.Flattened[ifStmt] = true } funcDecls = append(funcDecls, &Decl{ InitCode: c.CatchOutput(1, func() { c.translateStmt(ifStmt, nil) }), }) } // named types var typeDecls []*Decl for _, o := range c.p.typeNames { typeName := c.objectName(o) d := Decl{ Vars: []string{typeName}, DceObjectFilter: o.Name(), } d.DceDeps = collectDependencies(func() { d.DeclCode = c.CatchOutput(0, func() { typeName := c.objectName(o) lhs := typeName if isPkgLevel(o) { lhs += " = $pkg." + encodeIdent(o.Name()) } size := int64(0) constructor := "null" switch t := o.Type().Underlying().(type) { case *types.Struct: params := make([]string, t.NumFields()) for i := 0; i < t.NumFields(); i++ { params[i] = fieldName(t, i) + "_" } constructor = fmt.Sprintf("function(%s) {\n\t\tthis.$val = this;\n\t\tif (arguments.length === 0) {\n", strings.Join(params, ", ")) for i := 0; i < t.NumFields(); i++ { constructor += fmt.Sprintf("\t\t\tthis.%s = %s;\n", fieldName(t, i), c.translateExpr(c.zeroValue(t.Field(i).Type())).String()) } constructor += "\t\t\treturn;\n\t\t}\n" for i := 0; i < t.NumFields(); i++ { constructor += fmt.Sprintf("\t\tthis.%[1]s = %[1]s_;\n", fieldName(t, i)) } constructor += "\t}" case *types.Basic, *types.Array, *types.Slice, *types.Chan, *types.Signature, *types.Interface, *types.Pointer, *types.Map: size = sizes32.Sizeof(t) } c.Printf(`%s = $newType(%d, %s, "%s.%s", "%s", "%s", %s);`, lhs, size, typeKind(o.Type()), o.Pkg().Name(), o.Name(), o.Name(), o.Pkg().Path(), constructor) }) d.MethodListCode = c.CatchOutput(0, func() { if _, isInterface := o.Type().Underlying().(*types.Interface); !isInterface { named := o.Type().(*types.Named) var methods []string var ptrMethods []string for i := 0; i < named.NumMethods(); i++ { method := named.Method(i) name := method.Name() if reservedKeywords[name] { name += "$" } pkgPath := "" if !method.Exported() { pkgPath = method.Pkg().Path() } t := method.Type().(*types.Signature) entry := fmt.Sprintf(`{prop: "%s", name: "%s", pkg: "%s", typ: $funcType(%s)}`, name, method.Name(), pkgPath, c.initArgs(t)) if _, isPtr := t.Recv().Type().(*types.Pointer); isPtr { ptrMethods = append(ptrMethods, entry) continue } methods = append(methods, entry) } if len(methods) > 0 { c.Printf("%s.methods = [%s];", c.typeName(o.Type()), strings.Join(methods, ", ")) } if len(ptrMethods) > 0 { c.Printf("%s.methods = [%s];", c.typeName(types.NewPointer(o.Type())), strings.Join(ptrMethods, ", ")) } } }) switch t := o.Type().Underlying().(type) { case *types.Array, *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Slice, *types.Signature, *types.Struct: d.TypeInitCode = c.CatchOutput(0, func() { c.Printf("%s.init(%s);", c.objectName(o), c.initArgs(t)) }) } }) typeDecls = append(typeDecls, &d) } // anonymous types for _, t := range c.p.anonTypes { d := Decl{ Vars: []string{t.Name()}, DceObjectFilter: t.Name(), } d.DceDeps = collectDependencies(func() { d.DeclCode = []byte(fmt.Sprintf("\t%s = $%sType(%s);\n", t.Name(), strings.ToLower(typeKind(t.Type())[5:]), c.initArgs(t.Type()))) }) typeDecls = append(typeDecls, &d) } var allDecls []*Decl for _, d := range append(append(append(importDecls, typeDecls...), varDecls...), funcDecls...) { d.DeclCode = removeWhitespace(d.DeclCode, minify) d.MethodListCode = removeWhitespace(d.MethodListCode, minify) d.TypeInitCode = removeWhitespace(d.TypeInitCode, minify) d.InitCode = removeWhitespace(d.InitCode, minify) allDecls = append(allDecls, d) } if len(c.p.errList) != 0 { return nil, c.p.errList } return &Archive{ ImportPath: importPath, Name: typesPkg.Name(), Imports: importedPaths, ExportData: exportData, Declarations: allDecls, FileSet: encodedFileSet.Bytes(), Minified: minify, types: typesPkg, }, nil }
// stmt typechecks statement s. func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) { // statements cannot use iota in general // (constant declarations set it explicitly) assert(check.iota == nil) // statements must end with the same top scope as they started with if debug { defer func(scope *Scope) { // don't check if code is panicking if p := recover(); p != nil { panic(p) } assert(scope == check.scope) }(check.scope) } inner := ctxt &^ fallthroughOk switch s := s.(type) { case *ast.BadStmt, *ast.EmptyStmt: // ignore case *ast.DeclStmt: check.declStmt(s.Decl) case *ast.LabeledStmt: check.hasLabel = true check.stmt(ctxt, s.Stmt) case *ast.ExprStmt: // spec: "With the exception of specific built-in functions, // function and method calls and receive operations can appear // in statement context. Such statements may be parenthesized." var x operand kind := check.rawExpr(&x, s.X, nil) var msg string switch x.mode { default: if kind == statement { return } msg = "is not used" case builtin: msg = "must be called" case typexpr: msg = "is not an expression" } check.errorf(x.pos(), "%s %s", &x, msg) case *ast.SendStmt: var ch, x operand check.expr(&ch, s.Chan) check.expr(&x, s.Value) if ch.mode == invalid || x.mode == invalid { return } tch, ok := ch.typ.Underlying().(*Chan) if !ok { check.invalidOp(s.Arrow, "cannot send to non-chan type %s", ch.typ) return } if tch.dir == RecvOnly { check.invalidOp(s.Arrow, "cannot send to receive-only type %s", tch) return } check.assignment(&x, tch.elem, "send") case *ast.IncDecStmt: var op token.Token switch s.Tok { case token.INC: op = token.ADD case token.DEC: op = token.SUB default: check.invalidAST(s.TokPos, "unknown inc/dec operation %s", s.Tok) return } var x operand check.expr(&x, s.X) if x.mode == invalid { return } if !isNumeric(x.typ) { check.invalidOp(s.X.Pos(), "%s%s (non-numeric type %s)", s.X, s.Tok, x.typ) return } Y := &ast.BasicLit{ValuePos: s.X.Pos(), Kind: token.INT, Value: "1"} // use x's position check.binary(&x, nil, s.X, Y, op) if x.mode == invalid { return } check.assignVar(s.X, &x) case *ast.AssignStmt: switch s.Tok { case token.ASSIGN, token.DEFINE: if len(s.Lhs) == 0 { check.invalidAST(s.Pos(), "missing lhs in assignment") return } if s.Tok == token.DEFINE { check.shortVarDecl(s.TokPos, s.Lhs, s.Rhs) } else { // regular assignment check.assignVars(s.Lhs, s.Rhs) } default: // assignment operations if len(s.Lhs) != 1 || len(s.Rhs) != 1 { check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok) return } op := assignOp(s.Tok) if op == token.ILLEGAL { check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok) return } var x operand check.binary(&x, nil, s.Lhs[0], s.Rhs[0], op) if x.mode == invalid { return } check.assignVar(s.Lhs[0], &x) } case *ast.GoStmt: check.suspendedCall("go", s.Call) case *ast.DeferStmt: check.suspendedCall("defer", s.Call) case *ast.ReturnStmt: res := check.sig.results if res.Len() > 0 { // function returns results // (if one, say the first, result parameter is named, all of them are named) if len(s.Results) == 0 && res.vars[0].name != "" { // spec: "Implementation restriction: A compiler may disallow an empty expression // list in a "return" statement if a different entity (constant, type, or variable) // with the same name as a result parameter is in scope at the place of the return." for _, obj := range res.vars { if _, alt := check.scope.LookupParent(obj.name, check.pos); alt != nil && alt != obj { check.errorf(s.Pos(), "result parameter %s not in scope at return", obj.name) check.errorf(alt.Pos(), "\tinner declaration of %s", obj) // ok to continue } } } else { // return has results or result parameters are unnamed check.initVars(res.vars, s.Results, s.Return) } } else if len(s.Results) > 0 { check.error(s.Results[0].Pos(), "no result values expected") check.use(s.Results...) } case *ast.BranchStmt: if s.Label != nil { check.hasLabel = true return // checked in 2nd pass (check.labels) } switch s.Tok { case token.BREAK: if ctxt&breakOk == 0 { check.error(s.Pos(), "break not in for, switch, or select statement") } case token.CONTINUE: if ctxt&continueOk == 0 { check.error(s.Pos(), "continue not in for statement") } case token.FALLTHROUGH: if ctxt&fallthroughOk == 0 { check.error(s.Pos(), "fallthrough statement out of place") } default: check.invalidAST(s.Pos(), "branch statement: %s", s.Tok) } case *ast.BlockStmt: check.openScope(s, "block") defer check.closeScope() check.stmtList(inner, s.List) case *ast.IfStmt: check.openScope(s, "if") defer check.closeScope() check.simpleStmt(s.Init) var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { check.error(s.Cond.Pos(), "non-boolean condition in if statement") } check.stmt(inner, s.Body) // The parser produces a correct AST but if it was modified // elsewhere the else branch may be invalid. Check again. switch s.Else.(type) { case nil, *ast.BadStmt: // valid or error already reported case *ast.IfStmt, *ast.BlockStmt: check.stmt(inner, s.Else) default: check.error(s.Else.Pos(), "invalid else branch in if statement") } case *ast.SwitchStmt: inner |= breakOk check.openScope(s, "switch") defer check.closeScope() check.simpleStmt(s.Init) var x operand if s.Tag != nil { check.expr(&x, s.Tag) // By checking assignment of x to an invisible temporary // (as a compiler would), we get all the relevant checks. check.assignment(&x, nil, "switch expression") } else { // spec: "A missing switch expression is // equivalent to the boolean value true." x.mode = constant_ x.typ = Typ[Bool] x.val = constant.MakeBool(true) x.expr = &ast.Ident{NamePos: s.Body.Lbrace, Name: "true"} } check.multipleDefaults(s.Body.List) seen := make(valueMap) // map of seen case values to positions and types for i, c := range s.Body.List { clause, _ := c.(*ast.CaseClause) if clause == nil { check.invalidAST(c.Pos(), "incorrect expression switch case") continue } check.caseValues(&x, clause.List, seen) check.openScope(clause, "case") inner := inner if i+1 < len(s.Body.List) { inner |= fallthroughOk } check.stmtList(inner, clause.Body) check.closeScope() } case *ast.TypeSwitchStmt: inner |= breakOk check.openScope(s, "type switch") defer check.closeScope() check.simpleStmt(s.Init) // A type switch guard must be of the form: // // TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . // // The parser is checking syntactic correctness; // remaining syntactic errors are considered AST errors here. // TODO(gri) better factoring of error handling (invalid ASTs) // var lhs *ast.Ident // lhs identifier or nil var rhs ast.Expr switch guard := s.Assign.(type) { case *ast.ExprStmt: rhs = guard.X case *ast.AssignStmt: if len(guard.Lhs) != 1 || guard.Tok != token.DEFINE || len(guard.Rhs) != 1 { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } lhs, _ = guard.Lhs[0].(*ast.Ident) if lhs == nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } if lhs.Name == "_" { // _ := x.(type) is an invalid short variable declaration check.softErrorf(lhs.Pos(), "no new variable on left side of :=") lhs = nil // avoid declared but not used error below } else { check.recordDef(lhs, nil) // lhs variable is implicitly declared in each cause clause } rhs = guard.Rhs[0] default: check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } // rhs must be of the form: expr.(type) and expr must be an interface expr, _ := rhs.(*ast.TypeAssertExpr) if expr == nil || expr.Type != nil { check.invalidAST(s.Pos(), "incorrect form of type switch guard") return } var x operand check.expr(&x, expr.X) if x.mode == invalid { return } xtyp, _ := x.typ.Underlying().(*Interface) if xtyp == nil { check.errorf(x.pos(), "%s is not an interface", &x) return } check.multipleDefaults(s.Body.List) var lhsVars []*Var // list of implicitly declared lhs variables seen := make(map[Type]token.Pos) // map of seen types to positions for _, s := range s.Body.List { clause, _ := s.(*ast.CaseClause) if clause == nil { check.invalidAST(s.Pos(), "incorrect type switch case") continue } // Check each type in this type switch case. T := check.caseTypes(&x, xtyp, clause.List, seen) check.openScope(clause, "case") // If lhs exists, declare a corresponding variable in the case-local scope. if lhs != nil { // spec: "The TypeSwitchGuard may include a short variable declaration. // When that form is used, the variable is declared at the beginning of // the implicit block in each clause. In clauses with a case listing // exactly one type, the variable has that type; otherwise, the variable // has the type of the expression in the TypeSwitchGuard." if len(clause.List) != 1 || T == nil { T = x.typ } obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) scopePos := clause.End() if len(clause.Body) > 0 { scopePos = clause.Body[0].Pos() } check.declare(check.scope, nil, obj, scopePos) check.recordImplicit(clause, obj) // For the "declared but not used" error, all lhs variables act as // one; i.e., if any one of them is 'used', all of them are 'used'. // Collect them for later analysis. lhsVars = append(lhsVars, obj) } check.stmtList(inner, clause.Body) check.closeScope() } // If lhs exists, we must have at least one lhs variable that was used. if lhs != nil { var used bool for _, v := range lhsVars { if v.used { used = true } v.used = true // avoid usage error when checking entire function } if !used { check.softErrorf(lhs.Pos(), "%s declared but not used", lhs.Name) } } case *ast.SelectStmt: inner |= breakOk check.multipleDefaults(s.Body.List) for _, s := range s.Body.List { clause, _ := s.(*ast.CommClause) if clause == nil { continue // error reported before } // clause.Comm must be a SendStmt, RecvStmt, or default case valid := false var rhs ast.Expr // rhs of RecvStmt, or nil switch s := clause.Comm.(type) { case nil, *ast.SendStmt: valid = true case *ast.AssignStmt: if len(s.Rhs) == 1 { rhs = s.Rhs[0] } case *ast.ExprStmt: rhs = s.X } // if present, rhs must be a receive operation if rhs != nil { if x, _ := unparen(rhs).(*ast.UnaryExpr); x != nil && x.Op == token.ARROW { valid = true } } if !valid { check.error(clause.Comm.Pos(), "select case must be send or receive (possibly with assignment)") continue } check.openScope(s, "case") if clause.Comm != nil { check.stmt(inner, clause.Comm) } check.stmtList(inner, clause.Body) check.closeScope() } case *ast.ForStmt: inner |= breakOk | continueOk check.openScope(s, "for") defer check.closeScope() check.simpleStmt(s.Init) if s.Cond != nil { var x operand check.expr(&x, s.Cond) if x.mode != invalid && !isBoolean(x.typ) { check.error(s.Cond.Pos(), "non-boolean condition in for statement") } } check.simpleStmt(s.Post) // spec: "The init statement may be a short variable // declaration, but the post statement must not." if s, _ := s.Post.(*ast.AssignStmt); s != nil && s.Tok == token.DEFINE { check.softErrorf(s.Pos(), "cannot declare in post statement") check.use(s.Lhs...) // avoid follow-up errors } check.stmt(inner, s.Body) case *ast.RangeStmt: inner |= breakOk | continueOk check.openScope(s, "for") defer check.closeScope() // check expression to iterate over var x operand check.expr(&x, s.X) // determine key/value types var key, val Type if x.mode != invalid { switch typ := x.typ.Underlying().(type) { case *Basic: if isString(typ) { key = Typ[Int] val = universeRune // use 'rune' name } case *Array: key = Typ[Int] val = typ.elem case *Slice: key = Typ[Int] val = typ.elem case *Pointer: if typ, _ := typ.base.Underlying().(*Array); typ != nil { key = Typ[Int] val = typ.elem } case *Map: key = typ.key val = typ.elem case *Chan: key = typ.elem val = Typ[Invalid] if typ.dir == SendOnly { check.errorf(x.pos(), "cannot range over send-only channel %s", &x) // ok to continue } if s.Value != nil { check.errorf(s.Value.Pos(), "iteration over %s permits only one iteration variable", &x) // ok to continue } } } if key == nil { check.errorf(x.pos(), "cannot range over %s", &x) // ok to continue } // check assignment to/declaration of iteration variables // (irregular assignment, cannot easily map to existing assignment checks) // lhs expressions and initialization value (rhs) types lhs := [2]ast.Expr{s.Key, s.Value} rhs := [2]Type{key, val} // key, val may be nil if s.Tok == token.DEFINE { // short variable declaration; variable scope starts after the range clause // (the for loop opens a new scope, so variables on the lhs never redeclare // previously declared variables) var vars []*Var for i, lhs := range lhs { if lhs == nil { continue } // determine lhs variable var obj *Var if ident, _ := lhs.(*ast.Ident); ident != nil { // declare new variable name := ident.Name obj = NewVar(ident.Pos(), check.pkg, name, nil) check.recordDef(ident, obj) // _ variables don't count as new variables if name != "_" { vars = append(vars, obj) } } else { check.errorf(lhs.Pos(), "cannot declare %s", lhs) obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable } // initialize lhs variable if typ := rhs[i]; typ != nil { x.mode = value x.expr = lhs // we don't have a better rhs expression to use here x.typ = typ check.initVar(obj, &x, "range clause") } else { obj.typ = Typ[Invalid] obj.used = true // don't complain about unused variable } } // declare variables if len(vars) > 0 { for _, obj := range vars { // spec: "The scope of a constant or variable identifier declared inside // a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl // for short variable declarations) and ends at the end of the innermost // containing block." scopePos := s.End() check.declare(check.scope, nil /* recordDef already called */, obj, scopePos) } } else { check.error(s.TokPos, "no new variables on left side of :=") } } else { // ordinary assignment for i, lhs := range lhs { if lhs == nil { continue } if typ := rhs[i]; typ != nil { x.mode = value x.expr = lhs // we don't have a better rhs expression to use here x.typ = typ check.assignVar(lhs, &x) } } } check.stmt(inner, s.Body) default: check.error(s.Pos(), "invalid statement") } }
// ConstValue = string | "false" | "true" | ["-"] (int ["'"] | FloatOrComplex) . // FloatOrComplex = float ["i" | ("+"|"-") float "i"] . func (p *parser) parseConstValue() (val constant.Value, typ types.Type) { switch p.tok { case scanner.String: str := p.parseString() val = constant.MakeString(str) typ = types.Typ[types.UntypedString] return case scanner.Ident: b := false switch p.lit { case "false": case "true": b = true default: p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit) } p.next() val = constant.MakeBool(b) typ = types.Typ[types.UntypedBool] return } sign := "" if p.tok == '-' { p.next() sign = "-" } switch p.tok { case scanner.Int: val = constant.MakeFromLiteral(sign+p.lit, token.INT, 0) if val == nil { p.error("could not parse integer literal") } p.next() if p.tok == '\'' { p.next() typ = types.Typ[types.UntypedRune] } else { typ = types.Typ[types.UntypedInt] } case scanner.Float: re := sign + p.lit p.next() var im string switch p.tok { case '+': p.next() im = p.expect(scanner.Float) case '-': p.next() im = "-" + p.expect(scanner.Float) case scanner.Ident: // re is in fact the imaginary component. Expect "i" below. im = re re = "0" default: val = constant.MakeFromLiteral(re, token.FLOAT, 0) if val == nil { p.error("could not parse float literal") } typ = types.Typ[types.UntypedFloat] return } p.expectKeyword("i") reval := constant.MakeFromLiteral(re, token.FLOAT, 0) if reval == nil { p.error("could not parse real component of complex literal") } imval := constant.MakeFromLiteral(im+"i", token.IMAG, 0) if imval == nil { p.error("could not parse imag component of complex literal") } val = constant.BinaryOp(reval, token.ADD, imval) typ = types.Typ[types.UntypedComplex] default: p.errorf("expected const value, got %s (%q)", scanner.TokenString(p.tok), p.lit) } return }
// Error has a nil package in its qualified name since it is in no package res := NewVar(token.NoPos, nil, "", Typ[String]) sig := &Signature{results: NewTuple(res)} err := NewFunc(token.NoPos, nil, "Error", sig) typ := &Named{underlying: NewInterface([]*Func{err}, nil).Complete()} sig.recv = NewVar(token.NoPos, nil, "", typ) def(NewTypeName(token.NoPos, nil, "error", typ)) } var predeclaredConsts = [...]struct { name string kind BasicKind val exact.Value }{ {"true", UntypedBool, exact.MakeBool(true)}, {"false", UntypedBool, exact.MakeBool(false)}, {"iota", UntypedInt, exact.MakeInt64(0)}, } func defPredeclaredConsts() { for _, c := range predeclaredConsts { def(NewConst(token.NoPos, nil, c.name, Typ[c.kind], c.val)) } } func defPredeclaredNil() { def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}}) } // A builtinId is the id of a builtin function.
func (scope *EvalScope) evalBinary(node *ast.BinaryExpr) (*Variable, error) { switch node.Op { case token.INC, token.DEC, token.ARROW: return nil, fmt.Errorf("operator %s not supported", node.Op.String()) } xv, err := scope.evalAST(node.X) if err != nil { return nil, err } yv, err := scope.evalAST(node.Y) if err != nil { return nil, err } xv.loadValue() yv.loadValue() if xv.Unreadable != nil { return nil, xv.Unreadable } if yv.Unreadable != nil { return nil, yv.Unreadable } typ, err := negotiateType(node.Op, xv, yv) if err != nil { return nil, err } op := node.Op if typ != nil && (op == token.QUO) { _, isint := typ.(*dwarf.IntType) _, isuint := typ.(*dwarf.UintType) if isint || isuint { // forces integer division if the result type is integer op = token.QUO_ASSIGN } } switch op { case token.EQL, token.LSS, token.GTR, token.NEQ, token.LEQ, token.GEQ: v, err := compareOp(op, xv, yv) if err != nil { return nil, err } return newConstant(constant.MakeBool(v), xv.mem), nil default: if xv.Value == nil { return nil, fmt.Errorf("operator %s can not be applied to \"%s\"", node.Op.String(), exprToString(node.X)) } if yv.Value == nil { return nil, fmt.Errorf("operator %s can not be applied to \"%s\"", node.Op.String(), exprToString(node.Y)) } rc, err := constantBinaryOp(op, xv.Value, yv.Value) if err != nil { return nil, err } if typ == nil { return newConstant(rc, xv.mem), nil } r := xv.newVariable("", 0, typ) r.Value = rc return r, nil } }
func (v *Variable) loadValueInternal(recurseLevel int) { if v.Unreadable != nil || v.loaded || (v.Addr == 0 && v.base == 0) { return } v.loaded = true switch v.Kind { case reflect.Ptr, reflect.UnsafePointer: v.Len = 1 v.Children = []Variable{*v.maybeDereference()} // Don't increase the recursion level when dereferencing pointers v.Children[0].loadValueInternal(recurseLevel) case reflect.Chan: sv := v.maybeDereference() sv.loadValueInternal(recurseLevel) v.Children = sv.Children v.Len = sv.Len v.base = sv.Addr case reflect.Map: v.loadMap(recurseLevel) case reflect.String: var val string val, v.Unreadable = v.thread.readStringValue(v.base, v.Len) v.Value = constant.MakeString(val) case reflect.Slice, reflect.Array: v.loadArrayValues(recurseLevel) case reflect.Struct: t := v.RealType.(*dwarf.StructType) v.Len = int64(len(t.Field)) // Recursively call extractValue to grab // the value of all the members of the struct. if recurseLevel <= maxVariableRecurse { v.Children = make([]Variable, 0, len(t.Field)) for i, field := range t.Field { f, _ := v.toField(field) v.Children = append(v.Children, *f) v.Children[i].Name = field.Name v.Children[i].loadValueInternal(recurseLevel + 1) } } case reflect.Complex64, reflect.Complex128: v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var val int64 val, v.Unreadable = v.thread.readIntRaw(v.Addr, v.RealType.(*dwarf.IntType).ByteSize) v.Value = constant.MakeInt64(val) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: var val uint64 val, v.Unreadable = v.thread.readUintRaw(v.Addr, v.RealType.(*dwarf.UintType).ByteSize) v.Value = constant.MakeUint64(val) case reflect.Bool: val, err := v.thread.readMemory(v.Addr, 1) v.Unreadable = err if err == nil { v.Value = constant.MakeBool(val[0] != 0) } case reflect.Float32, reflect.Float64: var val float64 val, v.Unreadable = v.readFloatRaw(v.RealType.(*dwarf.FloatType).ByteSize) v.Value = constant.MakeFloat64(val) case reflect.Func: v.readFunctionPtr() default: v.Unreadable = fmt.Errorf("unknown or unsupported kind: \"%s\"", v.Kind.String()) } }
// Error has a nil package in its qualified name since it is in no package res := NewVar(token.NoPos, nil, "", Typ[String]) sig := &Signature{results: NewTuple(res)} err := NewFunc(token.NoPos, nil, "Error", sig) typ := &Named{underlying: NewInterface([]*Func{err}, nil).Complete()} sig.recv = NewVar(token.NoPos, nil, "", typ) def(NewTypeName(token.NoPos, nil, "error", typ)) } var predeclaredConsts = [...]struct { name string kind BasicKind val constant.Value }{ {"true", UntypedBool, constant.MakeBool(true)}, {"false", UntypedBool, constant.MakeBool(false)}, {"iota", UntypedInt, constant.MakeInt64(0)}, } func defPredeclaredConsts() { for _, c := range predeclaredConsts { def(NewConst(token.NoPos, nil, c.name, Typ[c.kind], c.val)) } } func defPredeclaredNil() { def(&Nil{object{name: "nil", typ: Typ[UntypedNil]}}) } // A builtinId is the id of a builtin function.
// ConstDecl = "const" ExportedName [ Type ] "=" Literal . // Literal = bool_lit | int_lit | float_lit | complex_lit | rune_lit | string_lit . // bool_lit = "true" | "false" . // complex_lit = "(" float_lit "+" float_lit "i" ")" . // rune_lit = "(" int_lit "+" int_lit ")" . // string_lit = `"` { unicode_char } `"` . // func (p *parser) parseConstDecl() { p.expectKeyword("const") pkg, name := p.parseExportedName() var typ0 types.Type if p.tok != '=' { // constant types are never structured - no need for parent type typ0 = p.parseType(nil) } p.expect('=') var typ types.Type var val exact.Value switch p.tok { case scanner.Ident: // bool_lit if p.lit != "true" && p.lit != "false" { p.error("expected true or false") } typ = types.Typ[types.UntypedBool] val = exact.MakeBool(p.lit == "true") p.next() case '-', scanner.Int: // int_lit typ, val = p.parseNumber() case '(': // complex_lit or rune_lit p.next() if p.tok == scanner.Char { p.next() p.expect('+') typ = types.Typ[types.UntypedRune] _, val = p.parseNumber() p.expect(')') break } _, re := p.parseNumber() p.expect('+') _, im := p.parseNumber() p.expectKeyword("i") p.expect(')') typ = types.Typ[types.UntypedComplex] val = exact.BinaryOp(re, token.ADD, exact.MakeImag(im)) case scanner.Char: // rune_lit typ = types.Typ[types.UntypedRune] val = exact.MakeFromLiteral(p.lit, token.CHAR, 0) p.next() case scanner.String: // string_lit typ = types.Typ[types.UntypedString] val = exact.MakeFromLiteral(p.lit, token.STRING, 0) p.next() default: p.errorf("expected literal got %s", scanner.TokenString(p.tok)) } if typ0 == nil { typ0 = typ } pkg.Scope().Insert(types.NewConst(token.NoPos, pkg, name, typ0, val)) }
func (v *Variable) loadValueInternal(recurseLevel int, cfg LoadConfig) { if v.Unreadable != nil || v.loaded || (v.Addr == 0 && v.Base == 0) { return } v.loaded = true switch v.Kind { case reflect.Ptr, reflect.UnsafePointer: v.Len = 1 v.Children = []Variable{*v.maybeDereference()} if cfg.FollowPointers { // Don't increase the recursion level when dereferencing pointers v.Children[0].loadValueInternal(recurseLevel, cfg) } else { v.Children[0].OnlyAddr = true } case reflect.Chan: sv := v.clone() sv.RealType = resolveTypedef(&(sv.RealType.(*dwarf.ChanType).TypedefType)) sv = sv.maybeDereference() sv.loadValueInternal(0, loadFullValue) v.Children = sv.Children v.Len = sv.Len v.Base = sv.Addr case reflect.Map: if recurseLevel <= cfg.MaxVariableRecurse { v.loadMap(recurseLevel, cfg) } case reflect.String: var val string val, v.Unreadable = readStringValue(v.mem, v.Base, v.Len, cfg) v.Value = constant.MakeString(val) case reflect.Slice, reflect.Array: v.loadArrayValues(recurseLevel, cfg) case reflect.Struct: v.mem = cacheMemory(v.mem, v.Addr, int(v.RealType.Size())) t := v.RealType.(*dwarf.StructType) v.Len = int64(len(t.Field)) // Recursively call extractValue to grab // the value of all the members of the struct. if recurseLevel <= cfg.MaxVariableRecurse { v.Children = make([]Variable, 0, len(t.Field)) for i, field := range t.Field { if cfg.MaxStructFields >= 0 && len(v.Children) >= cfg.MaxStructFields { break } f, _ := v.toField(field) v.Children = append(v.Children, *f) v.Children[i].Name = field.Name v.Children[i].loadValueInternal(recurseLevel+1, cfg) } } case reflect.Interface: v.loadInterface(recurseLevel, true, cfg) case reflect.Complex64, reflect.Complex128: v.readComplex(v.RealType.(*dwarf.ComplexType).ByteSize) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: var val int64 val, v.Unreadable = readIntRaw(v.mem, v.Addr, v.RealType.(*dwarf.IntType).ByteSize) v.Value = constant.MakeInt64(val) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: var val uint64 val, v.Unreadable = readUintRaw(v.mem, v.Addr, v.RealType.(*dwarf.UintType).ByteSize) v.Value = constant.MakeUint64(val) case reflect.Bool: val, err := v.mem.readMemory(v.Addr, 1) v.Unreadable = err if err == nil { v.Value = constant.MakeBool(val[0] != 0) } case reflect.Float32, reflect.Float64: var val float64 val, v.Unreadable = v.readFloatRaw(v.RealType.(*dwarf.FloatType).ByteSize) v.Value = constant.MakeFloat64(val) case reflect.Func: v.readFunctionPtr() default: v.Unreadable = fmt.Errorf("unknown or unsupported kind: \"%s\"", v.Kind.String()) } }
func (c *funcContext) translateStmt(stmt ast.Stmt, label *types.Label) { c.SetPos(stmt.Pos()) stmt = filter.IncDecStmt(stmt, c.p.Info) stmt = filter.Assign(stmt, c.p.Info) switch s := stmt.(type) { case *ast.BlockStmt: c.translateStmtList(s.List) case *ast.IfStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } var caseClauses []ast.Stmt ifStmt := s for { caseClauses = append(caseClauses, &ast.CaseClause{List: []ast.Expr{ifStmt.Cond}, Body: ifStmt.Body.List}) switch elseStmt := ifStmt.Else.(type) { case *ast.IfStmt: if elseStmt.Init != nil { caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: []ast.Stmt{elseStmt}}) break } ifStmt = elseStmt continue case *ast.BlockStmt: caseClauses = append(caseClauses, &ast.CaseClause{List: nil, Body: elseStmt.List}) case *ast.EmptyStmt, nil: // no else clause default: panic(fmt.Sprintf("Unhandled else: %T\n", elseStmt)) } break } c.translateBranchingStmt(caseClauses, false, nil, nil, nil, c.Flattened[s]) case *ast.SwitchStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } tag := s.Tag if tag == nil { tag = ast.NewIdent("true") c.p.Types[tag] = types.TypeAndValue{Type: types.Typ[types.Bool], Value: constant.MakeBool(true)} } if c.p.Types[tag].Value == nil { refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(tag)) tag = c.newIdent(refVar, c.p.TypeOf(tag)) } translateCond := func(cond ast.Expr) *expression { return c.translateExpr(&ast.BinaryExpr{ X: tag, Op: token.EQL, Y: cond, }) } c.translateBranchingStmt(s.Body.List, true, translateCond, nil, label, c.Flattened[s]) case *ast.TypeSwitchStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } refVar := c.newVariable("_ref") var expr ast.Expr var printCaseBodyPrefix func(index int) switch a := s.Assign.(type) { case *ast.AssignStmt: expr = a.Rhs[0].(*ast.TypeAssertExpr).X printCaseBodyPrefix = func(index int) { value := refVar caseClause := s.Body.List[index].(*ast.CaseClause) if len(caseClause.List) == 1 { t := c.p.TypeOf(caseClause.List[0]) if _, isInterface := t.Underlying().(*types.Interface); !isInterface && !types.Identical(t, types.Typ[types.UntypedNil]) { value += ".$val" } } c.Printf("%s = %s;", c.objectName(c.p.Implicits[caseClause]), value) } case *ast.ExprStmt: expr = a.X.(*ast.TypeAssertExpr).X } c.Printf("%s = %s;", refVar, c.translateExpr(expr)) translateCond := func(cond ast.Expr) *expression { if types.Identical(c.p.TypeOf(cond), types.Typ[types.UntypedNil]) { return c.formatExpr("%s === $ifaceNil", refVar) } return c.formatExpr("$assertType(%s, %s, true)[1]", refVar, c.typeName(c.p.TypeOf(cond))) } c.translateBranchingStmt(s.Body.List, true, translateCond, printCaseBodyPrefix, label, c.Flattened[s]) case *ast.ForStmt: if s.Init != nil { c.translateStmt(s.Init, nil) } cond := func() string { if s.Cond == nil { return "true" } return c.translateExpr(s.Cond).String() } c.translateLoopingStmt(cond, s.Body, nil, func() { if s.Post != nil { c.translateStmt(s.Post, nil) } }, label, c.Flattened[s]) case *ast.RangeStmt: refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.X)) switch t := c.p.TypeOf(s.X).Underlying().(type) { case *types.Basic: iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) runeVar := c.newVariable("_rune") c.translateLoopingStmt(func() string { return iVar + " < " + refVar + ".length" }, s.Body, func() { c.Printf("%s = $decodeRune(%s, %s);", runeVar, refVar, iVar) if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, c.newIdent(iVar, types.Typ[types.Int]), s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, c.newIdent(runeVar+"[0]", types.Typ[types.Rune]), s.Tok == token.DEFINE)) } }, func() { c.Printf("%s += %s[1];", iVar, runeVar) }, label, c.Flattened[s]) case *types.Map: iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) keysVar := c.newVariable("_keys") c.Printf("%s = $keys(%s);", keysVar, refVar) c.translateLoopingStmt(func() string { return iVar + " < " + keysVar + ".length" }, s.Body, func() { entryVar := c.newVariable("_entry") c.Printf("%s = %s[%s[%s]];", entryVar, refVar, keysVar, iVar) c.translateStmt(&ast.IfStmt{ Cond: c.newIdent(entryVar+" === undefined", types.Typ[types.Bool]), Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.CONTINUE}}}, }, nil) if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, c.newIdent(entryVar+".k", t.Key()), s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, c.newIdent(entryVar+".v", t.Elem()), s.Tok == token.DEFINE)) } }, func() { c.Printf("%s++;", iVar) }, label, c.Flattened[s]) case *types.Array, *types.Pointer, *types.Slice: var length string var elemType types.Type switch t2 := t.(type) { case *types.Array: length = fmt.Sprintf("%d", t2.Len()) elemType = t2.Elem() case *types.Pointer: length = fmt.Sprintf("%d", t2.Elem().Underlying().(*types.Array).Len()) elemType = t2.Elem().Underlying().(*types.Array).Elem() case *types.Slice: length = refVar + ".$length" elemType = t2.Elem() } iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) c.translateLoopingStmt(func() string { return iVar + " < " + length }, s.Body, func() { if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, c.newIdent(iVar, types.Typ[types.Int]), s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, c.setType(&ast.IndexExpr{ X: c.newIdent(refVar, t), Index: c.newIdent(iVar, types.Typ[types.Int]), }, elemType), s.Tok == token.DEFINE)) } }, func() { c.Printf("%s++;", iVar) }, label, c.Flattened[s]) case *types.Chan: okVar := c.newIdent(c.newVariable("_ok"), types.Typ[types.Bool]) key := s.Key tok := s.Tok if key == nil { key = ast.NewIdent("_") tok = token.ASSIGN } forStmt := &ast.ForStmt{ Body: &ast.BlockStmt{ List: []ast.Stmt{ &ast.AssignStmt{ Lhs: []ast.Expr{ key, okVar, }, Rhs: []ast.Expr{ 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]))), }, Tok: tok, }, &ast.IfStmt{ Cond: &ast.UnaryExpr{X: okVar, Op: token.NOT}, Body: &ast.BlockStmt{List: []ast.Stmt{&ast.BranchStmt{Tok: token.BREAK}}}, }, s.Body, }, }, } c.Flattened[forStmt] = true c.translateStmt(forStmt, label) default: panic("") } case *ast.BranchStmt: normalLabel := "" blockingLabel := "" data := c.flowDatas[nil] if s.Label != nil { normalLabel = " " + s.Label.Name blockingLabel = " s" // use explicit label "s", because surrounding loop may not be flattened data = c.flowDatas[c.p.Uses[s.Label].(*types.Label)] } switch s.Tok { case token.BREAK: c.PrintCond(data.endCase == 0, fmt.Sprintf("break%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.endCase, blockingLabel)) case token.CONTINUE: data.postStmt() c.PrintCond(data.beginCase == 0, fmt.Sprintf("continue%s;", normalLabel), fmt.Sprintf("$s = %d; continue%s;", data.beginCase, blockingLabel)) case token.GOTO: c.PrintCond(false, "goto "+s.Label.Name, fmt.Sprintf("$s = %d; continue;", c.labelCase(c.p.Uses[s.Label].(*types.Label)))) case token.FALLTHROUGH: // handled in CaseClause default: panic("Unhandled branch statment: " + s.Tok.String()) } case *ast.ReturnStmt: results := s.Results if c.resultNames != nil { if len(s.Results) != 0 { c.translateStmt(&ast.AssignStmt{ Lhs: c.resultNames, Tok: token.ASSIGN, Rhs: s.Results, }, nil) } results = c.resultNames } rVal := c.translateResults(results) if c.Flattened[s] { resumeCase := c.caseCounter c.caseCounter++ c.Printf("/* */ $s = %[1]d; case %[1]d:", resumeCase) } c.Printf("return%s;", rVal) case *ast.DeferStmt: isBuiltin := false isJs := false switch fun := s.Call.Fun.(type) { case *ast.Ident: var builtin *types.Builtin builtin, isBuiltin = c.p.Uses[fun].(*types.Builtin) if isBuiltin && builtin.Name() == "recover" { c.Printf("$deferred.push([$recover, []]);") return } case *ast.SelectorExpr: isJs = typesutil.IsJsPackage(c.p.Uses[fun.Sel].Pkg()) } sig := c.p.TypeOf(s.Call.Fun).Underlying().(*types.Signature) args := c.translateArgs(sig, s.Call.Args, s.Call.Ellipsis.IsValid(), true) if isBuiltin || isJs { vars := make([]string, len(s.Call.Args)) callArgs := make([]ast.Expr, len(s.Call.Args)) for i, arg := range s.Call.Args { v := c.newVariable("_arg") vars[i] = v callArgs[i] = c.newIdent(v, c.p.TypeOf(arg)) } call := c.translateExpr(&ast.CallExpr{ Fun: s.Call.Fun, Args: callArgs, Ellipsis: s.Call.Ellipsis, }) c.Printf("$deferred.push([function(%s) { %s; }, [%s]]);", strings.Join(vars, ", "), call, strings.Join(args, ", ")) return } c.Printf("$deferred.push([%s, [%s]]);", c.translateExpr(s.Call.Fun), strings.Join(args, ", ")) case *ast.AssignStmt: if s.Tok != token.ASSIGN && s.Tok != token.DEFINE { panic(s.Tok) } switch { case len(s.Lhs) == 1 && len(s.Rhs) == 1: lhs := astutil.RemoveParens(s.Lhs[0]) if isBlank(lhs) { if analysis.HasSideEffect(s.Rhs[0], c.p.Info.Info) { c.Printf("%s;", c.translateExpr(s.Rhs[0])) } return } c.Printf("%s", c.translateAssign(lhs, s.Rhs[0], s.Tok == token.DEFINE)) case len(s.Lhs) > 1 && len(s.Rhs) == 1: tupleVar := c.newVariable("_tuple") c.Printf("%s = %s;", tupleVar, c.translateExpr(s.Rhs[0])) tuple := c.p.TypeOf(s.Rhs[0]).(*types.Tuple) for i, lhs := range s.Lhs { lhs = astutil.RemoveParens(lhs) if !isBlank(lhs) { c.Printf("%s", c.translateAssign(lhs, c.newIdent(fmt.Sprintf("%s[%d]", tupleVar, i), tuple.At(i).Type()), s.Tok == token.DEFINE)) } } case len(s.Lhs) == len(s.Rhs): tmpVars := make([]string, len(s.Rhs)) for i, rhs := range s.Rhs { tmpVars[i] = c.newVariable("_tmp") if isBlank(astutil.RemoveParens(s.Lhs[i])) { if analysis.HasSideEffect(rhs, c.p.Info.Info) { c.Printf("%s;", c.translateExpr(rhs)) } continue } c.Printf("%s", c.translateAssign(c.newIdent(tmpVars[i], c.p.TypeOf(s.Lhs[i])), rhs, true)) } for i, lhs := range s.Lhs { lhs = astutil.RemoveParens(lhs) if !isBlank(lhs) { c.Printf("%s", c.translateAssign(lhs, c.newIdent(tmpVars[i], c.p.TypeOf(lhs)), s.Tok == token.DEFINE)) } } default: panic("Invalid arity of AssignStmt.") } case *ast.DeclStmt: decl := s.Decl.(*ast.GenDecl) switch decl.Tok { case token.VAR: for _, spec := range s.Decl.(*ast.GenDecl).Specs { valueSpec := spec.(*ast.ValueSpec) lhs := make([]ast.Expr, len(valueSpec.Names)) for i, name := range valueSpec.Names { lhs[i] = name } rhs := valueSpec.Values if len(rhs) == 0 { rhs = make([]ast.Expr, len(lhs)) for i, e := range lhs { rhs[i] = c.zeroValue(c.p.TypeOf(e)) } } c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: rhs, }, nil) } case token.TYPE: for _, spec := range decl.Specs { o := c.p.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) c.p.typeNames = append(c.p.typeNames, o) c.p.objectNames[o] = c.newVariableWithLevel(o.Name(), true) c.p.dependencies[o] = true } case token.CONST: // skip, constants are inlined } case *ast.ExprStmt: expr := c.translateExpr(s.X) if expr != nil && expr.String() != "" { c.Printf("%s;", expr) } case *ast.LabeledStmt: label := c.p.Defs[s.Label].(*types.Label) if c.GotoLabel[label] { c.PrintCond(false, s.Label.Name+":", fmt.Sprintf("case %d:", c.labelCase(label))) } c.translateStmt(s.Stmt, label) case *ast.GoStmt: 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(), true), ", ")) case *ast.SendStmt: chanType := c.p.TypeOf(s.Chan).Underlying().(*types.Chan) call := &ast.CallExpr{ Fun: c.newIdent("$send", types.NewSignature(nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)), Args: []ast.Expr{s.Chan, c.newIdent(c.translateImplicitConversionWithCloning(s.Value, chanType.Elem()).String(), chanType.Elem())}, } c.Blocking[call] = true c.translateStmt(&ast.ExprStmt{X: call}, label) case *ast.SelectStmt: var channels []string var caseClauses []ast.Stmt flattened := false hasDefault := false for i, s := range s.Body.List { clause := s.(*ast.CommClause) switch comm := clause.Comm.(type) { case nil: channels = append(channels, "[]") hasDefault = true case *ast.ExprStmt: channels = append(channels, c.formatExpr("[%e]", astutil.RemoveParens(comm.X).(*ast.UnaryExpr).X).String()) case *ast.AssignStmt: channels = append(channels, c.formatExpr("[%e]", astutil.RemoveParens(comm.Rhs[0]).(*ast.UnaryExpr).X).String()) case *ast.SendStmt: chanType := c.p.TypeOf(comm.Chan).Underlying().(*types.Chan) channels = append(channels, c.formatExpr("[%e, %s]", comm.Chan, c.translateImplicitConversionWithCloning(comm.Value, chanType.Elem())).String()) default: panic(fmt.Sprintf("unhandled: %T", comm)) } indexLit := &ast.BasicLit{Kind: token.INT} c.p.Types[indexLit] = types.TypeAndValue{Type: types.Typ[types.Int], Value: constant.MakeInt64(int64(i))} caseClauses = append(caseClauses, &ast.CaseClause{ List: []ast.Expr{indexLit}, Body: clause.Body, }) flattened = flattened || c.Flattened[clause] } selectCall := c.setType(&ast.CallExpr{ 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)), Args: []ast.Expr{c.newIdent(fmt.Sprintf("[%s]", strings.Join(channels, ", ")), types.NewInterface(nil, nil))}, }, types.Typ[types.Int]) c.Blocking[selectCall] = !hasDefault selectionVar := c.newVariable("_selection") c.Printf("%s = %s;", selectionVar, c.translateExpr(selectCall)) translateCond := func(cond ast.Expr) *expression { return c.formatExpr("%s[0] === %e", selectionVar, cond) } printCaseBodyPrefix := func(index int) { if assign, ok := s.Body.List[index].(*ast.CommClause).Comm.(*ast.AssignStmt); ok { switch rhsType := c.p.TypeOf(assign.Rhs[0]).(type) { case *types.Tuple: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1]", rhsType)}, Tok: assign.Tok}, nil) default: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1][0]", rhsType)}, Tok: assign.Tok}, nil) } } } c.translateBranchingStmt(caseClauses, true, translateCond, printCaseBodyPrefix, label, flattened) case *ast.EmptyStmt: // skip default: panic(fmt.Sprintf("Unhandled statement: %T\n", s)) } }