git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/toml/type_fields.go (about)

     1  package toml
     2  
     3  // Struct field handling is adapted from code in encoding/json:
     4  //
     5  // Copyright 2010 The Go Authors.  All rights reserved.
     6  // Use of this source code is governed by a BSD-style
     7  // license that can be found in the Go distribution.
     8  
     9  import (
    10  	"reflect"
    11  	"sort"
    12  	"sync"
    13  )
    14  
    15  // A field represents a single field found in a struct.
    16  type field struct {
    17  	name  string       // the name of the field (`toml` tag included)
    18  	tag   bool         // whether field has a `toml` tag
    19  	index []int        // represents the depth of an anonymous field
    20  	typ   reflect.Type // the type of the field
    21  }
    22  
    23  // byName sorts field by name, breaking ties with depth,
    24  // then breaking ties with "name came from toml tag", then
    25  // breaking ties with index sequence.
    26  type byName []field
    27  
    28  func (x byName) Len() int { return len(x) }
    29  
    30  func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
    31  
    32  func (x byName) Less(i, j int) bool {
    33  	if x[i].name != x[j].name {
    34  		return x[i].name < x[j].name
    35  	}
    36  	if len(x[i].index) != len(x[j].index) {
    37  		return len(x[i].index) < len(x[j].index)
    38  	}
    39  	if x[i].tag != x[j].tag {
    40  		return x[i].tag
    41  	}
    42  	return byIndex(x).Less(i, j)
    43  }
    44  
    45  // byIndex sorts field by index sequence.
    46  type byIndex []field
    47  
    48  func (x byIndex) Len() int { return len(x) }
    49  
    50  func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
    51  
    52  func (x byIndex) Less(i, j int) bool {
    53  	for k, xik := range x[i].index {
    54  		if k >= len(x[j].index) {
    55  			return false
    56  		}
    57  		if xik != x[j].index[k] {
    58  			return xik < x[j].index[k]
    59  		}
    60  	}
    61  	return len(x[i].index) < len(x[j].index)
    62  }
    63  
    64  // typeFields returns a list of fields that TOML should recognize for the given
    65  // type. The algorithm is breadth-first search over the set of structs to
    66  // include - the top struct and then any reachable anonymous structs.
    67  func typeFields(t reflect.Type) []field {
    68  	// Anonymous fields to explore at the current level and the next.
    69  	current := []field{}
    70  	next := []field{{typ: t}}
    71  
    72  	// Count of queued names for current level and the next.
    73  	var count map[reflect.Type]int
    74  	var nextCount map[reflect.Type]int
    75  
    76  	// Types already visited at an earlier level.
    77  	visited := map[reflect.Type]bool{}
    78  
    79  	// Fields found.
    80  	var fields []field
    81  
    82  	for len(next) > 0 {
    83  		current, next = next, current[:0]
    84  		count, nextCount = nextCount, map[reflect.Type]int{}
    85  
    86  		for _, f := range current {
    87  			if visited[f.typ] {
    88  				continue
    89  			}
    90  			visited[f.typ] = true
    91  
    92  			// Scan f.typ for fields to include.
    93  			for i := 0; i < f.typ.NumField(); i++ {
    94  				sf := f.typ.Field(i)
    95  				if sf.PkgPath != "" && !sf.Anonymous { // unexported
    96  					continue
    97  				}
    98  				opts := getOptions(sf.Tag)
    99  				if opts.skip {
   100  					continue
   101  				}
   102  				index := make([]int, len(f.index)+1)
   103  				copy(index, f.index)
   104  				index[len(f.index)] = i
   105  
   106  				ft := sf.Type
   107  				if ft.Name() == "" && ft.Kind() == reflect.Ptr {
   108  					// Follow pointer.
   109  					ft = ft.Elem()
   110  				}
   111  
   112  				// Record found field and index sequence.
   113  				if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
   114  					tagged := opts.name != ""
   115  					name := opts.name
   116  					if name == "" {
   117  						name = sf.Name
   118  					}
   119  					fields = append(fields, field{name, tagged, index, ft})
   120  					if count[f.typ] > 1 {
   121  						// If there were multiple instances, add a second,
   122  						// so that the annihilation code will see a duplicate.
   123  						// It only cares about the distinction between 1 or 2,
   124  						// so don't bother generating any more copies.
   125  						fields = append(fields, fields[len(fields)-1])
   126  					}
   127  					continue
   128  				}
   129  
   130  				// Record new anonymous struct to explore in next round.
   131  				nextCount[ft]++
   132  				if nextCount[ft] == 1 {
   133  					f := field{name: ft.Name(), index: index, typ: ft}
   134  					next = append(next, f)
   135  				}
   136  			}
   137  		}
   138  	}
   139  
   140  	sort.Sort(byName(fields))
   141  
   142  	// Delete all fields that are hidden by the Go rules for embedded fields,
   143  	// except that fields with TOML tags are promoted.
   144  
   145  	// The fields are sorted in primary order of name, secondary order
   146  	// of field index length. Loop over names; for each name, delete
   147  	// hidden fields by choosing the one dominant field that survives.
   148  	out := fields[:0]
   149  	for advance, i := 0, 0; i < len(fields); i += advance {
   150  		// One iteration per name.
   151  		// Find the sequence of fields with the name of this first field.
   152  		fi := fields[i]
   153  		name := fi.name
   154  		for advance = 1; i+advance < len(fields); advance++ {
   155  			fj := fields[i+advance]
   156  			if fj.name != name {
   157  				break
   158  			}
   159  		}
   160  		if advance == 1 { // Only one field with this name
   161  			out = append(out, fi)
   162  			continue
   163  		}
   164  		dominant, ok := dominantField(fields[i : i+advance])
   165  		if ok {
   166  			out = append(out, dominant)
   167  		}
   168  	}
   169  
   170  	fields = out
   171  	sort.Sort(byIndex(fields))
   172  
   173  	return fields
   174  }
   175  
   176  // dominantField looks through the fields, all of which are known to
   177  // have the same name, to find the single field that dominates the
   178  // others using Go's embedding rules, modified by the presence of
   179  // TOML tags. If there are multiple top-level fields, the boolean
   180  // will be false: This condition is an error in Go and we skip all
   181  // the fields.
   182  func dominantField(fields []field) (field, bool) {
   183  	// The fields are sorted in increasing index-length order. The winner
   184  	// must therefore be one with the shortest index length. Drop all
   185  	// longer entries, which is easy: just truncate the slice.
   186  	length := len(fields[0].index)
   187  	tagged := -1 // Index of first tagged field.
   188  	for i, f := range fields {
   189  		if len(f.index) > length {
   190  			fields = fields[:i]
   191  			break
   192  		}
   193  		if f.tag {
   194  			if tagged >= 0 {
   195  				// Multiple tagged fields at the same level: conflict.
   196  				// Return no field.
   197  				return field{}, false
   198  			}
   199  			tagged = i
   200  		}
   201  	}
   202  	if tagged >= 0 {
   203  		return fields[tagged], true
   204  	}
   205  	// All remaining fields have the same length. If there's more than one,
   206  	// we have a conflict (two fields named "X" at the same level) and we
   207  	// return no field.
   208  	if len(fields) > 1 {
   209  		return field{}, false
   210  	}
   211  	return fields[0], true
   212  }
   213  
   214  var fieldCache struct {
   215  	sync.RWMutex
   216  	m map[reflect.Type][]field
   217  }
   218  
   219  // cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
   220  func cachedTypeFields(t reflect.Type) []field {
   221  	fieldCache.RLock()
   222  	f := fieldCache.m[t]
   223  	fieldCache.RUnlock()
   224  	if f != nil {
   225  		return f
   226  	}
   227  
   228  	// Compute fields without lock.
   229  	// Might duplicate effort but won't hold other computations back.
   230  	f = typeFields(t)
   231  	if f == nil {
   232  		f = []field{}
   233  	}
   234  
   235  	fieldCache.Lock()
   236  	if fieldCache.m == nil {
   237  		fieldCache.m = map[reflect.Type][]field{}
   238  	}
   239  	fieldCache.m[t] = f
   240  	fieldCache.Unlock()
   241  	return f
   242  }