compiler: push additional type data into the bindings file

Structures/arrays and maps.
This commit is contained in:
Roman Khimov 2022-12-01 21:05:54 +03:00
parent 027e94fbde
commit b9d20b32e9
5 changed files with 168 additions and 62 deletions

View file

@ -914,7 +914,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.convertMap(n) c.convertMap(n)
default: default:
if tn, ok := t.(*types.Named); ok && isInteropPath(tn.String()) { if tn, ok := t.(*types.Named); ok && isInteropPath(tn.String()) {
st, _, _ := scAndVMInteropTypeFromExpr(tn, false) st, _, _, _ := scAndVMInteropTypeFromExpr(tn, false)
expectedLen := -1 expectedLen := -1
switch st { switch st {
case smartcontract.Hash160Type: case smartcontract.Hash160Type:

View file

@ -291,13 +291,23 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
continue continue
} }
for _, p := range m.Parameters { for _, p := range m.Parameters {
pname := m.Name.Name + "." + p.Name
if p.RealType.TypeName != "" { if p.RealType.TypeName != "" {
cfg.Overrides[m.Name.Name+"."+p.Name] = p.RealType cfg.Overrides[pname] = p.RealType
}
if p.ExtendedType != nil {
cfg.Types[pname] = *p.ExtendedType
} }
} }
if m.ReturnTypeReal.TypeName != "" { if m.ReturnTypeReal.TypeName != "" {
cfg.Overrides[m.Name.Name] = m.ReturnTypeReal cfg.Overrides[m.Name.Name] = m.ReturnTypeReal
} }
if m.ReturnTypeExtended != nil {
cfg.Types[m.Name.Name] = *m.ReturnTypeExtended
}
}
if len(di.NamedTypes) > 0 {
cfg.NamedTypes = di.NamedTypes
} }
data, err := yaml.Marshal(&cfg) data, err := yaml.Marshal(&cfg)
if err != nil { if err != nil {

View file

@ -26,6 +26,9 @@ type DebugInfo struct {
Hash util.Uint160 `json:"hash"` Hash util.Uint160 `json:"hash"`
Documents []string `json:"documents"` Documents []string `json:"documents"`
Methods []MethodDebugInfo `json:"methods"` Methods []MethodDebugInfo `json:"methods"`
// NamedTypes are exported structured types that have some name (even
// if the original structure doesn't) and a number of internal fields.
NamedTypes map[string]binding.ExtendedType `json:"-"`
Events []EventDebugInfo `json:"events"` Events []EventDebugInfo `json:"events"`
// EmittedEvents contains events occurring in code. // EmittedEvents contains events occurring in code.
EmittedEvents map[string][][]string `json:"-"` EmittedEvents map[string][][]string `json:"-"`
@ -55,6 +58,8 @@ type MethodDebugInfo struct {
ReturnType string `json:"return"` ReturnType string `json:"return"`
// ReturnTypeReal is the method's return type as specified in Go code. // ReturnTypeReal is the method's return type as specified in Go code.
ReturnTypeReal binding.Override `json:"-"` ReturnTypeReal binding.Override `json:"-"`
// ReturnTypeExtended is the method's return type with additional data.
ReturnTypeExtended *binding.ExtendedType `json:"-"`
// ReturnTypeSC is a return type to use in manifest. // ReturnTypeSC is a return type to use in manifest.
ReturnTypeSC smartcontract.ParamType `json:"-"` ReturnTypeSC smartcontract.ParamType `json:"-"`
Variables []string `json:"variables"` Variables []string `json:"variables"`
@ -103,6 +108,7 @@ type DebugParam struct {
Name string `json:"name"` Name string `json:"name"`
Type string `json:"type"` Type string `json:"type"`
RealType binding.Override `json:"-"` RealType binding.Override `json:"-"`
ExtendedType *binding.ExtendedType `json:"-"`
TypeSC smartcontract.ParamType `json:"-"` TypeSC smartcontract.ParamType `json:"-"`
} }
@ -185,8 +191,9 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo {
} }
start := len(d.Methods) start := len(d.Methods)
d.NamedTypes = make(map[string]binding.ExtendedType)
for name, scope := range c.funcs { for name, scope := range c.funcs {
m := c.methodInfoFromScope(name, scope) m := c.methodInfoFromScope(name, scope, d.NamedTypes)
if m.Range.Start == m.Range.End { if m.Range.Start == m.Range.End {
continue continue
} }
@ -201,7 +208,7 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo {
} }
func (c *codegen) registerDebugVariable(name string, expr ast.Expr) { func (c *codegen) registerDebugVariable(name string, expr ast.Expr) {
_, vt, _ := c.scAndVMTypeFromExpr(expr) _, vt, _, _ := c.scAndVMTypeFromExpr(expr, nil)
if c.scope == nil { if c.scope == nil {
c.staticVariables = append(c.staticVariables, name+","+vt.String()) c.staticVariables = append(c.staticVariables, name+","+vt.String())
return return
@ -209,15 +216,16 @@ func (c *codegen) registerDebugVariable(name string, expr ast.Expr) {
c.scope.variables = append(c.scope.variables, name+","+vt.String()) c.scope.variables = append(c.scope.variables, name+","+vt.String())
} }
func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebugInfo { func (c *codegen) methodInfoFromScope(name string, scope *funcScope, exts map[string]binding.ExtendedType) *MethodDebugInfo {
ps := scope.decl.Type.Params ps := scope.decl.Type.Params
params := make([]DebugParam, 0, ps.NumFields()) params := make([]DebugParam, 0, ps.NumFields())
for i := range ps.List { for i := range ps.List {
for j := range ps.List[i].Names { for j := range ps.List[i].Names {
st, vt, rt := c.scAndVMTypeFromExpr(ps.List[i].Type) st, vt, rt, et := c.scAndVMTypeFromExpr(ps.List[i].Type, exts)
params = append(params, DebugParam{ params = append(params, DebugParam{
Name: ps.List[i].Names[j].Name, Name: ps.List[i].Names[j].Name,
Type: vt.String(), Type: vt.String(),
ExtendedType: et,
RealType: rt, RealType: rt,
TypeSC: st, TypeSC: st,
}) })
@ -226,7 +234,7 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu
ss := strings.Split(name, ".") ss := strings.Split(name, ".")
name = ss[len(ss)-1] name = ss[len(ss)-1]
r, n := utf8.DecodeRuneInString(name) r, n := utf8.DecodeRuneInString(name)
st, vt, rt := c.scAndVMReturnTypeFromScope(scope) st, vt, rt, et := c.scAndVMReturnTypeFromScope(scope, exts)
return &MethodDebugInfo{ return &MethodDebugInfo{
ID: name, ID: name,
@ -239,6 +247,7 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu
Range: scope.rng, Range: scope.rng,
Parameters: params, Parameters: params,
ReturnType: vt, ReturnType: vt,
ReturnTypeExtended: et,
ReturnTypeReal: rt, ReturnTypeReal: rt,
ReturnTypeSC: st, ReturnTypeSC: st,
SeqPoints: c.sequencePoints[name], SeqPoints: c.sequencePoints[name],
@ -246,33 +255,35 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu
} }
} }
func (c *codegen) scAndVMReturnTypeFromScope(scope *funcScope) (smartcontract.ParamType, string, binding.Override) { func (c *codegen) scAndVMReturnTypeFromScope(scope *funcScope, exts map[string]binding.ExtendedType) (smartcontract.ParamType, string, binding.Override, *binding.ExtendedType) {
results := scope.decl.Type.Results results := scope.decl.Type.Results
switch results.NumFields() { switch results.NumFields() {
case 0: case 0:
return smartcontract.VoidType, "Void", binding.Override{} return smartcontract.VoidType, "Void", binding.Override{}, nil
case 1: case 1:
st, vt, s := c.scAndVMTypeFromExpr(results.List[0].Type) st, vt, s, et := c.scAndVMTypeFromExpr(results.List[0].Type, exts)
return st, vt.String(), s return st, vt.String(), s, et
default: default:
// multiple return values are not supported in debugger // multiple return values are not supported in debugger
return smartcontract.AnyType, "Any", binding.Override{} return smartcontract.AnyType, "Any", binding.Override{}, nil
} }
} }
func scAndVMInteropTypeFromExpr(named *types.Named, isPointer bool) (smartcontract.ParamType, stackitem.Type, binding.Override) { func scAndVMInteropTypeFromExpr(named *types.Named, isPointer bool) (smartcontract.ParamType, stackitem.Type, binding.Override, *binding.ExtendedType) {
name := named.Obj().Name() name := named.Obj().Name()
pkg := named.Obj().Pkg().Name() pkg := named.Obj().Pkg().Name()
switch pkg { switch pkg {
case "ledger", "contract": case "ledger", "contract":
// Block, Transaction, Contract.
typeName := pkg + "." + name typeName := pkg + "." + name
et := &binding.ExtendedType{Base: smartcontract.ArrayType, Name: typeName}
if isPointer { if isPointer {
typeName = "*" + typeName typeName = "*" + typeName
} }
return smartcontract.ArrayType, stackitem.ArrayT, binding.Override{ return smartcontract.ArrayType, stackitem.ArrayT, binding.Override{
Package: named.Obj().Pkg().Path(), Package: named.Obj().Pkg().Path(),
TypeName: typeName, TypeName: typeName,
} // Block, Transaction, Contract }, et
case "interop": case "interop":
if name != "Interface" { if name != "Interface" {
over := binding.Override{ over := binding.Override{
@ -281,26 +292,29 @@ func scAndVMInteropTypeFromExpr(named *types.Named, isPointer bool) (smartcontra
} }
switch name { switch name {
case "Hash160": case "Hash160":
return smartcontract.Hash160Type, stackitem.ByteArrayT, over return smartcontract.Hash160Type, stackitem.ByteArrayT, over, nil
case "Hash256": case "Hash256":
return smartcontract.Hash256Type, stackitem.ByteArrayT, over return smartcontract.Hash256Type, stackitem.ByteArrayT, over, nil
case "PublicKey": case "PublicKey":
return smartcontract.PublicKeyType, stackitem.ByteArrayT, over return smartcontract.PublicKeyType, stackitem.ByteArrayT, over, nil
case "Signature": case "Signature":
return smartcontract.SignatureType, stackitem.ByteArrayT, over return smartcontract.SignatureType, stackitem.ByteArrayT, over, nil
} }
} }
} }
return smartcontract.InteropInterfaceType, stackitem.InteropT, binding.Override{TypeName: "interface{}"} return smartcontract.InteropInterfaceType,
stackitem.InteropT,
binding.Override{TypeName: "interface{}"},
&binding.ExtendedType{Base: smartcontract.InteropInterfaceType, Interface: "iterator"} // Temporarily all interops are iterators.
} }
func (c *codegen) scAndVMTypeFromExpr(typ ast.Expr) (smartcontract.ParamType, stackitem.Type, binding.Override) { func (c *codegen) scAndVMTypeFromExpr(typ ast.Expr, exts map[string]binding.ExtendedType) (smartcontract.ParamType, stackitem.Type, binding.Override, *binding.ExtendedType) {
return c.scAndVMTypeFromType(c.typeOf(typ)) return c.scAndVMTypeFromType(c.typeOf(typ), exts)
} }
func (c *codegen) scAndVMTypeFromType(t types.Type) (smartcontract.ParamType, stackitem.Type, binding.Override) { func (c *codegen) scAndVMTypeFromType(t types.Type, exts map[string]binding.ExtendedType) (smartcontract.ParamType, stackitem.Type, binding.Override, *binding.ExtendedType) {
if t == nil { if t == nil {
return smartcontract.AnyType, stackitem.AnyT, binding.Override{TypeName: "interface{}"} return smartcontract.AnyType, stackitem.AnyT, binding.Override{TypeName: "interface{}"}, nil
} }
var isPtr bool var isPtr bool
@ -314,7 +328,11 @@ func (c *codegen) scAndVMTypeFromType(t types.Type) (smartcontract.ParamType, st
} }
if isNamed { if isNamed {
if isInteropPath(named.String()) { if isInteropPath(named.String()) {
return scAndVMInteropTypeFromExpr(named, isPtr) st, vt, over, et := scAndVMInteropTypeFromExpr(named, isPtr)
if et != nil && et.Base == smartcontract.ArrayType && exts != nil && exts[et.Name].Name != et.Name {
_ = c.genStructExtended(named.Underlying().(*types.Struct), et.Name, exts)
}
return st, vt, over, et
} }
} }
@ -325,43 +343,103 @@ func (c *codegen) scAndVMTypeFromType(t types.Type) (smartcontract.ParamType, st
switch { switch {
case info&types.IsInteger != 0: case info&types.IsInteger != 0:
over.TypeName = "int" over.TypeName = "int"
return smartcontract.IntegerType, stackitem.IntegerT, over return smartcontract.IntegerType, stackitem.IntegerT, over, nil
case info&types.IsBoolean != 0: case info&types.IsBoolean != 0:
over.TypeName = "bool" over.TypeName = "bool"
return smartcontract.BoolType, stackitem.BooleanT, over return smartcontract.BoolType, stackitem.BooleanT, over, nil
case info&types.IsString != 0: case info&types.IsString != 0:
over.TypeName = "string" over.TypeName = "string"
return smartcontract.StringType, stackitem.ByteArrayT, over return smartcontract.StringType, stackitem.ByteArrayT, over, nil
default: default:
over.TypeName = "interface{}" over.TypeName = "interface{}"
return smartcontract.AnyType, stackitem.AnyT, over return smartcontract.AnyType, stackitem.AnyT, over, nil
} }
case *types.Map: case *types.Map:
_, _, over := c.scAndVMTypeFromType(t.Elem()) et := &binding.ExtendedType{
Base: smartcontract.MapType,
}
et.Key, _, _, _ = c.scAndVMTypeFromType(t.Key(), exts)
vt, _, over, vet := c.scAndVMTypeFromType(t.Elem(), exts)
et.Value = vet
if et.Value == nil {
et.Value = &binding.ExtendedType{Base: vt}
}
over.TypeName = "map[" + t.Key().String() + "]" + over.TypeName over.TypeName = "map[" + t.Key().String() + "]" + over.TypeName
return smartcontract.MapType, stackitem.MapT, over return smartcontract.MapType, stackitem.MapT, over, et
case *types.Struct: case *types.Struct:
if isNamed { if isNamed {
over.Package = named.Obj().Pkg().Path() over.Package = named.Obj().Pkg().Path()
over.TypeName = named.Obj().Pkg().Name() + "." + named.Obj().Name() over.TypeName = named.Obj().Pkg().Name() + "." + named.Obj().Name()
_ = c.genStructExtended(t, over.TypeName, exts)
} else {
name := "unnamed"
if exts != nil {
for exts[name].Name == name {
name = name + "X"
} }
return smartcontract.ArrayType, stackitem.StructT, over _ = c.genStructExtended(t, name, exts)
}
}
return smartcontract.ArrayType, stackitem.StructT, over,
&binding.ExtendedType{ // Value-less, refer to exts.
Base: smartcontract.ArrayType,
Name: over.TypeName,
}
case *types.Slice: case *types.Slice:
if isByte(t.Elem()) { if isByte(t.Elem()) {
over.TypeName = "[]byte" over.TypeName = "[]byte"
return smartcontract.ByteArrayType, stackitem.ByteArrayT, over return smartcontract.ByteArrayType, stackitem.ByteArrayT, over, nil
}
et := &binding.ExtendedType{
Base: smartcontract.ArrayType,
}
vt, _, over, vet := c.scAndVMTypeFromType(t.Elem(), exts)
et.Value = vet
if et.Value == nil {
et.Value = &binding.ExtendedType{
Base: vt,
}
} }
_, _, over := c.scAndVMTypeFromType(t.Elem())
if over.TypeName != "" { if over.TypeName != "" {
over.TypeName = "[]" + over.TypeName over.TypeName = "[]" + over.TypeName
} }
return smartcontract.ArrayType, stackitem.ArrayT, over return smartcontract.ArrayType, stackitem.ArrayT, over, et
default: default:
over.TypeName = "interface{}" over.TypeName = "interface{}"
return smartcontract.AnyType, stackitem.AnyT, over return smartcontract.AnyType, stackitem.AnyT, over, nil
} }
} }
func (c *codegen) genStructExtended(t *types.Struct, name string, exts map[string]binding.ExtendedType) *binding.ExtendedType {
var et *binding.ExtendedType
if exts != nil {
if exts[name].Name != name {
et = &binding.ExtendedType{
Base: smartcontract.ArrayType,
Name: name,
Fields: make([]binding.FieldExtendedType, t.NumFields()),
}
exts[name] = *et // Prefill to solve recursive structures.
for i := range et.Fields {
field := t.Field(i)
ft, _, _, fet := c.scAndVMTypeFromType(field.Type(), exts)
if fet == nil {
et.Fields[i].ExtendedType.Base = ft
} else {
et.Fields[i].ExtendedType = *fet
}
et.Fields[i].Field = field.Name()
}
exts[name] = *et // Set real structure data.
} else {
et = new(binding.ExtendedType)
*et = exts[name]
}
}
return et
}
// MarshalJSON implements the json.Marshaler interface. // MarshalJSON implements the json.Marshaler interface.
func (d *DebugRange) MarshalJSON() ([]byte, error) { func (d *DebugRange) MarshalJSON() ([]byte, error) {
return []byte(`"` + strconv.FormatUint(uint64(d.Start), 10) + `-` + return []byte(`"` + strconv.FormatUint(uint64(d.Start), 10) + `-` +

View file

@ -175,7 +175,7 @@ func (c *codegen) processNotify(f *funcScope, args []ast.Expr, hasEllipsis bool)
params := make([]string, 0, len(args[1:])) params := make([]string, 0, len(args[1:]))
vParams := make([]*stackitem.Type, 0, len(args[1:])) vParams := make([]*stackitem.Type, 0, len(args[1:]))
for _, p := range args[1:] { for _, p := range args[1:] {
st, vt, _ := c.scAndVMTypeFromExpr(p) st, vt, _, _ := c.scAndVMTypeFromExpr(p, nil)
params = append(params, st.String()) params = append(params, st.String())
vParams = append(vParams, &vt) vParams = append(vParams, &vt)
} }

View file

@ -53,9 +53,25 @@ type (
Hash util.Uint160 `yaml:"hash,omitempty"` Hash util.Uint160 `yaml:"hash,omitempty"`
Overrides map[string]Override `yaml:"overrides,omitempty"` Overrides map[string]Override `yaml:"overrides,omitempty"`
CallFlags map[string]callflag.CallFlag `yaml:"callflags,omitempty"` CallFlags map[string]callflag.CallFlag `yaml:"callflags,omitempty"`
NamedTypes map[string]ExtendedType `yaml:"namedtypes,omitempty"`
Types map[string]ExtendedType `yaml:"types,omitempty"`
Output io.Writer `yaml:"-"` Output io.Writer `yaml:"-"`
} }
ExtendedType struct {
Base smartcontract.ParamType `yaml:"base"`
Name string `yaml:"name,omitempty"` // Structure name, omitted for arrays, interfaces and maps.
Interface string `yaml:"interface,omitempty"` // Interface type name, "iterator" only for now.
Key smartcontract.ParamType `yaml:"key,omitempty"` // Key type (only simple types can be used for keys) for maps.
Value *ExtendedType `yaml:"value,omitempty"` // Value type for iterators and arrays.
Fields []FieldExtendedType `yaml:"fields,omitempty"` // Ordered type data for structure fields.
}
FieldExtendedType struct {
Field string `yaml:"field"`
ExtendedType `yaml:",inline"`
}
ContractTmpl struct { ContractTmpl struct {
PackageName string PackageName string
ContractName string ContractName string
@ -86,6 +102,8 @@ func NewConfig() Config {
return Config{ return Config{
Overrides: make(map[string]Override), Overrides: make(map[string]Override),
CallFlags: make(map[string]callflag.CallFlag), CallFlags: make(map[string]callflag.CallFlag),
NamedTypes: make(map[string]ExtendedType),
Types: make(map[string]ExtendedType),
} }
} }