ice/eval.go (123 lines of code) (raw):

package ice import ( "fmt" "reflect" "runtime" "runtime/debug" log "github.com/sirupsen/logrus" ) // Extract extracts a value from the MagicBag and puts it into dest, returning any errors // dest must be a pointer to a type this MagicBag knows how to construct. func (bag *MagicBag) Extract(dest interface{}) (result error) { defer func() { if r := recover(); r != nil { result = fmt.Errorf("Error injecting: %v", r) } }() // dest must be a pointer to our target destVal := reflect.ValueOf(dest) destType := destVal.Type() if destType.Kind() != reflect.Ptr { return fmt.Errorf("dest must be a pointer; was %v", destType) } // our target is what dest points to targetVal := destVal.Elem() targetType := destType.Elem() eval := &evaluation{ bag: bag, values: make(map[Key]Value), } targetVal.Set(reflect.Value(eval.construct(targetType))) return nil } // An evaluation holds the mutable state for an ice evaluation type evaluation struct { bag *MagicBag values map[Key]Value stack stack } // what we are evaluating to construct at this level type frame struct { key Key } // a stack is just the in-order frames of our evaluation type stack []frame func (s stack) LogStack() { log.Info("goice stacktrace (constructor chain):") for _, f := range s { log.Info(fmt.Sprintf("\t%s", f.key)) } log.Info("end goice stacktrace") } // one level of our evaluation. Constructs a Value for key func (e *evaluation) construct(key Key) Value { // Maintain our stack e.enter(key) defer e.exit() // Check for cycles (instead of just recurring infinitely and overflowing stack for _, f := range e.stack[0 : len(e.stack)-1] { if f.key == key { throw("cycle in object (dependency) graph: already constructing %v", key) } } // Have we already constructed this? v, ok := e.values[key] if ok { return v } // Find who makes this provider, ok := e.bag.bindings[key] if !ok { throw("target type %v is unbound (no constructor for %v found in bag)", key, key) } // providerType must be a function; we check this in Put so we assume it here providerVal := reflect.ValueOf(provider) providerType := providerVal.Type() // construct arguments. (Here's the recursion, and our basecase is NumIn() == 0) args := make([]reflect.Value, providerType.NumIn()) for i := 0; i < providerType.NumIn(); i++ { argType := providerType.In(i) args[i] = reflect.Value(e.construct(argType)) } // Call the provider results := providerVal.Call(args) // If provider returned an error, throw the error if len(results) == 2 { var err error reflect.ValueOf(&err).Elem().Set(results[1]) if err != nil { throw("provider %s threw error: %v", getFunctionName(provider), err) } } e.values[key] = Value(results[0]) log.Infof("Constructed: %T", results[0].Interface()) return e.values[key] } func (e *evaluation) enter(key Key) { // Push a frame e.stack = append(e.stack, frame{key}) } func (e *evaluation) exit() { if r := recover(); r != nil { // construct panic'ed. OK. Let's take what happened, and wrap it. // First into an error, and then into an InjectionError. err, ok := r.(error) if !ok { err = fmt.Errorf("%v", r) } iceErr, ok := err.(*InjectionError) if !ok { stackCopy := stack(nil) stackCopy = append(stackCopy, e.stack...) iceErr = &InjectionError{ underlying: err, goiceStack: stackCopy, goStack: string(debug.Stack()), } } panic(iceErr) } // Pop a frame e.stack = e.stack[:len(e.stack)-1] } type InjectionError struct { underlying error goiceStack stack goStack string } func (e *InjectionError) String() string { return fmt.Sprintf("goice injection error:\n\t%v\n%v\n%v", e.underlying.Error(), e.goiceStack, e.goStack, ) } func (e *InjectionError) Error() string { return e.String() } func throw(format string, a ...interface{}) { panic(fmt.Errorf(format, a...)) } func getFunctionName(i interface{}) string { return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() }