// Copyright (c) 2019 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. package dig import ( "errors" "fmt" "reflect" "go.uber.org/dig/internal/dot" ) // The result interface represents a result produced by a constructor. // // The following implementations exist: // resultList All values returned by the constructor. // resultSingle A single value produced by a constructor. // resultObject dig.Out struct where each field in the struct can be // another result. // resultGrouped A value produced by a constructor that is part of a value // group. type result interface { // Extracts the values for this result from the provided value and // stores them into the provided containerWriter. // // This MAY panic if the result does not consume a single value. Extract(containerWriter, reflect.Value) // DotResult returns a slice of dot.Result(s). DotResult() []*dot.Result } var ( _ result = resultSingle{} _ result = resultObject{} _ result = resultList{} _ result = resultGrouped{} ) type resultOptions struct { // If set, this is the name of the associated result value. // // For Result Objects, name:".." tags on fields override this. Name string Group string } // newResult builds a result from the given type. func newResult(t reflect.Type, opts resultOptions) (result, error) { switch { case IsIn(t) || (t.Kind() == reflect.Ptr && IsIn(t.Elem())) || embedsType(t, _inPtrType): return nil, fmt.Errorf("cannot provide parameter objects: %v embeds a dig.In", t) case isError(t): return nil, fmt.Errorf("cannot return an error here, return it from the constructor instead") case IsOut(t): return newResultObject(t, opts) case embedsType(t, _outPtrType): return nil, fmt.Errorf( "cannot build a result object by embedding *dig.Out, embed dig.Out instead: "+ "%v embeds *dig.Out", t) case t.Kind() == reflect.Ptr && IsOut(t.Elem()): return nil, fmt.Errorf( "cannot return a pointer to a result object, use a value instead: "+ "%v is a pointer to a struct that embeds dig.Out", t) case len(opts.Group) > 0: return resultGrouped{Type: t, Group: opts.Group}, nil default: return resultSingle{Type: t, Name: opts.Name}, nil } } // resultVisitor visits every result in a result tree, allowing tracking state // at each level. type resultVisitor interface { // Visit is called on the result being visited. // // If Visit returns a non-nil resultVisitor, that resultVisitor visits all // the child results of this result. Visit(result) resultVisitor // AnnotateWithField is called on each field of a resultObject after // visiting it but before walking its descendants. // // The same resultVisitor is used for all fields: the one returned upon // visiting the resultObject. // // For each visited field, if AnnotateWithField returns a non-nil // resultVisitor, it will be used to walk the result of that field. AnnotateWithField(resultObjectField) resultVisitor // AnnotateWithPosition is called with the index of each result of a // resultList after vising it but before walking its descendants. // // The same resultVisitor is used for all results: the one returned upon // visiting the resultList. // // For each position, if AnnotateWithPosition returns a non-nil // resultVisitor, it will be used to walk the result at that index. AnnotateWithPosition(idx int) resultVisitor } // walkResult walks the result tree for the given result with the provided // visitor. // // resultVisitor.Visit will be called on the provided result and if a non-nil // resultVisitor is received, it will be used to walk its descendants. If a // resultObject or resultList was visited, AnnotateWithField and // AnnotateWithPosition respectively will be called before visiting the // descendants of that resultObject/resultList. // // This is very similar to how go/ast.Walk works. func walkResult(r result, v resultVisitor) { v = v.Visit(r) if v == nil { return } switch res := r.(type) { case resultSingle, resultGrouped: // No sub-results case resultObject: w := v for _, f := range res.Fields { if v := w.AnnotateWithField(f); v != nil { walkResult(f.Result, v) } } case resultList: w := v for i, r := range res.Results { if v := w.AnnotateWithPosition(i); v != nil { walkResult(r, v) } } default: panic(fmt.Sprintf( "It looks like you have found a bug in dig. "+ "Please file an issue at https://github.com/uber-go/dig/issues/ "+ "and provide the following message: "+ "received unknown result type %T", res)) } } // resultList holds all values returned by the constructor as results. type resultList struct { ctype reflect.Type Results []result // For each item at index i returned by the constructor, resultIndexes[i] // is the index in .Results for the corresponding result object. // resultIndexes[i] is -1 for errors returned by constructors. resultIndexes []int } func (rl resultList) DotResult() []*dot.Result { var types []*dot.Result for _, result := range rl.Results { types = append(types, result.DotResult()...) } return types } func newResultList(ctype reflect.Type, opts resultOptions) (resultList, error) { rl := resultList{ ctype: ctype, Results: make([]result, 0, ctype.NumOut()), resultIndexes: make([]int, ctype.NumOut()), } resultIdx := 0 for i := 0; i < ctype.NumOut(); i++ { t := ctype.Out(i) if isError(t) { rl.resultIndexes[i] = -1 continue } r, err := newResult(t, opts) if err != nil { return rl, errWrapf(err, "bad result %d", i+1) } rl.Results = append(rl.Results, r) rl.resultIndexes[i] = resultIdx resultIdx++ } return rl, nil } func (resultList) Extract(containerWriter, reflect.Value) { panic("It looks like you have found a bug in dig. " + "Please file an issue at https://github.com/uber-go/dig/issues/ " + "and provide the following message: " + "resultList.Extract() must never be called") } func (rl resultList) ExtractList(cw containerWriter, values []reflect.Value) error { for i, v := range values { if resultIdx := rl.resultIndexes[i]; resultIdx >= 0 { rl.Results[resultIdx].Extract(cw, v) continue } if err, _ := v.Interface().(error); err != nil { return err } } return nil } // resultSingle is an explicit value produced by a constructor, optionally // with a name. // // This object will be added to the graph as-is. type resultSingle struct { Name string Type reflect.Type } func (rs resultSingle) DotResult() []*dot.Result { return []*dot.Result{ { Node: &dot.Node{ Type: rs.Type, Name: rs.Name, }, }, } } func (rs resultSingle) Extract(cw containerWriter, v reflect.Value) { cw.setValue(rs.Name, rs.Type, v) } // resultObject is a dig.Out struct where each field is another result. // // This object is not added to the graph. Its fields are interpreted as // results and added to the graph if needed. type resultObject struct { Type reflect.Type Fields []resultObjectField } func (ro resultObject) DotResult() []*dot.Result { var types []*dot.Result for _, field := range ro.Fields { types = append(types, field.DotResult()...) } return types } func newResultObject(t reflect.Type, opts resultOptions) (resultObject, error) { ro := resultObject{Type: t} if len(opts.Name) > 0 { return ro, fmt.Errorf( "cannot specify a name for result objects: %v embeds dig.Out", t) } if len(opts.Group) > 0 { return ro, fmt.Errorf( "cannot specify a group for result objects: %v embeds dig.Out", t) } for i := 0; i < t.NumField(); i++ { f := t.Field(i) if f.Type == _outType { // Skip over the dig.Out embed. continue } rof, err := newResultObjectField(i, f, opts) if err != nil { return ro, errWrapf(err, "bad field %q of %v", f.Name, t) } ro.Fields = append(ro.Fields, rof) } return ro, nil } func (ro resultObject) Extract(cw containerWriter, v reflect.Value) { for _, f := range ro.Fields { f.Result.Extract(cw, v.Field(f.FieldIndex)) } } // resultObjectField is a single field inside a dig.Out struct. type resultObjectField struct { // Name of the field in the struct. FieldName string // Index of the field in the struct. // // We need to track this separately because not all fields of the struct // map to results. FieldIndex int // Result produced by this field. Result result } func (rof resultObjectField) DotResult() []*dot.Result { return rof.Result.DotResult() } // newResultObjectField(i, f, opts) builds a resultObjectField from the field // f at index i. func newResultObjectField(idx int, f reflect.StructField, opts resultOptions) (resultObjectField, error) { rof := resultObjectField{ FieldName: f.Name, FieldIndex: idx, } var r result switch { case f.PkgPath != "": return rof, fmt.Errorf( "unexported fields not allowed in dig.Out, did you mean to export %q (%v)?", f.Name, f.Type) case f.Tag.Get(_groupTag) != "": var err error r, err = newResultGrouped(f) if err != nil { return rof, err } default: var err error if name := f.Tag.Get(_nameTag); len(name) > 0 { // can modify in-place because options are passed-by-value. opts.Name = name } r, err = newResult(f.Type, opts) if err != nil { return rof, err } } rof.Result = r return rof, nil } // resultGrouped is a value produced by a constructor that is part of a result // group. // // These will be produced as fields of a dig.Out struct. type resultGrouped struct { // Name of the group as specified in the `group:".."` tag. Group string // Type of value produced. Type reflect.Type } func (rt resultGrouped) DotResult() []*dot.Result { return []*dot.Result{ { Node: &dot.Node{ Type: rt.Type, Group: rt.Group, }, }, } } // newResultGrouped(f) builds a new resultGrouped from the provided field. func newResultGrouped(f reflect.StructField) (resultGrouped, error) { rg := resultGrouped{Group: f.Tag.Get(_groupTag), Type: f.Type} name := f.Tag.Get(_nameTag) optional, _ := isFieldOptional(f) switch { case name != "": return rg, fmt.Errorf( "cannot use named values with value groups: name:%q provided with group:%q", name, rg.Group) case optional: return rg, errors.New("value groups cannot be optional") } return rg, nil } func (rt resultGrouped) Extract(cw containerWriter, v reflect.Value) { cw.submitGroupedValue(rt.Group, rt.Type, v) }