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 }