//ranges through config file and checks all expressions. // prints result messages to stdout func (c *checker) CheckAll() ([]CheckResult, error) { result := []CheckResult{} cnf, err := conf.ReadConfigFile(c.configFile) if err != nil { return nil, err } for _, section := range cnf.GetSections() { if section == "default" { continue } expr, _ := cnf.GetString(section, "expr") _, r, err := types.Eval(expr, c.pkg, c.sc) if err != nil { fmt.Fprintln(os.Stderr, err) continue } cr := &CheckResult{ Name: section, } var m string if exact.BoolVal(r) { m, err = cnf.GetString(section, "true") if err != nil { continue } } else { m, err = cnf.GetString(section, "false") if err != nil { continue } } val, err := cnf.GetString(section, "val") if err == nil { t, v, err := types.Eval(val, c.pkg, c.sc) if err == nil { if types.Identical(t, types.Typ[types.UntypedFloat]) || types.Identical(t, types.Typ[types.Float64]) { x, _ := exact.Float64Val(v) cr.Value = x } } } owner, err := cnf.GetString(section, "owner") if err == nil { cr.Owner = owner } else { cr.Owner = "unknown" } _, msg, err := types.Eval(m, c.pkg, c.sc) if err != nil { cr.Message = m } else { cr.Message = exact.StringVal(msg) } result = append(result, *cr) } return result, nil }
// typeAssert checks whether dynamic type of itf is instr.AssertedType. // It returns the extracted value on success, and panics on failure, // unless instr.CommaOk, in which case it always returns a "value,ok" tuple. // func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value { var v value err := "" if itf.t == nil { err = fmt.Sprintf("interface conversion: interface is nil, not %s", instr.AssertedType) } else if idst, ok := instr.AssertedType.Underlying().(*types.Interface); ok { v = itf err = checkInterface(i, idst, itf) } else if types.Identical(itf.t, instr.AssertedType) { v = copyVal(itf.v) // extract value } else { err = fmt.Sprintf("interface conversion: interface is %s, not %s", itf.t, instr.AssertedType) } if err != "" { if !instr.CommaOk { panic(err) } return tuple{zero(instr.AssertedType), false} } if instr.CommaOk { return tuple{v, true} } return v }
// Set sets the map entry for key to val, // and returns the previous entry, if any. func (m *Map) Set(key types.Type, value interface{}) (prev interface{}) { if m.table != nil { hash := m.hasher.Hash(key) bucket := m.table[hash] var hole *entry for i, e := range bucket { if e.key == nil { hole = &bucket[i] } else if types.Identical(key, e.key) { prev = e.value bucket[i].value = value return } } if hole != nil { *hole = entry{key, value} // overwrite deleted entry } else { m.table[hash] = append(bucket, entry{key, value}) } } else { if m.hasher.memo == nil { m.hasher = MakeHasher() } hash := m.hasher.Hash(key) m.table = map[uint32][]entry{hash: {entry{key, value}}} } m.length++ return }
// computeTrackBits sets a.track to the necessary 'track' bits for the pointer queries. func (a *analysis) computeTrackBits() { var queryTypes []types.Type for v := range a.config.Queries { queryTypes = append(queryTypes, v.Type()) } for v := range a.config.IndirectQueries { queryTypes = append(queryTypes, mustDeref(v.Type())) } for _, t := range queryTypes { switch t.Underlying().(type) { case *types.Chan: a.track |= trackChan case *types.Map: a.track |= trackMap case *types.Pointer: a.track |= trackPtr case *types.Slice: a.track |= trackSlice case *types.Interface: a.track = trackAll return } if rVObj := a.reflectValueObj; rVObj != nil && types.Identical(t, rVObj.Type()) { a.track = trackAll return } } }
// containsAllIdsOf reports whether the method identifiers of T are a // superset of those in U. If U belongs to an interface type, the // result is equal to types.Assignable(T, U), but is cheaper to compute. // // TODO(gri): make this a method of *types.MethodSet. // func containsAllIdsOf(T, U *types.MethodSet) bool { t, tlen := 0, T.Len() u, ulen := 0, U.Len() for t < tlen && u < ulen { tMeth := T.At(t).Obj() uMeth := U.At(u).Obj() tId := tMeth.Id() uId := uMeth.Id() if tId > uId { // U has a method T lacks: fail. return false } if tId < uId { // T has a method U lacks: ignore it. t++ continue } // U and T both have a method of this Id. Check types. if !types.Identical(tMeth.Type(), uMeth.Type()) { return false // type mismatch } u++ t++ } return u == ulen }
// assign records pairs of distinct types that are related by // assignability, where the left-hand side is an interface and both // sides have methods. // // It should be called for all assignability checks, type assertions, // explicit conversions and comparisons between two types, unless the // types are uninteresting (e.g. lhs is a concrete type, or the empty // interface; rhs has no methods). // func (f *Finder) assign(lhs, rhs types.Type) { if types.Identical(lhs, rhs) { return } if !isInterface(lhs) { return } if f.msetcache.MethodSet(lhs).Len() == 0 { return } if f.msetcache.MethodSet(rhs).Len() == 0 { return } // canonicalize types lhsc, ok := f.canon.At(lhs).(types.Type) if !ok { lhsc = lhs f.canon.Set(lhs, lhsc) } rhsc, ok := f.canon.At(rhs).(types.Type) if !ok { rhsc = rhs f.canon.Set(rhs, rhsc) } // record the pair f.Result[Constraint{lhsc, rhsc}] = true }
func (c *funcContext) translateImplicitConversion(expr ast.Expr, desiredType types.Type) *expression { if desiredType == nil { return c.translateExpr(expr) } if expr == nil { return c.formatExpr("%s", c.zeroValue(desiredType)) } exprType := c.p.info.Types[expr].Type if types.Identical(exprType, desiredType) { return c.translateExpr(expr) } basicExprType, isBasicExpr := exprType.Underlying().(*types.Basic) if isBasicExpr && basicExprType.Kind() == types.UntypedNil { return c.formatExpr("%s", c.zeroValue(desiredType)) } switch desiredType.Underlying().(type) { case *types.Slice: return c.formatExpr("$subslice(new %1s(%2e.$array), %2e.$offset, %2e.$offset + %2e.$length)", c.typeName(desiredType), expr) case *types.Interface: if isWrapped(exprType) { return c.formatExpr("new %s(%e)", c.typeName(exprType), expr) } if _, isStruct := exprType.Underlying().(*types.Struct); isStruct { return c.formatExpr("new %1e.constructor.Struct(%1e)", expr) } } return c.translateExpr(expr) }
func checkEqualButNotIdentical(t *testing.T, x, y types.Type, comment string) { if !types.Identical(x, y) { t.Errorf("%s: not equal: %s, %s", comment, x, y) } if x == y { t.Errorf("%s: identical: %v, %v", comment, x, y) } }
// At returns the map entry for the given key. // The result is nil if the entry is not present. // func (m *Map) At(key types.Type) interface{} { if m != nil && m.table != nil { for _, e := range m.table[m.hasher.Hash(key)] { if e.key != nil && types.Identical(key, e.key) { return e.value } } } return nil }
// testMainSlice emits to fn code to construct a slice of type slice // (one of []testing.Internal{Test,Benchmark,Example}) for all // functions in expfuncs whose name starts with prefix (one of // "Test", "Benchmark" or "Example") and whose type is appropriate. // It returns the slice value. // func testMainSlice(fn *Function, expfuncs []*Function, prefix string, slice types.Type) Value { tElem := slice.(*types.Slice).Elem() tFunc := tElem.Underlying().(*types.Struct).Field(1).Type() var testfuncs []*Function for _, f := range expfuncs { if isTest(f.Name(), prefix) && types.Identical(f.Signature, tFunc) { testfuncs = append(testfuncs, f) } } if testfuncs == nil { return nilConst(slice) } tString := types.Typ[types.String] tPtrString := types.NewPointer(tString) tPtrElem := types.NewPointer(tElem) tPtrFunc := types.NewPointer(tFunc) // Emit: array = new [n]testing.InternalTest tArray := types.NewArray(tElem, int64(len(testfuncs))) array := emitNew(fn, tArray, token.NoPos) array.Comment = "test main" for i, testfunc := range testfuncs { // Emit: pitem = &array[i] ia := &IndexAddr{X: array, Index: intConst(int64(i))} ia.setType(tPtrElem) pitem := fn.emit(ia) // Emit: pname = &pitem.Name fa := &FieldAddr{X: pitem, Field: 0} // .Name fa.setType(tPtrString) pname := fn.emit(fa) // Emit: *pname = "testfunc" emitStore(fn, pname, NewConst(exact.MakeString(testfunc.Name()), tString)) // Emit: pfunc = &pitem.F fa = &FieldAddr{X: pitem, Field: 1} // .F fa.setType(tPtrFunc) pfunc := fn.emit(fa) // Emit: *pfunc = testfunc emitStore(fn, pfunc, testfunc) } // Emit: slice array[:] sl := &Slice{X: array} sl.setType(slice) return fn.emit(sl) }
// findAssignments returns the set of types to or from which type T is // assigned in the program syntax. func (r *renamer) findAssignments(T types.Type) map[types.Type]bool { if r.satisfyConstraints == nil { // Compute on demand: it's expensive. var f satisfy.Finder for _, info := range r.packages { f.Find(&info.Info, info.Files) } r.satisfyConstraints = f.Result } result := make(map[types.Type]bool) for key := range r.satisfyConstraints { // key = (lhs, rhs) where lhs is always an interface. if types.Identical(key.RHS, T) { result[key.LHS] = true } if isInterface(T) && types.Identical(key.LHS, T) { // must check both sides result[key.RHS] = true } } return result }
// Delete removes the entry with the given key, if any. // It returns true if the entry was found. // func (m *Map) Delete(key types.Type) bool { if m != nil && m.table != nil { hash := m.hasher.Hash(key) bucket := m.table[hash] for i, e := range bucket { if e.key != nil && types.Identical(key, e.key) { // We can't compact the bucket as it // would disturb iterators. bucket[i] = entry{} m.length-- return true } } } return false }
func checkFuncValue(t *testing.T, prog *ssa.Program, obj *types.Func) { fn := prog.FuncValue(obj) // fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging if fn == nil { t.Errorf("FuncValue(%s) == nil", obj) return } if fnobj := fn.Object(); fnobj != obj { t.Errorf("FuncValue(%s).Object() == %s; value was %s", obj, fnobj, fn.Name()) return } if !types.Identical(fn.Type(), obj.Type()) { t.Errorf("FuncValue(%s).Type() == %s", obj, fn.Type()) return } }
// isValuePreserving returns true if a conversion from ut_src to // ut_dst is value-preserving, i.e. just a change of type. // Precondition: neither argument is a named type. // func isValuePreserving(ut_src, ut_dst types.Type) bool { // Identical underlying types? if types.Identical(ut_dst, ut_src) { return true } switch ut_dst.(type) { case *types.Chan: // Conversion between channel types? _, ok := ut_src.(*types.Chan) return ok case *types.Pointer: // Conversion between pointers with identical base types? _, ok := ut_src.(*types.Pointer) return ok } return false }
// isErrorMethodCall reports whether the call is of a method with signature // func Error() string // where "string" is the universe's string type. We know the method is called "Error". func (f *File) isErrorMethodCall(call *ast.CallExpr) bool { typ := f.pkg.types[call].Type if typ != nil { // We know it's called "Error", so just check the function signature. return types.Identical(f.pkg.types[call.Fun].Type, stringerMethodType) } // Without types, we can still check by hand. // Is it a selector expression? Otherwise it's a function call, not a method call. sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return false } // The package is type-checked, so if there are no arguments, we're done. if len(call.Args) > 0 { return false } // Check the type of the method declaration typ = f.pkg.types[sel].Type if typ == nil { return false } // The type must be a signature, but be sure for safety. sig, ok := typ.(*types.Signature) if !ok { return false } // There must be a receiver for it to be a method call. Otherwise it is // a function, not something that satisfies the error interface. if sig.Recv() == nil { return false } // There must be no arguments. Already verified by type checking, but be thorough. if sig.Params().Len() > 0 { return false } // Finally the real questions. // There must be one result. if sig.Results().Len() != 1 { return false } // It must have return type "string" from the universe. return sig.Results().At(0).Type() == types.Typ[types.String] }
func checkConstValue(t *testing.T, prog *ssa.Program, obj *types.Const) { c := prog.ConstValue(obj) // fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging if c == nil { t.Errorf("ConstValue(%s) == nil", obj) return } if !types.Identical(c.Type(), obj.Type()) { t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type()) return } if obj.Name() != "nil" { if !exact.Compare(c.Value, token.EQL, obj.Val()) { t.Errorf("ConstValue(%s).Value (%s) != %s", obj, c.Value, obj.Val()) return } } }
// checkShadowing checks whether the identifier shadows an identifier in an outer scope. func (f *File) checkShadowing(ident *ast.Ident) { if ident.Name == "_" { // Can't shadow the blank identifier. return } obj := f.pkg.defs[ident] if obj == nil { return } // obj.Parent.Parent is the surrounding scope. If we can find another declaration // starting from there, we have a shadowed variable. shadowed := obj.Parent().Parent().LookupParent(obj.Name()) if shadowed == nil { return } // Don't complain if it's shadowing a universe-declared variable; that's fine. if shadowed.Parent() == types.Universe { return } if *strictShadowing { // The shadowed variable must appear before this one to be an instance of shadowing. if shadowed.Pos() > ident.Pos() { return } } else { // Don't complain if the span of validity of the shadowed variable doesn't include // the shadowing variable. span, ok := f.pkg.spans[shadowed] if !ok { f.Badf(ident.Pos(), "internal error: no range for %s", ident.Name) return } if !span.contains(ident.Pos()) { return } } // Don't complain if the types differ: that implies the programmer really wants two variables. if types.Identical(obj.Type(), shadowed.Type()) { f.Badf(ident.Pos(), "declaration of %s shadows declaration at %s", obj.Name(), f.loc(shadowed.Pos())) } }
// emitCompare emits to f code compute the boolean result of // comparison comparison 'x op y'. // func emitCompare(f *Function, op token.Token, x, y Value, pos token.Pos) Value { xt := x.Type().Underlying() yt := y.Type().Underlying() // Special case to optimise a tagless SwitchStmt so that // these are equivalent // switch { case e: ...} // switch true { case e: ... } // if e==true { ... } // even in the case when e's type is an interface. // TODO(adonovan): opt: generalise to x==true, false!=y, etc. if x == vTrue && op == token.EQL { if yt, ok := yt.(*types.Basic); ok && yt.Info()&types.IsBoolean != 0 { return y } } if types.Identical(xt, yt) { // no conversion necessary } else if _, ok := xt.(*types.Interface); ok { y = emitConv(f, y, x.Type()) } else if _, ok := yt.(*types.Interface); ok { x = emitConv(f, x, y.Type()) } else if _, ok := x.(*Const); ok { x = emitConv(f, x, y.Type()) } else if _, ok := y.(*Const); ok { y = emitConv(f, y, x.Type()) } else { // other cases, e.g. channels. No-op. } v := &BinOp{ Op: op, X: x, Y: y, } v.setPos(pos) v.setType(tBool) return f.emit(v) }
func checkVarValue(t *testing.T, prog *ssa.Program, pkg *ssa.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) { // The prefix of all assertions messages. prefix := fmt.Sprintf("VarValue(%s @ L%d)", obj, prog.Fset.Position(ref[0].Pos()).Line) v, gotAddr := prog.VarValue(obj, pkg, ref) // Kind is the concrete type of the ssa Value. gotKind := "nil" if v != nil { gotKind = fmt.Sprintf("%T", v)[len("*ssa."):] } // fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging // Check the kinds match. // "nil" indicates expected failure (e.g. optimized away). if expKind != gotKind { t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind) } // Check the types match. // If wantAddr, the expected type is the object's address. if v != nil { expType := obj.Type() if wantAddr { expType = types.NewPointer(expType) if !gotAddr { t.Errorf("%s: got value, want address", prefix) } } else if gotAddr { t.Errorf("%s: got address, want value", prefix) } if !types.Identical(v.Type(), expType) { t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType) } } }
// emitConv emits to f code to convert Value val to exactly type typ, // and returns the converted value. Implicit conversions are required // by language assignability rules in assignments, parameter passing, // etc. Conversions cannot fail dynamically. // func emitConv(f *Function, val Value, typ types.Type) Value { t_src := val.Type() // Identical types? Conversion is a no-op. if types.Identical(t_src, typ) { return val } ut_dst := typ.Underlying() ut_src := t_src.Underlying() // Just a change of type, but not value or representation? if isValuePreserving(ut_src, ut_dst) { c := &ChangeType{X: val} c.setType(typ) return f.emit(c) } // Conversion to, or construction of a value of, an interface type? if _, ok := ut_dst.(*types.Interface); ok { // Assignment from one interface type to another? if _, ok := ut_src.(*types.Interface); ok { c := &ChangeInterface{X: val} c.setType(typ) return f.emit(c) } // Untyped nil constant? Return interface-typed nil constant. if ut_src == tUntypedNil { return nilConst(typ) } // Convert (non-nil) "untyped" literals to their default type. if t, ok := ut_src.(*types.Basic); ok && t.Info()&types.IsUntyped != 0 { val = emitConv(f, val, DefaultType(ut_src)) } f.Pkg.needMethodsOf(val.Type()) mi := &MakeInterface{X: val} mi.setType(typ) return f.emit(mi) } // Conversion of a compile-time constant value? if c, ok := val.(*Const); ok { if _, ok := ut_dst.(*types.Basic); ok || c.IsNil() { // Conversion of a compile-time constant to // another constant type results in a new // constant of the destination type and // (initially) the same abstract value. // We don't truncate the value yet. return NewConst(c.Value, typ) } // We're converting from constant to non-constant type, // e.g. string -> []byte/[]rune. } // A representation-changing conversion. c := &Convert{X: val} c.setType(typ) return f.emit(c) }
// Ensure that, in debug mode, we can determine the ssa.Value // corresponding to every ast.Expr. func TestValueForExpr(t *testing.T) { conf := loader.Config{ParserMode: parser.ParseComments} f, err := conf.ParseFile("testdata/valueforexpr.go", nil) if err != nil { t.Error(err) return } conf.CreateFromFiles("main", f) iprog, err := conf.Load() if err != nil { t.Error(err) return } mainInfo := iprog.Created[0] prog := ssa.Create(iprog, 0) mainPkg := prog.Package(mainInfo.Pkg) mainPkg.SetDebugMode(true) mainPkg.Build() if false { // debugging for _, mem := range mainPkg.Members { if fn, ok := mem.(*ssa.Function); ok { fn.WriteTo(os.Stderr) } } } // Find the actual AST node for each canonical position. parenExprByPos := make(map[token.Pos]*ast.ParenExpr) ast.Inspect(f, func(n ast.Node) bool { if n != nil { if e, ok := n.(*ast.ParenExpr); ok { parenExprByPos[e.Pos()] = e } } return true }) // Find all annotations of form /*@kind*/. for _, c := range f.Comments { text := strings.TrimSpace(c.Text()) if text == "" || text[0] != '@' { continue } text = text[1:] pos := c.End() + 1 position := prog.Fset.Position(pos) var e ast.Expr if target := parenExprByPos[pos]; target == nil { t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text) continue } else { e = target.X } path, _ := astutil.PathEnclosingInterval(f, pos, pos) if path == nil { t.Errorf("%s: can't find AST path from root to comment: %s", position, text) continue } fn := ssa.EnclosingFunction(mainPkg, path) if fn == nil { t.Errorf("%s: can't find enclosing function", position) continue } v, gotAddr := fn.ValueForExpr(e) // (may be nil) got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ssa.") if want := text; got != want { t.Errorf("%s: got value %q, want %q", position, got, want) } if v != nil { T := v.Type() if gotAddr { T = T.Underlying().(*types.Pointer).Elem() // deref } if !types.Identical(T, mainInfo.TypeOf(e)) { t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T) } } } }
// Like isTest, but checks the signature too. func isTestSig(f *Function, prefix string, sig *types.Signature) bool { return isTest(f.Name(), prefix) && types.Identical(f.Signature, sig) }
func (c *funcContext) translateExpr(expr ast.Expr) *expression { exprType := c.p.info.Types[expr].Type if value := c.p.info.Types[expr].Value; value != nil { basic := types.Typ[types.String] if value.Kind() != exact.String { // workaround for bug in go/types basic = exprType.Underlying().(*types.Basic) } switch { case basic.Info()&types.IsBoolean != 0: return c.formatExpr("%s", strconv.FormatBool(exact.BoolVal(value))) case basic.Info()&types.IsInteger != 0: if is64Bit(basic) { d, _ := exact.Uint64Val(value) if basic.Kind() == types.Int64 { return c.formatExpr("new %s(%s, %s)", c.typeName(exprType), strconv.FormatInt(int64(d)>>32, 10), strconv.FormatUint(d&(1<<32-1), 10)) } return c.formatExpr("new %s(%s, %s)", c.typeName(exprType), strconv.FormatUint(d>>32, 10), strconv.FormatUint(d&(1<<32-1), 10)) } d, _ := exact.Int64Val(value) return c.formatExpr("%s", strconv.FormatInt(d, 10)) case basic.Info()&types.IsFloat != 0: f, _ := exact.Float64Val(value) return c.formatExpr("%s", strconv.FormatFloat(f, 'g', -1, 64)) case basic.Info()&types.IsComplex != 0: r, _ := exact.Float64Val(exact.Real(value)) i, _ := exact.Float64Val(exact.Imag(value)) if basic.Kind() == types.UntypedComplex { exprType = types.Typ[types.Complex128] } return c.formatExpr("new %s(%s, %s)", c.typeName(exprType), strconv.FormatFloat(r, 'g', -1, 64), strconv.FormatFloat(i, 'g', -1, 64)) case basic.Info()&types.IsString != 0: return c.formatExpr("%s", encodeString(exact.StringVal(value))) default: panic("Unhandled constant type: " + basic.String()) } } switch e := expr.(type) { case *ast.CompositeLit: if ptrType, isPointer := exprType.(*types.Pointer); isPointer { exprType = ptrType.Elem() } collectIndexedElements := func(elementType types.Type) []string { elements := make([]string, 0) i := 0 zero := c.zeroValue(elementType) for _, element := range e.Elts { if kve, isKve := element.(*ast.KeyValueExpr); isKve { key, _ := exact.Int64Val(c.p.info.Types[kve.Key].Value) i = int(key) element = kve.Value } for len(elements) <= i { elements = append(elements, zero) } elements[i] = c.translateImplicitConversionWithCloning(element, elementType).String() i++ } return elements } switch t := exprType.Underlying().(type) { case *types.Array: elements := collectIndexedElements(t.Elem()) if len(elements) == 0 { return c.formatExpr("%s", c.zeroValue(t)) } zero := c.zeroValue(t.Elem()) for len(elements) < int(t.Len()) { elements = append(elements, zero) } return c.formatExpr(`$toNativeArray("%s", [%s])`, typeKind(t.Elem()), strings.Join(elements, ", ")) case *types.Slice: return c.formatExpr("new %s([%s])", c.typeName(exprType), strings.Join(collectIndexedElements(t.Elem()), ", ")) case *types.Map: mapVar := c.newVariable("_map") keyVar := c.newVariable("_key") assignments := "" for _, element := range e.Elts { kve := element.(*ast.KeyValueExpr) assignments += c.formatExpr(`%s = %s, %s[%s] = { k: %s, v: %s }, `, keyVar, c.translateImplicitConversion(kve.Key, t.Key()), mapVar, c.makeKey(c.newIdent(keyVar, t.Key()), t.Key()), keyVar, c.translateImplicitConversion(kve.Value, t.Elem())).String() } return c.formatExpr("(%s = new $Map(), %s%s)", mapVar, assignments, mapVar) case *types.Struct: elements := make([]string, t.NumFields()) isKeyValue := true if len(e.Elts) != 0 { _, isKeyValue = e.Elts[0].(*ast.KeyValueExpr) } if !isKeyValue { for i, element := range e.Elts { elements[i] = c.translateImplicitConversion(element, t.Field(i).Type()).String() } } if isKeyValue { for i := range elements { elements[i] = c.zeroValue(t.Field(i).Type()) } for _, element := range e.Elts { kve := element.(*ast.KeyValueExpr) for j := range elements { if kve.Key.(*ast.Ident).Name == t.Field(j).Name() { elements[j] = c.translateImplicitConversion(kve.Value, t.Field(j).Type()).String() break } } } } return c.formatExpr("new %s.Ptr(%s)", c.typeName(exprType), strings.Join(elements, ", ")) default: panic(fmt.Sprintf("Unhandled CompositeLit type: %T\n", t)) } case *ast.FuncLit: innerContext := c.p.analyzeFunction(exprType.(*types.Signature), e.Body) params, body := innerContext.translateFunction(e.Type, e.Body.List, c.allVars) if len(c.p.escapingVars) != 0 { names := make([]string, 0, len(c.p.escapingVars)) for obj := range c.p.escapingVars { names = append(names, c.p.objectVars[obj]) } list := strings.Join(names, ", ") return c.formatExpr("(function(%s) { return function(%s) {\n%s%s}; })(%s)", list, strings.Join(params, ", "), string(body), strings.Repeat("\t", c.p.indentation), list) } return c.formatExpr("(function(%s) {\n%s%s})", strings.Join(params, ", "), string(body), strings.Repeat("\t", c.p.indentation)) case *ast.UnaryExpr: t := c.p.info.Types[e.X].Type switch e.Op { case token.AND: switch t.Underlying().(type) { case *types.Struct, *types.Array: return c.translateExpr(e.X) } switch x := removeParens(e.X).(type) { case *ast.CompositeLit: return c.formatExpr("$newDataPointer(%e, %s)", x, c.typeName(c.p.info.Types[e].Type)) case *ast.Ident: if obj := c.p.info.Uses[x]; c.p.escapingVars[obj] { return c.formatExpr("new %s(function() { return this.$target[0]; }, function($v) { this.$target[0] = $v; }, %s)", c.typeName(exprType), c.p.objectVars[obj]) } return c.formatExpr("new %s(function() { return %e; }, function($v) { %s })", c.typeName(exprType), x, c.translateAssign(x, "$v", exprType, false)) case *ast.SelectorExpr: newSel := &ast.SelectorExpr{X: c.newIdent("this.$target", c.p.info.Types[x.X].Type), Sel: x.Sel} c.p.info.Selections[newSel] = c.p.info.Selections[x] return c.formatExpr("new %s(function() { return %e; }, function($v) { %s }, %e)", c.typeName(exprType), newSel, c.translateAssign(newSel, "$v", exprType, false), x.X) case *ast.IndexExpr: newIndex := &ast.IndexExpr{X: c.newIdent("this.$target", c.p.info.Types[x.X].Type), Index: x.Index} return c.formatExpr("new %s(function() { return %e; }, function($v) { %s }, %e)", c.typeName(exprType), newIndex, c.translateAssign(newIndex, "$v", exprType, false), x.X) default: panic(fmt.Sprintf("Unhandled: %T\n", x)) } case token.ARROW: call := &ast.CallExpr{ Fun: c.newIdent("$recv", types.NewSignature(nil, nil, types.NewTuple(types.NewVar(0, nil, "", t)), types.NewTuple(types.NewVar(0, nil, "", exprType), types.NewVar(0, nil, "", types.Typ[types.Bool])), false)), Args: []ast.Expr{e.X}, } c.blocking[call] = true if _, isTuple := exprType.(*types.Tuple); isTuple { return c.formatExpr("%e", call) } return c.formatExpr("%e[0]", call) } basic := t.Underlying().(*types.Basic) switch e.Op { case token.ADD: return c.translateExpr(e.X) case token.SUB: switch { case is64Bit(basic): return c.formatExpr("new %1s(-%2h, -%2l)", c.typeName(t), e.X) case basic.Info()&types.IsComplex != 0: return c.formatExpr("new %1s(-%2r, -%2i)", c.typeName(t), e.X) case basic.Info()&types.IsUnsigned != 0: return c.fixNumber(c.formatExpr("-%e", e.X), basic) default: return c.formatExpr("-%e", e.X) } case token.XOR: if is64Bit(basic) { return c.formatExpr("new %1s(~%2h, ~%2l >>> 0)", c.typeName(t), e.X) } return c.fixNumber(c.formatExpr("~%e", e.X), basic) case token.NOT: x := c.translateExpr(e.X) if x.String() == "true" { return c.formatExpr("false") } if x.String() == "false" { return c.formatExpr("true") } return c.formatExpr("!%s", x) default: panic(e.Op) } case *ast.BinaryExpr: if e.Op == token.NEQ { return c.formatExpr("!(%s)", c.translateExpr(&ast.BinaryExpr{ X: e.X, Op: token.EQL, Y: e.Y, })) } t := c.p.info.Types[e.X].Type t2 := c.p.info.Types[e.Y].Type _, isInterface := t2.Underlying().(*types.Interface) if isInterface { t = t2 } if basic, isBasic := t.Underlying().(*types.Basic); isBasic && basic.Info()&types.IsNumeric != 0 { if is64Bit(basic) { switch e.Op { case token.MUL: return c.formatExpr("$mul64(%e, %e)", e.X, e.Y) case token.QUO: return c.formatExpr("$div64(%e, %e, false)", e.X, e.Y) case token.REM: return c.formatExpr("$div64(%e, %e, true)", e.X, e.Y) case token.SHL: return c.formatExpr("$shiftLeft64(%e, %f)", e.X, e.Y) case token.SHR: return c.formatExpr("$shiftRight%s(%e, %f)", toJavaScriptType(basic), e.X, e.Y) case token.EQL: return c.formatExpr("(%1h === %2h && %1l === %2l)", e.X, e.Y) case token.LSS: return c.formatExpr("(%1h < %2h || (%1h === %2h && %1l < %2l))", e.X, e.Y) case token.LEQ: return c.formatExpr("(%1h < %2h || (%1h === %2h && %1l <= %2l))", e.X, e.Y) case token.GTR: return c.formatExpr("(%1h > %2h || (%1h === %2h && %1l > %2l))", e.X, e.Y) case token.GEQ: return c.formatExpr("(%1h > %2h || (%1h === %2h && %1l >= %2l))", e.X, e.Y) case token.ADD, token.SUB: return c.formatExpr("new %3s(%1h %4t %2h, %1l %4t %2l)", e.X, e.Y, c.typeName(t), e.Op) case token.AND, token.OR, token.XOR: return c.formatExpr("new %3s(%1h %4t %2h, (%1l %4t %2l) >>> 0)", e.X, e.Y, c.typeName(t), e.Op) case token.AND_NOT: return c.formatExpr("new %3s(%1h &~ %2h, (%1l &~ %2l) >>> 0)", e.X, e.Y, c.typeName(t)) default: panic(e.Op) } } if basic.Info()&types.IsComplex != 0 { switch e.Op { case token.EQL: if basic.Kind() == types.Complex64 { return c.formatExpr("($float32IsEqual(%1r, %2r) && $float32IsEqual(%1i, %2i))", e.X, e.Y) } return c.formatExpr("(%1r === %2r && %1i === %2i)", e.X, e.Y) case token.ADD, token.SUB: return c.formatExpr("new %3s(%1r %4t %2r, %1i %4t %2i)", e.X, e.Y, c.typeName(t), e.Op) case token.MUL: return c.formatExpr("new %3s(%1r * %2r - %1i * %2i, %1r * %2i + %1i * %2r)", e.X, e.Y, c.typeName(t)) case token.QUO: return c.formatExpr("$divComplex(%e, %e)", e.X, e.Y) default: panic(e.Op) } } switch e.Op { case token.EQL: if basic.Kind() == types.Float32 { return c.formatParenExpr("$float32IsEqual(%e, %e)", e.X, e.Y) } return c.formatParenExpr("%e === %e", e.X, e.Y) case token.LSS, token.LEQ, token.GTR, token.GEQ: return c.formatExpr("%e %t %e", e.X, e.Op, e.Y) case token.ADD, token.SUB: if basic.Info()&types.IsInteger != 0 { return c.fixNumber(c.formatExpr("%e %t %e", e.X, e.Op, e.Y), basic) } return c.formatExpr("%e %t %e", e.X, e.Op, e.Y) case token.MUL: switch basic.Kind() { case types.Int32, types.Int: return c.formatParenExpr("(((%1e >>> 16 << 16) * %2e >> 0) + (%1e << 16 >>> 16) * %2e) >> 0", e.X, e.Y) case types.Uint32, types.Uint, types.Uintptr: return c.formatParenExpr("(((%1e >>> 16 << 16) * %2e >>> 0) + (%1e << 16 >>> 16) * %2e) >>> 0", e.X, e.Y) case types.Float32, types.Float64: return c.formatExpr("%e * %e", e.X, e.Y) default: return c.fixNumber(c.formatExpr("%e * %e", e.X, e.Y), basic) } case token.QUO: if basic.Info()&types.IsInteger != 0 { // cut off decimals shift := ">>" if basic.Info()&types.IsUnsigned != 0 { shift = ">>>" } return c.formatExpr(`(%1s = %2e / %3e, (%1s === %1s && %1s !== 1/0 && %1s !== -1/0) ? %1s %4s 0 : $throwRuntimeError("integer divide by zero"))`, c.newVariable("_q"), e.X, e.Y, shift) } return c.formatExpr("%e / %e", e.X, e.Y) case token.REM: return c.formatExpr(`(%1s = %2e %% %3e, %1s === %1s ? %1s : $throwRuntimeError("integer divide by zero"))`, c.newVariable("_r"), e.X, e.Y) case token.SHL, token.SHR: op := e.Op.String() if e.Op == token.SHR && basic.Info()&types.IsUnsigned != 0 { op = ">>>" } if c.p.info.Types[e.Y].Value != nil { return c.fixNumber(c.formatExpr("%e %s %e", e.X, op, e.Y), basic) } if e.Op == token.SHR && basic.Info()&types.IsUnsigned == 0 { return c.fixNumber(c.formatParenExpr("%e >> $min(%e, 31)", e.X, e.Y), basic) } y := c.newVariable("y") return c.fixNumber(c.formatExpr("(%s = %s, %s < 32 ? (%e %s %s) : 0)", y, c.translateImplicitConversion(e.Y, types.Typ[types.Uint]), y, e.X, op, y), basic) case token.AND, token.OR: if basic.Info()&types.IsUnsigned != 0 { return c.formatParenExpr("(%e %t %e) >>> 0", e.X, e.Op, e.Y) } return c.formatParenExpr("%e %t %e", e.X, e.Op, e.Y) case token.AND_NOT: return c.formatParenExpr("%e & ~%e", e.X, e.Y) case token.XOR: return c.fixNumber(c.formatParenExpr("%e ^ %e", e.X, e.Y), basic) default: panic(e.Op) } } switch e.Op { case token.ADD, token.LSS, token.LEQ, token.GTR, token.GEQ: return c.formatExpr("%e %t %e", e.X, e.Op, e.Y) case token.LAND: x := c.translateExpr(e.X) y := c.translateExpr(e.Y) if x.String() == "false" { return c.formatExpr("false") } return c.formatExpr("%s && %s", x, y) case token.LOR: x := c.translateExpr(e.X) y := c.translateExpr(e.Y) if x.String() == "true" { return c.formatExpr("true") } return c.formatExpr("%s || %s", x, y) case token.EQL: switch u := t.Underlying().(type) { case *types.Array, *types.Struct: return c.formatExpr("$equal(%e, %e, %s)", e.X, e.Y, c.typeName(t)) case *types.Interface: if isJsObject(t) { return c.formatExpr("%s === %s", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t)) } return c.formatExpr("$interfaceIsEqual(%s, %s)", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t)) case *types.Pointer: xUnary, xIsUnary := e.X.(*ast.UnaryExpr) yUnary, yIsUnary := e.Y.(*ast.UnaryExpr) if xIsUnary && xUnary.Op == token.AND && yIsUnary && yUnary.Op == token.AND { xIndex, xIsIndex := xUnary.X.(*ast.IndexExpr) yIndex, yIsIndex := yUnary.X.(*ast.IndexExpr) if xIsIndex && yIsIndex { return c.formatExpr("$sliceIsEqual(%e, %f, %e, %f)", xIndex.X, xIndex.Index, yIndex.X, yIndex.Index) } } switch u.Elem().Underlying().(type) { case *types.Struct, *types.Interface: return c.formatExpr("%s === %s", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t)) case *types.Array: return c.formatExpr("$equal(%s, %s, %s)", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t), c.typeName(u.Elem())) default: return c.formatExpr("$pointerIsEqual(%s, %s)", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t)) } default: return c.formatExpr("%s === %s", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t)) } default: panic(e.Op) } case *ast.ParenExpr: x := c.translateExpr(e.X) if x.String() == "true" || x.String() == "false" { return x } return c.formatParenExpr("%s", x) case *ast.IndexExpr: switch t := c.p.info.Types[e.X].Type.Underlying().(type) { case *types.Array, *types.Pointer: if c.p.info.Types[e.Index].Value != nil { return c.formatExpr("%e[%f]", e.X, e.Index) } return c.formatExpr(`((%2f < 0 || %2f >= %1e.length) ? $throwRuntimeError("index out of range") : %1e[%2f])`, e.X, e.Index) case *types.Slice: return c.formatExpr(`((%2f < 0 || %2f >= %1e.$length) ? $throwRuntimeError("index out of range") : %1e.$array[%1e.$offset + %2f])`, e.X, e.Index) case *types.Map: key := c.makeKey(e.Index, t.Key()) if _, isTuple := exprType.(*types.Tuple); isTuple { return c.formatExpr(`(%1s = %2e[%3s], %1s !== undefined ? [%1s.v, true] : [%4s, false])`, c.newVariable("_entry"), e.X, key, c.zeroValue(t.Elem())) } return c.formatExpr(`(%1s = %2e[%3s], %1s !== undefined ? %1s.v : %4s)`, c.newVariable("_entry"), e.X, key, c.zeroValue(t.Elem())) case *types.Basic: return c.formatExpr("%e.charCodeAt(%f)", e.X, e.Index) default: panic(fmt.Sprintf("Unhandled IndexExpr: %T\n", t)) } case *ast.SliceExpr: if b, isBasic := c.p.info.Types[e.X].Type.Underlying().(*types.Basic); isBasic && b.Info()&types.IsString != 0 { switch { case e.Low == nil && e.High == nil: return c.translateExpr(e.X) case e.Low == nil: return c.formatExpr("%e.substring(0, %f)", e.X, e.High) case e.High == nil: return c.formatExpr("%e.substring(%f)", e.X, e.Low) default: return c.formatExpr("%e.substring(%f, %f)", e.X, e.Low, e.High) } } slice := c.translateConversionToSlice(e.X, exprType) switch { case e.Low == nil && e.High == nil: return c.formatExpr("%s", slice) case e.Low == nil: if e.Max != nil { return c.formatExpr("$subslice(%s, 0, %f, %f)", slice, e.High, e.Max) } return c.formatExpr("$subslice(%s, 0, %f)", slice, e.High) case e.High == nil: return c.formatExpr("$subslice(%s, %f)", slice, e.Low) default: if e.Max != nil { return c.formatExpr("$subslice(%s, %f, %f, %f)", slice, e.Low, e.High, e.Max) } return c.formatExpr("$subslice(%s, %f, %f)", slice, e.Low, e.High) } case *ast.SelectorExpr: sel, ok := c.p.info.Selections[e] if !ok { // qualified identifier obj := c.p.info.Uses[e.Sel] if isJsPackage(obj.Pkg()) { switch obj.Name() { case "Global": return c.formatExpr("$global") case "This": if len(c.flattened) != 0 { return c.formatExpr("$this") } return c.formatExpr("this") case "Arguments": args := "arguments" if len(c.flattened) != 0 { args = "$args" } return c.formatExpr(`new ($sliceType(%s.Object))($global.Array.prototype.slice.call(%s, []))`, c.p.pkgVars["github.com/gopherjs/gopherjs/js"], args) case "Module": return c.formatExpr("$module") default: panic("Invalid js package object: " + obj.Name()) } } return c.formatExpr("%s", c.objectName(obj)) } parameterName := func(v *types.Var) string { if v.Anonymous() || v.Name() == "" { return c.newVariable("param") } return c.newVariable(v.Name()) } makeParametersList := func() []string { params := sel.Obj().Type().(*types.Signature).Params() names := make([]string, params.Len()) for i := 0; i < params.Len(); i++ { names[i] = parameterName(params.At(i)) } return names } switch sel.Kind() { case types.FieldVal: fields, jsTag := c.translateSelection(sel) if jsTag != "" { if _, ok := sel.Type().(*types.Signature); ok { return c.formatExpr("$internalize(%1e.%2s.%3s, %4s, %1e.%2s)", e.X, strings.Join(fields, "."), jsTag, c.typeName(sel.Type())) } return c.internalize(c.formatExpr("%e.%s.%s", e.X, strings.Join(fields, "."), jsTag), sel.Type()) } return c.formatExpr("%e.%s", e.X, strings.Join(fields, ".")) case types.MethodVal: if !sel.Obj().Exported() { c.p.dependencies[sel.Obj()] = true } parameters := makeParametersList() target := c.translateExpr(e.X) if isWrapped(sel.Recv()) { target = c.formatParenExpr("new %s(%s)", c.typeName(sel.Recv()), target) } recv := c.newVariable("_recv") return c.formatExpr("(%s = %s, function(%s) { $stackDepthOffset--; try { return %s.%s(%s); } finally { $stackDepthOffset++; } })", recv, target, strings.Join(parameters, ", "), recv, e.Sel.Name, strings.Join(parameters, ", ")) case types.MethodExpr: if !sel.Obj().Exported() { c.p.dependencies[sel.Obj()] = true } recv := "recv" if isWrapped(sel.Recv()) { recv = fmt.Sprintf("(new %s(recv))", c.typeName(sel.Recv())) } parameters := makeParametersList() return c.formatExpr("(function(%s) { $stackDepthOffset--; try { return %s.%s(%s); } finally { $stackDepthOffset++; } })", strings.Join(append([]string{"recv"}, parameters...), ", "), recv, sel.Obj().(*types.Func).Name(), strings.Join(parameters, ", ")) } panic("") case *ast.CallExpr: plainFun := e.Fun for { if p, isParen := plainFun.(*ast.ParenExpr); isParen { plainFun = p.X continue } break } var isType func(ast.Expr) bool isType = func(expr ast.Expr) bool { switch e := expr.(type) { case *ast.ArrayType, *ast.ChanType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.StructType: return true case *ast.StarExpr: return isType(e.X) case *ast.Ident: _, ok := c.p.info.Uses[e].(*types.TypeName) return ok case *ast.SelectorExpr: _, ok := c.p.info.Uses[e.Sel].(*types.TypeName) return ok case *ast.ParenExpr: return isType(e.X) default: return false } } if isType(plainFun) { return c.formatExpr("%s", c.translateConversion(e.Args[0], c.p.info.Types[plainFun].Type)) } var fun *expression switch f := plainFun.(type) { case *ast.Ident: if o, ok := c.p.info.Uses[f].(*types.Builtin); ok { return c.translateBuiltin(o.Name(), e.Args, e.Ellipsis.IsValid(), exprType) } fun = c.translateExpr(plainFun) case *ast.SelectorExpr: sel, ok := c.p.info.Selections[f] if !ok { // qualified identifier obj := c.p.info.Uses[f.Sel] if isJsPackage(obj.Pkg()) { switch obj.Name() { case "InternalObject": return c.translateExpr(e.Args[0]) } } fun = c.translateExpr(f) break } externalizeExpr := func(e ast.Expr) string { t := c.p.info.Types[e].Type if types.Identical(t, types.Typ[types.UntypedNil]) { return "null" } return c.externalize(c.translateExpr(e).String(), t) } externalizeArgs := func(args []ast.Expr) string { s := make([]string, len(args)) for i, arg := range args { s[i] = externalizeExpr(arg) } return strings.Join(s, ", ") } switch sel.Kind() { case types.MethodVal: if !sel.Obj().Exported() { c.p.dependencies[sel.Obj()] = true } methodName := sel.Obj().Name() if reservedKeywords[methodName] { methodName += "$" } recvType := sel.Recv() _, isPointer := recvType.Underlying().(*types.Pointer) methodsRecvType := sel.Obj().Type().(*types.Signature).Recv().Type() _, pointerExpected := methodsRecvType.(*types.Pointer) var recv *expression switch { case !isPointer && pointerExpected: recv = c.translateExpr(c.setType(&ast.UnaryExpr{Op: token.AND, X: f.X}, methodsRecvType)) default: recv = c.translateExpr(f.X) } for _, index := range sel.Index()[:len(sel.Index())-1] { if ptr, isPtr := recvType.(*types.Pointer); isPtr { recvType = ptr.Elem() } s := recvType.Underlying().(*types.Struct) recv = c.formatExpr("%s.%s", recv, fieldName(s, index)) recvType = s.Field(index).Type() } if isJsPackage(sel.Obj().Pkg()) { globalRef := func(id string) string { if recv.String() == "$global" && id[0] == '$' { return id } return recv.String() + "." + id } switch sel.Obj().Name() { case "Get": if id, ok := c.identifierConstant(e.Args[0]); ok { return c.formatExpr("%s", globalRef(id)) } return c.formatExpr("%s[$externalize(%e, $String)]", recv, e.Args[0]) case "Set": if id, ok := c.identifierConstant(e.Args[0]); ok { return c.formatExpr("%s = %s", globalRef(id), externalizeExpr(e.Args[1])) } return c.formatExpr("%s[$externalize(%e, $String)] = %s", recv, e.Args[0], externalizeExpr(e.Args[1])) case "Delete": return c.formatExpr("delete %s[$externalize(%e, $String)]", recv, e.Args[0]) case "Length": return c.formatExpr("$parseInt(%s.length)", recv) case "Index": return c.formatExpr("%s[%e]", recv, e.Args[0]) case "SetIndex": return c.formatExpr("%s[%e] = %s", recv, e.Args[0], externalizeExpr(e.Args[1])) case "Call": if id, ok := c.identifierConstant(e.Args[0]); ok { if e.Ellipsis.IsValid() { objVar := c.newVariable("obj") return c.formatExpr("(%s = %s, %s.%s.apply(%s, %s))", objVar, recv, objVar, id, objVar, externalizeExpr(e.Args[1])) } return c.formatExpr("%s(%s)", globalRef(id), externalizeArgs(e.Args[1:])) } if e.Ellipsis.IsValid() { objVar := c.newVariable("obj") return c.formatExpr("(%s = %s, %s[$externalize(%e, $String)].apply(%s, %s))", objVar, recv, objVar, e.Args[0], objVar, externalizeExpr(e.Args[1])) } return c.formatExpr("%s[$externalize(%e, $String)](%s)", recv, e.Args[0], externalizeArgs(e.Args[1:])) case "Invoke": if e.Ellipsis.IsValid() { return c.formatExpr("%s.apply(undefined, %s)", recv, externalizeExpr(e.Args[0])) } return c.formatExpr("%s(%s)", recv, externalizeArgs(e.Args)) case "New": if e.Ellipsis.IsValid() { return c.formatExpr("new ($global.Function.prototype.bind.apply(%s, [undefined].concat(%s)))", recv, externalizeExpr(e.Args[0])) } return c.formatExpr("new (%s)(%s)", recv, externalizeArgs(e.Args)) case "Bool": return c.internalize(recv, types.Typ[types.Bool]) case "Str": return c.internalize(recv, types.Typ[types.String]) case "Int": return c.internalize(recv, types.Typ[types.Int]) case "Int64": return c.internalize(recv, types.Typ[types.Int64]) case "Uint64": return c.internalize(recv, types.Typ[types.Uint64]) case "Float": return c.internalize(recv, types.Typ[types.Float64]) case "Interface": return c.internalize(recv, types.NewInterface(nil, nil)) case "Unsafe": return recv case "IsUndefined": return c.formatParenExpr("%s === undefined", recv) case "IsNull": return c.formatParenExpr("%s === null", recv) default: panic("Invalid js package object: " + sel.Obj().Name()) } } if isWrapped(methodsRecvType) { fun = c.formatExpr("(new %s(%s)).%s", c.typeName(methodsRecvType), recv, methodName) break } fun = c.formatExpr("%s.%s", recv, methodName) case types.FieldVal: fields, jsTag := c.translateSelection(sel) if jsTag != "" { sig := sel.Type().(*types.Signature) return c.internalize(c.formatExpr("%e.%s.%s(%s)", f.X, strings.Join(fields, "."), jsTag, externalizeArgs(e.Args)), sig.Results().At(0).Type()) } fun = c.formatExpr("%e.%s", f.X, strings.Join(fields, ".")) case types.MethodExpr: fun = c.translateExpr(f) default: panic("") } default: fun = c.translateExpr(plainFun) } sig := c.p.info.Types[plainFun].Type.Underlying().(*types.Signature) if len(e.Args) == 1 { if tuple, isTuple := c.p.info.Types[e.Args[0]].Type.(*types.Tuple); isTuple { tupleVar := c.newVariable("_tuple") args := make([]ast.Expr, tuple.Len()) for i := range args { args[i] = c.newIdent(c.formatExpr("%s[%d]", tupleVar, i).String(), tuple.At(i).Type()) } return c.formatExpr("(%s = %e, %s(%s))", tupleVar, e.Args[0], fun, strings.Join(c.translateArgs(sig, args, false), ", ")) } } args := c.translateArgs(sig, e.Args, e.Ellipsis.IsValid()) if c.blocking[e] { resumeCase := c.caseCounter c.caseCounter++ returnVar := "$r" if sig.Results().Len() != 0 { returnVar = c.newVariable("_r") } c.Printf("%[1]s = %[2]s(%[3]s); /* */ $s = %[4]d; case %[4]d: if (%[1]s && %[1]s.constructor === Function) { %[1]s = %[1]s(); }", returnVar, fun, strings.Join(append(args, "true"), ", "), resumeCase) if sig.Results().Len() != 0 { return c.formatExpr("%s", returnVar) } return nil } return c.formatExpr("%s(%s)", fun, strings.Join(args, ", ")) case *ast.StarExpr: if c1, isCall := e.X.(*ast.CallExpr); isCall && len(c1.Args) == 1 { if c2, isCall := c1.Args[0].(*ast.CallExpr); isCall && len(c2.Args) == 1 && types.Identical(c.p.info.Types[c2.Fun].Type, types.Typ[types.UnsafePointer]) { if unary, isUnary := c2.Args[0].(*ast.UnaryExpr); isUnary && unary.Op == token.AND { return c.translateExpr(unary.X) // unsafe conversion } } } switch exprType.Underlying().(type) { case *types.Struct, *types.Array: return c.translateExpr(e.X) } return c.formatExpr("%e.$get()", e.X) case *ast.TypeAssertExpr: if e.Type == nil { return c.translateExpr(e.X) } t := c.p.info.Types[e.Type].Type check := "%1e !== null && " + c.typeCheck("%1e.constructor", t) valueSuffix := "" if _, isInterface := t.Underlying().(*types.Interface); !isInterface { valueSuffix = ".$val" } if _, isTuple := exprType.(*types.Tuple); isTuple { return c.formatExpr("("+check+" ? [%1e%2s, true] : [%3s, false])", e.X, valueSuffix, c.zeroValue(c.p.info.Types[e.Type].Type)) } return c.formatExpr("("+check+" ? %1e%2s : $typeAssertionFailed(%1e, %3s))", e.X, valueSuffix, c.typeName(t)) case *ast.Ident: if e.Name == "_" { panic("Tried to translate underscore identifier.") } obj := c.p.info.Defs[e] if obj == nil { obj = c.p.info.Uses[e] } switch o := obj.(type) { case *types.PkgName: return c.formatExpr("%s", c.p.pkgVars[o.Pkg().Path()]) case *types.Var, *types.Const: return c.formatExpr("%s", c.objectName(o)) case *types.Func: return c.formatExpr("%s", c.objectName(o)) case *types.TypeName: return c.formatExpr("%s", c.typeName(o.Type())) case *types.Nil: return c.formatExpr("%s", c.zeroValue(c.p.info.Types[e].Type)) default: panic(fmt.Sprintf("Unhandled object: %T\n", o)) } case *This: this := "this" if len(c.flattened) != 0 { this = "$this" } if isWrapped(c.p.info.Types[e].Type) { this += ".$val" } return c.formatExpr(this) case nil: return c.formatExpr("") default: panic(fmt.Sprintf("Unhandled expression: %T\n", e)) } }
// NewTransformer returns a transformer based on the specified template, // a package containing "before" and "after" functions as described // in the package documentation. // func NewTransformer(fset *token.FileSet, template *loader.PackageInfo, verbose bool) (*Transformer, error) { // Check the template. beforeSig := funcSig(template.Pkg, "before") if beforeSig == nil { return nil, fmt.Errorf("no 'before' func found in template") } afterSig := funcSig(template.Pkg, "after") if afterSig == nil { return nil, fmt.Errorf("no 'after' func found in template") } // TODO(adonovan): should we also check the names of the params match? if !types.Identical(afterSig, beforeSig) { return nil, fmt.Errorf("before %s and after %s functions have different signatures", beforeSig, afterSig) } templateFile := template.Files[0] for _, imp := range templateFile.Imports { if imp.Name != nil && imp.Name.Name == "." { // Dot imports are currently forbidden. We // make the simplifying assumption that all // imports are regular, without local renames. //TODO document return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value) } } var beforeDecl, afterDecl *ast.FuncDecl for _, decl := range templateFile.Decls { if decl, ok := decl.(*ast.FuncDecl); ok { switch decl.Name.Name { case "before": beforeDecl = decl case "after": afterDecl = decl } } } before, err := soleExpr(beforeDecl) if err != nil { return nil, fmt.Errorf("before: %s", err) } after, err := soleExpr(afterDecl) if err != nil { return nil, fmt.Errorf("after: %s", err) } wildcards := make(map[*types.Var]bool) for i := 0; i < beforeSig.Params().Len(); i++ { wildcards[beforeSig.Params().At(i)] = true } // checkExprTypes returns an error if Tb (type of before()) is not // safe to replace with Ta (type of after()). // // Only superficial checks are performed, and they may result in both // false positives and negatives. // // Ideally, we would only require that the replacement be assignable // to the context of a specific pattern occurrence, but the type // checker doesn't record that information and it's complex to deduce. // A Go type cannot capture all the constraints of a given expression // context, which may include the size, constness, signedness, // namedness or constructor of its type, and even the specific value // of the replacement. (Consider the rule that array literal keys // must be unique.) So we cannot hope to prove the safety of a // transformation in general. Tb := template.TypeOf(before) Ta := template.TypeOf(after) if types.AssignableTo(Tb, Ta) { // safe: replacement is assignable to pattern. } else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 { // safe: pattern has void type (must appear in an ExprStmt). } else { return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb) } tr := &Transformer{ fset: fset, verbose: verbose, wildcards: wildcards, allowWildcards: true, seenInfos: make(map[*types.Info]bool), importedObjs: make(map[types.Object]*ast.SelectorExpr), before: before, after: after, } // Combine type info from the template and input packages, and // type info for the synthesized ASTs too. This saves us // having to book-keep where each ast.Node originated as we // construct the resulting hybrid AST. // // TODO(adonovan): move type utility methods of PackageInfo to // types.Info, or at least into go/types.typeutil. tr.info.Info = types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), } mergeTypeInfo(&tr.info.Info, &template.Info) // Compute set of imported objects required by after(). // TODO reject dot-imports in pattern ast.Inspect(after, func(n ast.Node) bool { if n, ok := n.(*ast.SelectorExpr); ok { if _, ok := tr.info.Selections[n]; !ok { // qualified ident obj := tr.info.Uses[n.Sel] tr.importedObjs[obj] = n return false // prune } } return true // recur }) return tr, nil }
// peers enumerates, for a given channel send (or receive) operation, // the set of possible receives (or sends) that correspond to it. // // TODO(adonovan): support reflect.{Select,Recv,Send,Close}. // TODO(adonovan): permit the user to query based on a MakeChan (not send/recv), // or the implicit receive in "for v := range ch". // TODO(adonovan): support "close" as a channel op. // func peers(o *Oracle, qpos *QueryPos) (queryResult, error) { arrowPos := findArrow(qpos) if arrowPos == token.NoPos { return nil, fmt.Errorf("there is no send/receive here") } buildSSA(o) var queryOp chanOp // the originating send or receive operation var ops []chanOp // all sends/receives of opposite direction // Look at all send/receive instructions in the whole ssa.Program. // Build a list of those of same type to query. allFuncs := ssautil.AllFunctions(o.prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { for _, op := range chanOps(instr) { ops = append(ops, op) if op.pos == arrowPos { queryOp = op // we found the query op } } } } } if queryOp.ch == nil { return nil, fmt.Errorf("ssa.Instruction for send/receive not found") } // Discard operations of wrong channel element type. // Build set of channel ssa.Values as query to pointer analysis. // We compare channels by element types, not channel types, to // ignore both directionality and type names. queryType := queryOp.ch.Type() queryElemType := queryType.Underlying().(*types.Chan).Elem() o.ptaConfig.AddQuery(queryOp.ch) i := 0 for _, op := range ops { if types.Identical(op.ch.Type().Underlying().(*types.Chan).Elem(), queryElemType) { o.ptaConfig.AddQuery(op.ch) ops[i] = op i++ } } ops = ops[:i] // Run the pointer analysis. ptares := ptrAnalysis(o) // Find the points-to set. queryChanPtr := ptares.Queries[queryOp.ch] // Ascertain which make(chan) labels the query's channel can alias. var makes []token.Pos for _, label := range queryChanPtr.PointsTo().Labels() { makes = append(makes, label.Pos()) } sort.Sort(byPos(makes)) // Ascertain which send/receive operations can alias the same make(chan) labels. var sends, receives []token.Pos for _, op := range ops { if ptr, ok := ptares.Queries[op.ch]; ok && ptr.MayAlias(queryChanPtr) { if op.dir == types.SendOnly { sends = append(sends, op.pos) } else { receives = append(receives, op.pos) } } } sort.Sort(byPos(sends)) sort.Sort(byPos(receives)) return &peersResult{ queryPos: arrowPos, queryType: queryType, makes: makes, sends: sends, receives: receives, }, nil }
func (c *funcContext) translateStmt(stmt ast.Stmt, label string) { c.Write([]byte{'\b'}) binary.Write(c, binary.BigEndian, uint32(stmt.Pos())) switch s := stmt.(type) { case *ast.BlockStmt: c.translateStmtList(s.List) case *ast.IfStmt: c.printLabel(label) if s.Init != nil { c.translateStmt(s.Init, "") } 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, c.translateExpr, nil, "", c.hasGoto[s]) case *ast.SwitchStmt: if s.Init != nil { c.translateStmt(s.Init, "") } translateCond := func(cond ast.Expr) *expression { return c.translateExpr(cond) } if s.Tag != nil { refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.Tag)) translateCond = func(cond ast.Expr) *expression { refIdent := c.newIdent(refVar, c.p.info.Types[s.Tag].Type) return c.translateExpr(&ast.BinaryExpr{ X: refIdent, Op: token.EQL, Y: cond, }) } } c.translateBranchingStmt(s.Body.List, true, translateCond, nil, label, c.hasGoto[s]) case *ast.TypeSwitchStmt: if s.Init != nil { c.translateStmt(s.Init, "") } var expr ast.Expr var typeSwitchVar string switch a := s.Assign.(type) { case *ast.AssignStmt: expr = a.Rhs[0].(*ast.TypeAssertExpr).X typeSwitchVar = c.newVariable(a.Lhs[0].(*ast.Ident).Name) for _, caseClause := range s.Body.List { c.p.objectVars[c.p.info.Implicits[caseClause]] = typeSwitchVar } case *ast.ExprStmt: expr = a.X.(*ast.TypeAssertExpr).X } refVar := c.newVariable("_ref") typeVar := c.newVariable("_type") c.Printf("%s = %s;", refVar, c.translateExpr(expr)) c.Printf("%s = %s !== null ? %s.constructor : null;", typeVar, refVar, refVar) translateCond := func(cond ast.Expr) *expression { return c.formatExpr("%s", c.typeCheck(typeVar, c.p.info.Types[cond].Type)) } printCaseBodyPrefix := func(conds []ast.Expr) { if typeSwitchVar == "" { return } value := refVar if len(conds) == 1 { t := c.p.info.Types[conds[0]].Type if _, isInterface := t.Underlying().(*types.Interface); !isInterface && !types.Identical(t, types.Typ[types.UntypedNil]) { value += ".go$val" } } c.Printf("%s = %s;", typeSwitchVar, value) } c.translateBranchingStmt(s.Body.List, true, translateCond, printCaseBodyPrefix, label, c.hasGoto[s]) case *ast.ForStmt: if s.Init != nil { c.translateStmt(s.Init, "") } cond := "true" if s.Cond != nil { cond = c.translateExpr(s.Cond).String() } c.translateLoopingStmt(cond, s.Body, nil, func() { if s.Post != nil { c.translateStmt(s.Post, "") } }, label, c.hasGoto[s]) case *ast.RangeStmt: refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.X)) iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) switch t := c.p.info.Types[s.X].Type.Underlying().(type) { case *types.Basic: runeVar := c.newVariable("_rune") c.translateLoopingStmt(iVar+" < "+refVar+".length", s.Body, func() { c.Printf("%s = go$decodeRune(%s, %s);", runeVar, refVar, iVar) if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, runeVar+"[0]")) } if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar)) } }, func() { c.Printf("%s += %s[1];", iVar, runeVar) }, label, c.hasGoto[s]) case *types.Map: keysVar := c.newVariable("_keys") c.Printf("%s = go$keys(%s);", keysVar, refVar) c.translateLoopingStmt(iVar+" < "+keysVar+".length", s.Body, func() { entryVar := c.newVariable("_entry") c.Printf("%s = %s[%s[%s]];", entryVar, refVar, keysVar, iVar) if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, entryVar+".v")) } if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, entryVar+".k")) } }, func() { c.Printf("%s++;", iVar) }, label, c.hasGoto[s]) case *types.Array, *types.Pointer, *types.Slice: var length string switch t2 := t.(type) { case *types.Array: length = fmt.Sprintf("%d", t2.Len()) case *types.Pointer: length = fmt.Sprintf("%d", t2.Elem().Underlying().(*types.Array).Len()) case *types.Slice: length = refVar + ".length" } c.translateLoopingStmt(iVar+" < "+length, s.Body, func() { if !isBlank(s.Value) { indexExpr := &ast.IndexExpr{ X: c.newIdent(refVar, t), Index: c.newIdent(iVar, types.Typ[types.Int]), } et := elemType(t) c.p.info.Types[indexExpr] = types.TypeAndValue{Type: et} c.Printf("%s", c.translateAssign(s.Value, c.translateImplicitConversion(indexExpr, et).String())) } if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar)) } }, func() { c.Printf("%s++;", iVar) }, label, c.hasGoto[s]) case *types.Chan: c.printLabel(label) // skip default: panic("") } case *ast.BranchStmt: c.printLabel(label) labelSuffix := "" data := c.flowDatas[""] if s.Label != nil { labelSuffix = " " + s.Label.Name data = c.flowDatas[s.Label.Name] } switch s.Tok { case token.BREAK: c.PrintCond(data.endCase == 0, fmt.Sprintf("break%s;", labelSuffix), fmt.Sprintf("go$s = %d; continue;", data.endCase)) case token.CONTINUE: data.postStmt() c.PrintCond(data.beginCase == 0, fmt.Sprintf("continue%s;", labelSuffix), fmt.Sprintf("go$s = %d; continue;", data.beginCase)) case token.GOTO: c.PrintCond(false, "goto "+s.Label.Name, fmt.Sprintf("go$s = %d; continue;", c.labelCases[s.Label.Name])) case token.FALLTHROUGH: // handled in CaseClause default: panic("Unhandled branch statment: " + s.Tok.String()) } case *ast.ReturnStmt: c.printLabel(label) 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, }, "") } results = c.resultNames } switch len(results) { case 0: c.Printf("return;") case 1: if c.sig.Results().Len() > 1 { c.Printf("return %s;", c.translateExpr(results[0])) break } v := c.translateImplicitConversion(results[0], c.sig.Results().At(0).Type()) c.delayedOutput = nil c.Printf("return %s;", v) default: values := make([]string, len(results)) for i, result := range results { values[i] = c.translateImplicitConversion(result, c.sig.Results().At(i).Type()).String() } c.delayedOutput = nil c.Printf("return [%s];", strings.Join(values, ", ")) } case *ast.DeferStmt: c.printLabel(label) if ident, isIdent := s.Call.Fun.(*ast.Ident); isIdent { if builtin, isBuiltin := c.p.info.Uses[ident].(*types.Builtin); isBuiltin { if builtin.Name() == "recover" { c.Printf("go$deferred.push({ fun: go$recover, args: [] });") return } args := make([]ast.Expr, len(s.Call.Args)) for i, arg := range s.Call.Args { args[i] = c.newIdent(c.newVariable("_arg"), c.p.info.Types[arg].Type) } call := c.translateExpr(&ast.CallExpr{ Fun: s.Call.Fun, Args: args, Ellipsis: s.Call.Ellipsis, }) c.Printf("go$deferred.push({ fun: function(%s) { %s; }, args: [%s] });", strings.Join(c.translateExprSlice(args, nil), ", "), call, strings.Join(c.translateExprSlice(s.Call.Args, nil), ", ")) return } } sig := c.p.info.Types[s.Call.Fun].Type.Underlying().(*types.Signature) args := strings.Join(c.translateArgs(sig, s.Call.Args, s.Call.Ellipsis.IsValid()), ", ") if sel, isSelector := s.Call.Fun.(*ast.SelectorExpr); isSelector { obj := c.p.info.Selections[sel].Obj() if !obj.Exported() { c.p.dependencies[obj] = true } c.Printf(`go$deferred.push({ recv: %s, method: "%s", args: [%s] });`, c.translateExpr(sel.X), sel.Sel.Name, args) return } c.Printf("go$deferred.push({ fun: %s, args: [%s] });", c.translateExpr(s.Call.Fun), args) case *ast.AssignStmt: c.printLabel(label) if s.Tok != token.ASSIGN && s.Tok != token.DEFINE { var op token.Token switch s.Tok { case token.ADD_ASSIGN: op = token.ADD case token.SUB_ASSIGN: op = token.SUB case token.MUL_ASSIGN: op = token.MUL case token.QUO_ASSIGN: op = token.QUO case token.REM_ASSIGN: op = token.REM case token.AND_ASSIGN: op = token.AND case token.OR_ASSIGN: op = token.OR case token.XOR_ASSIGN: op = token.XOR case token.SHL_ASSIGN: op = token.SHL case token.SHR_ASSIGN: op = token.SHR case token.AND_NOT_ASSIGN: op = token.AND_NOT default: panic(s.Tok) } var parts []string lhs := s.Lhs[0] switch l := lhs.(type) { case *ast.IndexExpr: lhsVar := c.newVariable("_lhs") indexVar := c.newVariable("_index") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") parts = append(parts, indexVar+" = "+c.translateExpr(l.Index).String()+";") lhs = &ast.IndexExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), Index: c.newIdent(indexVar, c.p.info.Types[l.Index].Type), } c.p.info.Types[lhs] = c.p.info.Types[l] case *ast.StarExpr: lhsVar := c.newVariable("_lhs") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") lhs = &ast.StarExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), } c.p.info.Types[lhs] = c.p.info.Types[l] case *ast.SelectorExpr: v := hasCallVisitor{c.p.info, false} ast.Walk(&v, l.X) if v.hasCall { lhsVar := c.newVariable("_lhs") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") lhs = &ast.SelectorExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), Sel: l.Sel, } c.p.info.Types[lhs] = c.p.info.Types[l] c.p.info.Selections[lhs.(*ast.SelectorExpr)] = c.p.info.Selections[l] } } parenExpr := &ast.ParenExpr{X: s.Rhs[0]} c.p.info.Types[parenExpr] = c.p.info.Types[s.Rhs[0]] binaryExpr := &ast.BinaryExpr{ X: lhs, Op: op, Y: parenExpr, } c.p.info.Types[binaryExpr] = c.p.info.Types[s.Lhs[0]] parts = append(parts, c.translateAssign(lhs, c.translateExpr(binaryExpr).String())) c.Printf("%s", strings.Join(parts, " ")) return } if s.Tok == token.DEFINE { for _, lhs := range s.Lhs { if !isBlank(lhs) { c.p.info.Types[lhs] = types.TypeAndValue{Type: c.p.info.Defs[lhs.(*ast.Ident)].Type()} } } } removeParens := func(e ast.Expr) ast.Expr { for { if p, isParen := e.(*ast.ParenExpr); isParen { e = p.X continue } break } return e } switch { case len(s.Lhs) == 1 && len(s.Rhs) == 1: lhs := removeParens(s.Lhs[0]) if isBlank(lhs) { v := hasCallVisitor{c.p.info, false} ast.Walk(&v, s.Rhs[0]) if v.hasCall { c.Printf("%s;", c.translateExpr(s.Rhs[0]).String()) } return } c.Printf("%s", c.translateAssign(lhs, c.translateImplicitConversion(s.Rhs[0], c.p.info.Types[s.Lhs[0]].Type).String())) case len(s.Lhs) > 1 && len(s.Rhs) == 1: tupleVar := c.newVariable("_tuple") out := tupleVar + " = " + c.translateExpr(s.Rhs[0]).String() + ";" tuple := c.p.info.Types[s.Rhs[0]].Type.(*types.Tuple) for i, lhs := range s.Lhs { lhs = removeParens(lhs) if !isBlank(lhs) { out += " " + c.translateAssign(lhs, c.translateImplicitConversion(c.newIdent(fmt.Sprintf("%s[%d]", tupleVar, i), tuple.At(i).Type()), c.p.info.Types[s.Lhs[i]].Type).String()) } } c.Printf("%s", out) case len(s.Lhs) == len(s.Rhs): parts := make([]string, len(s.Rhs)) for i, rhs := range s.Rhs { parts[i] = c.translateImplicitConversion(rhs, c.p.info.Types[s.Lhs[i]].Type).String() } tupleVar := c.newVariable("_tuple") out := tupleVar + " = [" + strings.Join(parts, ", ") + "];" for i, lhs := range s.Lhs { lhs = removeParens(lhs) if !isBlank(lhs) { out += " " + c.translateAssign(lhs, fmt.Sprintf("%s[%d]", tupleVar, i)) } } c.Printf("%s", out) default: panic("Invalid arity of AssignStmt.") } case *ast.IncDecStmt: t := c.p.info.Types[s.X].Type if iExpr, isIExpr := s.X.(*ast.IndexExpr); isIExpr { switch u := c.p.info.Types[iExpr.X].Type.Underlying().(type) { case *types.Array: t = u.Elem() case *types.Slice: t = u.Elem() case *types.Map: t = u.Elem() } } tok := token.ADD_ASSIGN if s.Tok == token.DEC { tok = token.SUB_ASSIGN } one := &ast.BasicLit{ Kind: token.INT, Value: "1", } c.p.info.Types[one] = types.TypeAndValue{Type: t, Value: exact.MakeInt64(1)} c.translateStmt(&ast.AssignStmt{ Lhs: []ast.Expr{s.X}, Tok: tok, Rhs: []ast.Expr{one}, }, label) case *ast.ExprStmt: c.printLabel(label) c.Printf("%s;", c.translateExpr(s.X).String()) case *ast.DeclStmt: c.printLabel(label) 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 isTuple := false if len(rhs) == 1 { _, isTuple = c.p.info.Types[rhs[0]].Type.(*types.Tuple) } for len(rhs) < len(lhs) && !isTuple { rhs = append(rhs, nil) } c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: rhs, }, "") } case token.TYPE: for _, spec := range decl.Specs { o := c.p.info.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) c.translateType(o, false) c.initType(o) } case token.CONST: // skip, constants are inlined } case *ast.LabeledStmt: c.printLabel(label) c.translateStmt(s.Stmt, s.Label.Name) case *ast.SelectStmt: c.printLabel(label) c.Printf(`go$notSupported("select");`) case *ast.GoStmt: c.printLabel(label) c.Printf(`go$notSupported("go");`) case *ast.SendStmt: c.printLabel(label) c.Printf(`go$notSupported("send");`) case *ast.EmptyStmt: // skip default: panic(fmt.Sprintf("Unhandled statement: %T\n", s)) } }
func (c *funcContext) translateBuiltin(name string, args []ast.Expr, ellipsis bool, typ types.Type) *expression { switch name { case "new": t := typ.(*types.Pointer) if c.p.pkg.Path() == "syscall" && types.Identical(t.Elem().Underlying(), types.Typ[types.Uintptr]) { return c.formatExpr("new Uint8Array(8)") } switch t.Elem().Underlying().(type) { case *types.Struct, *types.Array: return c.formatExpr("%s", c.zeroValue(t.Elem())) default: return c.formatExpr("$newDataPointer(%s, %s)", c.zeroValue(t.Elem()), c.typeName(t)) } case "make": switch argType := c.p.info.Types[args[0]].Type.Underlying().(type) { case *types.Slice: t := c.typeName(c.p.info.Types[args[0]].Type) if len(args) == 3 { return c.formatExpr("%s.make(%f, %f)", t, args[1], args[2]) } return c.formatExpr("%s.make(%f)", t, args[1]) case *types.Map: return c.formatExpr("new $Map()") case *types.Chan: length := "0" if len(args) == 2 { length = c.translateExpr(args[1]).String() } return c.formatExpr("new %s(%s)", c.typeName(c.p.info.Types[args[0]].Type), length) default: panic(fmt.Sprintf("Unhandled make type: %T\n", argType)) } case "len": switch argType := c.p.info.Types[args[0]].Type.Underlying().(type) { case *types.Basic: return c.formatExpr("%e.length", args[0]) case *types.Slice: return c.formatExpr("%e.$length", args[0]) case *types.Pointer: return c.formatExpr("(%e, %d)", args[0], argType.Elem().(*types.Array).Len()) case *types.Map: return c.formatExpr("$keys(%e).length", args[0]) case *types.Chan: return c.formatExpr("%e.$buffer.length", args[0]) // length of array is constant default: panic(fmt.Sprintf("Unhandled len type: %T\n", argType)) } case "cap": switch argType := c.p.info.Types[args[0]].Type.Underlying().(type) { case *types.Slice: return c.formatExpr("%e.$capacity", args[0]) case *types.Pointer: return c.formatExpr("(%e, %d)", args[0], argType.Elem().(*types.Array).Len()) case *types.Chan: return c.formatExpr("%e.$capacity", args[0]) // capacity of array is constant default: panic(fmt.Sprintf("Unhandled cap type: %T\n", argType)) } case "panic": return c.formatExpr("$panic(%s)", c.translateImplicitConversion(args[0], types.NewInterface(nil, nil))) case "append": if len(args) == 1 { return c.translateExpr(args[0]) } if ellipsis { return c.formatExpr("$appendSlice(%e, %s)", args[0], c.translateConversionToSlice(args[1], typ)) } sliceType := typ.Underlying().(*types.Slice) return c.formatExpr("$append(%e, %s)", args[0], strings.Join(c.translateExprSlice(args[1:], sliceType.Elem()), ", ")) case "delete": return c.formatExpr(`delete %e[%s]`, args[0], c.makeKey(args[1], c.p.info.Types[args[0]].Type.Underlying().(*types.Map).Key())) case "copy": if basic, isBasic := c.p.info.Types[args[1]].Type.Underlying().(*types.Basic); isBasic && basic.Info()&types.IsString != 0 { return c.formatExpr("$copyString(%e, %e)", args[0], args[1]) } return c.formatExpr("$copySlice(%e, %e)", args[0], args[1]) case "print", "println": return c.formatExpr("console.log(%s)", strings.Join(c.translateExprSlice(args, nil), ", ")) case "complex": return c.formatExpr("new %s(%e, %e)", c.typeName(typ), args[0], args[1]) case "real": return c.formatExpr("%e.$real", args[0]) case "imag": return c.formatExpr("%e.$imag", args[0]) case "recover": return c.formatExpr("$recover()") case "close": return c.formatExpr(`$close(%e)`, args[0]) default: panic(fmt.Sprintf("Unhandled builtin: %s\n", name)) } }
// matchArgTypeInternal is the internal version of matchArgType. It carries a map // remembering what types are in progress so we don't recur when faced with recursive // types or mutually recursive types. func (f *File) matchArgTypeInternal(t printfArgType, typ types.Type, arg ast.Expr, inProgress map[types.Type]bool) bool { // %v, %T accept any argument type. if t == anyType { return true } if typ == nil { // external call typ = f.pkg.types[arg].Type if typ == nil { return true // probably a type check problem } } // If the type implements fmt.Formatter, we have nothing to check. // But (see issue 6259) that's not easy to verify, so instead we see // if its method set contains a Format function. We could do better, // even now, but we don't need to be 100% accurate. Wait for 6259 to // be fixed instead. TODO. if f.hasMethod(typ, "Format") { return true } // If we can use a string, might arg (dynamically) implement the Stringer or Error interface? if t&argString != 0 { if types.AssertableTo(errorType, typ) || types.AssertableTo(stringerType, typ) { return true } } typ = typ.Underlying() if inProgress[typ] { // We're already looking at this type. The call that started it will take care of it. return true } inProgress[typ] = true switch typ := typ.(type) { case *types.Signature: return t&argPointer != 0 case *types.Map: // Recur: map[int]int matches %d. return t&argPointer != 0 || (f.matchArgTypeInternal(t, typ.Key(), arg, inProgress) && f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress)) case *types.Chan: return t&argPointer != 0 case *types.Array: // Same as slice. if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { return true // %s matches []byte } // Recur: []int matches %d. return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem().Underlying(), arg, inProgress) case *types.Slice: // Same as array. if types.Identical(typ.Elem().Underlying(), types.Typ[types.Byte]) && t&argString != 0 { return true // %s matches []byte } // Recur: []int matches %d. But watch out for // type T []T // If the element is a pointer type (type T[]*T), it's handled fine by the Pointer case below. return t&argPointer != 0 || f.matchArgTypeInternal(t, typ.Elem(), arg, inProgress) case *types.Pointer: // Ugly, but dealing with an edge case: a known pointer to an invalid type, // probably something from a failed import. if typ.Elem().String() == "invalid type" { if *verbose { f.Warnf(arg.Pos(), "printf argument %v is pointer to invalid or unknown type", f.gofmt(arg)) } return true // special case } // If it's actually a pointer with %p, it prints as one. if t == argPointer { return true } // If it's pointer to struct, that's equivalent in our analysis to whether we can print the struct. if str, ok := typ.Elem().Underlying().(*types.Struct); ok { return f.matchStructArgType(t, str, arg, inProgress) } // The rest can print with %p as pointers, or as integers with %x etc. return t&(argInt|argPointer) != 0 case *types.Struct: return f.matchStructArgType(t, typ, arg, inProgress) case *types.Interface: // If the static type of the argument is empty interface, there's little we can do. // Example: // func f(x interface{}) { fmt.Printf("%s", x) } // Whether x is valid for %s depends on the type of the argument to f. One day // we will be able to do better. For now, we assume that empty interface is OK // but non-empty interfaces, with Stringer and Error handled above, are errors. return typ.NumMethods() == 0 case *types.Basic: switch typ.Kind() { case types.UntypedBool, types.Bool: return t&argBool != 0 case types.UntypedInt, types.Int, types.Int8, types.Int16, types.Int32, types.Int64, types.Uint, types.Uint8, types.Uint16, types.Uint32, types.Uint64, types.Uintptr: return t&argInt != 0 case types.UntypedFloat, types.Float32, types.Float64: return t&argFloat != 0 case types.UntypedComplex, types.Complex64, types.Complex128: return t&argComplex != 0 case types.UntypedString, types.String: return t&argString != 0 case types.UnsafePointer: return t&(argPointer|argInt) != 0 case types.UntypedRune: return t&(argInt|argRune) != 0 case types.UntypedNil: return t&argPointer != 0 // TODO? case types.Invalid: if *verbose { f.Warnf(arg.Pos(), "printf argument %v has invalid or unknown type", f.gofmt(arg)) } return true // Probably a type check problem. } panic("unreachable") } return false }
func (c *funcContext) translateStmt(stmt ast.Stmt, label string) { c.WritePos(stmt.Pos()) switch s := stmt.(type) { case *ast.BlockStmt: c.printLabel(label) c.translateStmtList(s.List) case *ast.IfStmt: c.printLabel(label) if s.Init != nil { c.translateStmt(s.Init, "") } 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, c.translateExpr, nil, "", c.flattened[s]) case *ast.SwitchStmt: if s.Init != nil { c.translateStmt(s.Init, "") } translateCond := func(cond ast.Expr) *expression { return c.translateExpr(cond) } if s.Tag != nil { refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.Tag)) translateCond = func(cond ast.Expr) *expression { return c.translateExpr(&ast.BinaryExpr{ X: c.newIdent(refVar, c.p.info.Types[s.Tag].Type), 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, "") } var expr ast.Expr var typeSwitchVar string switch a := s.Assign.(type) { case *ast.AssignStmt: expr = a.Rhs[0].(*ast.TypeAssertExpr).X typeSwitchVar = c.newVariable(a.Lhs[0].(*ast.Ident).Name) for _, caseClause := range s.Body.List { c.p.objectVars[c.p.info.Implicits[caseClause]] = typeSwitchVar } case *ast.ExprStmt: expr = a.X.(*ast.TypeAssertExpr).X } refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(expr)) translateCond := func(cond ast.Expr) *expression { if types.Identical(c.p.info.Types[cond].Type, types.Typ[types.UntypedNil]) { return c.formatExpr("%s === $ifaceNil", refVar) } return c.formatExpr("$assertType(%s, %s, true)[1]", refVar, c.typeName(c.p.info.Types[cond].Type)) } printCaseBodyPrefix := func(index int) { if typeSwitchVar == "" { return } value := refVar if conds := s.Body.List[index].(*ast.CaseClause).List; len(conds) == 1 { t := c.p.info.Types[conds[0]].Type if _, isInterface := t.Underlying().(*types.Interface); !isInterface && !types.Identical(t, types.Typ[types.UntypedNil]) { value += ".$val" } } c.Printf("%s = %s;", typeSwitchVar, value) } c.translateBranchingStmt(s.Body.List, true, translateCond, printCaseBodyPrefix, label, c.flattened[s]) case *ast.ForStmt: if s.Init != nil { c.translateStmt(s.Init, "") } cond := "true" if s.Cond != nil { cond = c.translateExpr(s.Cond).String() } c.translateLoopingStmt(cond, s.Body, nil, func() { if s.Post != nil { c.translateStmt(s.Post, "") } }, label, c.flattened[s]) case *ast.RangeStmt: refVar := c.newVariable("_ref") c.Printf("%s = %s;", refVar, c.translateExpr(s.X)) switch t := c.p.info.Types[s.X].Type.Underlying().(type) { case *types.Basic: iVar := c.newVariable("_i") c.Printf("%s = 0;", iVar) runeVar := c.newVariable("_rune") c.translateLoopingStmt(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, iVar, types.Typ[types.Int], s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, 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(iVar+" < "+keysVar+".length", s.Body, func() { entryVar := c.newVariable("_entry") c.Printf("%s = %s[%s[%s]];", entryVar, refVar, keysVar, iVar) if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, entryVar+".k", t.Key(), s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, 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(iVar+" < "+length, s.Body, func() { if !isBlank(s.Key) { c.Printf("%s", c.translateAssign(s.Key, iVar, types.Typ[types.Int], s.Tok == token.DEFINE)) } if !isBlank(s.Value) { c.Printf("%s", c.translateAssign(s.Value, c.translateImplicitConversion(c.setType(&ast.IndexExpr{ X: c.newIdent(refVar, t), Index: c.newIdent(iVar, types.Typ[types.Int]), }, elemType), elemType).String(), 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]) forStmt := &ast.ForStmt{ Body: &ast.BlockStmt{ List: []ast.Stmt{ &ast.AssignStmt{ Lhs: []ast.Expr{ s.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: s.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: c.printLabel(label) labelSuffix := "" data := c.flowDatas[""] if s.Label != nil { labelSuffix = " " + s.Label.Name data = c.flowDatas[s.Label.Name] } switch s.Tok { case token.BREAK: c.PrintCond(data.endCase == 0, fmt.Sprintf("break%s;", labelSuffix), fmt.Sprintf("$s = %d; continue;", data.endCase)) case token.CONTINUE: data.postStmt() c.PrintCond(data.beginCase == 0, fmt.Sprintf("continue%s;", labelSuffix), fmt.Sprintf("$s = %d; continue;", data.beginCase)) case token.GOTO: c.PrintCond(false, "goto "+s.Label.Name, fmt.Sprintf("$s = %d; continue;", c.labelCases[s.Label.Name])) case token.FALLTHROUGH: // handled in CaseClause default: panic("Unhandled branch statment: " + s.Tok.String()) } case *ast.ReturnStmt: c.printLabel(label) 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, }, "") } results = c.resultNames } switch len(results) { case 0: c.Printf("return;") case 1: if c.sig.Results().Len() > 1 { c.Printf("return %s;", c.translateExpr(results[0])) return } v := c.translateImplicitConversion(results[0], c.sig.Results().At(0).Type()) c.delayedOutput = nil c.Printf("return %s;", v) default: values := make([]string, len(results)) for i, result := range results { values[i] = c.translateImplicitConversion(result, c.sig.Results().At(i).Type()).String() } c.delayedOutput = nil c.Printf("return [%s];", strings.Join(values, ", ")) } case *ast.DeferStmt: c.printLabel(label) isBuiltin := false isJs := false switch fun := s.Call.Fun.(type) { case *ast.Ident: var builtin *types.Builtin builtin, isBuiltin = c.p.info.Uses[fun].(*types.Builtin) if isBuiltin && builtin.Name() == "recover" { c.Printf("$deferred.push([$recover, []]);") return } case *ast.SelectorExpr: isJs = isJsPackage(c.p.info.Uses[fun.Sel].Pkg()) } if isBuiltin || isJs { args := make([]ast.Expr, len(s.Call.Args)) for i, arg := range s.Call.Args { args[i] = c.newIdent(c.newVariable("_arg"), c.p.info.Types[arg].Type) } call := c.translateExpr(&ast.CallExpr{ Fun: s.Call.Fun, Args: args, Ellipsis: s.Call.Ellipsis, }) c.Printf("$deferred.push([function(%s) { %s; }, [%s]]);", strings.Join(c.translateExprSlice(args, nil), ", "), call, strings.Join(c.translateExprSlice(s.Call.Args, nil), ", ")) return } sig := c.p.info.Types[s.Call.Fun].Type.Underlying().(*types.Signature) args := c.translateArgs(sig, s.Call.Args, s.Call.Ellipsis.IsValid()) if len(c.blocking) != 0 { args = append(args, "true") } c.Printf("$deferred.push([%s, [%s]]);", c.translateExpr(s.Call.Fun), strings.Join(args, ", ")) case *ast.AssignStmt: c.printLabel(label) if s.Tok != token.ASSIGN && s.Tok != token.DEFINE { var op token.Token switch s.Tok { case token.ADD_ASSIGN: op = token.ADD case token.SUB_ASSIGN: op = token.SUB case token.MUL_ASSIGN: op = token.MUL case token.QUO_ASSIGN: op = token.QUO case token.REM_ASSIGN: op = token.REM case token.AND_ASSIGN: op = token.AND case token.OR_ASSIGN: op = token.OR case token.XOR_ASSIGN: op = token.XOR case token.SHL_ASSIGN: op = token.SHL case token.SHR_ASSIGN: op = token.SHR case token.AND_NOT_ASSIGN: op = token.AND_NOT default: panic(s.Tok) } var parts []string lhs := s.Lhs[0] switch l := lhs.(type) { case *ast.IndexExpr: lhsVar := c.newVariable("_lhs") indexVar := c.newVariable("_index") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") parts = append(parts, indexVar+" = "+c.translateExpr(l.Index).String()+";") lhs = c.setType(&ast.IndexExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), Index: c.newIdent(indexVar, c.p.info.Types[l.Index].Type), }, c.p.info.Types[l].Type) case *ast.StarExpr: lhsVar := c.newVariable("_lhs") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") lhs = c.setType(&ast.StarExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), }, c.p.info.Types[l].Type) case *ast.SelectorExpr: v := hasCallVisitor{c.p.info, false} ast.Walk(&v, l.X) if v.hasCall { lhsVar := c.newVariable("_lhs") parts = append(parts, lhsVar+" = "+c.translateExpr(l.X).String()+";") lhs = c.setType(&ast.SelectorExpr{ X: c.newIdent(lhsVar, c.p.info.Types[l.X].Type), Sel: l.Sel, }, c.p.info.Types[l].Type) c.p.info.Selections[lhs.(*ast.SelectorExpr)] = c.p.info.Selections[l] } } lhsType := c.p.info.Types[s.Lhs[0]].Type parts = append(parts, c.translateAssign(lhs, c.translateExpr(c.setType(&ast.BinaryExpr{ X: lhs, Op: op, Y: c.setType(&ast.ParenExpr{X: s.Rhs[0]}, c.p.info.Types[s.Rhs[0]].Type), }, lhsType)).String(), lhsType, s.Tok == token.DEFINE)) c.Printf("%s", strings.Join(parts, " ")) return } if s.Tok == token.DEFINE { for _, lhs := range s.Lhs { if !isBlank(lhs) { obj := c.p.info.Defs[lhs.(*ast.Ident)] if obj == nil { obj = c.p.info.Uses[lhs.(*ast.Ident)] } c.setType(lhs, obj.Type()) } } } switch { case len(s.Lhs) == 1 && len(s.Rhs) == 1: lhs := removeParens(s.Lhs[0]) if isBlank(lhs) { v := hasCallVisitor{c.p.info, false} ast.Walk(&v, s.Rhs[0]) if v.hasCall { c.Printf("%s;", c.translateExpr(s.Rhs[0]).String()) } return } lhsType := c.p.info.Types[s.Lhs[0]].Type c.Printf("%s", c.translateAssignOfExpr(lhs, s.Rhs[0], lhsType, s.Tok == token.DEFINE)) case len(s.Lhs) > 1 && len(s.Rhs) == 1: tupleVar := c.newVariable("_tuple") out := tupleVar + " = " + c.translateExpr(s.Rhs[0]).String() + ";" tuple := c.p.info.Types[s.Rhs[0]].Type.(*types.Tuple) for i, lhs := range s.Lhs { lhs = removeParens(lhs) if !isBlank(lhs) { lhsType := c.p.info.Types[s.Lhs[i]].Type out += " " + c.translateAssignOfExpr(lhs, c.newIdent(fmt.Sprintf("%s[%d]", tupleVar, i), tuple.At(i).Type()), lhsType, s.Tok == token.DEFINE) } } c.Printf("%s", out) case len(s.Lhs) == len(s.Rhs): tmpVars := make([]string, len(s.Rhs)) var parts []string for i, rhs := range s.Rhs { tmpVars[i] = c.newVariable("_tmp") if isBlank(removeParens(s.Lhs[i])) { v := hasCallVisitor{c.p.info, false} ast.Walk(&v, rhs) if v.hasCall { c.Printf("%s;", c.translateExpr(rhs).String()) } continue } lhsType := c.p.info.Types[s.Lhs[i]].Type parts = append(parts, c.translateAssignOfExpr(c.newIdent(tmpVars[i], c.p.info.Types[s.Lhs[i]].Type), rhs, lhsType, true)) } for i, lhs := range s.Lhs { lhs = removeParens(lhs) if !isBlank(lhs) { parts = append(parts, c.translateAssign(lhs, tmpVars[i], c.p.info.Types[lhs].Type, s.Tok == token.DEFINE)) } } c.Printf("%s", strings.Join(parts, " ")) default: panic("Invalid arity of AssignStmt.") } case *ast.IncDecStmt: t := c.p.info.Types[s.X].Type if iExpr, isIExpr := s.X.(*ast.IndexExpr); isIExpr { switch u := c.p.info.Types[iExpr.X].Type.Underlying().(type) { case *types.Array: t = u.Elem() case *types.Slice: t = u.Elem() case *types.Map: t = u.Elem() } } tok := token.ADD_ASSIGN if s.Tok == token.DEC { tok = token.SUB_ASSIGN } c.translateStmt(&ast.AssignStmt{ Lhs: []ast.Expr{s.X}, Tok: tok, Rhs: []ast.Expr{c.newInt(1, t)}, }, label) case *ast.DeclStmt: c.printLabel(label) 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 isTuple := false if len(rhs) == 1 { _, isTuple = c.p.info.Types[rhs[0]].Type.(*types.Tuple) } for len(rhs) < len(lhs) && !isTuple { rhs = append(rhs, nil) } c.translateStmt(&ast.AssignStmt{ Lhs: lhs, Tok: token.DEFINE, Rhs: rhs, }, "") } case token.TYPE: for _, spec := range decl.Specs { o := c.p.info.Defs[spec.(*ast.TypeSpec).Name].(*types.TypeName) c.translateType(o, false) c.initType(o) } case token.CONST: // skip, constants are inlined } case *ast.ExprStmt: c.printLabel(label) expr := c.translateExpr(s.X) if expr != nil { c.Printf("%s;", expr) } case *ast.LabeledStmt: c.printLabel(label) c.translateStmt(s.Stmt, s.Label.Name) case *ast.GoStmt: c.printLabel(label) c.Printf("$go(%s, [%s]);", c.translateExpr(s.Call.Fun), strings.Join(c.translateArgs(c.p.info.Types[s.Call.Fun].Type.Underlying().(*types.Signature), s.Call.Args, s.Call.Ellipsis.IsValid()), ", ")) case *ast.SendStmt: chanType := c.p.info.Types[s.Chan].Type.Underlying().(*types.Chan) call := &ast.CallExpr{ Fun: c.newIdent("$send", types.NewSignature(nil, nil, types.NewTuple(types.NewVar(0, nil, "", chanType), types.NewVar(0, nil, "", chanType.Elem())), nil, false)), Args: []ast.Expr{s.Chan, s.Value}, } c.blocking[call] = true c.translateStmt(&ast.ExprStmt{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]", removeParens(comm.X).(*ast.UnaryExpr).X).String()) case *ast.AssignStmt: channels = append(channels, c.formatExpr("[%e]", removeParens(comm.Rhs[0]).(*ast.UnaryExpr).X).String()) case *ast.SendStmt: channels = append(channels, c.formatExpr("[%e, %e]", comm.Chan, comm.Value).String()) default: panic(fmt.Sprintf("unhandled: %T", comm)) } caseClauses = append(caseClauses, &ast.CaseClause{ List: []ast.Expr{c.newInt(i, types.Typ[types.Int])}, Body: clause.Body, }) flattened = flattened || c.flattened[clause] } selectCall := c.setType(&ast.CallExpr{ Fun: c.newIdent("$select", types.NewSignature(nil, 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.info.Types[assign.Rhs[0]].Type.(type) { case *types.Tuple: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1]", rhsType)}, Tok: assign.Tok}, "") default: c.translateStmt(&ast.AssignStmt{Lhs: assign.Lhs, Rhs: []ast.Expr{c.newIdent(selectionVar+"[1][0]", rhsType)}, Tok: assign.Tok}, "") } } } c.translateBranchingStmt(caseClauses, true, translateCond, printCaseBodyPrefix, label, flattened) case *ast.EmptyStmt: // skip default: panic(fmt.Sprintf("Unhandled statement: %T\n", s)) } }
func (c *funcContext) translateConversion(expr ast.Expr, desiredType types.Type) *expression { exprType := c.p.info.Types[expr].Type if types.Identical(exprType, desiredType) { return c.translateExpr(expr) } if c.p.pkg.Path() == "reflect" { if call, isCall := expr.(*ast.CallExpr); isCall && types.Identical(c.p.info.Types[call.Fun].Type, types.Typ[types.UnsafePointer]) { if ptr, isPtr := desiredType.(*types.Pointer); isPtr { if named, isNamed := ptr.Elem().(*types.Named); isNamed { switch named.Obj().Name() { case "arrayType", "chanType", "funcType", "interfaceType", "mapType", "ptrType", "sliceType", "structType": return c.formatExpr("%e.%s", call.Args[0], named.Obj().Name()) // unsafe conversion default: return c.translateExpr(expr) } } } } } switch t := desiredType.Underlying().(type) { case *types.Basic: switch { case t.Info()&types.IsInteger != 0: basicExprType := exprType.Underlying().(*types.Basic) switch { case is64Bit(t): if !is64Bit(basicExprType) { if basicExprType.Kind() == types.Uintptr { // this might be an Object returned from reflect.Value.Pointer() return c.formatExpr("new %1s(0, %2e.constructor === Number ? %2e : 1)", c.typeName(desiredType), expr) } return c.formatExpr("new %s(0, %e)", c.typeName(desiredType), expr) } return c.formatExpr("new %1s(%2h, %2l)", c.typeName(desiredType), expr) case is64Bit(basicExprType): if t.Info()&types.IsUnsigned == 0 && basicExprType.Info()&types.IsUnsigned == 0 { return c.fixNumber(c.formatParenExpr("%1l + ((%1h >> 31) * 4294967296)", expr), t) } return c.fixNumber(c.formatExpr("%s.$low", c.translateExpr(expr)), t) case basicExprType.Info()&types.IsFloat != 0: return c.formatParenExpr("%e >> 0", expr) case types.Identical(exprType, types.Typ[types.UnsafePointer]): return c.translateExpr(expr) default: return c.fixNumber(c.translateExpr(expr), t) } case t.Info()&types.IsFloat != 0: if t.Kind() == types.Float64 && exprType.Underlying().(*types.Basic).Kind() == types.Float32 { return c.formatExpr("$coerceFloat32(%f)", expr) } return c.formatExpr("%f", expr) case t.Info()&types.IsComplex != 0: return c.formatExpr("new %1s(%2r, %2i)", c.typeName(desiredType), expr) case t.Info()&types.IsString != 0: value := c.translateExpr(expr) switch et := exprType.Underlying().(type) { case *types.Basic: if is64Bit(et) { value = c.formatExpr("%s.$low", value) } if et.Info()&types.IsNumeric != 0 { return c.formatExpr("$encodeRune(%s)", value) } return value case *types.Slice: if types.Identical(et.Elem().Underlying(), types.Typ[types.Rune]) { return c.formatExpr("$runesToString(%s)", value) } return c.formatExpr("$bytesToString(%s)", value) default: panic(fmt.Sprintf("Unhandled conversion: %v\n", et)) } case t.Kind() == types.UnsafePointer: if unary, isUnary := expr.(*ast.UnaryExpr); isUnary && unary.Op == token.AND { if indexExpr, isIndexExpr := unary.X.(*ast.IndexExpr); isIndexExpr { return c.formatExpr("$sliceToArray(%s)", c.translateConversionToSlice(indexExpr.X, types.NewSlice(types.Typ[types.Uint8]))) } if ident, isIdent := unary.X.(*ast.Ident); isIdent && ident.Name == "_zero" { return c.formatExpr("new Uint8Array(0)") } } if ptr, isPtr := c.p.info.Types[expr].Type.(*types.Pointer); c.p.pkg.Path() == "syscall" && isPtr { if s, isStruct := ptr.Elem().Underlying().(*types.Struct); isStruct { array := c.newVariable("_array") target := c.newVariable("_struct") c.Printf("%s = new Uint8Array(%d);", array, sizes32.Sizeof(s)) c.Delayed(func() { c.Printf("%s = %s, %s;", target, c.translateExpr(expr), c.loadStruct(array, target, s)) }) return c.formatExpr("%s", array) } } if call, ok := expr.(*ast.CallExpr); ok { if id, ok := call.Fun.(*ast.Ident); ok && id.Name == "new" { return c.formatExpr("new Uint8Array(%d)", int(sizes32.Sizeof(c.p.info.Types[call.Args[0]].Type))) } } } case *types.Slice: switch et := exprType.Underlying().(type) { case *types.Basic: if et.Info()&types.IsString != 0 { if types.Identical(t.Elem().Underlying(), types.Typ[types.Rune]) { return c.formatExpr("new %s($stringToRunes(%e))", c.typeName(desiredType), expr) } return c.formatExpr("new %s($stringToBytes(%e))", c.typeName(desiredType), expr) } case *types.Array, *types.Pointer: return c.formatExpr("new %s(%e)", c.typeName(desiredType), expr) } case *types.Pointer: if s, isStruct := t.Elem().Underlying().(*types.Struct); isStruct { if c.p.pkg.Path() == "syscall" && types.Identical(exprType, types.Typ[types.UnsafePointer]) { array := c.newVariable("_array") target := c.newVariable("_struct") return c.formatExpr("(%s = %e, %s = %s, %s, %s)", array, expr, target, c.zeroValue(t.Elem()), c.loadStruct(array, target, s), target) } return c.formatExpr("$clone(%e, %s)", expr, c.typeName(t.Elem())) } if !types.Identical(exprType, types.Typ[types.UnsafePointer]) { return c.formatExpr("new %1s(%2e.$get, %2e.$set)", c.typeName(desiredType), expr) } case *types.Interface: if types.Identical(exprType, types.Typ[types.UnsafePointer]) { return c.translateExpr(expr) } } return c.translateImplicitConversion(expr, desiredType) }