// isHTTPFuncOrMethodOnClient checks whether the given call expression is on // either a function of the net/http package or a method of http.Client that // returns (*http.Response, error). func isHTTPFuncOrMethodOnClient(f *File, expr *ast.CallExpr) bool { fun, _ := expr.Fun.(*ast.SelectorExpr) sig, _ := f.pkg.types[fun].Type.(*types.Signature) if sig == nil { return false // the call is not on of the form x.f() } res := sig.Results() if res.Len() != 2 { return false // the function called does not return two values. } if ptr, ok := res.At(0).Type().(*types.Pointer); !ok || !types.Identical(ptr.Elem(), httpResponseType) { return false // the first return type is not *http.Response. } if !types.Identical(res.At(1).Type().Underlying(), errorType) { return false // the second return type is not error } typ := f.pkg.types[fun.X].Type if typ == nil { id, ok := fun.X.(*ast.Ident) return ok && id.Name == "http" // function in net/http package. } if types.Identical(typ, httpClientType) { return true // method on http.Client. } ptr, ok := typ.(*types.Pointer) return ok && types.Identical(ptr.Elem(), httpClientType) // method on *http.Client. }
// 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 }
func visitTypeAssert(inst *ssa.TypeAssert, fr *frame) { if iface, ok := inst.AssertedType.(*types.Interface); ok { if meth, _ := types.MissingMethod(inst.X.Type(), iface, true); meth == nil { // No missing methods switch vd, kind := fr.get(inst.X); kind { case Struct, LocalStruct, Array, LocalArray, Chan: fr.tuples[inst] = make(Tuples, 2) fr.tuples[inst][0] = vd fmt.Fprintf(os.Stderr, " %s = %s.(type assert %s) iface\n", reg(inst), reg(inst.X), inst.AssertedType.String()) fmt.Fprintf(os.Stderr, " ^ defined as %s\n", vd.String()) default: fmt.Fprintf(os.Stderr, " %s = %s.(type assert %s)\n", red(reg(inst)), reg(inst.X), inst.AssertedType.String()) fmt.Fprintf(os.Stderr, " ^ untracked/unknown\n") } return } } else { // Concrete type if types.Identical(inst.AssertedType.Underlying(), inst.X.Type().Underlying()) { switch vd, kind := fr.get(inst.X); kind { case Struct, LocalStruct, Array, LocalArray, Chan: fr.tuples[inst] = make(Tuples, 2) fr.tuples[inst][0] = vd fmt.Fprintf(os.Stderr, " %s = %s.(type assert %s) concrete\n", reg(inst), reg(inst.X), inst.AssertedType.String()) fmt.Fprintf(os.Stderr, " ^ defined as %s\n", vd.String()) default: fmt.Fprintf(os.Stderr, " %s = %s.(type assert %s)\n", red(reg(inst)), reg(inst.X), inst.AssertedType.String()) fmt.Fprintf(os.Stderr, " ^ untracked/unknown\n") } return } } fmt.Fprintf(os.Stderr, " # %s = %s.(%s) impossible type assertion\n", red(reg(inst)), reg(inst.X), inst.AssertedType.String()) }
// 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 = 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 }
func (c *funcContext) translateImplicitConversion(expr ast.Expr, desiredType types.Type) *expression { if desiredType == nil { return c.translateExpr(expr) } exprType := c.p.TypeOf(expr) if types.Identical(exprType, desiredType) { return c.translateExpr(expr) } basicExprType, isBasicExpr := exprType.Underlying().(*types.Basic) if isBasicExpr && basicExprType.Kind() == types.UntypedNil { return c.formatExpr("%e", 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 typesutil.IsJsObject(exprType) { // wrap JS object into js.Object struct when converting to interface return c.formatExpr("new $jsObjectPtr(%e)", expr) } 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.elem(%1e)", expr) } } return c.translateExpr(expr) }
// 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 } } }
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 }
// purgeChanOps removes channels that are of different type as queryOp, i.e. // channel we are looking for. func purgeChanOps(ops []ChanOp, ch ssa.Value) []ChanOp { i := 0 for _, op := range ops { if types.Identical(op.Value.Type().Underlying().(*types.Chan).Elem(), ch.Type().Underlying().(*types.Chan).Elem()) { ops[i] = op i++ } } ops = ops[:i] return ops }
// FindTests returns the Test, Benchmark, and Example functions // (as defined by "go test") defined in the specified package, // and its TestMain function, if any. func FindTests(pkg *Package) (tests, benchmarks, examples []*Function, main *Function) { prog := pkg.Prog // The first two of these may be nil: if the program doesn't import "testing", // it can't contain any tests, but it may yet contain Examples. var testSig *types.Signature // func(*testing.T) var benchmarkSig *types.Signature // func(*testing.B) var exampleSig = types.NewSignature(nil, nil, nil, false) // func() // Obtain the types from the parameters of testing.MainStart. if testingPkg := prog.ImportedPackage("testing"); testingPkg != nil { mainStart := testingPkg.Func("MainStart") params := mainStart.Signature.Params() testSig = funcField(params.At(1).Type()) benchmarkSig = funcField(params.At(2).Type()) // Does the package define this function? // func TestMain(*testing.M) if f := pkg.Func("TestMain"); f != nil { sig := f.Type().(*types.Signature) starM := mainStart.Signature.Results().At(0).Type() // *testing.M if sig.Results().Len() == 0 && sig.Params().Len() == 1 && types.Identical(sig.Params().At(0).Type(), starM) { main = f } } } // TODO(adonovan): use a stable order, e.g. lexical. for _, mem := range pkg.Members { if f, ok := mem.(*Function); ok && ast.IsExported(f.Name()) && strings.HasSuffix(prog.Fset.Position(f.Pos()).Filename, "_test.go") { switch { case testSig != nil && isTestSig(f, "Test", testSig): tests = append(tests, f) case benchmarkSig != nil && isTestSig(f, "Benchmark", benchmarkSig): benchmarks = append(benchmarks, f) case isTestSig(f, "Example", exampleSig): examples = append(examples, f) default: continue } } } return }
func (c *funcContext) translateResults(results []ast.Expr) string { tuple := c.sig.Results() switch tuple.Len() { case 0: return "" case 1: result := c.zeroValue(tuple.At(0).Type()) if results != nil { result = results[0] } v := c.translateImplicitConversion(result, tuple.At(0).Type()) c.delayedOutput = nil return " " + v.String() default: if len(results) == 1 { resultTuple := c.p.TypeOf(results[0]).(*types.Tuple) if resultTuple.Len() != tuple.Len() { panic("invalid tuple return assignment") } resultExpr := c.translateExpr(results[0]).String() if types.Identical(resultTuple, tuple) { return " " + resultExpr } tmpVar := c.newVariable("_returncast") c.Printf("%s = %s;", tmpVar, resultExpr) // Not all the return types matched, map everything out for implicit casting results = make([]ast.Expr, resultTuple.Len()) for i := range results { results[i] = c.newIdent(fmt.Sprintf("%s[%d]", tmpVar, i), resultTuple.At(i).Type()) } } values := make([]string, tuple.Len()) for i := range values { result := c.zeroValue(tuple.At(i).Type()) if results != nil { result = results[i] } values[i] = c.translateImplicitConversion(result, tuple.At(i).Type()).String() } c.delayedOutput = nil return " [" + strings.Join(values, ", ") + "]" } }
// 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 }
// 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 // (stringerType has exactly one method, String). if stringerType != nil && stringerType.NumMethods() == 1 { return types.Identical(f.pkg.types[call.Fun].Type, stringerType.Method(0).Type()) } } // 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] }
// 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 } // record the pair f.Result[Constraint{lhs, rhs}] = true }
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 { if obj.Name() != "interfaceMethod" { 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 } }
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 } } }
// 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 }
// checkShadowing checks whether the identifier shadows an identifier in an outer scope. func checkShadowing(f *File, 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 identifier. _, shadowed := obj.Parent().Parent().LookupParent(obj.Name(), obj.Pos()) if shadowed == nil { return } // Don't complain if it's shadowing a universe-declared identifier; that's fine. if shadowed.Parent() == types.Universe { return } if *strictShadowing { // The shadowed identifier 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 identifier doesn't include // the shadowing identifier. span, ok := f.pkg.spans[shadowed] if !ok { f.Badf(ident.Pos(), "internal error: no range for %q", ident.Name) return } if !span.contains(ident.Pos()) { return } } // Don't complain if the types differ: that implies the programmer really wants two different things. if types.Identical(obj.Type(), shadowed.Type()) { f.Badf(ident.Pos(), "declaration of %q shadows declaration at %s", obj.Name(), f.loc(shadowed.Pos())) } }
// findVisibleErrs returns a mapping from each package-level variable of type "error" to nil. func findVisibleErrs(prog *ssa.Program, qpos *queryPos) map[*ssa.Global]ssa.Value { globals := make(map[*ssa.Global]ssa.Value) for _, pkg := range prog.AllPackages() { for _, mem := range pkg.Members { gbl, ok := mem.(*ssa.Global) if !ok { continue } gbltype := gbl.Type() // globals are always pointers if !types.Identical(deref(gbltype), builtinErrorType) { continue } if !isAccessibleFrom(gbl.Object(), qpos.info.Pkg) { continue } globals[gbl] = nil } } return globals }
// 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) } } }
func checkUnusedResult(f *File, n ast.Node) { call, ok := unparen(n.(*ast.ExprStmt).X).(*ast.CallExpr) if !ok { return // not a call statement } fun := unparen(call.Fun) if f.pkg.types[fun].IsType() { return // a conversion, not a call } selector, ok := fun.(*ast.SelectorExpr) if !ok { return // neither a method call nor a qualified ident } sel, ok := f.pkg.selectors[selector] if ok && sel.Kind() == types.MethodVal { // method (e.g. foo.String()) obj := sel.Obj().(*types.Func) sig := sel.Type().(*types.Signature) if types.Identical(sig, sigNoArgsStringResult) { if unusedStringMethods[obj.Name()] { f.Badf(call.Lparen, "result of (%s).%s call not used", sig.Recv().Type(), obj.Name()) } } } else if !ok { // package-qualified function (e.g. fmt.Errorf) obj, _ := f.pkg.uses[selector.Sel] if obj, ok := obj.(*types.Func); ok { qname := obj.Pkg().Path() + "." + obj.Name() if unusedFuncs[qname] { f.Badf(call.Lparen, "result of %v call not used", qname) } } } }
// 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. if f.isFormatter(typ) { 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) || stringerType != nil && 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: // There's little we can do. // Whether any particular verb is valid depends on the argument. // The user may have reasonable prior knowledge of the contents of the interface. return true 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) translateConversion(expr ast.Expr, desiredType types.Type) *expression { exprType := c.p.TypeOf(expr) 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.TypeOf(call.Fun), 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.kindType", call.Args[0]) // unsafe conversion default: return c.translateExpr(expr) } } } } } switch t := desiredType.Underlying().(type) { case *types.Basic: switch { case isInteger(t): 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 !isUnsigned(t) && !isUnsigned(basicExprType) { return c.fixNumber(c.formatParenExpr("%1l + ((%1h >> 31) * 4294967296)", expr), t) } return c.fixNumber(c.formatExpr("%s.$low", c.translateExpr(expr)), t) case isFloat(basicExprType): 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 isFloat(t): if t.Kind() == types.Float32 && exprType.Underlying().(*types.Basic).Kind() == types.Float64 { return c.formatExpr("$fround(%e)", expr) } return c.formatExpr("%f", expr) case isComplex(t): return c.formatExpr("new %1s(%2r, %2i)", c.typeName(desiredType), expr) case isString(t): value := c.translateExpr(expr) switch et := exprType.Underlying().(type) { case *types.Basic: if is64Bit(et) { value = c.formatExpr("%s.$low", value) } if isNumeric(et) { 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.TypeOf(expr).(*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.TypeOf(call.Args[0])))) } } } case *types.Slice: switch et := exprType.Underlying().(type) { case *types.Basic: if isString(et) { 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: switch u := t.Elem().Underlying().(type) { case *types.Array: return c.translateExpr(expr) case *types.Struct: 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 = %e, %s, %s)", array, expr, target, c.zeroValue(t.Elem()), c.loadStruct(array, target, u), target) } return c.formatExpr("$pointerOfStructConversion(%e, %s)", expr, c.typeName(t)) } if !types.Identical(exprType, types.Typ[types.UnsafePointer]) { exprTypeElem := exprType.Underlying().(*types.Pointer).Elem() ptrVar := c.newVariable("_ptr") getterConv := c.translateConversion(c.setType(&ast.StarExpr{X: c.newIdent(ptrVar, exprType)}, exprTypeElem), t.Elem()) setterConv := c.translateConversion(c.newIdent("$v", t.Elem()), exprTypeElem) return c.formatExpr("(%1s = %2e, new %3s(function() { return %4s; }, function($v) { %1s.$set(%5s); }, %1s.$target))", ptrVar, expr, c.typeName(desiredType), getterConv, setterConv) } case *types.Interface: if types.Identical(exprType, types.Typ[types.UnsafePointer]) { return c.translateExpr(expr) } } return c.translateImplicitConversionWithCloning(expr, desiredType) }
func (c *funcContext) translateBuiltin(name string, sig *types.Signature, args []ast.Expr, ellipsis bool) *expression { switch name { case "new": t := sig.Results().At(0).Type().(*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("%e", c.zeroValue(t.Elem())) default: return c.formatExpr("$newDataPointer(%e, %s)", c.zeroValue(t.Elem()), c.typeName(t)) } case "make": switch argType := c.p.TypeOf(args[0]).Underlying().(type) { case *types.Slice: t := c.typeName(c.p.TypeOf(args[0])) if len(args) == 3 { return c.formatExpr("$makeSlice(%s, %f, %f)", t, args[1], args[2]) } return c.formatExpr("$makeSlice(%s, %f)", t, args[1]) case *types.Map: if len(args) == 2 && c.p.Types[args[1]].Value == nil { return c.formatExpr(`((%1f < 0 || %1f > 2147483647) ? $throwRuntimeError("makemap: size out of range") : {})`, args[1]) } return c.formatExpr("{}") case *types.Chan: length := "0" if len(args) == 2 { length = c.formatExpr("%f", args[1]).String() } return c.formatExpr("new $Chan(%s, %s)", c.typeName(c.p.TypeOf(args[0]).Underlying().(*types.Chan).Elem()), length) default: panic(fmt.Sprintf("Unhandled make type: %T\n", argType)) } case "len": switch argType := c.p.TypeOf(args[0]).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.TypeOf(args[0]).Underlying().(type) { case *types.Slice, *types.Chan: return c.formatExpr("%e.$capacity", args[0]) case *types.Pointer: return c.formatExpr("(%e, %d)", args[0], argType.Elem().(*types.Array).Len()) // 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 ellipsis || len(args) == 1 { argStr := c.translateArgs(sig, args, ellipsis, false) return c.formatExpr("$appendSlice(%s, %s)", argStr[0], argStr[1]) } sliceType := sig.Results().At(0).Type().Underlying().(*types.Slice) return c.formatExpr("$append(%e, %s)", args[0], strings.Join(c.translateExprSlice(args[1:], sliceType.Elem()), ", ")) case "delete": keyType := c.p.TypeOf(args[0]).Underlying().(*types.Map).Key() return c.formatExpr(`delete %e[%s.keyFor(%s)]`, args[0], c.typeName(keyType), c.translateImplicitConversion(args[1], keyType)) case "copy": if basic, isBasic := c.p.TypeOf(args[1]).Underlying().(*types.Basic); isBasic && isString(basic) { 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": argStr := c.translateArgs(sig, args, ellipsis, false) return c.formatExpr("new %s(%s, %s)", c.typeName(sig.Results().At(0).Type()), argStr[0], argStr[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)) } }
func (c *funcContext) translateExpr(expr ast.Expr) *expression { exprType := c.p.TypeOf(expr) if value := c.p.Types[expr].Value; value != nil { basic := exprType.Underlying().(*types.Basic) switch { case isBoolean(basic): return c.formatExpr("%s", strconv.FormatBool(constant.BoolVal(value))) case isInteger(basic): if is64Bit(basic) { if basic.Kind() == types.Int64 { d, ok := constant.Int64Val(constant.ToInt(value)) if !ok { panic("could not get exact uint") } return c.formatExpr("new %s(%s, %s)", c.typeName(exprType), strconv.FormatInt(d>>32, 10), strconv.FormatUint(uint64(d)&(1<<32-1), 10)) } d, ok := constant.Uint64Val(constant.ToInt(value)) if !ok { panic("could not get exact uint") } return c.formatExpr("new %s(%s, %s)", c.typeName(exprType), strconv.FormatUint(d>>32, 10), strconv.FormatUint(d&(1<<32-1), 10)) } d, ok := constant.Int64Val(constant.ToInt(value)) if !ok { panic("could not get exact int") } return c.formatExpr("%s", strconv.FormatInt(d, 10)) case isFloat(basic): f, _ := constant.Float64Val(value) return c.formatExpr("%s", strconv.FormatFloat(f, 'g', -1, 64)) case isComplex(basic): r, _ := constant.Float64Val(constant.Real(value)) i, _ := constant.Float64Val(constant.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 isString(basic): return c.formatExpr("%s", encodeString(constant.StringVal(value))) default: panic("Unhandled constant type: " + basic.String()) } } var obj types.Object switch e := expr.(type) { case *ast.SelectorExpr: obj = c.p.Uses[e.Sel] case *ast.Ident: obj = c.p.Defs[e] if obj == nil { obj = c.p.Uses[e] } } if obj != nil && typesutil.IsJsPackage(obj.Pkg()) { switch obj.Name() { case "Global": return c.formatExpr("$global") case "Module": return c.formatExpr("$module") case "Undefined": return c.formatExpr("undefined") } } switch e := expr.(type) { case *ast.CompositeLit: if ptrType, isPointer := exprType.(*types.Pointer); isPointer { exprType = ptrType.Elem() } collectIndexedElements := func(elementType types.Type) []string { var elements []string i := 0 zero := c.translateExpr(c.zeroValue(elementType)).String() for _, element := range e.Elts { if kve, isKve := element.(*ast.KeyValueExpr); isKve { key, ok := constant.Int64Val(constant.ToInt(c.p.Types[kve.Key].Value)) if !ok { panic("could not get exact int") } 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.zero()", c.typeName(t)) } zero := c.translateExpr(c.zeroValue(t.Elem())).String() 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: entries := make([]string, len(e.Elts)) for i, element := range e.Elts { kve := element.(*ast.KeyValueExpr) entries[i] = fmt.Sprintf("{ k: %s, v: %s }", c.translateImplicitConversionWithCloning(kve.Key, t.Key()), c.translateImplicitConversionWithCloning(kve.Value, t.Elem())) } return c.formatExpr("$makeMap(%s.keyFor, [%s])", c.typeName(t.Key()), strings.Join(entries, ", ")) 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.translateImplicitConversionWithCloning(element, t.Field(i).Type()).String() } } if isKeyValue { for i := range elements { elements[i] = c.translateExpr(c.zeroValue(t.Field(i).Type())).String() } 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.translateImplicitConversionWithCloning(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: _, fun := translateFunction(e.Type, nil, e.Body, c, exprType.(*types.Signature), c.p.FuncLitInfos[e], "") 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.objectNames[obj]) } sort.Strings(names) list := strings.Join(names, ", ") return c.formatExpr("(function(%s) { return %s; })(%s)", list, fun, list) } return c.formatExpr("(%s)", fun) case *ast.UnaryExpr: t := c.p.TypeOf(e.X) switch e.Op { case token.AND: if typesutil.IsJsObject(exprType) { return c.formatExpr("%e.object", e.X) } switch t.Underlying().(type) { case *types.Struct, *types.Array: return c.translateExpr(e.X) } switch x := astutil.RemoveParens(e.X).(type) { case *ast.CompositeLit: return c.formatExpr("$newDataPointer(%e, %s)", x, c.typeName(c.p.TypeOf(e))) case *ast.Ident: obj := c.p.Uses[x].(*types.Var) if c.p.escapingVars[obj] { return c.formatExpr("(%1s.$ptr || (%1s.$ptr = new %2s(function() { return this.$target[0]; }, function($v) { this.$target[0] = $v; }, %1s)))", c.p.objectNames[obj], c.typeName(exprType)) } return c.formatExpr(`(%1s || (%1s = new %2s(function() { return %3s; }, function($v) { %4s })))`, c.varPtrName(obj), c.typeName(exprType), c.objectName(obj), c.translateAssign(x, c.newIdent("$v", exprType), false)) case *ast.SelectorExpr: sel, ok := c.p.SelectionOf(x) if !ok { // qualified identifier obj := c.p.Uses[x.Sel].(*types.Var) return c.formatExpr(`(%1s || (%1s = new %2s(function() { return %3s; }, function($v) { %4s })))`, c.varPtrName(obj), c.typeName(exprType), c.objectName(obj), c.translateAssign(x, c.newIdent("$v", exprType), false)) } newSel := &ast.SelectorExpr{X: c.newIdent("this.$target", c.p.TypeOf(x.X)), Sel: x.Sel} c.setType(newSel, exprType) c.p.additionalSelections[newSel] = sel return c.formatExpr("(%1e.$ptr_%2s || (%1e.$ptr_%2s = new %3s(function() { return %4e; }, function($v) { %5s }, %1e)))", x.X, x.Sel.Name, c.typeName(exprType), newSel, c.translateAssign(newSel, c.newIdent("$v", exprType), false)) case *ast.IndexExpr: if _, ok := c.p.TypeOf(x.X).Underlying().(*types.Slice); ok { return c.formatExpr("$indexPtr(%1e.$array, %1e.$offset + %2e, %3s)", x.X, x.Index, c.typeName(exprType)) } return c.formatExpr("$indexPtr(%e, %e, %s)", x.X, x.Index, c.typeName(exprType)) case *ast.StarExpr: return c.translateExpr(x.X) default: panic(fmt.Sprintf("Unhandled: %T\n", x)) } case token.ARROW: call := &ast.CallExpr{ Fun: c.newIdent("$recv", types.NewSignature(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 isComplex(basic): return c.formatExpr("new %1s(-%2r, -%2i)", c.typeName(t), e.X) case isUnsigned(basic): 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: return c.formatExpr("!%e", e.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.TypeOf(e.X) t2 := c.p.TypeOf(e.Y) _, isInterface := t2.Underlying().(*types.Interface) if isInterface || types.Identical(t, types.Typ[types.UntypedNil]) { t = t2 } if basic, isBasic := t.Underlying().(*types.Basic); isBasic && isNumeric(basic) { 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 isComplex(basic) { switch e.Op { case token.EQL: 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: 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: return c.fixNumber(c.formatExpr("%e %t %e", e.X, e.Op, e.Y), basic) case token.MUL: switch basic.Kind() { case types.Int32, types.Int: return c.formatParenExpr("$imul(%e, %e)", e.X, e.Y) case types.Uint32, types.Uintptr: return c.formatParenExpr("$imul(%e, %e) >>> 0", e.X, e.Y) } return c.fixNumber(c.formatExpr("%e * %e", e.X, e.Y), basic) case token.QUO: if isInteger(basic) { // cut off decimals shift := ">>" if isUnsigned(basic) { 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) } if basic.Kind() == types.Float32 { return c.fixNumber(c.formatExpr("%e / %e", e.X, e.Y), basic) } 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 && isUnsigned(basic) { op = ">>>" } if v := c.p.Types[e.Y].Value; v != nil { i, _ := constant.Uint64Val(constant.ToInt(v)) if i >= 32 { return c.formatExpr("0") } return c.fixNumber(c.formatExpr("%e %s %s", e.X, op, strconv.FormatUint(i, 10)), basic) } if e.Op == token.SHR && !isUnsigned(basic) { return c.fixNumber(c.formatParenExpr("%e >> $min(%f, 31)", e.X, e.Y), basic) } y := c.newVariable("y") return c.fixNumber(c.formatExpr("(%s = %f, %s < 32 ? (%e %s %s) : 0)", y, e.Y, y, e.X, op, y), basic) case token.AND, token.OR: if isUnsigned(basic) { 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.fixNumber(c.formatParenExpr("%e & ~%e", e.X, e.Y), basic) 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: if c.Blocking[e.Y] { skipCase := c.caseCounter c.caseCounter++ resultVar := c.newVariable("_v") c.Printf("if (!(%s)) { %s = false; $s = %d; continue s; }", c.translateExpr(e.X), resultVar, skipCase) c.Printf("%s = %s; case %d:", resultVar, c.translateExpr(e.Y), skipCase) return c.formatExpr("%s", resultVar) } return c.formatExpr("%e && %e", e.X, e.Y) case token.LOR: if c.Blocking[e.Y] { skipCase := c.caseCounter c.caseCounter++ resultVar := c.newVariable("_v") c.Printf("if (%s) { %s = true; $s = %d; continue s; }", c.translateExpr(e.X), resultVar, skipCase) c.Printf("%s = %s; case %d:", resultVar, c.translateExpr(e.Y), skipCase) return c.formatExpr("%s", resultVar) } return c.formatExpr("%e || %e", e.X, e.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: return c.formatExpr("$interfaceIsEqual(%s, %s)", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t)) case *types.Pointer: if _, ok := u.Elem().Underlying().(*types.Array); ok { return c.formatExpr("$equal(%s, %s, %s)", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t), c.typeName(u.Elem())) } case *types.Basic: if isBoolean(u) { if b, ok := analysis.BoolValue(e.X, c.p.Info.Info); ok && b { return c.translateExpr(e.Y) } if b, ok := analysis.BoolValue(e.Y, c.p.Info.Info); ok && b { return c.translateExpr(e.X) } } } return c.formatExpr("%s === %s", c.translateImplicitConversion(e.X, t), c.translateImplicitConversion(e.Y, t)) default: panic(e.Op) } case *ast.ParenExpr: return c.formatParenExpr("%e", e.X) case *ast.IndexExpr: switch t := c.p.TypeOf(e.X).Underlying().(type) { case *types.Array, *types.Pointer: pattern := rangeCheck("%1e[%2f]", c.p.Types[e.Index].Value != nil, true) if _, ok := t.(*types.Pointer); ok { // check pointer for nix (attribute getter causes a panic) pattern = `(%1e.nilCheck, ` + pattern + `)` } return c.formatExpr(pattern, e.X, e.Index) case *types.Slice: return c.formatExpr(rangeCheck("%1e.$array[%1e.$offset + %2f]", c.p.Types[e.Index].Value != nil, false), e.X, e.Index) case *types.Map: if typesutil.IsJsObject(c.p.TypeOf(e.Index)) { c.p.errList = append(c.p.errList, types.Error{Fset: c.p.fileSet, Pos: e.Index.Pos(), Msg: "cannot use js.Object as map key"}) } key := fmt.Sprintf("%s.keyFor(%s)", c.typeName(t.Key()), c.translateImplicitConversion(e.Index, t.Key())) if _, isTuple := exprType.(*types.Tuple); isTuple { return c.formatExpr(`(%1s = %2e[%3s], %1s !== undefined ? [%1s.v, true] : [%4e, false])`, c.newVariable("_entry"), e.X, key, c.zeroValue(t.Elem())) } return c.formatExpr(`(%1s = %2e[%3s], %1s !== undefined ? %1s.v : %4e)`, 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.TypeOf(e.X).Underlying().(*types.Basic); isBasic && isString(b) { 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.SelectionOf(e) if !ok { // qualified identifier return c.formatExpr("%s", c.objectName(obj)) } switch sel.Kind() { case types.FieldVal: fields, jsTag := c.translateSelection(sel, e.Pos()) 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: return c.formatExpr(`$methodVal(%s, "%s")`, c.makeReceiver(e), sel.Obj().(*types.Func).Name()) case types.MethodExpr: if !sel.Obj().Exported() { c.p.dependencies[sel.Obj()] = true } if _, ok := sel.Recv().Underlying().(*types.Interface); ok { return c.formatExpr(`$ifaceMethodExpr("%s")`, sel.Obj().(*types.Func).Name()) } return c.formatExpr(`$methodExpr(%s, "%s")`, c.typeName(sel.Recv()), sel.Obj().(*types.Func).Name()) default: panic(fmt.Sprintf("unexpected sel.Kind(): %T", sel.Kind())) } case *ast.CallExpr: plainFun := astutil.RemoveParens(e.Fun) if astutil.IsTypeExpr(plainFun, c.p.Info.Info) { return c.formatExpr("%s", c.translateConversion(e.Args[0], c.p.TypeOf(plainFun))) } sig := c.p.TypeOf(plainFun).Underlying().(*types.Signature) switch f := plainFun.(type) { case *ast.Ident: obj := c.p.Uses[f] if o, ok := obj.(*types.Builtin); ok { return c.translateBuiltin(o.Name(), sig, e.Args, e.Ellipsis.IsValid()) } if typesutil.IsJsPackage(obj.Pkg()) && obj.Name() == "InternalObject" { return c.translateExpr(e.Args[0]) } return c.translateCall(e, sig, c.translateExpr(f)) case *ast.SelectorExpr: sel, ok := c.p.SelectionOf(f) if !ok { // qualified identifier obj := c.p.Uses[f.Sel] if typesutil.IsJsPackage(obj.Pkg()) { switch obj.Name() { case "Debugger": return c.formatExpr("debugger") case "InternalObject": return c.translateExpr(e.Args[0]) } } return c.translateCall(e, sig, c.translateExpr(f)) } externalizeExpr := func(e ast.Expr) string { t := c.p.TypeOf(e) 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: recv := c.makeReceiver(f) declaredFuncRecv := sel.Obj().(*types.Func).Type().(*types.Signature).Recv().Type() if typesutil.IsJsObject(declaredFuncRecv) { globalRef := func(id string) string { if recv.String() == "$global" && id[0] == '$' && len(id) > 1 { 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 "String": 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 default: panic("Invalid js package object: " + sel.Obj().Name()) } } methodName := sel.Obj().Name() if reservedKeywords[methodName] { methodName += "$" } return c.translateCall(e, sig, c.formatExpr("%s.%s", recv, methodName)) case types.FieldVal: fields, jsTag := c.translateSelection(sel, f.Pos()) if jsTag != "" { call := c.formatExpr("%e.%s.%s(%s)", f.X, strings.Join(fields, "."), jsTag, externalizeArgs(e.Args)) switch sig.Results().Len() { case 0: return call case 1: return c.internalize(call, sig.Results().At(0).Type()) default: c.p.errList = append(c.p.errList, types.Error{Fset: c.p.fileSet, Pos: f.Pos(), Msg: "field with js tag can not have func type with multiple results"}) } } return c.translateCall(e, sig, c.formatExpr("%e.%s", f.X, strings.Join(fields, "."))) case types.MethodExpr: return c.translateCall(e, sig, c.translateExpr(f)) default: panic(fmt.Sprintf("unexpected sel.Kind(): %T", sel.Kind())) } default: return c.translateCall(e, sig, c.translateExpr(plainFun)) } case *ast.StarExpr: if typesutil.IsJsObject(c.p.TypeOf(e.X)) { return c.formatExpr("new $jsObjectPtr(%e)", e.X) } 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.TypeOf(c2.Fun), 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.TypeOf(e.Type) if _, isTuple := exprType.(*types.Tuple); isTuple { return c.formatExpr("$assertType(%e, %s, true)", e.X, c.typeName(t)) } return c.formatExpr("$assertType(%e, %s)", e.X, c.typeName(t)) case *ast.Ident: if e.Name == "_" { panic("Tried to translate underscore identifier.") } switch o := obj.(type) { 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: if typesutil.IsJsObject(exprType) { return c.formatExpr("null") } switch t := exprType.Underlying().(type) { case *types.Basic: if t.Kind() != types.UnsafePointer { panic("unexpected basic type") } return c.formatExpr("0") case *types.Slice, *types.Pointer: return c.formatExpr("%s.nil", c.typeName(exprType)) case *types.Chan: return c.formatExpr("$chanNil") case *types.Map: return c.formatExpr("false") case *types.Interface: return c.formatExpr("$ifaceNil") case *types.Signature: return c.formatExpr("$throwNilPointerError") default: panic(fmt.Sprintf("unexpected type: %T", t)) } default: panic(fmt.Sprintf("Unhandled object: %T\n", o)) } case *this: if isWrapped(c.p.TypeOf(e)) { return c.formatExpr("this.$val") } return c.formatExpr("this") case nil: return c.formatExpr("") default: panic(fmt.Sprintf("Unhandled expression: %T\n", e)) } }
// whicherrs takes an position to an error and tries to find all types, constants // and global value which a given error can point to and which can be checked from the // scope where the error lives. // In short, it returns a list of things that can be checked against in order to handle // an error properly. // // TODO(dmorsing): figure out if fields in errors like *os.PathError.Err // can be queried recursively somehow. func whicherrs(q *Query) error { lconf := loader.Config{Build: q.Build} if err := setPTAScope(&lconf, q.Scope); err != nil { return err } // Load/parse/type-check the program. lprog, err := lconf.Load() if err != nil { return err } q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos if err != nil { return err } prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug) ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection) if err != nil { return err } path, action := findInterestingNode(qpos.info, qpos.path) if action != actionExpr { return fmt.Errorf("whicherrs wants an expression; got %s", astutil.NodeDescription(qpos.path[0])) } var expr ast.Expr var obj types.Object switch n := path[0].(type) { case *ast.ValueSpec: // ambiguous ValueSpec containing multiple names return fmt.Errorf("multiple value specification") case *ast.Ident: obj = qpos.info.ObjectOf(n) expr = n case ast.Expr: expr = n default: return fmt.Errorf("unexpected AST for expr: %T", n) } typ := qpos.info.TypeOf(expr) if !types.Identical(typ, builtinErrorType) { return fmt.Errorf("selection is not an expression of type 'error'") } // Determine the ssa.Value for the expression. var value ssa.Value if obj != nil { // def/ref of func/var object value, _, err = ssaValueForIdent(prog, qpos.info, obj, path) } else { value, _, err = ssaValueForExpr(prog, qpos.info, path) } if err != nil { return err // e.g. trivially dead code } // Defer SSA construction till after errors are reported. prog.Build() globals := findVisibleErrs(prog, qpos) constants := findVisibleConsts(prog, qpos) res := &whicherrsResult{ qpos: qpos, errpos: expr.Pos(), } // TODO(adonovan): the following code is heavily duplicated // w.r.t. "pointsto". Refactor? // Find the instruction which initialized the // global error. If more than one instruction has stored to the global // remove the global from the set of values that we want to query. allFuncs := ssautil.AllFunctions(prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { store, ok := instr.(*ssa.Store) if !ok { continue } gval, ok := store.Addr.(*ssa.Global) if !ok { continue } gbl, ok := globals[gval] if !ok { continue } // we already found a store to this global // The normal error define is just one store in the init // so we just remove this global from the set we want to query if gbl != nil { delete(globals, gval) } globals[gval] = store.Val } } } ptaConfig.AddQuery(value) for _, v := range globals { ptaConfig.AddQuery(v) } ptares := ptrAnalysis(ptaConfig) valueptr := ptares.Queries[value] for g, v := range globals { ptr, ok := ptares.Queries[v] if !ok { continue } if !ptr.MayAlias(valueptr) { continue } res.globals = append(res.globals, g) } pts := valueptr.PointsTo() dedup := make(map[*ssa.NamedConst]bool) for _, label := range pts.Labels() { // These values are either MakeInterfaces or reflect // generated interfaces. For the purposes of this // analysis, we don't care about reflect generated ones makeiface, ok := label.Value().(*ssa.MakeInterface) if !ok { continue } constval, ok := makeiface.X.(*ssa.Const) if !ok { continue } c := constants[*constval] if c != nil && !dedup[c] { dedup[c] = true res.consts = append(res.consts, c) } } concs := pts.DynamicTypes() concs.Iterate(func(conc types.Type, _ interface{}) { // go/types is a bit annoying here. // We want to find all the types that we can // typeswitch or assert to. This means finding out // if the type pointed to can be seen by us. // // For the purposes of this analysis, the type is always // either a Named type or a pointer to one. // There are cases where error can be implemented // by unnamed types, but in that case, we can't assert to // it, so we don't care about it for this analysis. var name *types.TypeName switch t := conc.(type) { case *types.Pointer: named, ok := t.Elem().(*types.Named) if !ok { return } name = named.Obj() case *types.Named: name = t.Obj() default: return } if !isAccessibleFrom(name, qpos.info.Pkg) { return } res.types = append(res.types, &errorType{conc, name}) }) sort.Sort(membersByPosAndString(res.globals)) sort.Sort(membersByPosAndString(res.consts)) sort.Sort(sorterrorType(res.types)) q.result = res return nil }
// Ensure that, in debug mode, we can determine the ssa.Value // corresponding to every ast.Expr. func TestValueForExpr(t *testing.T) { if runtime.GOOS == "android" { t.Skipf("no testdata dir on %s", runtime.GOOS) } 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 := ssautil.CreateProgram(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) } } } }
// Implements displays the "implements" relation as it pertains to the // selected type. // If the selection is a method, 'implements' displays // the corresponding methods of the types that would have been reported // by an implements query on the receiver type. // func implements(q *Query) error { lconf := loader.Config{Build: q.Build} allowErrors(&lconf) qpkg, err := importQueryPackage(q.Pos, &lconf) if err != nil { return err } // Set the packages to search. if len(q.Scope) > 0 { // Inspect all packages in the analysis scope, if specified. if err := setPTAScope(&lconf, q.Scope); err != nil { return err } } else { // Otherwise inspect the forward and reverse // transitive closure of the selected package. // (In theory even this is incomplete.) _, rev, _ := importgraph.Build(q.Build) for path := range rev.Search(qpkg) { lconf.ImportWithTests(path) } // TODO(adonovan): for completeness, we should also // type-check and inspect function bodies in all // imported packages. This would be expensive, but we // could optimize by skipping functions that do not // contain type declarations. This would require // changing the loader's TypeCheckFuncBodies hook to // provide the []*ast.File. } // Load/parse/type-check the program. lprog, err := lconf.Load() if err != nil { return err } q.Fset = lprog.Fset qpos, err := parseQueryPos(lprog, q.Pos, false) if err != nil { return err } // Find the selected type. path, action := findInterestingNode(qpos.info, qpos.path) var method *types.Func var T types.Type // selected type (receiver if method != nil) switch action { case actionExpr: // method? if id, ok := path[0].(*ast.Ident); ok { if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok { recv := obj.Type().(*types.Signature).Recv() if recv == nil { return fmt.Errorf("this function is not a method") } method = obj T = recv.Type() } } case actionType: T = qpos.info.TypeOf(path[0].(ast.Expr)) } if T == nil { return fmt.Errorf("no type or method here") } // Find all named types, even local types (which can have // methods via promotion) and the built-in "error". var allNamed []types.Type for _, info := range lprog.AllPackages { for _, obj := range info.Defs { if obj, ok := obj.(*types.TypeName); ok { allNamed = append(allNamed, obj.Type()) } } } allNamed = append(allNamed, types.Universe.Lookup("error").Type()) var msets typeutil.MethodSetCache // Test each named type. var to, from, fromPtr []types.Type for _, U := range allNamed { if isInterface(T) { if msets.MethodSet(T).Len() == 0 { continue // empty interface } if isInterface(U) { if msets.MethodSet(U).Len() == 0 { continue // empty interface } // T interface, U interface if !types.Identical(T, U) { if types.AssignableTo(U, T) { to = append(to, U) } if types.AssignableTo(T, U) { from = append(from, U) } } } else { // T interface, U concrete if types.AssignableTo(U, T) { to = append(to, U) } else if pU := types.NewPointer(U); types.AssignableTo(pU, T) { to = append(to, pU) } } } else if isInterface(U) { if msets.MethodSet(U).Len() == 0 { continue // empty interface } // T concrete, U interface if types.AssignableTo(T, U) { from = append(from, U) } else if pT := types.NewPointer(T); types.AssignableTo(pT, U) { fromPtr = append(fromPtr, U) } } } var pos interface{} = qpos if nt, ok := deref(T).(*types.Named); ok { pos = nt.Obj() } // Sort types (arbitrarily) to ensure test determinism. sort.Sort(typesByString(to)) sort.Sort(typesByString(from)) sort.Sort(typesByString(fromPtr)) var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils if method != nil { for _, t := range to { toMethod = append(toMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } for _, t := range from { fromMethod = append(fromMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } for _, t := range fromPtr { fromPtrMethod = append(fromPtrMethod, types.NewMethodSet(t).Lookup(method.Pkg(), method.Name())) } } q.result = &implementsResult{ qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod, } return nil }