github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/staticcheck/fakexml/typeinfo.go (about)

     1  // Copyright 2011 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package fakexml
     6  
     7  import (
     8  	"fmt"
     9  	"go/types"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/amarpal/go-tools/staticcheck/fakereflect"
    15  )
    16  
    17  // typeInfo holds details for the xml representation of a type.
    18  type typeInfo struct {
    19  	xmlname *fieldInfo
    20  	fields  []fieldInfo
    21  }
    22  
    23  // fieldInfo holds details for the xml representation of a single field.
    24  type fieldInfo struct {
    25  	idx     []int
    26  	name    string
    27  	xmlns   string
    28  	flags   fieldFlags
    29  	parents []string
    30  }
    31  
    32  type fieldFlags int
    33  
    34  const (
    35  	fElement fieldFlags = 1 << iota
    36  	fAttr
    37  	fCDATA
    38  	fCharData
    39  	fInnerXML
    40  	fComment
    41  	fAny
    42  
    43  	fOmitEmpty
    44  
    45  	fMode = fElement | fAttr | fCDATA | fCharData | fInnerXML | fComment | fAny
    46  
    47  	xmlName = "XMLName"
    48  )
    49  
    50  func (f fieldFlags) String() string {
    51  	switch f {
    52  	case fAttr:
    53  		return "attr"
    54  	case fCDATA:
    55  		return "cdata"
    56  	case fCharData:
    57  		return "chardata"
    58  	case fInnerXML:
    59  		return "innerxml"
    60  	case fComment:
    61  		return "comment"
    62  	case fAny:
    63  		return "any"
    64  	case fOmitEmpty:
    65  		return "omitempty"
    66  	case fAny | fAttr:
    67  		return "any,attr"
    68  	default:
    69  		return strconv.Itoa(int(f))
    70  	}
    71  }
    72  
    73  var tinfoMap sync.Map // map[reflect.Type]*typeInfo
    74  
    75  // getTypeInfo returns the typeInfo structure with details necessary
    76  // for marshaling and unmarshaling typ.
    77  func getTypeInfo(typ fakereflect.TypeAndCanAddr) (*typeInfo, error) {
    78  	if ti, ok := tinfoMap.Load(typ); ok {
    79  		return ti.(*typeInfo), nil
    80  	}
    81  
    82  	tinfo := &typeInfo{}
    83  	named, ok := typ.Type.(*types.Named)
    84  	if typ.IsStruct() && !(ok && named.Obj().Pkg().Path() == "encoding/xml" && named.Obj().Name() == "Name") {
    85  		n := typ.NumField()
    86  		for i := 0; i < n; i++ {
    87  			f := typ.Field(i)
    88  			if (!f.IsExported() && !f.Anonymous) || f.Tag.Get("xml") == "-" {
    89  				continue // Private field
    90  			}
    91  
    92  			// For embedded structs, embed its fields.
    93  			if f.Anonymous {
    94  				t := f.Type
    95  				if t.IsPtr() {
    96  					t = t.Elem()
    97  				}
    98  				if t.IsStruct() {
    99  					inner, err := getTypeInfo(t)
   100  					if err != nil {
   101  						return nil, err
   102  					}
   103  					if tinfo.xmlname == nil {
   104  						tinfo.xmlname = inner.xmlname
   105  					}
   106  					for _, finfo := range inner.fields {
   107  						finfo.idx = append([]int{i}, finfo.idx...)
   108  						if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
   109  							return nil, err
   110  						}
   111  					}
   112  					continue
   113  				}
   114  			}
   115  
   116  			finfo, err := StructFieldInfo(f)
   117  			if err != nil {
   118  				return nil, err
   119  			}
   120  
   121  			if f.Name == xmlName {
   122  				tinfo.xmlname = finfo
   123  				continue
   124  			}
   125  
   126  			// Add the field if it doesn't conflict with other fields.
   127  			if err := addFieldInfo(typ, tinfo, finfo); err != nil {
   128  				return nil, err
   129  			}
   130  		}
   131  	}
   132  
   133  	ti, _ := tinfoMap.LoadOrStore(typ, tinfo)
   134  	return ti.(*typeInfo), nil
   135  }
   136  
   137  // StructFieldInfo builds and returns a fieldInfo for f.
   138  func StructFieldInfo(f fakereflect.StructField) (*fieldInfo, error) {
   139  	finfo := &fieldInfo{idx: f.Index}
   140  
   141  	// Split the tag from the xml namespace if necessary.
   142  	tag := f.Tag.Get("xml")
   143  	if i := strings.Index(tag, " "); i >= 0 {
   144  		finfo.xmlns, tag = tag[:i], tag[i+1:]
   145  	}
   146  
   147  	// Parse flags.
   148  	tokens := strings.Split(tag, ",")
   149  	if len(tokens) == 1 {
   150  		finfo.flags = fElement
   151  	} else {
   152  		tag = tokens[0]
   153  		for _, flag := range tokens[1:] {
   154  			switch flag {
   155  			case "attr":
   156  				finfo.flags |= fAttr
   157  			case "cdata":
   158  				finfo.flags |= fCDATA
   159  			case "chardata":
   160  				finfo.flags |= fCharData
   161  			case "innerxml":
   162  				finfo.flags |= fInnerXML
   163  			case "comment":
   164  				finfo.flags |= fComment
   165  			case "any":
   166  				finfo.flags |= fAny
   167  			case "omitempty":
   168  				finfo.flags |= fOmitEmpty
   169  			}
   170  		}
   171  
   172  		// Validate the flags used.
   173  		switch mode := finfo.flags & fMode; mode {
   174  		case 0:
   175  			finfo.flags |= fElement
   176  		case fAttr, fCDATA, fCharData, fInnerXML, fComment, fAny, fAny | fAttr:
   177  			if f.Name == xmlName {
   178  				return nil, fmt.Errorf("cannot use option %s on XMLName field", mode)
   179  			} else if tag != "" && mode != fAttr {
   180  				return nil, fmt.Errorf("cannot specify name together with option ,%s", mode)
   181  			}
   182  		default:
   183  			// This will also catch multiple modes in a single field.
   184  			return nil, fmt.Errorf("invalid combination of options: %q", f.Tag.Get("xml"))
   185  		}
   186  		if finfo.flags&fMode == fAny {
   187  			finfo.flags |= fElement
   188  		}
   189  		if finfo.flags&fOmitEmpty != 0 && finfo.flags&(fElement|fAttr) == 0 {
   190  			return nil, fmt.Errorf("can only use omitempty on elements and attributes")
   191  		}
   192  	}
   193  
   194  	// Use of xmlns without a name is not allowed.
   195  	if finfo.xmlns != "" && tag == "" {
   196  		return nil, fmt.Errorf("namespace without name: %q", f.Tag.Get("xml"))
   197  	}
   198  
   199  	if f.Name == xmlName {
   200  		// The XMLName field records the XML element name. Don't
   201  		// process it as usual because its name should default to
   202  		// empty rather than to the field name.
   203  		finfo.name = tag
   204  		return finfo, nil
   205  	}
   206  
   207  	if tag == "" {
   208  		// If the name part of the tag is completely empty, get
   209  		// default from XMLName of underlying struct if feasible,
   210  		// or field name otherwise.
   211  		if xmlname := lookupXMLName(f.Type); xmlname != nil {
   212  			finfo.xmlns, finfo.name = xmlname.xmlns, xmlname.name
   213  		} else {
   214  			finfo.name = f.Name
   215  		}
   216  		return finfo, nil
   217  	}
   218  
   219  	// Prepare field name and parents.
   220  	parents := strings.Split(tag, ">")
   221  	if parents[0] == "" {
   222  		parents[0] = f.Name
   223  	}
   224  	if parents[len(parents)-1] == "" {
   225  		return nil, fmt.Errorf("trailing '>'")
   226  	}
   227  	finfo.name = parents[len(parents)-1]
   228  	if len(parents) > 1 {
   229  		if (finfo.flags & fElement) == 0 {
   230  			return nil, fmt.Errorf("%s chain not valid with %s flag", tag, strings.Join(tokens[1:], ","))
   231  		}
   232  		finfo.parents = parents[:len(parents)-1]
   233  	}
   234  
   235  	// If the field type has an XMLName field, the names must match
   236  	// so that the behavior of both marshaling and unmarshaling
   237  	// is straightforward and unambiguous.
   238  	if finfo.flags&fElement != 0 {
   239  		ftyp := f.Type
   240  		xmlname := lookupXMLName(ftyp)
   241  		if xmlname != nil && xmlname.name != finfo.name {
   242  			return nil, fmt.Errorf("name %q conflicts with name %q in %s.XMLName", finfo.name, xmlname.name, ftyp)
   243  		}
   244  	}
   245  	return finfo, nil
   246  }
   247  
   248  // lookupXMLName returns the fieldInfo for typ's XMLName field
   249  // in case it exists and has a valid xml field tag, otherwise
   250  // it returns nil.
   251  func lookupXMLName(typ fakereflect.TypeAndCanAddr) (xmlname *fieldInfo) {
   252  	seen := map[fakereflect.TypeAndCanAddr]struct{}{}
   253  	for typ.IsPtr() {
   254  		typ = typ.Elem()
   255  		if _, ok := seen[typ]; ok {
   256  			// Loop in type graph, e.g. 'type P *P'
   257  			return nil
   258  		}
   259  		seen[typ] = struct{}{}
   260  	}
   261  	if !typ.IsStruct() {
   262  		return nil
   263  	}
   264  	for i, n := 0, typ.NumField(); i < n; i++ {
   265  		f := typ.Field(i)
   266  		if f.Name != xmlName {
   267  			continue
   268  		}
   269  		finfo, err := StructFieldInfo(f)
   270  		if err == nil && finfo.name != "" {
   271  			return finfo
   272  		}
   273  		// Also consider errors as a non-existent field tag
   274  		// and let getTypeInfo itself report the error.
   275  		break
   276  	}
   277  	return nil
   278  }
   279  
   280  func min(a, b int) int {
   281  	if a <= b {
   282  		return a
   283  	}
   284  	return b
   285  }
   286  
   287  // addFieldInfo adds finfo to tinfo.fields if there are no
   288  // conflicts, or if conflicts arise from previous fields that were
   289  // obtained from deeper embedded structures than finfo. In the latter
   290  // case, the conflicting entries are dropped.
   291  // A conflict occurs when the path (parent + name) to a field is
   292  // itself a prefix of another path, or when two paths match exactly.
   293  // It is okay for field paths to share a common, shorter prefix.
   294  func addFieldInfo(typ fakereflect.TypeAndCanAddr, tinfo *typeInfo, newf *fieldInfo) error {
   295  	var conflicts []int
   296  Loop:
   297  	// First, figure all conflicts. Most working code will have none.
   298  	for i := range tinfo.fields {
   299  		oldf := &tinfo.fields[i]
   300  		if oldf.flags&fMode != newf.flags&fMode {
   301  			continue
   302  		}
   303  		if oldf.xmlns != "" && newf.xmlns != "" && oldf.xmlns != newf.xmlns {
   304  			continue
   305  		}
   306  		minl := min(len(newf.parents), len(oldf.parents))
   307  		for p := 0; p < minl; p++ {
   308  			if oldf.parents[p] != newf.parents[p] {
   309  				continue Loop
   310  			}
   311  		}
   312  		if len(oldf.parents) > len(newf.parents) {
   313  			if oldf.parents[len(newf.parents)] == newf.name {
   314  				conflicts = append(conflicts, i)
   315  			}
   316  		} else if len(oldf.parents) < len(newf.parents) {
   317  			if newf.parents[len(oldf.parents)] == oldf.name {
   318  				conflicts = append(conflicts, i)
   319  			}
   320  		} else {
   321  			if newf.name == oldf.name {
   322  				conflicts = append(conflicts, i)
   323  			}
   324  		}
   325  	}
   326  	// Without conflicts, add the new field and return.
   327  	if conflicts == nil {
   328  		tinfo.fields = append(tinfo.fields, *newf)
   329  		return nil
   330  	}
   331  
   332  	// If any conflict is shallower, ignore the new field.
   333  	// This matches the Go field resolution on embedding.
   334  	for _, i := range conflicts {
   335  		if len(tinfo.fields[i].idx) < len(newf.idx) {
   336  			return nil
   337  		}
   338  	}
   339  
   340  	// Otherwise, if any of them is at the same depth level, it's an error.
   341  	for _, i := range conflicts {
   342  		oldf := &tinfo.fields[i]
   343  		if len(oldf.idx) == len(newf.idx) {
   344  			f1 := typ.FieldByIndex(oldf.idx)
   345  			f2 := typ.FieldByIndex(newf.idx)
   346  			return &TagPathError{typ, f1.Name, f1.Tag.Get("xml"), f2.Name, f2.Tag.Get("xml")}
   347  		}
   348  	}
   349  
   350  	// Otherwise, the new field is shallower, and thus takes precedence,
   351  	// so drop the conflicting fields from tinfo and append the new one.
   352  	for c := len(conflicts) - 1; c >= 0; c-- {
   353  		i := conflicts[c]
   354  		copy(tinfo.fields[i:], tinfo.fields[i+1:])
   355  		tinfo.fields = tinfo.fields[:len(tinfo.fields)-1]
   356  	}
   357  	tinfo.fields = append(tinfo.fields, *newf)
   358  	return nil
   359  }
   360  
   361  // A TagPathError represents an error in the unmarshaling process
   362  // caused by the use of field tags with conflicting paths.
   363  type TagPathError struct {
   364  	Struct       fakereflect.TypeAndCanAddr
   365  	Field1, Tag1 string
   366  	Field2, Tag2 string
   367  }
   368  
   369  func (e *TagPathError) Error() string {
   370  	return fmt.Sprintf("%s field %q with tag %q conflicts with field %q with tag %q", e.Struct, e.Field1, e.Tag1, e.Field2, e.Tag2)
   371  }
   372  
   373  // value returns v's field value corresponding to finfo.
   374  // It's equivalent to v.FieldByIndex(finfo.idx), but when passed
   375  // initNilPointers, it initializes and dereferences pointers as necessary.
   376  // When passed dontInitNilPointers and a nil pointer is reached, the function
   377  // returns a zero reflect.Value.
   378  func (finfo *fieldInfo) value(v fakereflect.TypeAndCanAddr) fakereflect.TypeAndCanAddr {
   379  	for i, x := range finfo.idx {
   380  		if i > 0 {
   381  			t := v
   382  			if t.IsPtr() && t.Elem().IsStruct() {
   383  				v = v.Elem()
   384  			}
   385  		}
   386  		v = v.Field(x).Type
   387  	}
   388  	return v
   389  }