github.com/mavryk-network/mvgo@v1.19.9/micheline/typeinfo.go (about)

     1  // Copyright (c) 2022 Blockwatch Data Inc.
     2  // Author: alex@blockwatch.cc
     3  
     4  package micheline
     5  
     6  import (
     7  	"encoding"
     8  	"fmt"
     9  	"reflect"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  )
    14  
    15  const tagName = "prim"
    16  
    17  // typeInfo holds details for the representation of a type.
    18  type typeInfo struct {
    19  	fields []fieldInfo
    20  }
    21  
    22  // fieldInfo holds details for the representation of a single field.
    23  type fieldInfo struct {
    24  	idx    []int  // Go struct index
    25  	name   string // field name (= Go struct name)
    26  	typ    OpCode
    27  	path   []int
    28  	nofail bool
    29  }
    30  
    31  func (f fieldInfo) String() string {
    32  	return fmt.Sprintf("FieldInfo: name=%s typ=%s goloc=%v primloc=%v", f.name, f.typ, f.idx, f.path)
    33  }
    34  
    35  var tinfoMap = make(map[reflect.Type]*typeInfo)
    36  var tinfoLock sync.RWMutex
    37  
    38  var (
    39  	binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
    40  	// binaryMarshalerType   = reflect.TypeOf((*encoding.BinaryMarshaler)(nil)).Elem()
    41  	textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
    42  	// textMarshalerType     = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
    43  	primUnmarshalerType = reflect.TypeOf((*PrimUnmarshaler)(nil)).Elem()
    44  	// primMarshalerType     = reflect.TypeOf((*PrimMarshaler)(nil)).Elem()
    45  	// stringerType  = reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
    46  	byteSliceType = reflect.TypeOf([]byte(nil))
    47  	szPrim        = int(reflect.TypeOf(Prim{}).Size())
    48  )
    49  
    50  func canTypUnmarshalBinary(typ reflect.Type) bool {
    51  	return reflect.PointerTo(typ).Implements(binaryUnmarshalerType) ||
    52  		typ.Implements(binaryUnmarshalerType)
    53  }
    54  
    55  func canTypUnmarshalText(typ reflect.Type) bool {
    56  	return reflect.PointerTo(typ).Implements(textUnmarshalerType) ||
    57  		typ.Implements(textUnmarshalerType)
    58  }
    59  
    60  // getTypeInfo returns the typeInfo structure with details necessary
    61  // for marshaling and unmarshaling of typ.
    62  func getTypeInfo(typ reflect.Type) (*typeInfo, error) {
    63  	tinfoLock.RLock()
    64  	tinfo, ok := tinfoMap[typ]
    65  	tinfoLock.RUnlock()
    66  	if ok {
    67  		return tinfo, nil
    68  	}
    69  	tinfo = &typeInfo{}
    70  	if typ.Kind() != reflect.Struct {
    71  		primType, err := mapGoTypeToPrimType(typ)
    72  		if err != nil {
    73  			return nil, fmt.Errorf("micheline: %v", err)
    74  		}
    75  		tinfo.fields = []fieldInfo{{typ: primType}}
    76  		return tinfo, nil
    77  	}
    78  	n := typ.NumField()
    79  	for i := 0; i < n; i++ {
    80  		f := typ.Field(i)
    81  		if (f.PkgPath != "" && !f.Anonymous) || f.Tag.Get(tagName) == "-" {
    82  			continue // Private field
    83  		}
    84  
    85  		// For embedded structs, embed their fields.
    86  		if f.Anonymous {
    87  			t := f.Type
    88  			if t.Kind() == reflect.Ptr {
    89  				t = t.Elem()
    90  			}
    91  			if t.Kind() == reflect.Struct {
    92  				inner, err := getTypeInfo(t)
    93  				if err != nil {
    94  					return nil, err
    95  				}
    96  				for _, finfo := range inner.fields {
    97  					finfo.idx = append([]int{i}, finfo.idx...)
    98  					if err := addFieldInfo(typ, tinfo, &finfo); err != nil {
    99  						return nil, err
   100  					}
   101  				}
   102  				continue
   103  			}
   104  		}
   105  
   106  		finfo, err := structFieldInfo(&f)
   107  		if err != nil {
   108  			return nil, err
   109  		}
   110  
   111  		// Add the field if it doesn't conflict with other fields.
   112  		if err := addFieldInfo(typ, tinfo, finfo); err != nil {
   113  			return nil, err
   114  		}
   115  	}
   116  	tinfoLock.Lock()
   117  	tinfoMap[typ] = tinfo
   118  	tinfoLock.Unlock()
   119  	return tinfo, nil
   120  }
   121  
   122  // structFieldInfo builds and returns a fieldInfo for f.
   123  func structFieldInfo(f *reflect.StructField) (*fieldInfo, error) {
   124  	finfo := &fieldInfo{
   125  		idx:  f.Index,
   126  		name: f.Name,
   127  	}
   128  	tag := f.Tag.Get(tagName)
   129  
   130  	// Parse struct tag
   131  	tokens := strings.Split(tag, ",")
   132  	if len(tokens) > 1 {
   133  		finfo.name = tokens[0]
   134  		for _, flag := range tokens[1:] {
   135  			ff := strings.Split(flag, "=")
   136  			switch ff[0] {
   137  			case "path":
   138  				for _, v := range strings.Split(strings.TrimSuffix(strings.TrimPrefix(ff[1], "/"), "/"), "/") {
   139  					i, err := strconv.Atoi(v)
   140  					if err != nil {
   141  						return nil, fmt.Errorf("micheline: invalid path %q in field %s: %v", ff[1], f.Name, err)
   142  					}
   143  					finfo.path = append(finfo.path, i)
   144  				}
   145  			case "nofail":
   146  				finfo.nofail = true
   147  			}
   148  		}
   149  	}
   150  	if typ, err := mapGoTypeToPrimType(f.Type); err != nil {
   151  		return nil, fmt.Errorf("micheline: field %s: %v", finfo.name, err)
   152  	} else {
   153  		finfo.typ = typ
   154  	}
   155  
   156  	return finfo, nil
   157  }
   158  
   159  func mapGoTypeToPrimType(typ reflect.Type) (oc OpCode, err error) {
   160  	switch typ.Kind() {
   161  	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
   162  		oc = T_NAT
   163  	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
   164  		oc = T_INT
   165  	case reflect.Slice:
   166  		switch {
   167  		case canTypUnmarshalBinary(typ):
   168  			oc = T_BYTES
   169  		case canTypUnmarshalText(typ):
   170  			oc = T_STRING
   171  		case typ == byteSliceType:
   172  			oc = T_BYTES
   173  		default:
   174  			oc = T_LIST
   175  		}
   176  	case reflect.Map:
   177  		oc = T_MAP
   178  	case reflect.String:
   179  		oc = T_STRING
   180  	case reflect.Bool:
   181  		oc = T_BOOL
   182  	case reflect.Array:
   183  		switch typ.String() {
   184  		case "mavryk.Address":
   185  			oc = T_ADDRESS
   186  		case "mavryk.ChainIdHash":
   187  			oc = T_CHAIN_ID
   188  		default:
   189  			if canTypUnmarshalBinary(typ) {
   190  				oc = T_BYTES
   191  			} else {
   192  				err = fmt.Errorf("unsupported embedded array type %s", typ.String())
   193  			}
   194  		}
   195  	case reflect.Struct:
   196  		switch typ.String() {
   197  		case "time.Time":
   198  			oc = T_TIMESTAMP
   199  		case "mavryk.Z":
   200  			oc = T_NAT
   201  		case "mavryk.N":
   202  			oc = T_NAT
   203  		case "mavryk.Key":
   204  			oc = T_KEY
   205  		case "mavryk.Signature":
   206  			oc = T_SIGNATURE
   207  		default:
   208  			if canTypUnmarshalBinary(typ) {
   209  				oc = T_BYTES
   210  			} else {
   211  				err = fmt.Errorf("unsupported embedded struct type %s", typ.String())
   212  			}
   213  		}
   214  	default:
   215  		err = fmt.Errorf("unsupported type %s (%v)", typ.String(), typ.Kind())
   216  	}
   217  	return
   218  }
   219  
   220  func addFieldInfo(typ reflect.Type, tinfo *typeInfo, newf *fieldInfo) error {
   221  	var conflicts []int
   222  	// Find all conflicts.
   223  	for i := range tinfo.fields {
   224  		oldf := &tinfo.fields[i]
   225  		if newf.name == oldf.name {
   226  			conflicts = append(conflicts, i)
   227  		}
   228  	}
   229  
   230  	// Return the first error.
   231  	for _, i := range conflicts {
   232  		oldf := &tinfo.fields[i]
   233  		f1 := typ.FieldByIndex(oldf.idx)
   234  		f2 := typ.FieldByIndex(newf.idx)
   235  		return fmt.Errorf("micheline: %s field %q with tag %q conflicts with field %q with tag %q", typ, f1.Name, f1.Tag.Get(tagName), f2.Name, f2.Tag.Get(tagName))
   236  	}
   237  
   238  	// Without conflicts, add the new field and return.
   239  	tinfo.fields = append(tinfo.fields, *newf)
   240  	return nil
   241  }
   242  
   243  // value returns v's field value corresponding to finfo.
   244  // It's equivalent to v.FieldByIndex(finfo.idx), but initializes
   245  // and dereferences pointers as necessary.
   246  func (finfo *fieldInfo) value(v reflect.Value) reflect.Value {
   247  	for i, x := range finfo.idx {
   248  		if i > 0 {
   249  			t := v.Type()
   250  			if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
   251  				if v.IsNil() {
   252  					v.Set(reflect.New(v.Type().Elem()))
   253  				}
   254  				v = v.Elem()
   255  			}
   256  		}
   257  		v = v.Field(x)
   258  	}
   259  
   260  	return v
   261  }
   262  
   263  func derefValue(val reflect.Value) reflect.Value {
   264  	if val.Kind() == reflect.Interface && !val.IsNil() {
   265  		e := val.Elem()
   266  		if e.Kind() == reflect.Ptr && !e.IsNil() {
   267  			val = e
   268  		}
   269  	}
   270  
   271  	if val.Kind() == reflect.Ptr {
   272  		if val.IsNil() {
   273  			val.Set(reflect.New(val.Type().Elem()))
   274  		}
   275  		val = val.Elem()
   276  	}
   277  	return val
   278  }
   279  
   280  func indirectType(typ reflect.Type) reflect.Type {
   281  	if typ.Kind() == reflect.Ptr {
   282  		val := reflect.New(typ.Elem())
   283  		return val.Elem().Type()
   284  	}
   285  	return typ
   286  }