Пример #1
0
// makeBound returns a bound method wrapper (or "bound"), a synthetic
// function that delegates to a concrete or interface method denoted
// by obj.  The resulting function has no receiver, but has one free
// variable which will be used as the method's receiver in the
// tail-call.
//
// Use MakeClosure with such a wrapper to construct a bound method
// closure.  e.g.:
//
//   type T int          or:  type T interface { meth() }
//   func (t T) meth()
//   var t T
//   f := t.meth
//   f() // calls t.meth()
//
// f is a closure of a synthetic wrapper defined as if by:
//
//   f := func() { return t.meth() }
//
// Unlike makeWrapper, makeBound need perform no indirection or field
// selections because that can be done before the closure is
// constructed.
//
// EXCLUSIVE_LOCKS_ACQUIRED(meth.Prog.methodsMu)
//
func makeBound(prog *Program, obj *types.Func) *Function {
	prog.methodsMu.Lock()
	defer prog.methodsMu.Unlock()
	fn, ok := prog.bounds[obj]
	if !ok {
		description := fmt.Sprintf("bound method wrapper for %s", obj)
		if prog.mode&LogSource != 0 {
			defer logStack("%s", description)()
		}
		fn = &Function{
			name:      obj.Name() + "$bound",
			object:    obj,
			Signature: changeRecv(obj.Type().(*types.Signature), nil), // drop receiver
			Synthetic: description,
			Prog:      prog,
			pos:       obj.Pos(),
		}

		fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn}
		fn.FreeVars = []*FreeVar{fv}
		fn.startBody()
		createParams(fn, 0)
		var c Call

		if !isInterface(recvType(obj)) { // concrete
			c.Call.Value = prog.declaredFunc(obj)
			c.Call.Args = []Value{fv}
		} else {
			c.Call.Value = fv
			c.Call.Method = obj
		}
		for _, arg := range fn.Params {
			c.Call.Args = append(c.Call.Args, arg)
		}
		emitTailCall(fn, &c)
		fn.finishBody()

		prog.bounds[obj] = fn
	}
	return fn
}
Пример #2
0
// checkMethod performs safety checks for renaming a method.
// There are three hazards:
// - declaration conflicts
// - selection ambiguity/changes
// - entailed renamings of assignable concrete/interface types.
//   We reject renamings initiated at concrete methods if it would
//   change the assignability relation.  For renamings of abstract
//   methods, we rename all methods transitively coupled to it via
//   assignability.
func (r *Unexporter) checkMethod(objsToUpdate map[types.Object]string, from *types.Func, to string) {
	// e.g. error.Error
	if from.Pkg() == nil {
		r.warn(from, r.errorf(from.Pos(), "you cannot rename built-in method %s", from))
		return
	}

	// ASSIGNABILITY: We reject renamings of concrete methods that
	// would break a 'satisfy' constraint; but renamings of abstract
	// methods are allowed to proceed, and we rename affected
	// concrete and abstract methods as necessary.  It is the
	// initial method that determines the policy.

	// Check for conflict at point of declaration.
	// Check to ensure preservation of assignability requirements.
	R := recv(from).Type()
	if isInterface(R) {
		// Abstract method

		// declaration
		prev, _, _ := types.LookupFieldOrMethod(R, false, from.Pkg(), to)
		if prev != nil {
			r.warn(from,
				r.errorf(from.Pos(), "renaming this interface method %q to %q",
					from.Name(), to),
				r.errorf(prev.Pos(), "\twould conflict with this method"))
			return
		}

		// Check all interfaces that embed this one for
		// declaration conflicts too.
		for _, info := range r.packages {
			// Start with named interface types (better errors)
			for _, obj := range info.Defs {
				if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) {
					f, _, _ := types.LookupFieldOrMethod(
						obj.Type(), false, from.Pkg(), from.Name())
					if f == nil {
						continue
					}
					t, _, _ := types.LookupFieldOrMethod(
						obj.Type(), false, from.Pkg(), to)
					if t == nil {
						continue
					}
					r.warn(from,
						r.errorf(from.Pos(), "renaming this interface method %q to %q",
							from.Name(), to),
						r.errorf(t.Pos(), "\twould conflict with this method"),
						r.errorf(obj.Pos(), "\tin named interface type %q", obj.Name()))
				}
			}

			// Now look at all literal interface types (includes named ones again).
			for e, tv := range info.Types {
				if e, ok := e.(*ast.InterfaceType); ok {
					_ = e
					_ = tv.Type.(*types.Interface)
					// TODO(adonovan): implement same check as above.
				}
			}
		}

		// assignability
		//
		// Find the set of concrete or abstract methods directly
		// coupled to abstract method 'from' by some
		// satisfy.Constraint, and rename them too.
		for key := range r.satisfy() {
			// key = (lhs, rhs) where lhs is always an interface.

			lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name())
			if lsel == nil {
				continue
			}
			rmethods := r.msets.MethodSet(key.RHS)
			rsel := rmethods.Lookup(from.Pkg(), from.Name())
			if rsel == nil {
				continue
			}

			// If both sides have a method of this name,
			// and one of them is m, the other must be coupled.
			var coupled *types.Func
			switch from {
			case lsel.Obj():
				coupled = rsel.Obj().(*types.Func)
			case rsel.Obj():
				coupled = lsel.Obj().(*types.Func)
			default:
				continue
			}

			// We must treat concrete-to-interface
			// constraints like an implicit selection C.f of
			// each interface method I.f, and check that the
			// renaming leaves the selection unchanged and
			// unambiguous.
			//
			// Fun fact: the implicit selection of C.f
			// 	type I interface{f()}
			// 	type C struct{I}
			// 	func (C) g()
			//      var _ I = C{} // here
			// yields abstract method I.f.  This can make error
			// messages less than obvious.
			//
			if !isInterface(key.RHS) {
				// The logic below was derived from checkSelections.

				rtosel := rmethods.Lookup(from.Pkg(), to)
				if rtosel != nil {
					rto := rtosel.Obj().(*types.Func)
					delta := len(rsel.Index()) - len(rtosel.Index())
					if delta < 0 {
						continue // no ambiguity
					}

					// TODO(adonovan): record the constraint's position.
					keyPos := token.NoPos

					rename := r.errorf(from.Pos(), "renaming this method %q to %q",
						from.Name(), to)
					if delta == 0 {
						// analogous to same-block conflict
						r.warn(from, rename,
							r.errorf(keyPos, "\twould make the %s method of %s invoked via interface %s ambiguous",
								to, key.RHS, key.LHS),
							r.errorf(rto.Pos(), "\twith (%s).%s",
								recv(rto).Type(), to))
					} else {
						// analogous to super-block conflict
						r.warn(from, rename,
							r.errorf(keyPos, "\twould change the %s method of %s invoked via interface %s",
								to, key.RHS, key.LHS),
							r.errorf(coupled.Pos(), "\tfrom (%s).%s",
								recv(coupled).Type(), to),
							r.errorf(rto.Pos(), "\tto (%s).%s",
								recv(rto).Type(), to))
					}
					return // one error is enough
				}
			}

			if !r.changeMethods {
				// This should be unreachable.
				r.warn(from,
					r.errorf(from.Pos(), "internal error: during renaming of abstract method %s", from),
					r.errorf(coupled.Pos(), "\tchangedMethods=false, coupled method=%s", coupled),
					r.errorf(from.Pos(), "\tPlease file a bug report"))
				return
			}

			// Rename the coupled method to preserve assignability.
			r.check(objsToUpdate, coupled, to)
		}
	} else {
		// Concrete method

		// declaration
		prev, indices, _ := types.LookupFieldOrMethod(R, true, from.Pkg(), to)
		if prev != nil && len(indices) == 1 {
			r.warn(from,
				r.errorf(from.Pos(), "renaming this method %q to %q",
					from.Name(), to),
				r.errorf(prev.Pos(), "\twould conflict with this %s",
					objectKind(prev)))
			return
		}

		// assignability
		//
		// Find the set of abstract methods coupled to concrete
		// method 'from' by some satisfy.Constraint, and rename
		// them too.
		//
		// Coupling may be indirect, e.g. I.f <-> C.f via type D.
		//
		// 	type I interface {f()}
		//	type C int
		//	type (C) f()
		//	type D struct{C}
		//	var _ I = D{}
		//
		for key := range r.satisfy() {
			// key = (lhs, rhs) where lhs is always an interface.
			if isInterface(key.RHS) {
				continue
			}
			rsel := r.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name())
			if rsel == nil || rsel.Obj() != from {
				continue // rhs does not have the method
			}
			lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name())
			if lsel == nil {
				continue
			}
			imeth := lsel.Obj().(*types.Func)

			// imeth is the abstract method (e.g. I.f)
			// and key.RHS is the concrete coupling type (e.g. D).
			if !r.changeMethods {
				rename := r.errorf(from.Pos(), "renaming this method %q to %q",
					from.Name(), to)
				var pos token.Pos
				var iface string

				i := recv(imeth).Type()
				if named, ok := i.(*types.Named); ok {
					pos = named.Obj().Pos()
					iface = "interface " + named.Obj().Name()
				} else {
					pos = from.Pos()
					iface = i.String()
				}
				r.warn(from, rename,
					r.errorf(pos, "\twould make %s no longer assignable to %s",
						key.RHS, iface),
					r.errorf(imeth.Pos(), "\t(rename %s.%s if you intend to change both types)",
						i, from.Name()))
				return // one error is enough
			}

			// Rename the coupled interface method to preserve assignability.
			r.check(objsToUpdate, imeth, to)
		}
	}

	// Check integrity of existing (field and method) selections.
	// We skip this if there were errors above, to avoid redundant errors.
	r.checkSelections(objsToUpdate, from, to)
}
Пример #3
0
// checkMethod performs safety checks for renaming a method.
// There are three hazards:
// - declaration conflicts
// - selection ambiguity/changes
// - entailed renamings of assignable concrete/interface types (for now, just reject)
func (r *renamer) checkMethod(from *types.Func) {
	// e.g. error.Error
	if from.Pkg() == nil {
		r.errorf(from.Pos(), "you cannot rename built-in method %s", from)
		return
	}

	// As always, having to support concrete methods with pointer
	// and non-pointer receivers, and named vs unnamed types with
	// methods, makes tooling fun.

	// ASSIGNABILITY
	//
	// For now, if any method renaming breaks a required
	// assignability to another type, we reject it.
	//
	// TODO(adonovan): probably we should compute the entailed
	// renamings so that an interface method renaming causes
	// concrete methods to change too.  But which ones?
	//
	// There is no correct answer, only heuristics, because Go's
	// "duck typing" doesn't distinguish intentional from contingent
	// assignability.  There are two obvious approaches:
	//
	// (1) Update the minimum set of types to preserve the
	//     assignability of types all syntactic assignments
	//     (incl. implicit ones in calls, returns, sends, etc).
	//     The satisfy.Finder enumerates these.
	//     This is likely to be an underapproximation.
	//
	// (2) Update all types that are assignable to/from the changed
	//     type.  This requires computing the "implements" relation
	//     for all pairs of types (as godoc and oracle do).
	//     This is likely to be an overapproximation.
	//
	// If a concrete type is renamed, we probably do not want to
	// rename corresponding interfaces; interface renamings should
	// probably be initiated at the interface.  (But what if a
	// concrete type implements multiple interfaces with the same
	// method?  Then the user is stuck.)
	//
	// We need some experience before we decide how to implement this.

	// Check for conflict at point of declaration.
	// Check to ensure preservation of assignability requirements.
	recv := from.Type().(*types.Signature).Recv().Type()
	if isInterface(recv) {
		// Abstract method

		// declaration
		prev, _, _ := types.LookupFieldOrMethod(recv, false, from.Pkg(), r.to)
		if prev != nil {
			r.errorf(from.Pos(), "renaming this interface method %q to %q",
				from.Name(), r.to)
			r.errorf(prev.Pos(), "\twould conflict with this method")
			return
		}

		// Check all interfaces that embed this one for
		// declaration conflicts too.
		for _, info := range r.packages {
			// Start with named interface types (better errors)
			for _, obj := range info.Defs {
				if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) {
					f, _, _ := types.LookupFieldOrMethod(
						obj.Type(), false, from.Pkg(), from.Name())
					if f == nil {
						continue
					}
					t, _, _ := types.LookupFieldOrMethod(
						obj.Type(), false, from.Pkg(), r.to)
					if t == nil {
						continue
					}
					r.errorf(from.Pos(), "renaming this interface method %q to %q",
						from.Name(), r.to)
					r.errorf(t.Pos(), "\twould conflict with this method")
					r.errorf(obj.Pos(), "\tin named interface type %q", obj.Name())
				}
			}

			// Now look at all literal interface types (includes named ones again).
			for e, tv := range info.Types {
				if e, ok := e.(*ast.InterfaceType); ok {
					_ = e
					_ = tv.Type.(*types.Interface)
					// TODO(adonovan): implement same check as above.
				}
			}
		}

		// assignability
		for T := range r.findAssignments(recv) {
			if obj, _, _ := types.LookupFieldOrMethod(T, false, from.Pkg(), from.Name()); obj == nil {
				continue
			}

			r.errorf(from.Pos(), "renaming this method %q to %q",
				from.Name(), r.to)
			var pos token.Pos
			var other string
			if named, ok := T.(*types.Named); ok {
				pos = named.Obj().Pos()
				other = named.Obj().Name()
			} else {
				pos = from.Pos()
				other = T.String()
			}
			r.errorf(pos, "\twould make %s no longer assignable to it", other)
			return
		}
	} else {
		// Concrete method

		// declaration
		prev, indices, _ := types.LookupFieldOrMethod(recv, true, from.Pkg(), r.to)
		if prev != nil && len(indices) == 1 {
			r.errorf(from.Pos(), "renaming this method %q to %q",
				from.Name(), r.to)
			r.errorf(prev.Pos(), "\twould conflict with this %s",
				objectKind(prev))
			return
		}

		// assignability (of both T and *T)
		recvBase := deref(recv)
		for _, R := range []types.Type{recvBase, types.NewPointer(recvBase)} {
			for I := range r.findAssignments(R) {
				if obj, _, _ := types.LookupFieldOrMethod(I, true, from.Pkg(), from.Name()); obj == nil {
					continue
				}
				r.errorf(from.Pos(), "renaming this method %q to %q",
					from.Name(), r.to)
				var pos token.Pos
				var iface string
				if named, ok := I.(*types.Named); ok {
					pos = named.Obj().Pos()
					iface = "interface " + named.Obj().Name()
				} else {
					pos = from.Pos()
					iface = I.String()
				}
				r.errorf(pos, "\twould make it no longer assignable to %s", iface)
				return // one is enough
			}
		}
	}

	// Check integrity of existing (field and method) selections.
	// We skip this if there were errors above, to avoid redundant errors.
	r.checkSelections(from)
}