github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/amino/genproto/types.go (about)

     1  package genproto
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"reflect"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/gnolang/gno/tm2/pkg/amino"
    12  	"github.com/gnolang/gno/tm2/pkg/amino/libs/press"
    13  )
    14  
    15  //----------------------------------------
    16  
    17  // NOTE: The goal is not complete Proto3 compatibility (unless there is
    18  // widespread demand for maintaining this repo for that purpose).  Rather, the
    19  // point is to define enough such that the subset that is needed for Amino
    20  // Go->Proto3 is supported.  For example, there is explicitly no plan to
    21  // support the automatic conversion of Proto3->Go, so not all features need to
    22  // be supported.
    23  // NOTE: enums are not supported, as Amino's philosophy is that value checking
    24  // should primarily be done on the application side.
    25  
    26  type P3Type interface {
    27  	AssertIsP3Type()
    28  	GetPackageName() string // proto3 package prefix
    29  	GetName() string        // proto3 name
    30  	GetFullName() string    // proto3 full name
    31  }
    32  
    33  func (P3ScalarType) AssertIsP3Type()  {}
    34  func (P3MessageType) AssertIsP3Type() {}
    35  
    36  type P3ScalarType string
    37  
    38  func (P3ScalarType) GetPackageName() string { return "" }
    39  func (st P3ScalarType) GetName() string     { return string(st) }
    40  func (st P3ScalarType) GetFullName() string { return string(st) }
    41  
    42  const (
    43  	P3ScalarTypeDouble   P3ScalarType = "double"
    44  	P3ScalarTypeFloat    P3ScalarType = "float"
    45  	P3ScalarTypeInt32    P3ScalarType = "int32"
    46  	P3ScalarTypeInt64    P3ScalarType = "int64"
    47  	P3ScalarTypeUint32   P3ScalarType = "uint32"
    48  	P3ScalarTypeUint64   P3ScalarType = "uint64"
    49  	P3ScalarTypeSint32   P3ScalarType = "sint32"
    50  	P3ScalarTypeSint64   P3ScalarType = "sint64"
    51  	P3ScalarTypeFixed32  P3ScalarType = "fixed32"
    52  	P3ScalarTypeFixed64  P3ScalarType = "fixed64"
    53  	P3ScalarTypeSfixed32 P3ScalarType = "sfixed32"
    54  	P3ScalarTypeSfixed64 P3ScalarType = "sfixed64"
    55  	P3ScalarTypeBool     P3ScalarType = "bool"
    56  	P3ScalarTypeString   P3ScalarType = "string"
    57  	P3ScalarTypeBytes    P3ScalarType = "bytes"
    58  )
    59  
    60  type P3MessageType struct {
    61  	PackageName string // proto3 package name, optional.
    62  	Name        string // message name.
    63  	OmitPackage bool   // if true, PackageName is not printed.
    64  }
    65  
    66  func NewP3MessageType(pkg string, name string) P3MessageType {
    67  	if name == string(P3ScalarTypeDouble) ||
    68  		name == string(P3ScalarTypeFloat) ||
    69  		name == string(P3ScalarTypeInt32) ||
    70  		name == string(P3ScalarTypeInt64) ||
    71  		name == string(P3ScalarTypeUint32) ||
    72  		name == string(P3ScalarTypeUint64) ||
    73  		name == string(P3ScalarTypeSint32) ||
    74  		name == string(P3ScalarTypeSint64) ||
    75  		name == string(P3ScalarTypeFixed32) ||
    76  		name == string(P3ScalarTypeFixed64) ||
    77  		name == string(P3ScalarTypeSfixed32) ||
    78  		name == string(P3ScalarTypeSfixed64) ||
    79  		name == string(P3ScalarTypeBool) ||
    80  		name == string(P3ScalarTypeString) ||
    81  		name == string(P3ScalarTypeBytes) {
    82  		panic(fmt.Sprintf("field type %v already defined", name))
    83  	}
    84  	// check name
    85  	if len(name) == 0 {
    86  		panic("custom p3 type name can't be empty")
    87  	}
    88  	return P3MessageType{PackageName: pkg, Name: name}
    89  }
    90  
    91  var P3AnyType P3MessageType = NewP3MessageType("google.protobuf", "Any")
    92  
    93  // May be empty if it isn't set (for locally declared messages).
    94  func (p3mt P3MessageType) GetPackageName() string {
    95  	return p3mt.PackageName
    96  }
    97  
    98  func (p3mt P3MessageType) GetName() string {
    99  	return p3mt.Name
   100  }
   101  
   102  func (p3mt P3MessageType) GetFullName() string {
   103  	if p3mt.OmitPackage || p3mt.PackageName == "" {
   104  		return p3mt.Name
   105  	} else {
   106  		return fmt.Sprintf("%v.%v", p3mt.PackageName, p3mt.Name)
   107  	}
   108  }
   109  
   110  func (p3mt *P3MessageType) SetOmitPackage() {
   111  	p3mt.OmitPackage = true
   112  }
   113  
   114  func (p3mt P3MessageType) String() string {
   115  	return p3mt.GetFullName()
   116  }
   117  
   118  // NOTE: P3Doc and its fields are meant to hold basic AST-like information.  No
   119  // validity checking happens here... it should happen before these values are
   120  // set.  Convenience functions that require much more context like P3Context are OK.
   121  type P3Doc struct {
   122  	PackageName string
   123  	GoPackage   string // TODO replace with general options
   124  	Comment     string
   125  	Imports     []P3Import
   126  	Messages    []P3Message
   127  	// Enums []P3Enums // enums not supported, no need.
   128  }
   129  
   130  func (doc *P3Doc) AddImport(path string) {
   131  	for _, p3import := range doc.Imports {
   132  		if p3import.Path == path {
   133  			return // do nothing.
   134  		}
   135  	}
   136  	doc.Imports = append(doc.Imports, P3Import{Path: path})
   137  }
   138  
   139  type P3Import struct {
   140  	Path string
   141  	// Public bool // not used (yet)
   142  }
   143  
   144  type P3Message struct {
   145  	Comment string
   146  	Name    string
   147  	Fields  []P3Field
   148  }
   149  
   150  type P3Field struct {
   151  	Comment  string
   152  	Repeated bool
   153  	Type     P3Type
   154  	Name     string
   155  	JSONName string
   156  	Number   uint32
   157  }
   158  
   159  //----------------------------------------
   160  // Functions for printing P3 objects
   161  
   162  // NOTE: P3Doc imports must be set correctly.
   163  func (doc P3Doc) Print() string {
   164  	p := press.NewPress()
   165  	return strings.TrimSpace(doc.PrintCode(p).Print())
   166  }
   167  
   168  func (doc P3Doc) PrintCode(p *press.Press) *press.Press {
   169  	p.Pl("syntax = \"proto3\";")
   170  	if doc.PackageName != "" {
   171  		p.Pl("package %v;", doc.PackageName)
   172  	}
   173  	// Print comments, if any.
   174  	p.Ln()
   175  	if doc.Comment != "" {
   176  		printComments(p, doc.Comment)
   177  		p.Ln()
   178  	}
   179  	// Print options, if any.
   180  	if doc.GoPackage != "" {
   181  		p.Pl("option go_package = \"%v\";", doc.GoPackage)
   182  		p.Ln()
   183  	}
   184  	// Print imports, if any.
   185  	for i, imp := range doc.Imports {
   186  		if i == 0 {
   187  			p.Pl("// imports")
   188  		}
   189  		imp.PrintCode(p)
   190  		if i == len(doc.Imports)-1 {
   191  			p.Ln()
   192  		}
   193  	}
   194  	// Print message schemas, if any.
   195  	for i, msg := range doc.Messages {
   196  		if i == 0 {
   197  			p.Pl("// messages")
   198  		}
   199  		msg.PrintCode(p)
   200  		p.Ln()
   201  		if i == len(doc.Messages)-1 {
   202  			p.Ln()
   203  		}
   204  	}
   205  	return p
   206  }
   207  
   208  func (imp P3Import) PrintCode(p *press.Press) *press.Press {
   209  	p.Pl("import %v;", strconv.Quote(imp.Path))
   210  	return p
   211  }
   212  
   213  func (msg P3Message) Print() string {
   214  	p := press.NewPress()
   215  	return msg.PrintCode(p).Print()
   216  }
   217  
   218  func (msg P3Message) PrintCode(p *press.Press) *press.Press {
   219  	printComments(p, msg.Comment)
   220  	p.Pl("message %v {", msg.Name).I(func(p *press.Press) {
   221  		for _, fld := range msg.Fields {
   222  			fld.PrintCode(p)
   223  		}
   224  	}).Pl("}")
   225  	return p
   226  }
   227  
   228  func (fld P3Field) PrintCode(p *press.Press) *press.Press {
   229  	fieldOptions := ""
   230  	if fld.JSONName != "" && fld.JSONName != fld.Name {
   231  		fieldOptions = " [json_name = \"" + fld.JSONName + "\"]"
   232  	}
   233  	printComments(p, fld.Comment)
   234  	if fld.Repeated {
   235  		p.Pl("repeated %v %v = %v%v;", fld.Type, fld.Name, fld.Number, fieldOptions)
   236  	} else {
   237  		p.Pl("%v %v = %v%v;", fld.Type, fld.Name, fld.Number, fieldOptions)
   238  	}
   239  	return p
   240  }
   241  
   242  func printComments(p *press.Press, comment string) {
   243  	if comment == "" {
   244  		return
   245  	}
   246  	commentLines := strings.Split(comment, "\n")
   247  	for _, line := range commentLines {
   248  		p.Pl("// %v", line)
   249  	}
   250  }
   251  
   252  //----------------------------------------
   253  // Synthetic type for nested lists
   254  
   255  // This exists as a workaround due to Proto deficiencies,
   256  // namely how fields can only be repeated, not nestedly-repeated.
   257  type NList struct {
   258  	// Define dimension as followes:
   259  	// []struct{} has dimension 1, as well as [][]byte.
   260  	// [][]struct{} has dimension 2, as well as [][][]byte.
   261  	// When dimension is 2 or greater, we need implicit structs.
   262  	// The NestedType is meant to represent these types,
   263  	// so Dimensions is usually 2 or greater.
   264  	Dimensions int
   265  
   266  	// UltiElem.ReprType might not be UltiElem.
   267  	// Could be []byte.
   268  	UltiElem *amino.TypeInfo
   269  
   270  	// Optional Package, where this nested list was used.
   271  	// NOTE: two packages can't (yet?) share nested lists.
   272  	Package *amino.Package
   273  
   274  	// If embedded in a struct.
   275  	// Should be sanitized to uniq properly.
   276  	FieldOptions amino.FieldOptions
   277  }
   278  
   279  // filter to field options that matter for NLists.
   280  func nListFieldOptions(fopts amino.FieldOptions) amino.FieldOptions {
   281  	return amino.FieldOptions{
   282  		BinFixed64:     fopts.BinFixed64,
   283  		BinFixed32:     fopts.BinFixed32,
   284  		UseGoogleTypes: fopts.UseGoogleTypes,
   285  	}
   286  }
   287  
   288  // info: a list's TypeInfo.
   289  func newNList(pkg *amino.Package, info *amino.TypeInfo, fopts amino.FieldOptions) NList {
   290  	if !isListType(info.ReprType.Type) {
   291  		panic("should not happen")
   292  	}
   293  	if !isListType(info.ReprType.Type) {
   294  		panic("should not happen")
   295  	}
   296  	if info.ReprType.Elem.ReprType.Type.Kind() == reflect.Uint8 {
   297  		panic("should not happen")
   298  	}
   299  	fopts = nListFieldOptions(fopts)
   300  	einfo := info
   301  	leinfo := (*amino.TypeInfo)(nil)
   302  	counter := 0
   303  	for isListType(einfo.ReprType.Type) {
   304  		leinfo = einfo
   305  		einfo = einfo.ReprType.Elem
   306  		counter++
   307  	}
   308  	if einfo.ReprType.Type.Name() == "uint8" {
   309  		einfo = leinfo
   310  		counter--
   311  	}
   312  	return NList{
   313  		Package:      pkg,
   314  		Dimensions:   counter,
   315  		UltiElem:     einfo,
   316  		FieldOptions: fopts,
   317  	}
   318  }
   319  
   320  func (nl NList) Name() string {
   321  	if nl.Dimensions <= 0 {
   322  		panic("should not happen")
   323  	}
   324  	pkgname := strings.ToUpper(nl.Package.GoPkgName) // must be exposed.
   325  	var prefix string
   326  	var ename string
   327  	listSfx := strings.Repeat("List", nl.Dimensions)
   328  
   329  	ert := nl.UltiElem.ReprType.Type
   330  	if isListType(ert) {
   331  		if nl.UltiElem.ReprType.Elem.ReprType.Type.Kind() != reflect.Uint8 {
   332  			panic("should not happen")
   333  		}
   334  		ename = "Bytes"
   335  	} else {
   336  		// Get name from .Type, not ReprType.Type.
   337  		ename = nl.UltiElem.Name
   338  	}
   339  
   340  	if nl.FieldOptions.BinFixed64 {
   341  		prefix = "Fixed64"
   342  	} else if nl.FieldOptions.BinFixed32 {
   343  		prefix = "Fixed32"
   344  	}
   345  	if nl.FieldOptions.UseGoogleTypes {
   346  		prefix = "G" + prefix
   347  	}
   348  
   349  	return fmt.Sprintf("%s_%v%v%v", pkgname, prefix, ename, listSfx)
   350  }
   351  
   352  func (nl NList) P3GoExprString(imports *ast.GenDecl, scope *ast.Scope) string {
   353  	pkgName := addImportAuto(imports, scope, nl.Package.GoPkgName+"pb", nl.Package.P3GoPkgPath)
   354  	return fmt.Sprintf("*%v.%v", pkgName, nl.Name())
   355  }
   356  
   357  // NOTE: requires nl.Package.
   358  func (nl NList) P3Type() P3Type {
   359  	return NewP3MessageType(
   360  		nl.Package.P3PkgName,
   361  		nl.Name(),
   362  	)
   363  }
   364  
   365  func (nl NList) Elem() NList {
   366  	if nl.Dimensions == 1 {
   367  		panic("should not happen")
   368  	}
   369  	return NList{
   370  		Package:      nl.Package,
   371  		Dimensions:   nl.Dimensions - 1,
   372  		UltiElem:     nl.UltiElem,
   373  		FieldOptions: nl.FieldOptions,
   374  	}
   375  }
   376  
   377  func (nl NList) ElemP3Type() P3Type {
   378  	if nl.Dimensions == 1 {
   379  		p3type, repeated, implicit := typeToP3Type(
   380  			nl.Package,
   381  			nl.UltiElem,
   382  			nl.FieldOptions,
   383  		)
   384  		if repeated || implicit {
   385  			panic("should not happen")
   386  		}
   387  		return p3type
   388  	} else {
   389  		return nl.Elem().P3Type()
   390  	}
   391  }
   392  
   393  // For uniq'ing.
   394  func (nl NList) Key() string {
   395  	return fmt.Sprintf("%v.%v", nl.Package.GoPkgName, nl.Name())
   396  }
   397  
   398  //----------------------------------------
   399  // Other
   400  
   401  // Find root struct fields that are nested list types.
   402  // If not a struct, assume an implicit struct with single field.
   403  // If type is amino.Marshaler, find values/fields from the repr.
   404  // Pointers are ignored, even for the terminal type.
   405  // e.g. if TypeInfo.ReprType.Type is
   406  //   - struct{ [][]int, [][]string } -> return [][]int, [][]string
   407  //   - [][]int -> return [][]int
   408  //   - [][][]int -> return [][][]int, [][]int
   409  //   - [][][]byte -> return [][][]byte (but not [][]byte, which is just repeated bytes).
   410  //   - [][][][]int -> return [][][][]int, [][][]int, [][]int.
   411  //
   412  // The results are uniq'd and sorted somehow.
   413  func findNLists(root *amino.Package, info *amino.TypeInfo, found *map[string]NList) {
   414  	if found == nil {
   415  		*found = map[string]NList{}
   416  	}
   417  	switch info.ReprType.Type.Kind() {
   418  	case reflect.Struct:
   419  		for _, field := range info.ReprType.Fields {
   420  			fert := field.TypeInfo.ReprType.Type
   421  			fopts := field.FieldOptions
   422  			if isListType(fert) {
   423  				lists := findNLists2(root, field.TypeInfo, fopts)
   424  				for _, list := range lists {
   425  					if list.Dimensions >= 1 {
   426  						(*found)[list.Key()] = list
   427  					}
   428  				}
   429  			}
   430  		}
   431  		return
   432  	case reflect.Array, reflect.Slice:
   433  		lists := findNLists2(root, info, amino.FieldOptions{})
   434  		for _, list := range lists {
   435  			if list.Dimensions >= 2 {
   436  				(*found)[list.Key()] = list
   437  			}
   438  		}
   439  	}
   440  }
   441  
   442  // The last item of res is the deepest.
   443  // As a special recursive case, may return Dimensions:1 for bytes.
   444  func findNLists2(root *amino.Package, list *amino.TypeInfo, fopts amino.FieldOptions) []NList {
   445  	fopts = nListFieldOptions(fopts)
   446  	switch list.ReprType.Type.Kind() {
   447  	case reflect.Ptr:
   448  		panic("should not happen")
   449  	case reflect.Array, reflect.Slice:
   450  		elem := list.ReprType.Elem
   451  		if isListType(elem.ReprType.Type) {
   452  			if elem.ReprType.Elem.ReprType.Type.Kind() == reflect.Uint8 {
   453  				// elem is []byte or bytes, and list is []bytes.
   454  				// no need to look for sublists.
   455  				return []NList{
   456  					{
   457  						Package:      root,
   458  						Dimensions:   1,
   459  						UltiElem:     elem,
   460  						FieldOptions: fopts,
   461  					},
   462  				}
   463  			} else {
   464  				sublists := findNLists2(root, elem, fopts)
   465  				if len(sublists) == 0 {
   466  					return []NList{{
   467  						Package:      root,
   468  						Dimensions:   1,
   469  						UltiElem:     elem.ReprType.Elem,
   470  						FieldOptions: fopts,
   471  					}}
   472  				} else {
   473  					deepest := sublists[len(sublists)-1]
   474  					this := NList{
   475  						Package:      root,
   476  						Dimensions:   deepest.Dimensions + 1,
   477  						UltiElem:     deepest.UltiElem,
   478  						FieldOptions: fopts,
   479  					}
   480  					lists := append(sublists, this)
   481  					return lists
   482  				}
   483  			}
   484  		} else {
   485  			return nil // nothing.
   486  		}
   487  	default:
   488  		panic("should not happen")
   489  	}
   490  }
   491  
   492  func sortFound(found map[string]NList) (res []NList) {
   493  	for _, nl := range found {
   494  		res = append(res, nl)
   495  	}
   496  	sort.Slice(res, func(i, j int) bool {
   497  		if res[i].Name() < res[j].Name() {
   498  			return true
   499  		} else if res[i].Name() == res[j].Name() {
   500  			return res[i].Dimensions < res[j].Dimensions
   501  		} else {
   502  			return false
   503  		}
   504  	})
   505  	return res
   506  }