From 70cef5fdb7c97ac4fd5983f69abd36e7c215eef9 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Tue, 7 May 2024 16:16:24 +0800 Subject: [PATCH 01/35] initial fix --- gnovm/pkg/gnolang/machine.go | 2 +- gnovm/pkg/gnolang/preprocess.go | 202 ++++++++++++++++++----- gnovm/tests/debug/self_refer_struct2.gno | 25 +++ 3 files changed, 190 insertions(+), 39 deletions(-) create mode 100644 gnovm/tests/debug/self_refer_struct2.gno diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 018fac66e64..9c0e1310efd 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -559,7 +559,7 @@ func (m *Machine) runFiles(fns ...*FileNode) { // fb := pv.GetFileBlock(nil, fn.Name) // get dependencies of decl. deps := make(map[Name]struct{}) - findDependentNames(decl, deps) + findDependentNames(decl, deps, false) for dep := range deps { // if dep already defined as import, skip. if _, ok := fn.GetLocalIndex(dep); ok { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 64fe1fbff45..0fca4fce1bf 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -95,6 +95,8 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { } } +var dependencies = make(map[string]*Dependency) + // This counter ensures (during testing) that certain functions // (like ConvertUntypedTo() for bigints and strings) // are only called during the preprocessing stage. @@ -625,6 +627,18 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // elide composite lit element (nested) composite types. elideCompositeElements(clx, clt) } + dumpDependencies() + // Check for cyclic dependencies + if ok, graph := hasCycle(); ok { + dependencies = nil + panic(graph) + } else { + fmt.Println("No cyclic dependency detected.") + } + switch n.(type) { + case *FileNode: + dependencies = nil + } }() // The main TRANS_LEAVE switch. @@ -1943,6 +1957,42 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *StructType: *dst = *(tmp.(*StructType)) case *DeclaredType: + if dependencies == nil { + dependencies = make(map[string]*Dependency) + } + fmt.Println("---type decl: ", n) + if st, ok := tmp.(*StructType); ok { + // check if fields contains declaredType + maybeRecursive := false + names := make([]Name, 0) + for _, f := range st.Fields { + fmt.Println("---f.Name: ", f.Name) + fmt.Println("---f.Type: ", f.Type) + if dt, ok := f.Type.(*DeclaredType); ok { // indirect does not count + maybeRecursive = true + fmt.Println("---dt.Name:", dt.Name) + names = append(names, dt.Name) + } + + if pt, ok := f.Type.(*PointerType); ok { // indirect does not count + fmt.Println("---it is pointer type, pt: ", pt) + } + } + if maybeRecursive { + deps := make(map[Name]struct{}) + findDependentNames(n, deps, false) + fmt.Println("---deps: ", deps) + fmt.Println("---names: ", names) + for dn := range deps { + // if dep name is declared type + for _, name := range names { + if name == dn { + insertDependency(string(dst.Name), &Dependency{Name: string(dn)}) + } + } + } + } + } // if store has this type, use that. tid := DeclaredTypeID(lastpn.PkgPath, n.Name) exists := false @@ -3540,53 +3590,67 @@ func countNumArgs(store Store, last BlockNode, n *CallExpr) (numArgs int) { } } +//func findDependentNames2(n Node, info map[Name]bool) { +// dst := make(map[Name]struct{}) +// indirect := findDependentNames(n, dst) +// if !indirect { +// info +// } +//} + // This is to be run *after* preprocessing is done, // to determine the order of var decl execution // (which may include functions which may refer to package vars). -func findDependentNames(n Node, dst map[Name]struct{}) { +func findDependentNames(n Node, dst map[Name]struct{}, directOnly bool) { switch cn := n.(type) { case *NameExpr: + fmt.Println("---name X, n: ", n) dst[cn.Name] = struct{}{} case *BasicLitExpr: case *BinaryExpr: - findDependentNames(cn.Left, dst) - findDependentNames(cn.Right, dst) + findDependentNames(cn.Left, dst, directOnly) + findDependentNames(cn.Right, dst, directOnly) case *SelectorExpr: - findDependentNames(cn.X, dst) + findDependentNames(cn.X, dst, directOnly) case *SliceExpr: - findDependentNames(cn.X, dst) + findDependentNames(cn.X, dst, directOnly) if cn.Low != nil { - findDependentNames(cn.Low, dst) + findDependentNames(cn.Low, dst, directOnly) } if cn.High != nil { - findDependentNames(cn.High, dst) + findDependentNames(cn.High, dst, directOnly) } if cn.Max != nil { - findDependentNames(cn.Max, dst) + findDependentNames(cn.Max, dst, directOnly) } case *StarExpr: - findDependentNames(cn.X, dst) + fmt.Println("---star X, n: ", n) + if directOnly { + return + } else { + findDependentNames(cn.X, dst, directOnly) + } case *RefExpr: - findDependentNames(cn.X, dst) + findDependentNames(cn.X, dst, directOnly) case *TypeAssertExpr: - findDependentNames(cn.X, dst) - findDependentNames(cn.Type, dst) + findDependentNames(cn.X, dst, directOnly) + findDependentNames(cn.Type, dst, directOnly) case *UnaryExpr: - findDependentNames(cn.X, dst) + findDependentNames(cn.X, dst, directOnly) case *CompositeLitExpr: - findDependentNames(cn.Type, dst) + findDependentNames(cn.Type, dst, directOnly) ct := getType(cn.Type) switch ct.Kind() { case ArrayKind, SliceKind, MapKind: for _, kvx := range cn.Elts { if kvx.Key != nil { - findDependentNames(kvx.Key, dst) + findDependentNames(kvx.Key, dst, directOnly) } - findDependentNames(kvx.Value, dst) + findDependentNames(kvx.Value, dst, directOnly) } case StructKind: for _, kvx := range cn.Elts { - findDependentNames(kvx.Value, dst) + findDependentNames(kvx.Value, dst, directOnly) } default: panic(fmt.Sprintf( @@ -3594,44 +3658,44 @@ func findDependentNames(n Node, dst map[Name]struct{}) { ct.String())) } case *FieldTypeExpr: - findDependentNames(cn.Type, dst) + findDependentNames(cn.Type, dst, directOnly) case *ArrayTypeExpr: - findDependentNames(cn.Elt, dst) + findDependentNames(cn.Elt, dst, directOnly) if cn.Len != nil { - findDependentNames(cn.Len, dst) + findDependentNames(cn.Len, dst, directOnly) } case *SliceTypeExpr: - findDependentNames(cn.Elt, dst) + findDependentNames(cn.Elt, dst, directOnly) case *InterfaceTypeExpr: for i := range cn.Methods { - findDependentNames(&cn.Methods[i], dst) + findDependentNames(&cn.Methods[i], dst, directOnly) } case *ChanTypeExpr: - findDependentNames(cn.Value, dst) + findDependentNames(cn.Value, dst, directOnly) case *FuncTypeExpr: for i := range cn.Params { - findDependentNames(&cn.Params[i], dst) + findDependentNames(&cn.Params[i], dst, directOnly) } for i := range cn.Results { - findDependentNames(&cn.Results[i], dst) + findDependentNames(&cn.Results[i], dst, directOnly) } case *MapTypeExpr: - findDependentNames(cn.Key, dst) - findDependentNames(cn.Value, dst) + findDependentNames(cn.Key, dst, directOnly) + findDependentNames(cn.Value, dst, directOnly) case *StructTypeExpr: for i := range cn.Fields { - findDependentNames(&cn.Fields[i], dst) + findDependentNames(&cn.Fields[i], dst, directOnly) } case *CallExpr: - findDependentNames(cn.Func, dst) + findDependentNames(cn.Func, dst, directOnly) for i := range cn.Args { - findDependentNames(cn.Args[i], dst) + findDependentNames(cn.Args[i], dst, directOnly) } case *IndexExpr: - findDependentNames(cn.X, dst) - findDependentNames(cn.Index, dst) + findDependentNames(cn.X, dst, directOnly) + findDependentNames(cn.Index, dst, directOnly) case *FuncLitExpr: - findDependentNames(&cn.Type, dst) + findDependentNames(&cn.Type, dst, directOnly) for _, n := range cn.GetExternNames() { dst[n] = struct{}{} } @@ -3640,17 +3704,17 @@ func findDependentNames(n Node, dst map[Name]struct{}) { case *ImportDecl: case *ValueDecl: if cn.Type != nil { - findDependentNames(cn.Type, dst) + findDependentNames(cn.Type, dst, directOnly) } for _, vx := range cn.Values { - findDependentNames(vx, dst) + findDependentNames(vx, dst, directOnly) } case *TypeDecl: - findDependentNames(cn.Type, dst) + findDependentNames(cn.Type, dst, directOnly) case *FuncDecl: - findDependentNames(&cn.Type, dst) + findDependentNames(&cn.Type, dst, directOnly) if cn.IsMethod { - findDependentNames(&cn.Recv, dst) + findDependentNames(&cn.Recv, dst, directOnly) for _, n := range cn.GetExternNames() { dst[n] = struct{}{} } @@ -3668,6 +3732,7 @@ func findDependentNames(n Node, dst map[Name]struct{}) { "unexpected node: %v (%v)", n, reflect.TypeOf(n))) } + return } // ---------------------------------------- @@ -3750,3 +3815,64 @@ func SaveBlockNodes(store Store, fn *FileNode) { return n, TRANS_CONTINUE }) } + +type Dependency struct { + Name string + Dependencies []*Dependency +} + +func hasCycle() (bool, string) { + visited := make(map[string]bool) + recStack := make(map[string]bool) + var cycle []string + + for depName := range dependencies { + if !visited[depName] { + if detectCycle(depName, visited, recStack, &cycle) { + return true, fmt.Sprintf("Cyclic dependency detected: %v\n", cycle) + } + } + } + return false, "" +} + +func detectCycle(depName string, visited, recStack map[string]bool, cycle *[]string) bool { + if dep, ok := dependencies[depName]; ok && dep != nil { + if !visited[depName] { + visited[depName] = true + recStack[depName] = true + *cycle = append(*cycle, depName) + + for _, d := range dep.Dependencies { + if !visited[d.Name] && detectCycle(d.Name, visited, recStack, cycle) { + return true + } else if recStack[d.Name] { + *cycle = append(*cycle, d.Name) + return true + } + } + } + } + recStack[depName] = false + if len(*cycle) > 0 && (*cycle)[len(*cycle)-1] == depName { + *cycle = (*cycle)[:len(*cycle)-1] + } + return false +} + +func insertDependency(name string, dependenciesList ...*Dependency) { + fmt.Println("---insertDependency, name: ", name) + fmt.Println("---insertDependency, dependenciesList: ", dependenciesList[0].Name) + dependencies[name] = &Dependency{Name: name, Dependencies: dependenciesList} +} + +func dumpDependencies() { + fmt.Println("Dependencies:") + for name, dep := range dependencies { + fmt.Printf("Dependency %s: ", name) + for _, d := range dep.Dependencies { + fmt.Printf("%s ", d.Name) + } + fmt.Println() + } +} diff --git a/gnovm/tests/debug/self_refer_struct2.gno b/gnovm/tests/debug/self_refer_struct2.gno new file mode 100644 index 00000000000..26622ab6369 --- /dev/null +++ b/gnovm/tests/debug/self_refer_struct2.gno @@ -0,0 +1,25 @@ +package main + +type A struct { + X B +} + +type B struct { + X C +} + +type C struct { + X A +} + +func main() { + var p, q A + println(p == q) +} + +// Error: +// main/debug/self_refer_struct2.gno:1: Cyclic dependency detected: [C A B C] +// : +// --- preprocess stack --- +// stack 0: file{ package main; type A (const-type main.A); type B (const-type main.B); type C (const-type main.C); func main() { var p, q A; println(p == q) } } +// ------------------------ From a758b1b3ea6b4e76208d4738dd027ec99989d1e4 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Tue, 7 May 2024 16:20:06 +0800 Subject: [PATCH 02/35] revert --- gnovm/pkg/gnolang/preprocess.go | 82 ++++++++++++++++----------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 0fca4fce1bf..49a06381737 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1980,7 +1980,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } if maybeRecursive { deps := make(map[Name]struct{}) - findDependentNames(n, deps, false) + findDependentNames(n, deps) fmt.Println("---deps: ", deps) fmt.Println("---names: ", names) for dn := range deps { @@ -3601,56 +3601,52 @@ func countNumArgs(store Store, last BlockNode, n *CallExpr) (numArgs int) { // This is to be run *after* preprocessing is done, // to determine the order of var decl execution // (which may include functions which may refer to package vars). -func findDependentNames(n Node, dst map[Name]struct{}, directOnly bool) { +func findDependentNames(n Node, dst map[Name]struct{}) { switch cn := n.(type) { case *NameExpr: fmt.Println("---name X, n: ", n) dst[cn.Name] = struct{}{} case *BasicLitExpr: case *BinaryExpr: - findDependentNames(cn.Left, dst, directOnly) - findDependentNames(cn.Right, dst, directOnly) + findDependentNames(cn.Left, dst) + findDependentNames(cn.Right, dst) case *SelectorExpr: - findDependentNames(cn.X, dst, directOnly) + findDependentNames(cn.X, dst) case *SliceExpr: - findDependentNames(cn.X, dst, directOnly) + findDependentNames(cn.X, dst) if cn.Low != nil { - findDependentNames(cn.Low, dst, directOnly) + findDependentNames(cn.Low, dst) } if cn.High != nil { - findDependentNames(cn.High, dst, directOnly) + findDependentNames(cn.High, dst) } if cn.Max != nil { - findDependentNames(cn.Max, dst, directOnly) + findDependentNames(cn.Max, dst) } case *StarExpr: fmt.Println("---star X, n: ", n) - if directOnly { - return - } else { - findDependentNames(cn.X, dst, directOnly) - } + findDependentNames(cn.X, dst) case *RefExpr: - findDependentNames(cn.X, dst, directOnly) + findDependentNames(cn.X, dst) case *TypeAssertExpr: - findDependentNames(cn.X, dst, directOnly) - findDependentNames(cn.Type, dst, directOnly) + findDependentNames(cn.X, dst) + findDependentNames(cn.Type, dst) case *UnaryExpr: - findDependentNames(cn.X, dst, directOnly) + findDependentNames(cn.X, dst) case *CompositeLitExpr: - findDependentNames(cn.Type, dst, directOnly) + findDependentNames(cn.Type, dst) ct := getType(cn.Type) switch ct.Kind() { case ArrayKind, SliceKind, MapKind: for _, kvx := range cn.Elts { if kvx.Key != nil { - findDependentNames(kvx.Key, dst, directOnly) + findDependentNames(kvx.Key, dst) } - findDependentNames(kvx.Value, dst, directOnly) + findDependentNames(kvx.Value, dst) } case StructKind: for _, kvx := range cn.Elts { - findDependentNames(kvx.Value, dst, directOnly) + findDependentNames(kvx.Value, dst) } default: panic(fmt.Sprintf( @@ -3658,44 +3654,44 @@ func findDependentNames(n Node, dst map[Name]struct{}, directOnly bool) { ct.String())) } case *FieldTypeExpr: - findDependentNames(cn.Type, dst, directOnly) + findDependentNames(cn.Type, dst) case *ArrayTypeExpr: - findDependentNames(cn.Elt, dst, directOnly) + findDependentNames(cn.Elt, dst) if cn.Len != nil { - findDependentNames(cn.Len, dst, directOnly) + findDependentNames(cn.Len, dst) } case *SliceTypeExpr: - findDependentNames(cn.Elt, dst, directOnly) + findDependentNames(cn.Elt, dst) case *InterfaceTypeExpr: for i := range cn.Methods { - findDependentNames(&cn.Methods[i], dst, directOnly) + findDependentNames(&cn.Methods[i], dst) } case *ChanTypeExpr: - findDependentNames(cn.Value, dst, directOnly) + findDependentNames(cn.Value, dst) case *FuncTypeExpr: for i := range cn.Params { - findDependentNames(&cn.Params[i], dst, directOnly) + findDependentNames(&cn.Params[i], dst) } for i := range cn.Results { - findDependentNames(&cn.Results[i], dst, directOnly) + findDependentNames(&cn.Results[i], dst) } case *MapTypeExpr: - findDependentNames(cn.Key, dst, directOnly) - findDependentNames(cn.Value, dst, directOnly) + findDependentNames(cn.Key, dst) + findDependentNames(cn.Value, dst) case *StructTypeExpr: for i := range cn.Fields { - findDependentNames(&cn.Fields[i], dst, directOnly) + findDependentNames(&cn.Fields[i], dst) } case *CallExpr: - findDependentNames(cn.Func, dst, directOnly) + findDependentNames(cn.Func, dst) for i := range cn.Args { - findDependentNames(cn.Args[i], dst, directOnly) + findDependentNames(cn.Args[i], dst) } case *IndexExpr: - findDependentNames(cn.X, dst, directOnly) - findDependentNames(cn.Index, dst, directOnly) + findDependentNames(cn.X, dst) + findDependentNames(cn.Index, dst) case *FuncLitExpr: - findDependentNames(&cn.Type, dst, directOnly) + findDependentNames(&cn.Type, dst) for _, n := range cn.GetExternNames() { dst[n] = struct{}{} } @@ -3704,17 +3700,17 @@ func findDependentNames(n Node, dst map[Name]struct{}, directOnly bool) { case *ImportDecl: case *ValueDecl: if cn.Type != nil { - findDependentNames(cn.Type, dst, directOnly) + findDependentNames(cn.Type, dst) } for _, vx := range cn.Values { - findDependentNames(vx, dst, directOnly) + findDependentNames(vx, dst) } case *TypeDecl: - findDependentNames(cn.Type, dst, directOnly) + findDependentNames(cn.Type, dst) case *FuncDecl: - findDependentNames(&cn.Type, dst, directOnly) + findDependentNames(&cn.Type, dst) if cn.IsMethod { - findDependentNames(&cn.Recv, dst, directOnly) + findDependentNames(&cn.Recv, dst) for _, n := range cn.GetExternNames() { dst[n] = struct{}{} } From 2776c881b9879af68962b2e9f7371eeb8024b2e4 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Tue, 7 May 2024 16:28:06 +0800 Subject: [PATCH 03/35] fixup --- gnovm/pkg/gnolang/preprocess.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 49a06381737..7694efbb0d6 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1957,9 +1957,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *StructType: *dst = *(tmp.(*StructType)) case *DeclaredType: - if dependencies == nil { - dependencies = make(map[string]*Dependency) - } fmt.Println("---type decl: ", n) if st, ok := tmp.(*StructType); ok { // check if fields contains declaredType @@ -3859,7 +3856,19 @@ func detectCycle(depName string, visited, recStack map[string]bool, cycle *[]str func insertDependency(name string, dependenciesList ...*Dependency) { fmt.Println("---insertDependency, name: ", name) fmt.Println("---insertDependency, dependenciesList: ", dependenciesList[0].Name) - dependencies[name] = &Dependency{Name: name, Dependencies: dependenciesList} + if dependencies == nil { + dependencies = make(map[string]*Dependency) + } + if _, exists := dependencies[name]; !exists { + dependencies[name] = &Dependency{Name: name} + } + + for _, dep := range dependenciesList { + if _, exists := dependencies[dep.Name]; !exists { + dependencies[dep.Name] = &Dependency{Name: dep.Name} + } + dependencies[name].Dependencies = append(dependencies[name].Dependencies, dependencies[dep.Name]) + } } func dumpDependencies() { From faa3920b153b0459fa2b7e186119e55dada67c0e Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Wed, 8 May 2024 10:58:14 +0800 Subject: [PATCH 04/35] fixup --- gnovm/pkg/gnolang/machine.go | 2 +- gnovm/pkg/gnolang/preprocess.go | 182 +++++++++++++--------- gnovm/tests/debug/self_refer_struct.gno | 13 ++ gnovm/tests/debug/self_refer_struct2.gno | 4 - gnovm/tests/debug/self_refer_struct_a.gno | 13 ++ gnovm/tests/debug/self_refer_struct_c.gno | 14 ++ gnovm/tests/debug/self_refer_struct_d.gno | 13 ++ 7 files changed, 163 insertions(+), 78 deletions(-) create mode 100644 gnovm/tests/debug/self_refer_struct.gno create mode 100644 gnovm/tests/debug/self_refer_struct_a.gno create mode 100644 gnovm/tests/debug/self_refer_struct_c.gno create mode 100644 gnovm/tests/debug/self_refer_struct_d.gno diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 9c0e1310efd..018fac66e64 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -559,7 +559,7 @@ func (m *Machine) runFiles(fns ...*FileNode) { // fb := pv.GetFileBlock(nil, fn.Name) // get dependencies of decl. deps := make(map[Name]struct{}) - findDependentNames(decl, deps, false) + findDependentNames(decl, deps) for dep := range deps { // if dep already defined as import, skip. if _, ok := fn.GetLocalIndex(dep); ok { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 7694efbb0d6..27671b06004 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -95,7 +95,8 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { } } -var dependencies = make(map[string]*Dependency) +// dependencies represents a slice of dependencies +var dependencies []*Dependency // This counter ensures (during testing) that certain functions // (like ConvertUntypedTo() for bigints and strings) @@ -627,18 +628,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // elide composite lit element (nested) composite types. elideCompositeElements(clx, clt) } - dumpDependencies() - // Check for cyclic dependencies - if ok, graph := hasCycle(); ok { - dependencies = nil - panic(graph) - } else { - fmt.Println("No cyclic dependency detected.") - } - switch n.(type) { - case *FileNode: - dependencies = nil - } }() // The main TRANS_LEAVE switch. @@ -1929,10 +1918,22 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } } - // TODO make note of constance in static block for - // future use, or consider "const paths". set as - // preprocessed. + // TODO make note of constance in static block for + // future use, or consider "const paths". set as + // preprocessed. + // TRANS_LEAVE ----------------------- + case *FileNode: + // clear fileNode wise + dumpDependencies() + // Check for cyclic dependencies + if ok, graph := hasCycle(); ok { + dependencies = nil + panic(graph) + } else { + fmt.Println("No cyclic dependency detected.") + } + dependencies = nil // TRANS_LEAVE ----------------------- case *TypeDecl: // Construct new Type, where any recursive @@ -1957,18 +1958,22 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *StructType: *dst = *(tmp.(*StructType)) case *DeclaredType: - fmt.Println("---type decl: ", n) + fmt.Println("---type decl, n, n.Type: ", n, n.Type) if st, ok := tmp.(*StructType); ok { + fmt.Println("---st.PkgPath: ", st.PkgPath) // check if fields contains declaredType maybeRecursive := false names := make([]Name, 0) for _, f := range st.Fields { fmt.Println("---f.Name: ", f.Name) - fmt.Println("---f.Type: ", f.Type) + fmt.Println("---f.Type, type of type: ", f.Type, reflect.TypeOf(f.Type)) if dt, ok := f.Type.(*DeclaredType); ok { // indirect does not count - maybeRecursive = true - fmt.Println("---dt.Name:", dt.Name) - names = append(names, dt.Name) + if st.PkgPath == dt.PkgPath { // not cross pkg + maybeRecursive = true + fmt.Println("---dt.Name:", dt.Name) + fmt.Println("---dt.PkgPath:", dt.PkgPath) + names = append(names, dt.Name) + } } if pt, ok := f.Type.(*PointerType); ok { // indirect does not count @@ -1984,7 +1989,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // if dep name is declared type for _, name := range names { if name == dn { - insertDependency(string(dst.Name), &Dependency{Name: string(dn)}) + insertDependency(dst.Name, dn) } } } @@ -3809,75 +3814,106 @@ func SaveBlockNodes(store Store, fn *FileNode) { }) } +// Dependency represents a node in the dependency graph type Dependency struct { - Name string + Name Dependencies []*Dependency } -func hasCycle() (bool, string) { - visited := make(map[string]bool) - recStack := make(map[string]bool) - var cycle []string - - for depName := range dependencies { - if !visited[depName] { - if detectCycle(depName, visited, recStack, &cycle) { - return true, fmt.Sprintf("Cyclic dependency detected: %v\n", cycle) +// insertDependency inserts a new dependency into the graph +func insertDependency(name Name, deps ...Name) { + fmt.Println("---insertDependency, name, deps: ", name, deps) + var dep *Dependency + for _, d := range dependencies { + if d.Name == name { + dep = d + break + } + } + fmt.Println("---dep 1: ", dep) + if dep == nil { + dep = &Dependency{Name: name} + dependencies = append(dependencies, dep) + } + fmt.Println("---dep 2: ", dep) + for _, depName := range deps { + println("---depName: ", depName) + var child *Dependency + for _, d := range dependencies { + if d.Name == depName { + child = d + break } } + fmt.Println("---child 1: ", child) + if child == nil { + child = &Dependency{Name: depName} + dependencies = append(dependencies, child) + } + fmt.Println("---child 2: ", child) + dep.Dependencies = append(dep.Dependencies, child) } - return false, "" } -func detectCycle(depName string, visited, recStack map[string]bool, cycle *[]string) bool { - if dep, ok := dependencies[depName]; ok && dep != nil { - if !visited[depName] { - visited[depName] = true - recStack[depName] = true - *cycle = append(*cycle, depName) - - for _, d := range dep.Dependencies { - if !visited[d.Name] && detectCycle(d.Name, visited, recStack, cycle) { - return true - } else if recStack[d.Name] { - *cycle = append(*cycle, d.Name) - return true - } - } +// dumpDependencies prints the current dependencies +func dumpDependencies() { + fmt.Println("---dump dependencies") + for _, dep := range dependencies { + fmt.Printf("%s -> ", dep.Name) + for _, d := range dep.Dependencies { + fmt.Printf("%s ", d.Name) } + fmt.Println() } - recStack[depName] = false - if len(*cycle) > 0 && (*cycle)[len(*cycle)-1] == depName { - *cycle = (*cycle)[:len(*cycle)-1] - } - return false } -func insertDependency(name string, dependenciesList ...*Dependency) { - fmt.Println("---insertDependency, name: ", name) - fmt.Println("---insertDependency, dependenciesList: ", dependenciesList[0].Name) - if dependencies == nil { - dependencies = make(map[string]*Dependency) - } - if _, exists := dependencies[name]; !exists { - dependencies[name] = &Dependency{Name: name} - } +// hasCycle checks if there is a cycle in the dependencies graph +func hasCycle() (bool, string) { + visited := make(map[Name]bool) + reStack := make(map[Name]bool) + var cycle []Name - for _, dep := range dependenciesList { - if _, exists := dependencies[dep.Name]; !exists { - dependencies[dep.Name] = &Dependency{Name: dep.Name} + for _, dep := range dependencies { + if detectCycle(dep, visited, reStack, &cycle) { + fmt.Println("cycle: ", cycle) + return true, fmt.Sprintf("Cyclic dependency detected: %v", cycle) } - dependencies[name].Dependencies = append(dependencies[name].Dependencies, dependencies[dep.Name]) } + return false, "" } -func dumpDependencies() { - fmt.Println("Dependencies:") - for name, dep := range dependencies { - fmt.Printf("Dependency %s: ", name) - for _, d := range dep.Dependencies { - fmt.Printf("%s ", d.Name) +// detectCycle detects cycle using DFS traversal +func detectCycle(dep *Dependency, visited, recStack map[Name]bool, cycle *[]Name) bool { + fmt.Printf("Traversing node: %s\n", dep.Name) + visited[dep.Name] = true + recStack[dep.Name] = true + *cycle = append(*cycle, dep.Name) + + fmt.Println("Visited map:", visited) + fmt.Println("Recursion stack:", recStack) + + for _, d := range dep.Dependencies { + if !visited[d.Name] { + if detectCycle(d, visited, recStack, cycle) { + return true + } + } else if recStack[d.Name] { + // If the node is in the recursion stack, a cycle is found + for _, node := range *cycle { + if node == d.Name { + startIndex := 0 + for ; (*cycle)[startIndex] != d.Name; startIndex++ { + } + *cycle = append((*cycle)[startIndex:], d.Name) + return true + } + } } - fmt.Println() } + + // Backtrack: Remove the last node from the cycle slice and mark as not in recStack + delete(recStack, dep.Name) + //*cycle = (*cycle)[:len(*cycle)-1] + + return false } diff --git a/gnovm/tests/debug/self_refer_struct.gno b/gnovm/tests/debug/self_refer_struct.gno new file mode 100644 index 00000000000..bf33debc8d3 --- /dev/null +++ b/gnovm/tests/debug/self_refer_struct.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + T S +} + +func main() { + var a, b S + println(a == b) +} + +// Error: +// main/debug/self_refer_struct.gno:1: Cyclic dependency detected: [S S] diff --git a/gnovm/tests/debug/self_refer_struct2.gno b/gnovm/tests/debug/self_refer_struct2.gno index 26622ab6369..76f12d2beef 100644 --- a/gnovm/tests/debug/self_refer_struct2.gno +++ b/gnovm/tests/debug/self_refer_struct2.gno @@ -19,7 +19,3 @@ func main() { // Error: // main/debug/self_refer_struct2.gno:1: Cyclic dependency detected: [C A B C] -// : -// --- preprocess stack --- -// stack 0: file{ package main; type A (const-type main.A); type B (const-type main.B); type C (const-type main.C); func main() { var p, q A; println(p == q) } } -// ------------------------ diff --git a/gnovm/tests/debug/self_refer_struct_a.gno b/gnovm/tests/debug/self_refer_struct_a.gno new file mode 100644 index 00000000000..552c086c91b --- /dev/null +++ b/gnovm/tests/debug/self_refer_struct_a.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + T *S +} + +func main() { + var a, b S + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/debug/self_refer_struct_c.gno b/gnovm/tests/debug/self_refer_struct_c.gno new file mode 100644 index 00000000000..eb59b67b590 --- /dev/null +++ b/gnovm/tests/debug/self_refer_struct_c.gno @@ -0,0 +1,14 @@ +package main + +import "time" + +type Duration struct { + t time.Duration +} + +func main() { + var a, b Duration + println(a == b) +} + +// Error: diff --git a/gnovm/tests/debug/self_refer_struct_d.gno b/gnovm/tests/debug/self_refer_struct_d.gno new file mode 100644 index 00000000000..177bac5c2a2 --- /dev/null +++ b/gnovm/tests/debug/self_refer_struct_d.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + S +} + +func main() { + var a, b S + println(a == b) +} + +// Error: +// main/debug/self_refer_struct_d.gno:1: Cyclic dependency detected: [S S] From 05ebcf21f872776ff581480f130a8b8d2af28e74 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Wed, 8 May 2024 19:19:48 +0800 Subject: [PATCH 05/35] check on predefineFileset, proper err messages --- examples/gno.land/r/demo/recursive/foo.gno | 9 ++ examples/gno.land/r/demo/recursive/main.gno | 10 ++ .../r/demo/recursive/z_0_filetest.gno | 12 ++ gnovm/pkg/gnolang/go2gno.go | 26 ++++- gnovm/pkg/gnolang/machine.go | 4 + gnovm/pkg/gnolang/nodes.go | 1 + gnovm/pkg/gnolang/preprocess.go | 107 +++++++++++------- gnovm/pkg/gnolang/store.go | 2 +- gnovm/pkg/gnolang/types.go | 4 + gnovm/tests/debug/self_refer_interface.gno | 23 ++++ gnovm/tests/debug/self_refer_struct.gno | 2 +- gnovm/tests/debug/self_refer_struct2.gno | 2 +- gnovm/tests/debug/self_refer_struct_d.gno | 2 +- 13 files changed, 159 insertions(+), 45 deletions(-) create mode 100644 examples/gno.land/r/demo/recursive/foo.gno create mode 100644 examples/gno.land/r/demo/recursive/main.gno create mode 100644 examples/gno.land/r/demo/recursive/z_0_filetest.gno create mode 100644 gnovm/tests/debug/self_refer_interface.gno diff --git a/examples/gno.land/r/demo/recursive/foo.gno b/examples/gno.land/r/demo/recursive/foo.gno new file mode 100644 index 00000000000..081ab9280d0 --- /dev/null +++ b/examples/gno.land/r/demo/recursive/foo.gno @@ -0,0 +1,9 @@ +package recur + +type B struct { + X C +} + +type C struct { + X A +} diff --git a/examples/gno.land/r/demo/recursive/main.gno b/examples/gno.land/r/demo/recursive/main.gno new file mode 100644 index 00000000000..8d5e2ffdacc --- /dev/null +++ b/examples/gno.land/r/demo/recursive/main.gno @@ -0,0 +1,10 @@ +package recur + +type A struct { + X B +} + +func Do() { + var p, q A + println(p == q) +} diff --git a/examples/gno.land/r/demo/recursive/z_0_filetest.gno b/examples/gno.land/r/demo/recursive/z_0_filetest.gno new file mode 100644 index 00000000000..0821ce9bb7a --- /dev/null +++ b/examples/gno.land/r/demo/recursive/z_0_filetest.gno @@ -0,0 +1,12 @@ +package main + +import ( + "gno.land/r/demo/recursive" +) + +func main() { + recursive.Do() +} + +// Error: +// main/gno.land/r/demo/recursive/z_0_filetest.gno:1: main.gno: Cyclic dependency detected: A (Line: 3) -> B (Line: 3) -> C (Line: 7) -> A (Line: 3) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 8ca985352a7..401bcd13fb5 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -100,6 +100,7 @@ func MustParseExpr(expr string) Expr { // resulting AST -- the resulting FileNode is returned, together with any other // error (including panics, which are recovered) from [Go2Gno]. func ParseFile(filename string, body string) (fn *FileNode, err error) { + fmt.Println("---ParseFile") // Use go parser to parse the body. fs := token.NewFileSet() f, err := parser.ParseFile(fs, filename, body, parser.ParseComments|parser.DeclarationErrors) @@ -124,17 +125,25 @@ func ParseFile(filename string, body string) (fn *FileNode, err error) { // parse with Go2Gno. fn = Go2Gno(fs, f).(*FileNode) fn.Name = Name(filename) + for _, d := range fn.Decls { + fmt.Println("---decl: ", d) + } return fn, nil } func setLoc(fs *token.FileSet, pos token.Pos, n Node) Node { + fmt.Println("---setLoc, n, type of n: ", n, reflect.TypeOf(n)) + fmt.Printf("---setLoc, addr of n: %p \n", n) posn := fs.Position(pos) + fmt.Println("---setLoc, posn, posn.Line: ", posn, posn.Line) n.SetLine(posn.Line) + fmt.Println("---after setLoc, n.line: ", n.GetLine()) return n } // If gon is a *ast.File, the name must be filled later. func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { + fmt.Println("---Go2Gno") if gon == nil { return nil } @@ -447,9 +456,11 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { case *ast.GenDecl: panic("unexpected *ast.GenDecl; use toDecls(fs,) instead") case *ast.File: + fmt.Println("---go 2 gno, file") pkgName := Name(gon.Name.Name) decls := make([]Decl, 0, len(gon.Decls)) for _, d := range gon.Decls { + fmt.Println("---1, d: ", d) if gd, ok := d.(*ast.GenDecl); ok { decls = append(decls, toDecls(fs, gd)...) } else { @@ -605,6 +616,7 @@ func toSimp(fs *token.FileSet, gos ast.Stmt) Stmt { } func toDecl(fs *token.FileSet, god ast.Decl) Decl { + fmt.Println("---toDecl..., god: ", god) gnod := Go2Gno(fs, god) if gnod == nil { return nil @@ -614,6 +626,8 @@ func toDecl(fs *token.FileSet, god ast.Decl) Decl { } func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { + fmt.Println("---toDecls..., gd: ", gd) + ds = make([]Decl, 0, len(gd.Specs)) /* Within a parenthesized const declaration list the @@ -627,14 +641,22 @@ func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { for si, s := range gd.Specs { switch s := s.(type) { case *ast.TypeSpec: + fmt.Println("---type spec") name := toName(s.Name) + fmt.Println("---type spec, name: ", name) tipe := toExpr(fs, s.Type) + fmt.Println("---type spec, tipe: ", tipe) alias := s.Assign != 0 - ds = append(ds, &TypeDecl{ + td := &TypeDecl{ NameExpr: NameExpr{Name: name}, Type: tipe, IsAlias: alias, - }) + } + fmt.Println("---type spec, td: ", td) + posn := fs.Position(gd.Pos()) + td.SetLine(posn.Line) + fmt.Println("---type spec, td.line: ", td.GetLine()) + ds = append(ds, td) case *ast.ValueSpec: if gd.Tok == token.CONST { var names []NameExpr diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 018fac66e64..817ace743aa 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -490,6 +490,7 @@ func (m *Machine) RunFiles(fns ...*FileNode) { } func (m *Machine) runFiles(fns ...*FileNode) { + fmt.Println("---runFiles, fns: ", fns) // Files' package names must match the machine's active one. // if there is one. for _, fn := range fns { @@ -524,6 +525,8 @@ func (m *Machine) runFiles(fns ...*FileNode) { // Preprocess each new file. for _, fn := range fns { + fmt.Println("---pn: ", pn) + //fmt.Println("---fn: ", fn) // Preprocess file. // NOTE: Most of the declaration is handled by // Preprocess and any constant values set on @@ -535,6 +538,7 @@ func (m *Machine) runFiles(fns ...*FileNode) { if debug { debug.Printf("PREPROCESSED FILE: %v\n", fn) } + //fmt.Printf("PREPROCESSED FILE: %v\n", fn) // After preprocessing, save blocknodes to store. SaveBlockNodes(m.Store, fn) // Make block for fn. diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 8f2c5054a8a..24acc33e528 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1171,6 +1171,7 @@ func ReadMemPackageFromList(list []string, pkgPath string) *std.MemPackage { // If one of the files has a different package name than memPkg.Name, // or [ParseFile] returns an error, ParseMemPackage panics. func ParseMemPackage(memPkg *std.MemPackage) (fset *FileSet) { + fmt.Println("---parse mem package") fset = &FileSet{} for _, mfile := range memPkg.Files { if !strings.HasSuffix(mfile.Name, ".gno") || diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 27671b06004..91ef83c1603 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -4,6 +4,7 @@ import ( "fmt" "math/big" "reflect" + "strings" "sync/atomic" "github.com/gnolang/gno/tm2/pkg/errors" @@ -14,6 +15,15 @@ import ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { + defer func() { + // clear fileNode wise + fmt.Println("---start clearing cycle... ") + dumpDependencies() + // Check for cyclic dependencies + assertNoCycle() + fmt.Println("No cyclic dependency detected.") + }() + fmt.Println("---predefine fileset, fset: ", fset) // First, initialize all file nodes and connect to package node. for _, fn := range fset.Files { SetNodeLocations(pn.PkgPath, string(fn.Name), fn) @@ -54,6 +64,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // dependent) } else { // recursively predefine dependencies. + fmt.Println("---going to predefine, d.line: ", d.GetLine()) d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } @@ -123,6 +134,8 @@ var preprocessing atomic.Int32 // - Assigns BlockValuePath to NameExprs. // - TODO document what it does. func Preprocess(store Store, ctx BlockNode, n Node) Node { + //fmt.Println("---preprocess, ctx: ", ctx) + //fmt.Println("---preprocess, n: ", n) // Increment preprocessing counter while preprocessing. preprocessing.Add(1) defer preprocessing.Add(-1) @@ -134,6 +147,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // if n is file node, set node locations recursively. if fn, ok := n.(*FileNode); ok { + //fmt.Println("---is file node, fn: ", fn) pkgPath := ctx.(*PackageNode).PkgPath fileName := string(fn.Name) SetNodeLocations(pkgPath, fileName, fn) @@ -181,6 +195,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { if debug { debug.Printf("Preprocess %s (%v) stage:%v\n", n.String(), reflect.TypeOf(n), stage) } + //fmt.Printf("Preprocess %s (%v) stage:%v\n", n.String(), reflect.TypeOf(n), stage) switch stage { // ---------------------------------------- @@ -503,6 +518,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } // TRANS_BLOCK ----------------------- case *FileNode: + //fmt.Println("---trans_block, fileNode, n :", n) // only for imports. pushInitBlock(n, &last, &stack) { @@ -1922,20 +1938,9 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // future use, or consider "const paths". set as // preprocessed. - // TRANS_LEAVE ----------------------- - case *FileNode: - // clear fileNode wise - dumpDependencies() - // Check for cyclic dependencies - if ok, graph := hasCycle(); ok { - dependencies = nil - panic(graph) - } else { - fmt.Println("No cyclic dependency detected.") - } - dependencies = nil // TRANS_LEAVE ----------------------- case *TypeDecl: + fmt.Println("---trans_leave, type decl") // Construct new Type, where any recursive // references refer to the old Type declared // during *TypeDecl:ENTER. Then, copy over the @@ -1989,10 +1994,22 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // if dep name is declared type for _, name := range names { if name == dn { - insertDependency(dst.Name, dn) + loc := last.GetLocation() + line := n.GetLine() + fmt.Println("---Loc: ", loc) + fmt.Println("---n: ", n) + fmt.Println("---line: ", line) + insertDependency(dst.Name, line, last.GetLocation(), dn) } } } + //// assert no cycle + //// clear fileNode wise + //fmt.Println("---start clearing cycle... ") + //dumpDependencies() + //// Check for cyclic dependencies + //assertNoCycle() + //fmt.Println("No cyclic dependency detected.") } } // if store has this type, use that. @@ -3014,9 +3031,12 @@ func checkIntegerType(xt Type) { // preprocess-able before other file-level declarations are // preprocessed). func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { + fmt.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { + fmt.Println("---defer predefine now, d: ", d) // before re-throwing the error, append location information to message. + fmt.Println("---nline: ", d.GetLine()) loc := last.GetLocation() if nline := d.GetLine(); nline > 0 { loc.Line = nline @@ -3151,6 +3171,10 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De case *ValueDecl: return Preprocess(store, last, cd).(Decl), true case *TypeDecl: + fmt.Println("---predefine now 2, type decl: ", d) + fmt.Println("---line", d.GetLine()) + fmt.Println("---name", cd.Name) + fmt.Println("---nx.line", cd.NameExpr.GetLine()) return Preprocess(store, last, cd).(Decl), true default: return d, false @@ -3592,21 +3616,12 @@ func countNumArgs(store Store, last BlockNode, n *CallExpr) (numArgs int) { } } -//func findDependentNames2(n Node, info map[Name]bool) { -// dst := make(map[Name]struct{}) -// indirect := findDependentNames(n, dst) -// if !indirect { -// info -// } -//} - // This is to be run *after* preprocessing is done, // to determine the order of var decl execution // (which may include functions which may refer to package vars). func findDependentNames(n Node, dst map[Name]struct{}) { switch cn := n.(type) { case *NameExpr: - fmt.Println("---name X, n: ", n) dst[cn.Name] = struct{}{} case *BasicLitExpr: case *BinaryExpr: @@ -3626,7 +3641,6 @@ func findDependentNames(n Node, dst map[Name]struct{}) { findDependentNames(cn.Max, dst) } case *StarExpr: - fmt.Println("---star X, n: ", n) findDependentNames(cn.X, dst) case *RefExpr: findDependentNames(cn.X, dst) @@ -3815,24 +3829,29 @@ func SaveBlockNodes(store Store, fn *FileNode) { } // Dependency represents a node in the dependency graph +// used to detect cycle definition of struct in `PredefineFileSet`. type Dependency struct { Name + Line int + Loc Location // file info Dependencies []*Dependency } // insertDependency inserts a new dependency into the graph -func insertDependency(name Name, deps ...Name) { +func insertDependency(name Name, line int, loc Location, deps ...Name) { fmt.Println("---insertDependency, name, deps: ", name, deps) var dep *Dependency for _, d := range dependencies { if d.Name == name { dep = d + dep.Line = line + dep.Loc = loc break } } fmt.Println("---dep 1: ", dep) if dep == nil { - dep = &Dependency{Name: name} + dep = &Dependency{Name: name, Line: line, Loc: loc} dependencies = append(dependencies, dep) } fmt.Println("---dep 2: ", dep) @@ -3859,35 +3878,43 @@ func insertDependency(name Name, deps ...Name) { func dumpDependencies() { fmt.Println("---dump dependencies") for _, dep := range dependencies { - fmt.Printf("%s -> ", dep.Name) + fmt.Printf("%s, %d -> ", dep.Name, dep.Line) for _, d := range dep.Dependencies { - fmt.Printf("%s ", d.Name) + fmt.Printf("%s, %d ", d.Name, d.Line) } fmt.Println() } } -// hasCycle checks if there is a cycle in the dependencies graph -func hasCycle() (bool, string) { +// assertNoCycle checks if there is a cycle in the dependencies graph +func assertNoCycle() { + defer func() { + dependencies = nil + }() visited := make(map[Name]bool) reStack := make(map[Name]bool) - var cycle []Name + var cycle []*Dependency for _, dep := range dependencies { if detectCycle(dep, visited, reStack, &cycle) { fmt.Println("cycle: ", cycle) - return true, fmt.Sprintf("Cyclic dependency detected: %v", cycle) + cycleNames := make([]string, len(cycle)) + for i, d := range cycle { + cycleNames[i] = fmt.Sprintf("%s (Line: %d)", d.Name, d.Line) + } + cycleMsg := strings.Join(cycleNames, " -> ") + panic(fmt.Sprintf("%s: Cyclic dependency detected: %v\n", cycle[0].Loc.File, cycleMsg)) + //panic(fmt.Sprintf("Cyclic dependency detected: %v", cycle)) } } - return false, "" } // detectCycle detects cycle using DFS traversal -func detectCycle(dep *Dependency, visited, recStack map[Name]bool, cycle *[]Name) bool { - fmt.Printf("Traversing node: %s\n", dep.Name) +func detectCycle(dep *Dependency, visited, recStack map[Name]bool, cycle *[]*Dependency) bool { + fmt.Printf("Traversing node: %s (Line: %d)\n", dep.Name, dep.Line) visited[dep.Name] = true recStack[dep.Name] = true - *cycle = append(*cycle, dep.Name) + *cycle = append(*cycle, dep) fmt.Println("Visited map:", visited) fmt.Println("Recursion stack:", recStack) @@ -3895,16 +3922,18 @@ func detectCycle(dep *Dependency, visited, recStack map[Name]bool, cycle *[]Name for _, d := range dep.Dependencies { if !visited[d.Name] { if detectCycle(d, visited, recStack, cycle) { + fmt.Println("---cycle detected: ", cycle) + dumpDependencies() return true } } else if recStack[d.Name] { // If the node is in the recursion stack, a cycle is found for _, node := range *cycle { - if node == d.Name { + if node == d { startIndex := 0 - for ; (*cycle)[startIndex] != d.Name; startIndex++ { + for ; (*cycle)[startIndex] != d; startIndex++ { } - *cycle = append((*cycle)[startIndex:], d.Name) + *cycle = append((*cycle)[startIndex:], d) return true } } @@ -3913,7 +3942,7 @@ func detectCycle(dep *Dependency, visited, recStack map[Name]bool, cycle *[]Name // Backtrack: Remove the last node from the cycle slice and mark as not in recStack delete(recStack, dep.Name) - //*cycle = (*cycle)[:len(*cycle)-1] + *cycle = (*cycle)[:len(*cycle)-1] return false } diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index d15976ec262..dc465a1b9c3 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -481,7 +481,7 @@ func (ds *defaultStore) SetBlockNode(bn BlockNode) { // save node to backend. if ds.baseStore != nil { // TODO: implement copyValueWithRefs() for Nodes. - // key := backendNodeKey(loc) + // key := backendNodeKey(Loc) // ds.backend.Set([]byte(key), bz) } // save node to cache. diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index e1814e8f243..637e593be26 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1384,6 +1384,7 @@ type DeclaredType struct { // returns an unsealed *DeclaredType. // do not use for aliases. func declareWith(pkgPath string, name Name, b Type) *DeclaredType { + //fmt.Println("---declareWith, pkgPath, name, type: ", pkgPath, name, b) dt := &DeclaredType{ PkgPath: pkgPath, Name: name, @@ -1428,7 +1429,10 @@ func (dt *DeclaredType) checkSeal() { } func (dt *DeclaredType) TypeID() TypeID { + //fmt.Println("---TypeID(), for dt: ", dt) if dt.typeid.IsZero() { + //fmt.Println("is 0!!!!!") + //panic("is 0!!!!!!!!!!!!!!!1") dt.typeid = DeclaredTypeID(dt.PkgPath, dt.Name) } return dt.typeid diff --git a/gnovm/tests/debug/self_refer_interface.gno b/gnovm/tests/debug/self_refer_interface.gno new file mode 100644 index 00000000000..73858b2ea1b --- /dev/null +++ b/gnovm/tests/debug/self_refer_interface.gno @@ -0,0 +1,23 @@ +package main + +type SelfReferencing interface { + Self() SelfReferencing +} + +type Implementation struct { + // Some implementation details... +} + +func (impl Implementation) Self() SelfReferencing { + return &impl +} + +func main() { + var obj Implementation + var intf SelfReferencing = obj + _ = intf.Self() + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/debug/self_refer_struct.gno b/gnovm/tests/debug/self_refer_struct.gno index bf33debc8d3..1d32c8324f3 100644 --- a/gnovm/tests/debug/self_refer_struct.gno +++ b/gnovm/tests/debug/self_refer_struct.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/debug/self_refer_struct.gno:1: Cyclic dependency detected: [S S] +// debug/self_refer_struct.gno: Cyclic dependency detected: S (Line: 3) -> S (Line: 3) diff --git a/gnovm/tests/debug/self_refer_struct2.gno b/gnovm/tests/debug/self_refer_struct2.gno index 76f12d2beef..ff333e6d444 100644 --- a/gnovm/tests/debug/self_refer_struct2.gno +++ b/gnovm/tests/debug/self_refer_struct2.gno @@ -18,4 +18,4 @@ func main() { } // Error: -// main/debug/self_refer_struct2.gno:1: Cyclic dependency detected: [C A B C] +// debug/self_refer_struct2.gno: Cyclic dependency detected: C (Line: 11) -> A (Line: 3) -> B (Line: 7) -> C (Line: 11) diff --git a/gnovm/tests/debug/self_refer_struct_d.gno b/gnovm/tests/debug/self_refer_struct_d.gno index 177bac5c2a2..2f347bb4b7c 100644 --- a/gnovm/tests/debug/self_refer_struct_d.gno +++ b/gnovm/tests/debug/self_refer_struct_d.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/debug/self_refer_struct_d.gno:1: Cyclic dependency detected: [S S] +// debug/self_refer_struct_d.gno: Cyclic dependency detected: S (Line: 3) -> S (Line: 3) From bff974b02f929bc2bdcc23b0f83d8ef2d01c8e36 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Thu, 9 May 2024 23:27:15 +0800 Subject: [PATCH 06/35] optimize cycle check logic --- .../r/demo/recursive/z_0_filetest.gno | 2 +- gnovm/pkg/gnolang/preprocess.go | 205 +++++++++--------- gnovm/tests/debug/self_refer_struct.gno | 2 +- gnovm/tests/debug/self_refer_struct2.gno | 2 +- gnovm/tests/debug/self_refer_struct2a.gno | 21 ++ gnovm/tests/debug/self_refer_struct_c.gno | 3 +- gnovm/tests/debug/self_refer_struct_d.gno | 2 +- 7 files changed, 130 insertions(+), 107 deletions(-) create mode 100644 gnovm/tests/debug/self_refer_struct2a.gno diff --git a/examples/gno.land/r/demo/recursive/z_0_filetest.gno b/examples/gno.land/r/demo/recursive/z_0_filetest.gno index 0821ce9bb7a..9ee378e9923 100644 --- a/examples/gno.land/r/demo/recursive/z_0_filetest.gno +++ b/examples/gno.land/r/demo/recursive/z_0_filetest.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/gno.land/r/demo/recursive/z_0_filetest.gno:1: main.gno: Cyclic dependency detected: A (Line: 3) -> B (Line: 3) -> C (Line: 7) -> A (Line: 3) +// main/gno.land/r/demo/recursive/z_0_filetest.gno:1: Cyclic dependency detected: A(File: main.gno, Line: 3) -> B(File: foo.gno, Line: 3) -> C(File: foo.gno, Line: 7) -> A(File: main.gno, Line: 3) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 91ef83c1603..897dcac60e0 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -15,15 +15,19 @@ import ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { + fmt.Println("---predefine fileset, fset: ", fset) defer func() { - // clear fileNode wise - fmt.Println("---start clearing cycle... ") - dumpDependencies() - // Check for cyclic dependencies - assertNoCycle() - fmt.Println("No cyclic dependency detected.") + fmt.Println("---defer in predefine fileSet") + // Check for cyclic declGraph + if len(declGraph) != 0 { + fmt.Println("---graph no empty, start checking cycle... ") + dumpGraph() + assertNoCycle() + fmt.Println("No cyclic dependency detected.") + } else { + fmt.Println("---do nothing") + } }() - fmt.Println("---predefine fileset, fset: ", fset) // First, initialize all file nodes and connect to package node. for _, fn := range fset.Files { SetNodeLocations(pn.PkgPath, string(fn.Name), fn) @@ -45,7 +49,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // (e.g. through recursion for a // dependent) } else { - // recursively predefine dependencies. + // recursively predefine declGraph. d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } @@ -63,8 +67,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // (e.g. through recursion for a // dependent) } else { - // recursively predefine dependencies. - fmt.Println("---going to predefine, d.line: ", d.GetLine()) + // recursively predefine declGraph. d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } @@ -82,7 +85,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // (e.g. through recursion for a // dependent) } else { - // recursively predefine dependencies. + // recursively predefine declGraph. d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } @@ -98,7 +101,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // skip declarations already predefined (e.g. // through recursion for a dependent) } else { - // recursively predefine dependencies. + // recursively predefine declGraph. d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } @@ -106,8 +109,8 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { } } -// dependencies represents a slice of dependencies -var dependencies []*Dependency +// declGraph represents a slice of declGraph +var declGraph []*DeclNode // This counter ensures (during testing) that certain functions // (like ConvertUntypedTo() for bigints and strings) @@ -238,7 +241,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // skip declarations already predefined // (e.g. through recursion for a dependent) } else { - // recursively predefine dependencies. + // recursively predefine declGraph. d2, ppd := predefineNow(store, last, n.(Decl)) if ppd { return d2, TRANS_SKIP @@ -538,7 +541,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // recursion for a dependent) } else { // recursively predefine - // dependencies. + // declGraph. d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } @@ -555,7 +558,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // recursion for a dependent) } else { // recursively predefine - // dependencies. + // declGraph. d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } @@ -572,7 +575,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // recursion for a dependent) } else { // recursively predefine - // dependencies. + // declGraph. d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } @@ -588,7 +591,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // recursion for a dependent) } else { // recursively predefine - // dependencies. + // declGraph. d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } @@ -1965,51 +1968,37 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *DeclaredType: fmt.Println("---type decl, n, n.Type: ", n, n.Type) if st, ok := tmp.(*StructType); ok { - fmt.Println("---st.PkgPath: ", st.PkgPath) // check if fields contains declaredType maybeRecursive := false names := make([]Name, 0) for _, f := range st.Fields { fmt.Println("---f.Name: ", f.Name) fmt.Println("---f.Type, type of type: ", f.Type, reflect.TypeOf(f.Type)) - if dt, ok := f.Type.(*DeclaredType); ok { // indirect does not count - if st.PkgPath == dt.PkgPath { // not cross pkg + if fdt, ok := f.Type.(*DeclaredType); ok { // indirect does not count + fmt.Println("---st.PkgPath: ", st.PkgPath) + fmt.Println("---fdt.PkgPath: ", fdt.PkgPath) + fmt.Println("---fdt.Name:", fdt.Name) + fmt.Println("---fdt.PkgPath:", fdt.PkgPath) + + if st.PkgPath == fdt.PkgPath { // not cross pkg + fmt.Println("---match pkg path!") maybeRecursive = true - fmt.Println("---dt.Name:", dt.Name) - fmt.Println("---dt.PkgPath:", dt.PkgPath) - names = append(names, dt.Name) + names = append(names, fdt.Name) + } else { + fmt.Println("---pkg path no match!") } } - if pt, ok := f.Type.(*PointerType); ok { // indirect does not count - fmt.Println("---it is pointer type, pt: ", pt) - } + //if pt, ok := f.Type.(*PointerType); ok { // indirect does not count + // fmt.Println("---it is pointer type, pt: ", pt) + //} } if maybeRecursive { - deps := make(map[Name]struct{}) - findDependentNames(n, deps) - fmt.Println("---deps: ", deps) fmt.Println("---names: ", names) - for dn := range deps { - // if dep name is declared type - for _, name := range names { - if name == dn { - loc := last.GetLocation() - line := n.GetLine() - fmt.Println("---Loc: ", loc) - fmt.Println("---n: ", n) - fmt.Println("---line: ", line) - insertDependency(dst.Name, line, last.GetLocation(), dn) - } - } + for _, name := range names { + line := n.GetLine() + insertDeclNode(dst.Name, line, last.GetLocation(), name) } - //// assert no cycle - //// clear fileNode wise - //fmt.Println("---start clearing cycle... ") - //dumpDependencies() - //// Check for cyclic dependencies - //assertNoCycle() - //fmt.Println("No cyclic dependency detected.") } } // if store has this type, use that. @@ -3023,15 +3012,16 @@ func checkIntegerType(xt Type) { // // First, tryPredefine(), which first predefines with placeholder // values/types to support recursive types, then returns yet -// un-predefined dependencies. +// un-predefined declGraph. // // Second, which immediately preprocesses type/value declarations -// after dependencies have first been predefined, or partially +// after declGraph have first been predefined, or partially // preprocesses function declarations (which may not be completely // preprocess-able before other file-level declarations are // preprocessed). func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { - fmt.Println("---predefine now, d.line", d.GetLine()) + fmt.Println("---predefine now, d", d) + //fmt.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { fmt.Println("---defer predefine now, d: ", d) @@ -3045,6 +3035,7 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { // NOTE: gotuna/gorilla expects error exceptions. panic(errors.Wrap(rerr, loc.String())) } else { + fmt.Println("---defer predefine now ,loc: ", loc.String()) // NOTE: gotuna/gorilla expects error exceptions. panic(fmt.Errorf("%s: %v", loc.String(), r)) } @@ -3064,7 +3055,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De } m[dn] = struct{}{} } - // recursively predefine dependencies. + // recursively predefine declGraph. for { un := tryPredefine(store, last, d) if un != "" { @@ -3250,7 +3241,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } } case *TypeDecl: - // before looking for dependencies, predefine empty type. + // before looking for declGraph, predefine empty type. last2 := skipFile(last) _, ok := last2.GetLocalIndex(d.Name) if !ok { @@ -3333,7 +3324,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { last2.Define(d.Name, asValue(t)) d.Path = last.GetPathForName(store, d.Name) } - // after predefinitions, return any undefined dependencies. + // after predefinitions, return any undefined declGraph. un = findUndefined(store, last, d.Type) if un != "" { return @@ -3828,20 +3819,21 @@ func SaveBlockNodes(store Store, fn *FileNode) { }) } -// Dependency represents a node in the dependency graph +// DeclNode represents a node in the dependency graph // used to detect cycle definition of struct in `PredefineFileSet`. -type Dependency struct { +type DeclNode struct { Name Line int Loc Location // file info - Dependencies []*Dependency + Dependencies []*DeclNode } -// insertDependency inserts a new dependency into the graph -func insertDependency(name Name, line int, loc Location, deps ...Name) { - fmt.Println("---insertDependency, name, deps: ", name, deps) - var dep *Dependency - for _, d := range dependencies { +// insertDeclNode inserts a new dependency into the graph +func insertDeclNode(name Name, line int, loc Location, deps ...Name) { + fmt.Println("---insertDeclNode, name, deps: ", name, deps) + fmt.Println("---insertDeclNode, loc: ", loc) + var dep *DeclNode + for _, d := range declGraph { if d.Name == name { dep = d dep.Line = line @@ -3851,14 +3843,14 @@ func insertDependency(name Name, line int, loc Location, deps ...Name) { } fmt.Println("---dep 1: ", dep) if dep == nil { - dep = &Dependency{Name: name, Line: line, Loc: loc} - dependencies = append(dependencies, dep) + dep = &DeclNode{Name: name, Line: line, Loc: loc} + declGraph = append(declGraph, dep) } fmt.Println("---dep 2: ", dep) for _, depName := range deps { println("---depName: ", depName) - var child *Dependency - for _, d := range dependencies { + var child *DeclNode + for _, d := range declGraph { if d.Name == depName { child = d break @@ -3866,83 +3858,92 @@ func insertDependency(name Name, line int, loc Location, deps ...Name) { } fmt.Println("---child 1: ", child) if child == nil { - child = &Dependency{Name: depName} - dependencies = append(dependencies, child) + child = &DeclNode{Name: depName} + declGraph = append(declGraph, child) } fmt.Println("---child 2: ", child) dep.Dependencies = append(dep.Dependencies, child) } } -// dumpDependencies prints the current dependencies -func dumpDependencies() { - fmt.Println("---dump dependencies") - for _, dep := range dependencies { - fmt.Printf("%s, %d -> ", dep.Name, dep.Line) - for _, d := range dep.Dependencies { +// dumpGraph prints the current declGraph +func dumpGraph() { + fmt.Println("-----------------------dump declGraph begin-------------------------") + fmt.Println("---len of declGraph: ", len(declGraph)) + for _, node := range declGraph { + fmt.Printf("%s, %d -> ", node.Name, node.Line) + for _, d := range node.Dependencies { fmt.Printf("%s, %d ", d.Name, d.Line) } fmt.Println() } + fmt.Println("-----------------------dump declGraph done-------------------------") } -// assertNoCycle checks if there is a cycle in the dependencies graph +// assertNoCycle checks if there is a cycle in the declGraph graph func assertNoCycle() { defer func() { - dependencies = nil + declGraph = nil }() visited := make(map[Name]bool) reStack := make(map[Name]bool) - var cycle []*Dependency + var cycle []*DeclNode - for _, dep := range dependencies { + for _, dep := range declGraph { if detectCycle(dep, visited, reStack, &cycle) { fmt.Println("cycle: ", cycle) cycleNames := make([]string, len(cycle)) - for i, d := range cycle { - cycleNames[i] = fmt.Sprintf("%s (Line: %d)", d.Name, d.Line) + for i, c := range cycle { + cycleNames[i] = fmt.Sprintf("%s(File: %s, Line: %d)", c.Name, c.Loc.File, c.Line) } cycleMsg := strings.Join(cycleNames, " -> ") - panic(fmt.Sprintf("%s: Cyclic dependency detected: %v\n", cycle[0].Loc.File, cycleMsg)) - //panic(fmt.Sprintf("Cyclic dependency detected: %v", cycle)) + panic(fmt.Sprintf("Cyclic dependency detected: %s", cycleMsg)) } } } // detectCycle detects cycle using DFS traversal -func detectCycle(dep *Dependency, visited, recStack map[Name]bool, cycle *[]*Dependency) bool { - fmt.Printf("Traversing node: %s (Line: %d)\n", dep.Name, dep.Line) - visited[dep.Name] = true - recStack[dep.Name] = true - *cycle = append(*cycle, dep) +func detectCycle(node *DeclNode, visited, recStack map[Name]bool, cycle *[]*DeclNode) bool { + fmt.Printf("Traversing node: %s (Line: %d)\n", node.Name, node.Line) + fmt.Println("len of node.Dependencies: ", len(node.Dependencies)) + + if visited[node.Name] { // existing visited node are not in cycle, otherwise it wil be elided + fmt.Println("---skip node: ", node.Name) + return false + } + visited[node.Name] = true + recStack[node.Name] = true + *cycle = append(*cycle, node) fmt.Println("Visited map:", visited) fmt.Println("Recursion stack:", recStack) - for _, d := range dep.Dependencies { - if !visited[d.Name] { - if detectCycle(d, visited, recStack, cycle) { - fmt.Println("---cycle detected: ", cycle) - dumpDependencies() - return true - } - } else if recStack[d.Name] { - // If the node is in the recursion stack, a cycle is found - for _, node := range *cycle { - if node == d { + for _, d := range node.Dependencies { + fmt.Println("---loop on d: ", d.Name) + // check if d is in recStack to form a cycle + if recStack[d.Name] { + for _, n := range *cycle { + if n == d { startIndex := 0 for ; (*cycle)[startIndex] != d; startIndex++ { } *cycle = append((*cycle)[startIndex:], d) - return true } } + return true + } else { + if detectCycle(d, visited, recStack, cycle) { + return true + } } } + fmt.Println("---nothing found, delete recStack, name: ", node.Name) + delete(recStack, node.Name) // Backtrack: Remove the last node from the cycle slice and mark as not in recStack - delete(recStack, dep.Name) + fmt.Println("---len of cycle before shrink: ", len(*cycle)) *cycle = (*cycle)[:len(*cycle)-1] + fmt.Println("---len of cycle after shrink: ", len(*cycle)) return false } diff --git a/gnovm/tests/debug/self_refer_struct.gno b/gnovm/tests/debug/self_refer_struct.gno index 1d32c8324f3..61c96ad72d0 100644 --- a/gnovm/tests/debug/self_refer_struct.gno +++ b/gnovm/tests/debug/self_refer_struct.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// debug/self_refer_struct.gno: Cyclic dependency detected: S (Line: 3) -> S (Line: 3) +// Cyclic dependency detected: S(File: debug/self_refer_struct.gno, Line: 3) -> S(File: debug/self_refer_struct.gno, Line: 3) diff --git a/gnovm/tests/debug/self_refer_struct2.gno b/gnovm/tests/debug/self_refer_struct2.gno index ff333e6d444..890d6a35fd8 100644 --- a/gnovm/tests/debug/self_refer_struct2.gno +++ b/gnovm/tests/debug/self_refer_struct2.gno @@ -18,4 +18,4 @@ func main() { } // Error: -// debug/self_refer_struct2.gno: Cyclic dependency detected: C (Line: 11) -> A (Line: 3) -> B (Line: 7) -> C (Line: 11) +// Cyclic dependency detected: C(File: debug/self_refer_struct2.gno, Line: 11) -> A(File: debug/self_refer_struct2.gno, Line: 3) -> B(File: debug/self_refer_struct2.gno, Line: 7) -> C(File: debug/self_refer_struct2.gno, Line: 11) diff --git a/gnovm/tests/debug/self_refer_struct2a.gno b/gnovm/tests/debug/self_refer_struct2a.gno new file mode 100644 index 00000000000..9c7dd3e179f --- /dev/null +++ b/gnovm/tests/debug/self_refer_struct2a.gno @@ -0,0 +1,21 @@ +package main + +type A struct { + X B +} + +type B struct { + X int +} + +type C struct { + X A +} + +func main() { + var p, q A + println(p == q) +} + +// Output: +// true diff --git a/gnovm/tests/debug/self_refer_struct_c.gno b/gnovm/tests/debug/self_refer_struct_c.gno index eb59b67b590..29392cb35ab 100644 --- a/gnovm/tests/debug/self_refer_struct_c.gno +++ b/gnovm/tests/debug/self_refer_struct_c.gno @@ -11,4 +11,5 @@ func main() { println(a == b) } -// Error: +// Output: +// true diff --git a/gnovm/tests/debug/self_refer_struct_d.gno b/gnovm/tests/debug/self_refer_struct_d.gno index 2f347bb4b7c..71e0c9d82c5 100644 --- a/gnovm/tests/debug/self_refer_struct_d.gno +++ b/gnovm/tests/debug/self_refer_struct_d.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// debug/self_refer_struct_d.gno: Cyclic dependency detected: S (Line: 3) -> S (Line: 3) +// Cyclic dependency detected: S(File: debug/self_refer_struct_d.gno, Line: 3) -> S(File: debug/self_refer_struct_d.gno, Line: 3) From e5d37f8848a53082fccfe9c310f5d6157528c2ec Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Fri, 10 May 2024 10:57:39 +0800 Subject: [PATCH 07/35] add txtar for recursive reference --- .../cmd/gnoland/testdata/decl_recursive.txtar | 32 +++++++++++++++++++ .../{self_refer_struct.gno => recursive1.gno} | 0 ...{self_refer_struct2.gno => recursive2.gno} | 0 ...elf_refer_struct2a.gno => recursive2a.gno} | 0 ...self_refer_struct_a.gno => recursive3.gno} | 0 ...self_refer_struct_c.gno => recursive4.gno} | 0 ...self_refer_struct_d.gno => recursive5.gno} | 0 ...elf_refer_interface.gno => recursive6.gno} | 0 gnovm/tests/files/recursive1.gno | 13 ++++++++ gnovm/tests/files/recursive2.gno | 21 ++++++++++++ gnovm/tests/files/recursive2a.gno | 21 ++++++++++++ gnovm/tests/files/recursive3.gno | 13 ++++++++ gnovm/tests/files/recursive4.gno | 15 +++++++++ gnovm/tests/files/recursive5.gno | 13 ++++++++ gnovm/tests/files/recursive6.gno | 23 +++++++++++++ 15 files changed, 151 insertions(+) create mode 100644 gno.land/cmd/gnoland/testdata/decl_recursive.txtar rename gnovm/tests/debug/{self_refer_struct.gno => recursive1.gno} (100%) rename gnovm/tests/debug/{self_refer_struct2.gno => recursive2.gno} (100%) rename gnovm/tests/debug/{self_refer_struct2a.gno => recursive2a.gno} (100%) rename gnovm/tests/debug/{self_refer_struct_a.gno => recursive3.gno} (100%) rename gnovm/tests/debug/{self_refer_struct_c.gno => recursive4.gno} (100%) rename gnovm/tests/debug/{self_refer_struct_d.gno => recursive5.gno} (100%) rename gnovm/tests/debug/{self_refer_interface.gno => recursive6.gno} (100%) create mode 100644 gnovm/tests/files/recursive1.gno create mode 100644 gnovm/tests/files/recursive2.gno create mode 100644 gnovm/tests/files/recursive2a.gno create mode 100644 gnovm/tests/files/recursive3.gno create mode 100644 gnovm/tests/files/recursive4.gno create mode 100644 gnovm/tests/files/recursive5.gno create mode 100644 gnovm/tests/files/recursive6.gno diff --git a/gno.land/cmd/gnoland/testdata/decl_recursive.txtar b/gno.land/cmd/gnoland/testdata/decl_recursive.txtar new file mode 100644 index 00000000000..11feb6f8a2e --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/decl_recursive.txtar @@ -0,0 +1,32 @@ +# test for add package + +## start a new node +gnoland start + +## add hello.gno package located in $WORK directory as gno.land/r/decl +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/decl -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 + +stderr '"gnokey" error: --= Error =--\nData: Cyclic dependency detected:' + +-- decl.gno -- +package decl + +type A struct { + X B +} + +func Cmp() { + var p, q A + println(p == q) +} + +-- decl2.gno -- +package decl + +type B struct { + X C +} + +type C struct { + X A +} \ No newline at end of file diff --git a/gnovm/tests/debug/self_refer_struct.gno b/gnovm/tests/debug/recursive1.gno similarity index 100% rename from gnovm/tests/debug/self_refer_struct.gno rename to gnovm/tests/debug/recursive1.gno diff --git a/gnovm/tests/debug/self_refer_struct2.gno b/gnovm/tests/debug/recursive2.gno similarity index 100% rename from gnovm/tests/debug/self_refer_struct2.gno rename to gnovm/tests/debug/recursive2.gno diff --git a/gnovm/tests/debug/self_refer_struct2a.gno b/gnovm/tests/debug/recursive2a.gno similarity index 100% rename from gnovm/tests/debug/self_refer_struct2a.gno rename to gnovm/tests/debug/recursive2a.gno diff --git a/gnovm/tests/debug/self_refer_struct_a.gno b/gnovm/tests/debug/recursive3.gno similarity index 100% rename from gnovm/tests/debug/self_refer_struct_a.gno rename to gnovm/tests/debug/recursive3.gno diff --git a/gnovm/tests/debug/self_refer_struct_c.gno b/gnovm/tests/debug/recursive4.gno similarity index 100% rename from gnovm/tests/debug/self_refer_struct_c.gno rename to gnovm/tests/debug/recursive4.gno diff --git a/gnovm/tests/debug/self_refer_struct_d.gno b/gnovm/tests/debug/recursive5.gno similarity index 100% rename from gnovm/tests/debug/self_refer_struct_d.gno rename to gnovm/tests/debug/recursive5.gno diff --git a/gnovm/tests/debug/self_refer_interface.gno b/gnovm/tests/debug/recursive6.gno similarity index 100% rename from gnovm/tests/debug/self_refer_interface.gno rename to gnovm/tests/debug/recursive6.gno diff --git a/gnovm/tests/files/recursive1.gno b/gnovm/tests/files/recursive1.gno new file mode 100644 index 00000000000..36df2e4d583 --- /dev/null +++ b/gnovm/tests/files/recursive1.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + T S +} + +func main() { + var a, b S + println(a == b) +} + +// Error: +// Cyclic dependency detected: S(File: files/recursive1.gno, Line: 3) -> S(File: files/recursive1.gno, Line: 3) diff --git a/gnovm/tests/files/recursive2.gno b/gnovm/tests/files/recursive2.gno new file mode 100644 index 00000000000..cdf0f7c6587 --- /dev/null +++ b/gnovm/tests/files/recursive2.gno @@ -0,0 +1,21 @@ +package main + +type A struct { + X B +} + +type B struct { + X C +} + +type C struct { + X A +} + +func main() { + var p, q A + println(p == q) +} + +// Error: +// Cyclic dependency detected: C(File: files/recursive2.gno, Line: 11) -> A(File: files/recursive2.gno, Line: 3) -> B(File: files/recursive2.gno, Line: 7) -> C(File: files/recursive2.gno, Line: 11) diff --git a/gnovm/tests/files/recursive2a.gno b/gnovm/tests/files/recursive2a.gno new file mode 100644 index 00000000000..9c7dd3e179f --- /dev/null +++ b/gnovm/tests/files/recursive2a.gno @@ -0,0 +1,21 @@ +package main + +type A struct { + X B +} + +type B struct { + X int +} + +type C struct { + X A +} + +func main() { + var p, q A + println(p == q) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive3.gno b/gnovm/tests/files/recursive3.gno new file mode 100644 index 00000000000..552c086c91b --- /dev/null +++ b/gnovm/tests/files/recursive3.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + T *S +} + +func main() { + var a, b S + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive4.gno b/gnovm/tests/files/recursive4.gno new file mode 100644 index 00000000000..29392cb35ab --- /dev/null +++ b/gnovm/tests/files/recursive4.gno @@ -0,0 +1,15 @@ +package main + +import "time" + +type Duration struct { + t time.Duration +} + +func main() { + var a, b Duration + println(a == b) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno new file mode 100644 index 00000000000..9ee98226a79 --- /dev/null +++ b/gnovm/tests/files/recursive5.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + S +} + +func main() { + var a, b S + println(a == b) +} + +// Error: +// Cyclic dependency detected: S(File: files/recursive5.gno, Line: 3) -> S(File: files/recursive5.gno, Line: 3) diff --git a/gnovm/tests/files/recursive6.gno b/gnovm/tests/files/recursive6.gno new file mode 100644 index 00000000000..73858b2ea1b --- /dev/null +++ b/gnovm/tests/files/recursive6.gno @@ -0,0 +1,23 @@ +package main + +type SelfReferencing interface { + Self() SelfReferencing +} + +type Implementation struct { + // Some implementation details... +} + +func (impl Implementation) Self() SelfReferencing { + return &impl +} + +func main() { + var obj Implementation + var intf SelfReferencing = obj + _ = intf.Self() + println("ok") +} + +// Output: +// ok From d7c883976a10afebc7c4b35582f06c556db71a94 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Mon, 13 May 2024 16:38:12 +0800 Subject: [PATCH 08/35] clean --- examples/gno.land/r/demo/recursive/foo.gno | 9 - examples/gno.land/r/demo/recursive/main.gno | 10 - .../r/demo/recursive/z_0_filetest.gno | 12 -- gnovm/pkg/gnolang/go2gno.go | 17 +- gnovm/pkg/gnolang/machine.go | 4 - gnovm/pkg/gnolang/misc.go | 122 +++++++++++ gnovm/pkg/gnolang/nodes.go | 1 - gnovm/pkg/gnolang/preprocess.go | 199 ++---------------- gnovm/pkg/gnolang/store.go | 2 +- gnovm/pkg/gnolang/types.go | 4 - gnovm/tests/files/circular_constant.gno | 10 + gnovm/tests/files/recursive1a.gno | 15 ++ 12 files changed, 170 insertions(+), 235 deletions(-) delete mode 100644 examples/gno.land/r/demo/recursive/foo.gno delete mode 100644 examples/gno.land/r/demo/recursive/main.gno delete mode 100644 examples/gno.land/r/demo/recursive/z_0_filetest.gno create mode 100644 gnovm/tests/files/circular_constant.gno create mode 100644 gnovm/tests/files/recursive1a.gno diff --git a/examples/gno.land/r/demo/recursive/foo.gno b/examples/gno.land/r/demo/recursive/foo.gno deleted file mode 100644 index 081ab9280d0..00000000000 --- a/examples/gno.land/r/demo/recursive/foo.gno +++ /dev/null @@ -1,9 +0,0 @@ -package recur - -type B struct { - X C -} - -type C struct { - X A -} diff --git a/examples/gno.land/r/demo/recursive/main.gno b/examples/gno.land/r/demo/recursive/main.gno deleted file mode 100644 index 8d5e2ffdacc..00000000000 --- a/examples/gno.land/r/demo/recursive/main.gno +++ /dev/null @@ -1,10 +0,0 @@ -package recur - -type A struct { - X B -} - -func Do() { - var p, q A - println(p == q) -} diff --git a/examples/gno.land/r/demo/recursive/z_0_filetest.gno b/examples/gno.land/r/demo/recursive/z_0_filetest.gno deleted file mode 100644 index 9ee378e9923..00000000000 --- a/examples/gno.land/r/demo/recursive/z_0_filetest.gno +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "gno.land/r/demo/recursive" -) - -func main() { - recursive.Do() -} - -// Error: -// main/gno.land/r/demo/recursive/z_0_filetest.gno:1: Cyclic dependency detected: A(File: main.gno, Line: 3) -> B(File: foo.gno, Line: 3) -> C(File: foo.gno, Line: 7) -> A(File: main.gno, Line: 3) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 401bcd13fb5..a19fb70a7e4 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -100,7 +100,6 @@ func MustParseExpr(expr string) Expr { // resulting AST -- the resulting FileNode is returned, together with any other // error (including panics, which are recovered) from [Go2Gno]. func ParseFile(filename string, body string) (fn *FileNode, err error) { - fmt.Println("---ParseFile") // Use go parser to parse the body. fs := token.NewFileSet() f, err := parser.ParseFile(fs, filename, body, parser.ParseComments|parser.DeclarationErrors) @@ -125,25 +124,17 @@ func ParseFile(filename string, body string) (fn *FileNode, err error) { // parse with Go2Gno. fn = Go2Gno(fs, f).(*FileNode) fn.Name = Name(filename) - for _, d := range fn.Decls { - fmt.Println("---decl: ", d) - } return fn, nil } func setLoc(fs *token.FileSet, pos token.Pos, n Node) Node { - fmt.Println("---setLoc, n, type of n: ", n, reflect.TypeOf(n)) - fmt.Printf("---setLoc, addr of n: %p \n", n) posn := fs.Position(pos) - fmt.Println("---setLoc, posn, posn.Line: ", posn, posn.Line) n.SetLine(posn.Line) - fmt.Println("---after setLoc, n.line: ", n.GetLine()) return n } // If gon is a *ast.File, the name must be filled later. func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { - fmt.Println("---Go2Gno") if gon == nil { return nil } @@ -456,11 +447,9 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { case *ast.GenDecl: panic("unexpected *ast.GenDecl; use toDecls(fs,) instead") case *ast.File: - fmt.Println("---go 2 gno, file") pkgName := Name(gon.Name.Name) decls := make([]Decl, 0, len(gon.Decls)) for _, d := range gon.Decls { - fmt.Println("---1, d: ", d) if gd, ok := d.(*ast.GenDecl); ok { decls = append(decls, toDecls(fs, gd)...) } else { @@ -641,21 +630,17 @@ func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { for si, s := range gd.Specs { switch s := s.(type) { case *ast.TypeSpec: - fmt.Println("---type spec") name := toName(s.Name) - fmt.Println("---type spec, name: ", name) tipe := toExpr(fs, s.Type) - fmt.Println("---type spec, tipe: ", tipe) alias := s.Assign != 0 td := &TypeDecl{ NameExpr: NameExpr{Name: name}, Type: tipe, IsAlias: alias, } - fmt.Println("---type spec, td: ", td) + // collect decl info for debug posn := fs.Position(gd.Pos()) td.SetLine(posn.Line) - fmt.Println("---type spec, td.line: ", td.GetLine()) ds = append(ds, td) case *ast.ValueSpec: if gd.Tok == token.CONST { diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 817ace743aa..018fac66e64 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -490,7 +490,6 @@ func (m *Machine) RunFiles(fns ...*FileNode) { } func (m *Machine) runFiles(fns ...*FileNode) { - fmt.Println("---runFiles, fns: ", fns) // Files' package names must match the machine's active one. // if there is one. for _, fn := range fns { @@ -525,8 +524,6 @@ func (m *Machine) runFiles(fns ...*FileNode) { // Preprocess each new file. for _, fn := range fns { - fmt.Println("---pn: ", pn) - //fmt.Println("---fn: ", fn) // Preprocess file. // NOTE: Most of the declaration is handled by // Preprocess and any constant values set on @@ -538,7 +535,6 @@ func (m *Machine) runFiles(fns ...*FileNode) { if debug { debug.Printf("PREPROCESSED FILE: %v\n", fn) } - //fmt.Printf("PREPROCESSED FILE: %v\n", fn) // After preprocessing, save blocknodes to store. SaveBlockNodes(m.Store, fn) // Make block for fn. diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index a05de8c74aa..590503eb011 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -167,3 +167,125 @@ func DerivePkgAddr(pkgPath string) crypto.Address { // NOTE: must not collide with pubkey addrs. return crypto.AddressFromPreimage([]byte("pkgPath:" + pkgPath)) } + +// circular detection +// DeclNode represents a node in the dependency graph +// used to detect cycle definition of struct in `PredefineFileSet`. +type DeclNode struct { + Name + Line int + Loc Location // file info + Dependencies []*DeclNode +} + +// insertDeclNode inserts a new dependency into the graph +func insertDeclNode(name Name, line int, loc Location, deps ...Name) { + var dep *DeclNode + for _, d := range declGraph { + if d.Name == name { + dep = d + dep.Line = line + dep.Loc = loc + break + } + } + if dep == nil { + dep = &DeclNode{Name: name, Line: line, Loc: loc} + declGraph = append(declGraph, dep) + } + for _, depName := range deps { + var child *DeclNode + for _, d := range declGraph { + if d.Name == depName { + child = d + break + } + } + if child == nil { + child = &DeclNode{Name: depName} + declGraph = append(declGraph, child) + } + dep.Dependencies = append(dep.Dependencies, child) + } +} + +// dumpGraph prints the current declGraph +func dumpGraph() { + fmt.Println("-----------------------dump declGraph begin-------------------------") + fmt.Println("---len of declGraph: ", len(declGraph)) + for _, node := range declGraph { + fmt.Printf("%s, %d -> ", node.Name, node.Line) + for _, d := range node.Dependencies { + fmt.Printf("%s, %d ", d.Name, d.Line) + } + fmt.Println() + } + fmt.Println("-----------------------dump declGraph done-------------------------") +} + +// assertNoCycle checks if there is a cycle in the declGraph graph +func assertNoCycle() { + defer func() { + declGraph = nil + }() + visited := make(map[Name]bool) + reStack := make(map[Name]bool) + var cycle []*DeclNode + + for _, dep := range declGraph { + if detectCycle(dep, visited, reStack, &cycle) { + cycleNames := make([]string, len(cycle)) + for i, c := range cycle { + cycleNames[i] = fmt.Sprintf("%s(File: %s, Line: %d)", c.Name, c.Loc.File, c.Line) + } + cycleMsg := strings.Join(cycleNames, " -> ") + panic(fmt.Sprintf("Cyclic dependency detected: %s", cycleMsg)) + } + } +} + +// detectCycle detects cycle using DFS traversal +func detectCycle(node *DeclNode, visited, recStack map[Name]bool, cycle *[]*DeclNode) bool { + fmt.Printf("Traversing node: %s (Line: %d)\n", node.Name, node.Line) + fmt.Println("len of node.Dependencies: ", len(node.Dependencies)) + + if visited[node.Name] { // existing visited node are not in cycle, otherwise it wil be elided + fmt.Println("---skip node: ", node.Name) + return false + } + visited[node.Name] = true + recStack[node.Name] = true + *cycle = append(*cycle, node) + + fmt.Println("Visited map:", visited) + fmt.Println("Recursion stack:", recStack) + + for _, d := range node.Dependencies { + fmt.Println("---loop on d: ", d.Name) + // check if d is in recStack to form a cycle + if recStack[d.Name] { + for _, n := range *cycle { + if n == d { + startIndex := 0 + for ; (*cycle)[startIndex] != d; startIndex++ { + } + *cycle = append((*cycle)[startIndex:], d) + } + } + return true + } else { + if detectCycle(d, visited, recStack, cycle) { + return true + } + } + } + + fmt.Println("---nothing found, delete recStack, name: ", node.Name) + delete(recStack, node.Name) + // Backtrack: Remove the last node from the cycle slice and mark as not in recStack + fmt.Println("---len of cycle before shrink: ", len(*cycle)) + *cycle = (*cycle)[:len(*cycle)-1] + fmt.Println("---len of cycle after shrink: ", len(*cycle)) + + return false +} diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 24acc33e528..8f2c5054a8a 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1171,7 +1171,6 @@ func ReadMemPackageFromList(list []string, pkgPath string) *std.MemPackage { // If one of the files has a different package name than memPkg.Name, // or [ParseFile] returns an error, ParseMemPackage panics. func ParseMemPackage(memPkg *std.MemPackage) (fset *FileSet) { - fmt.Println("---parse mem package") fset = &FileSet{} for _, mfile := range memPkg.Files { if !strings.HasSuffix(mfile.Name, ".gno") || diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 897dcac60e0..09821e78a16 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -4,7 +4,6 @@ import ( "fmt" "math/big" "reflect" - "strings" "sync/atomic" "github.com/gnolang/gno/tm2/pkg/errors" @@ -15,17 +14,14 @@ import ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { - fmt.Println("---predefine fileset, fset: ", fset) defer func() { - fmt.Println("---defer in predefine fileSet") - // Check for cyclic declGraph + // Check for cyclic if len(declGraph) != 0 { - fmt.Println("---graph no empty, start checking cycle... ") dumpGraph() assertNoCycle() - fmt.Println("No cyclic dependency detected.") - } else { - fmt.Println("---do nothing") + } + if err := recover(); err != nil { + panic(err) } }() // First, initialize all file nodes and connect to package node. @@ -49,7 +45,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // (e.g. through recursion for a // dependent) } else { - // recursively predefine declGraph. + // recursively predefine dependencies. d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } @@ -67,7 +63,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // (e.g. through recursion for a // dependent) } else { - // recursively predefine declGraph. + // recursively predefine dependencies. d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } @@ -85,7 +81,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // (e.g. through recursion for a // dependent) } else { - // recursively predefine declGraph. + // recursively predefine dependencies. d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } @@ -101,7 +97,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // skip declarations already predefined (e.g. // through recursion for a dependent) } else { - // recursively predefine declGraph. + // recursively predefine dependencies. d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } @@ -137,8 +133,6 @@ var preprocessing atomic.Int32 // - Assigns BlockValuePath to NameExprs. // - TODO document what it does. func Preprocess(store Store, ctx BlockNode, n Node) Node { - //fmt.Println("---preprocess, ctx: ", ctx) - //fmt.Println("---preprocess, n: ", n) // Increment preprocessing counter while preprocessing. preprocessing.Add(1) defer preprocessing.Add(-1) @@ -198,7 +192,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { if debug { debug.Printf("Preprocess %s (%v) stage:%v\n", n.String(), reflect.TypeOf(n), stage) } - //fmt.Printf("Preprocess %s (%v) stage:%v\n", n.String(), reflect.TypeOf(n), stage) switch stage { // ---------------------------------------- @@ -241,7 +234,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // skip declarations already predefined // (e.g. through recursion for a dependent) } else { - // recursively predefine declGraph. + // recursively predefine dependencies. d2, ppd := predefineNow(store, last, n.(Decl)) if ppd { return d2, TRANS_SKIP @@ -521,7 +514,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } // TRANS_BLOCK ----------------------- case *FileNode: - //fmt.Println("---trans_block, fileNode, n :", n) // only for imports. pushInitBlock(n, &last, &stack) { @@ -541,7 +533,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // recursion for a dependent) } else { // recursively predefine - // declGraph. + // dependencies. d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } @@ -558,7 +550,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // recursion for a dependent) } else { // recursively predefine - // declGraph. + // dependencies. d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } @@ -575,7 +567,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // recursion for a dependent) } else { // recursively predefine - // declGraph. + // dependencies. d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } @@ -591,7 +583,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // recursion for a dependent) } else { // recursively predefine - // declGraph. + // dependencies. d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } @@ -1949,7 +1941,9 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // during *TypeDecl:ENTER. Then, copy over the // values, completing the recursion. tmp := evalStaticType(store, last, n.Type) + fmt.Println("---tmp: ", tmp) dst := last.GetValueRef(store, n.Name).GetType() + fmt.Println("---dst: ", dst) switch dst := dst.(type) { case *FuncType: *dst = *(tmp.(*FuncType)) @@ -1972,29 +1966,14 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { maybeRecursive := false names := make([]Name, 0) for _, f := range st.Fields { - fmt.Println("---f.Name: ", f.Name) - fmt.Println("---f.Type, type of type: ", f.Type, reflect.TypeOf(f.Type)) if fdt, ok := f.Type.(*DeclaredType); ok { // indirect does not count - fmt.Println("---st.PkgPath: ", st.PkgPath) - fmt.Println("---fdt.PkgPath: ", fdt.PkgPath) - fmt.Println("---fdt.Name:", fdt.Name) - fmt.Println("---fdt.PkgPath:", fdt.PkgPath) - if st.PkgPath == fdt.PkgPath { // not cross pkg - fmt.Println("---match pkg path!") maybeRecursive = true names = append(names, fdt.Name) - } else { - fmt.Println("---pkg path no match!") } } - - //if pt, ok := f.Type.(*PointerType); ok { // indirect does not count - // fmt.Println("---it is pointer type, pt: ", pt) - //} } if maybeRecursive { - fmt.Println("---names: ", names) for _, name := range names { line := n.GetLine() insertDeclNode(dst.Name, line, last.GetLocation(), name) @@ -3012,21 +2991,18 @@ func checkIntegerType(xt Type) { // // First, tryPredefine(), which first predefines with placeholder // values/types to support recursive types, then returns yet -// un-predefined declGraph. +// un-predefined dependencies. // // Second, which immediately preprocesses type/value declarations -// after declGraph have first been predefined, or partially +// after dependencies have first been predefined, or partially // preprocesses function declarations (which may not be completely // preprocess-able before other file-level declarations are // preprocessed). func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { - fmt.Println("---predefine now, d", d) //fmt.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { - fmt.Println("---defer predefine now, d: ", d) // before re-throwing the error, append location information to message. - fmt.Println("---nline: ", d.GetLine()) loc := last.GetLocation() if nline := d.GetLine(); nline > 0 { loc.Line = nline @@ -3035,7 +3011,6 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { // NOTE: gotuna/gorilla expects error exceptions. panic(errors.Wrap(rerr, loc.String())) } else { - fmt.Println("---defer predefine now ,loc: ", loc.String()) // NOTE: gotuna/gorilla expects error exceptions. panic(fmt.Errorf("%s: %v", loc.String(), r)) } @@ -3048,14 +3023,15 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (Decl, bool) { pkg := packageOf(last) // pre-register d.GetName() to detect circular definition. - for _, dn := range d.GetDeclNames() { + for i, dn := range d.GetDeclNames() { + fmt.Printf("---dn[%d] is %v \n ", i, dn) if isUverseName(dn) { panic(fmt.Sprintf( "builtin identifiers cannot be shadowed: %s", dn)) } m[dn] = struct{}{} } - // recursively predefine declGraph. + // recursively predefine dependencies. for { un := tryPredefine(store, last, d) if un != "" { @@ -3162,10 +3138,6 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De case *ValueDecl: return Preprocess(store, last, cd).(Decl), true case *TypeDecl: - fmt.Println("---predefine now 2, type decl: ", d) - fmt.Println("---line", d.GetLine()) - fmt.Println("---name", cd.Name) - fmt.Println("---nx.line", cd.NameExpr.GetLine()) return Preprocess(store, last, cd).(Decl), true default: return d, false @@ -3241,7 +3213,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } } case *TypeDecl: - // before looking for declGraph, predefine empty type. + // before looking for dependencies, predefine empty type. last2 := skipFile(last) _, ok := last2.GetLocalIndex(d.Name) if !ok { @@ -3818,132 +3790,3 @@ func SaveBlockNodes(store Store, fn *FileNode) { return n, TRANS_CONTINUE }) } - -// DeclNode represents a node in the dependency graph -// used to detect cycle definition of struct in `PredefineFileSet`. -type DeclNode struct { - Name - Line int - Loc Location // file info - Dependencies []*DeclNode -} - -// insertDeclNode inserts a new dependency into the graph -func insertDeclNode(name Name, line int, loc Location, deps ...Name) { - fmt.Println("---insertDeclNode, name, deps: ", name, deps) - fmt.Println("---insertDeclNode, loc: ", loc) - var dep *DeclNode - for _, d := range declGraph { - if d.Name == name { - dep = d - dep.Line = line - dep.Loc = loc - break - } - } - fmt.Println("---dep 1: ", dep) - if dep == nil { - dep = &DeclNode{Name: name, Line: line, Loc: loc} - declGraph = append(declGraph, dep) - } - fmt.Println("---dep 2: ", dep) - for _, depName := range deps { - println("---depName: ", depName) - var child *DeclNode - for _, d := range declGraph { - if d.Name == depName { - child = d - break - } - } - fmt.Println("---child 1: ", child) - if child == nil { - child = &DeclNode{Name: depName} - declGraph = append(declGraph, child) - } - fmt.Println("---child 2: ", child) - dep.Dependencies = append(dep.Dependencies, child) - } -} - -// dumpGraph prints the current declGraph -func dumpGraph() { - fmt.Println("-----------------------dump declGraph begin-------------------------") - fmt.Println("---len of declGraph: ", len(declGraph)) - for _, node := range declGraph { - fmt.Printf("%s, %d -> ", node.Name, node.Line) - for _, d := range node.Dependencies { - fmt.Printf("%s, %d ", d.Name, d.Line) - } - fmt.Println() - } - fmt.Println("-----------------------dump declGraph done-------------------------") -} - -// assertNoCycle checks if there is a cycle in the declGraph graph -func assertNoCycle() { - defer func() { - declGraph = nil - }() - visited := make(map[Name]bool) - reStack := make(map[Name]bool) - var cycle []*DeclNode - - for _, dep := range declGraph { - if detectCycle(dep, visited, reStack, &cycle) { - fmt.Println("cycle: ", cycle) - cycleNames := make([]string, len(cycle)) - for i, c := range cycle { - cycleNames[i] = fmt.Sprintf("%s(File: %s, Line: %d)", c.Name, c.Loc.File, c.Line) - } - cycleMsg := strings.Join(cycleNames, " -> ") - panic(fmt.Sprintf("Cyclic dependency detected: %s", cycleMsg)) - } - } -} - -// detectCycle detects cycle using DFS traversal -func detectCycle(node *DeclNode, visited, recStack map[Name]bool, cycle *[]*DeclNode) bool { - fmt.Printf("Traversing node: %s (Line: %d)\n", node.Name, node.Line) - fmt.Println("len of node.Dependencies: ", len(node.Dependencies)) - - if visited[node.Name] { // existing visited node are not in cycle, otherwise it wil be elided - fmt.Println("---skip node: ", node.Name) - return false - } - visited[node.Name] = true - recStack[node.Name] = true - *cycle = append(*cycle, node) - - fmt.Println("Visited map:", visited) - fmt.Println("Recursion stack:", recStack) - - for _, d := range node.Dependencies { - fmt.Println("---loop on d: ", d.Name) - // check if d is in recStack to form a cycle - if recStack[d.Name] { - for _, n := range *cycle { - if n == d { - startIndex := 0 - for ; (*cycle)[startIndex] != d; startIndex++ { - } - *cycle = append((*cycle)[startIndex:], d) - } - } - return true - } else { - if detectCycle(d, visited, recStack, cycle) { - return true - } - } - } - - fmt.Println("---nothing found, delete recStack, name: ", node.Name) - delete(recStack, node.Name) - // Backtrack: Remove the last node from the cycle slice and mark as not in recStack - fmt.Println("---len of cycle before shrink: ", len(*cycle)) - *cycle = (*cycle)[:len(*cycle)-1] - fmt.Println("---len of cycle after shrink: ", len(*cycle)) - - return false -} diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index dc465a1b9c3..d15976ec262 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -481,7 +481,7 @@ func (ds *defaultStore) SetBlockNode(bn BlockNode) { // save node to backend. if ds.baseStore != nil { // TODO: implement copyValueWithRefs() for Nodes. - // key := backendNodeKey(Loc) + // key := backendNodeKey(loc) // ds.backend.Set([]byte(key), bz) } // save node to cache. diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 637e593be26..e1814e8f243 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1384,7 +1384,6 @@ type DeclaredType struct { // returns an unsealed *DeclaredType. // do not use for aliases. func declareWith(pkgPath string, name Name, b Type) *DeclaredType { - //fmt.Println("---declareWith, pkgPath, name, type: ", pkgPath, name, b) dt := &DeclaredType{ PkgPath: pkgPath, Name: name, @@ -1429,10 +1428,7 @@ func (dt *DeclaredType) checkSeal() { } func (dt *DeclaredType) TypeID() TypeID { - //fmt.Println("---TypeID(), for dt: ", dt) if dt.typeid.IsZero() { - //fmt.Println("is 0!!!!!") - //panic("is 0!!!!!!!!!!!!!!!1") dt.typeid = DeclaredTypeID(dt.PkgPath, dt.Name) } return dt.typeid diff --git a/gnovm/tests/files/circular_constant.gno b/gnovm/tests/files/circular_constant.gno new file mode 100644 index 00000000000..0f9b35fe8e5 --- /dev/null +++ b/gnovm/tests/files/circular_constant.gno @@ -0,0 +1,10 @@ +package main + +const A = B +const B = A + 1 + +func main() { +} + +// Error: +// main/files/circular_constant.gno:1: constant definition loop with A diff --git a/gnovm/tests/files/recursive1a.gno b/gnovm/tests/files/recursive1a.gno new file mode 100644 index 00000000000..87681e1fcdd --- /dev/null +++ b/gnovm/tests/files/recursive1a.gno @@ -0,0 +1,15 @@ +package main + +type S1 *S + +type S struct { + T S1 +} + +func main() { + var a, b S + println(a == b) +} + +// Output: +// true \ No newline at end of file From 47362d9db42c1f576297ae762946b0c476ffa720 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Mon, 13 May 2024 16:53:55 +0800 Subject: [PATCH 09/35] clean --- gnovm/pkg/gnolang/go2gno.go | 3 --- gnovm/pkg/gnolang/misc.go | 12 ------------ gnovm/pkg/gnolang/preprocess.go | 10 ++-------- gnovm/tests/debug/recursive2.gno | 21 --------------------- gnovm/tests/debug/recursive2a.gno | 21 --------------------- gnovm/tests/debug/recursive3.gno | 13 ------------- gnovm/tests/debug/recursive4.gno | 15 --------------- gnovm/tests/debug/recursive5.gno | 13 ------------- gnovm/tests/debug/recursive6.gno | 23 ----------------------- 9 files changed, 2 insertions(+), 129 deletions(-) delete mode 100644 gnovm/tests/debug/recursive2.gno delete mode 100644 gnovm/tests/debug/recursive2a.gno delete mode 100644 gnovm/tests/debug/recursive3.gno delete mode 100644 gnovm/tests/debug/recursive4.gno delete mode 100644 gnovm/tests/debug/recursive5.gno delete mode 100644 gnovm/tests/debug/recursive6.gno diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index a19fb70a7e4..89e293db448 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -605,7 +605,6 @@ func toSimp(fs *token.FileSet, gos ast.Stmt) Stmt { } func toDecl(fs *token.FileSet, god ast.Decl) Decl { - fmt.Println("---toDecl..., god: ", god) gnod := Go2Gno(fs, god) if gnod == nil { return nil @@ -615,8 +614,6 @@ func toDecl(fs *token.FileSet, god ast.Decl) Decl { } func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { - fmt.Println("---toDecls..., gd: ", gd) - ds = make([]Decl, 0, len(gd.Specs)) /* Within a parenthesized const declaration list the diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index 590503eb011..5bdb2f62f34 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -212,7 +212,6 @@ func insertDeclNode(name Name, line int, loc Location, deps ...Name) { // dumpGraph prints the current declGraph func dumpGraph() { fmt.Println("-----------------------dump declGraph begin-------------------------") - fmt.Println("---len of declGraph: ", len(declGraph)) for _, node := range declGraph { fmt.Printf("%s, %d -> ", node.Name, node.Line) for _, d := range node.Dependencies { @@ -246,22 +245,14 @@ func assertNoCycle() { // detectCycle detects cycle using DFS traversal func detectCycle(node *DeclNode, visited, recStack map[Name]bool, cycle *[]*DeclNode) bool { - fmt.Printf("Traversing node: %s (Line: %d)\n", node.Name, node.Line) - fmt.Println("len of node.Dependencies: ", len(node.Dependencies)) - if visited[node.Name] { // existing visited node are not in cycle, otherwise it wil be elided - fmt.Println("---skip node: ", node.Name) return false } visited[node.Name] = true recStack[node.Name] = true *cycle = append(*cycle, node) - fmt.Println("Visited map:", visited) - fmt.Println("Recursion stack:", recStack) - for _, d := range node.Dependencies { - fmt.Println("---loop on d: ", d.Name) // check if d is in recStack to form a cycle if recStack[d.Name] { for _, n := range *cycle { @@ -280,12 +271,9 @@ func detectCycle(node *DeclNode, visited, recStack map[Name]bool, cycle *[]*Decl } } - fmt.Println("---nothing found, delete recStack, name: ", node.Name) delete(recStack, node.Name) // Backtrack: Remove the last node from the cycle slice and mark as not in recStack - fmt.Println("---len of cycle before shrink: ", len(*cycle)) *cycle = (*cycle)[:len(*cycle)-1] - fmt.Println("---len of cycle after shrink: ", len(*cycle)) return false } diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 09821e78a16..38185fe8754 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -17,7 +17,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { defer func() { // Check for cyclic if len(declGraph) != 0 { - dumpGraph() + //dumpGraph() assertNoCycle() } if err := recover(); err != nil { @@ -1935,15 +1935,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *TypeDecl: - fmt.Println("---trans_leave, type decl") // Construct new Type, where any recursive // references refer to the old Type declared // during *TypeDecl:ENTER. Then, copy over the // values, completing the recursion. tmp := evalStaticType(store, last, n.Type) - fmt.Println("---tmp: ", tmp) dst := last.GetValueRef(store, n.Name).GetType() - fmt.Println("---dst: ", dst) switch dst := dst.(type) { case *FuncType: *dst = *(tmp.(*FuncType)) @@ -1960,7 +1957,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *StructType: *dst = *(tmp.(*StructType)) case *DeclaredType: - fmt.Println("---type decl, n, n.Type: ", n, n.Type) if st, ok := tmp.(*StructType); ok { // check if fields contains declaredType maybeRecursive := false @@ -3023,8 +3019,7 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (Decl, bool) { pkg := packageOf(last) // pre-register d.GetName() to detect circular definition. - for i, dn := range d.GetDeclNames() { - fmt.Printf("---dn[%d] is %v \n ", i, dn) + for _, dn := range d.GetDeclNames() { if isUverseName(dn) { panic(fmt.Sprintf( "builtin identifiers cannot be shadowed: %s", dn)) @@ -3707,7 +3702,6 @@ func findDependentNames(n Node, dst map[Name]struct{}) { "unexpected node: %v (%v)", n, reflect.TypeOf(n))) } - return } // ---------------------------------------- diff --git a/gnovm/tests/debug/recursive2.gno b/gnovm/tests/debug/recursive2.gno deleted file mode 100644 index 890d6a35fd8..00000000000 --- a/gnovm/tests/debug/recursive2.gno +++ /dev/null @@ -1,21 +0,0 @@ -package main - -type A struct { - X B -} - -type B struct { - X C -} - -type C struct { - X A -} - -func main() { - var p, q A - println(p == q) -} - -// Error: -// Cyclic dependency detected: C(File: debug/self_refer_struct2.gno, Line: 11) -> A(File: debug/self_refer_struct2.gno, Line: 3) -> B(File: debug/self_refer_struct2.gno, Line: 7) -> C(File: debug/self_refer_struct2.gno, Line: 11) diff --git a/gnovm/tests/debug/recursive2a.gno b/gnovm/tests/debug/recursive2a.gno deleted file mode 100644 index 9c7dd3e179f..00000000000 --- a/gnovm/tests/debug/recursive2a.gno +++ /dev/null @@ -1,21 +0,0 @@ -package main - -type A struct { - X B -} - -type B struct { - X int -} - -type C struct { - X A -} - -func main() { - var p, q A - println(p == q) -} - -// Output: -// true diff --git a/gnovm/tests/debug/recursive3.gno b/gnovm/tests/debug/recursive3.gno deleted file mode 100644 index 552c086c91b..00000000000 --- a/gnovm/tests/debug/recursive3.gno +++ /dev/null @@ -1,13 +0,0 @@ -package main - -type S struct { - T *S -} - -func main() { - var a, b S - println(a == b) -} - -// Output: -// true diff --git a/gnovm/tests/debug/recursive4.gno b/gnovm/tests/debug/recursive4.gno deleted file mode 100644 index 29392cb35ab..00000000000 --- a/gnovm/tests/debug/recursive4.gno +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import "time" - -type Duration struct { - t time.Duration -} - -func main() { - var a, b Duration - println(a == b) -} - -// Output: -// true diff --git a/gnovm/tests/debug/recursive5.gno b/gnovm/tests/debug/recursive5.gno deleted file mode 100644 index 71e0c9d82c5..00000000000 --- a/gnovm/tests/debug/recursive5.gno +++ /dev/null @@ -1,13 +0,0 @@ -package main - -type S struct { - S -} - -func main() { - var a, b S - println(a == b) -} - -// Error: -// Cyclic dependency detected: S(File: debug/self_refer_struct_d.gno, Line: 3) -> S(File: debug/self_refer_struct_d.gno, Line: 3) diff --git a/gnovm/tests/debug/recursive6.gno b/gnovm/tests/debug/recursive6.gno deleted file mode 100644 index 73858b2ea1b..00000000000 --- a/gnovm/tests/debug/recursive6.gno +++ /dev/null @@ -1,23 +0,0 @@ -package main - -type SelfReferencing interface { - Self() SelfReferencing -} - -type Implementation struct { - // Some implementation details... -} - -func (impl Implementation) Self() SelfReferencing { - return &impl -} - -func main() { - var obj Implementation - var intf SelfReferencing = obj - _ = intf.Self() - println("ok") -} - -// Output: -// ok From 40fb20fc82f4d4768c26f6d0e9c71184c070071b Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Thu, 16 May 2024 00:20:12 +0800 Subject: [PATCH 10/35] deprecate dependence graph, using the current logic instead --- .../cmd/gnoland/testdata/decl_recursive.txtar | 4 +- gnovm/pkg/gnolang/go2gno.go | 8 +- gnovm/pkg/gnolang/machine.go | 19 ++- gnovm/pkg/gnolang/misc.go | 110 ------------------ gnovm/pkg/gnolang/preprocess.go | 60 ++++------ gnovm/tests/files/recursive1.gno | 2 +- gnovm/tests/files/recursive2.gno | 2 +- gnovm/tests/files/recursive5.gno | 2 +- 8 files changed, 49 insertions(+), 158 deletions(-) diff --git a/gno.land/cmd/gnoland/testdata/decl_recursive.txtar b/gno.land/cmd/gnoland/testdata/decl_recursive.txtar index 11feb6f8a2e..e9ac2761c03 100644 --- a/gno.land/cmd/gnoland/testdata/decl_recursive.txtar +++ b/gno.land/cmd/gnoland/testdata/decl_recursive.txtar @@ -6,7 +6,7 @@ gnoland start ## add hello.gno package located in $WORK directory as gno.land/r/decl ! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/decl -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 -stderr '"gnokey" error: --= Error =--\nData: Cyclic dependency detected:' +stderr '"gnokey" error: --= Error =--\nData: loop in variable initialization: dependency trail' -- decl.gno -- package decl @@ -29,4 +29,4 @@ type B struct { type C struct { X A -} \ No newline at end of file +} diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 89e293db448..8ca985352a7 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -630,15 +630,11 @@ func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { name := toName(s.Name) tipe := toExpr(fs, s.Type) alias := s.Assign != 0 - td := &TypeDecl{ + ds = append(ds, &TypeDecl{ NameExpr: NameExpr{Name: name}, Type: tipe, IsAlias: alias, - } - // collect decl info for debug - posn := fs.Position(gd.Pos()) - td.SetLine(posn.Line) - ds = append(ds, td) + }) case *ast.ValueSpec: if gd.Tok == token.CONST { var names []NameExpr diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 018fac66e64..e59b241316e 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -555,11 +555,26 @@ func (m *Machine) runFiles(fns ...*FileNode) { // recursive function for var declarations. var runDeclarationFor func(fn *FileNode, decl Decl) runDeclarationFor = func(fn *FileNode, decl Decl) { + deps := make(map[Name]struct{}) + + isStructDecl := false + if td, ok := decl.(*TypeDecl); ok { + tdt := evalStaticType(m.Store, fn, td.Type) + if dt, ok := tdt.(*DeclaredType); ok { + if _, ok := dt.Base.(*StructType); ok { + isStructDecl = true + } + } + } + if isStructDecl { + // find dependent name in same pkg + findStructDeclDependentNames(m.Store, fn, decl, deps, pn.PkgPath) + } else { + findDependentNames(decl, deps) + } // get fileblock of fn. // fb := pv.GetFileBlock(nil, fn.Name) // get dependencies of decl. - deps := make(map[Name]struct{}) - findDependentNames(decl, deps) for dep := range deps { // if dep already defined as import, skip. if _, ok := fn.GetLocalIndex(dep); ok { diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index 5bdb2f62f34..a05de8c74aa 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -167,113 +167,3 @@ func DerivePkgAddr(pkgPath string) crypto.Address { // NOTE: must not collide with pubkey addrs. return crypto.AddressFromPreimage([]byte("pkgPath:" + pkgPath)) } - -// circular detection -// DeclNode represents a node in the dependency graph -// used to detect cycle definition of struct in `PredefineFileSet`. -type DeclNode struct { - Name - Line int - Loc Location // file info - Dependencies []*DeclNode -} - -// insertDeclNode inserts a new dependency into the graph -func insertDeclNode(name Name, line int, loc Location, deps ...Name) { - var dep *DeclNode - for _, d := range declGraph { - if d.Name == name { - dep = d - dep.Line = line - dep.Loc = loc - break - } - } - if dep == nil { - dep = &DeclNode{Name: name, Line: line, Loc: loc} - declGraph = append(declGraph, dep) - } - for _, depName := range deps { - var child *DeclNode - for _, d := range declGraph { - if d.Name == depName { - child = d - break - } - } - if child == nil { - child = &DeclNode{Name: depName} - declGraph = append(declGraph, child) - } - dep.Dependencies = append(dep.Dependencies, child) - } -} - -// dumpGraph prints the current declGraph -func dumpGraph() { - fmt.Println("-----------------------dump declGraph begin-------------------------") - for _, node := range declGraph { - fmt.Printf("%s, %d -> ", node.Name, node.Line) - for _, d := range node.Dependencies { - fmt.Printf("%s, %d ", d.Name, d.Line) - } - fmt.Println() - } - fmt.Println("-----------------------dump declGraph done-------------------------") -} - -// assertNoCycle checks if there is a cycle in the declGraph graph -func assertNoCycle() { - defer func() { - declGraph = nil - }() - visited := make(map[Name]bool) - reStack := make(map[Name]bool) - var cycle []*DeclNode - - for _, dep := range declGraph { - if detectCycle(dep, visited, reStack, &cycle) { - cycleNames := make([]string, len(cycle)) - for i, c := range cycle { - cycleNames[i] = fmt.Sprintf("%s(File: %s, Line: %d)", c.Name, c.Loc.File, c.Line) - } - cycleMsg := strings.Join(cycleNames, " -> ") - panic(fmt.Sprintf("Cyclic dependency detected: %s", cycleMsg)) - } - } -} - -// detectCycle detects cycle using DFS traversal -func detectCycle(node *DeclNode, visited, recStack map[Name]bool, cycle *[]*DeclNode) bool { - if visited[node.Name] { // existing visited node are not in cycle, otherwise it wil be elided - return false - } - visited[node.Name] = true - recStack[node.Name] = true - *cycle = append(*cycle, node) - - for _, d := range node.Dependencies { - // check if d is in recStack to form a cycle - if recStack[d.Name] { - for _, n := range *cycle { - if n == d { - startIndex := 0 - for ; (*cycle)[startIndex] != d; startIndex++ { - } - *cycle = append((*cycle)[startIndex:], d) - } - } - return true - } else { - if detectCycle(d, visited, recStack, cycle) { - return true - } - } - } - - delete(recStack, node.Name) - // Backtrack: Remove the last node from the cycle slice and mark as not in recStack - *cycle = (*cycle)[:len(*cycle)-1] - - return false -} diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 38185fe8754..9e479f4d317 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -14,17 +14,6 @@ import ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { - defer func() { - // Check for cyclic - if len(declGraph) != 0 { - //dumpGraph() - assertNoCycle() - } - if err := recover(); err != nil { - panic(err) - } - }() - // First, initialize all file nodes and connect to package node. for _, fn := range fset.Files { SetNodeLocations(pn.PkgPath, string(fn.Name), fn) fn.InitStaticBlock(fn, pn) @@ -105,9 +94,6 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { } } -// declGraph represents a slice of declGraph -var declGraph []*DeclNode - // This counter ensures (during testing) that certain functions // (like ConvertUntypedTo() for bigints and strings) // are only called during the preprocessing stage. @@ -1957,25 +1943,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *StructType: *dst = *(tmp.(*StructType)) case *DeclaredType: - if st, ok := tmp.(*StructType); ok { - // check if fields contains declaredType - maybeRecursive := false - names := make([]Name, 0) - for _, f := range st.Fields { - if fdt, ok := f.Type.(*DeclaredType); ok { // indirect does not count - if st.PkgPath == fdt.PkgPath { // not cross pkg - maybeRecursive = true - names = append(names, fdt.Name) - } - } - } - if maybeRecursive { - for _, name := range names { - line := n.GetLine() - insertDeclNode(dst.Name, line, last.GetLocation(), name) - } - } - } // if store has this type, use that. tid := DeclaredTypeID(lastpn.PkgPath, n.Name) exists := false @@ -2995,7 +2962,6 @@ func checkIntegerType(xt Type) { // preprocess-able before other file-level declarations are // preprocessed). func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { - //fmt.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { // before re-throwing the error, append location information to message. @@ -3291,7 +3257,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { last2.Define(d.Name, asValue(t)) d.Path = last.GetPathForName(store, d.Name) } - // after predefinitions, return any undefined declGraph. + // after predefinitions, return any undefined dependencies. un = findUndefined(store, last, d.Type) if un != "" { return @@ -3574,6 +3540,30 @@ func countNumArgs(store Store, last BlockNode, n *CallExpr) (numArgs int) { } } +func findStructDeclDependentNames(store Store, last BlockNode, n Node, dst map[Name]struct{}, pkgPath string) { + switch cn := n.(type) { + case *constTypeExpr: + if st, ok := cn.Source.(*StructTypeExpr); ok { + for _, f := range st.Fields { + ft := evalStaticType(store, last, f.Type) + if dt, ok := ft.(*DeclaredType); ok { + if _, ok := dt.Base.(*StructType); ok { + if dt.PkgPath == pkgPath { + dst[dt.Name] = struct{}{} + } + } + } + } + } + case *TypeDecl: + findStructDeclDependentNames(store, last, cn.Type, dst, pkgPath) + default: + panic(fmt.Sprintf( + "unexpected node: %v (%v)", + n, reflect.TypeOf(n))) + } +} + // This is to be run *after* preprocessing is done, // to determine the order of var decl execution // (which may include functions which may refer to package vars). diff --git a/gnovm/tests/files/recursive1.gno b/gnovm/tests/files/recursive1.gno index 36df2e4d583..a51643e143f 100644 --- a/gnovm/tests/files/recursive1.gno +++ b/gnovm/tests/files/recursive1.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive1.gno, Line: 3) -> S(File: files/recursive1.gno, Line: 3) +// loop in variable initialization: dependency trail [S] circularly depends on S diff --git a/gnovm/tests/files/recursive2.gno b/gnovm/tests/files/recursive2.gno index cdf0f7c6587..3b759364fbe 100644 --- a/gnovm/tests/files/recursive2.gno +++ b/gnovm/tests/files/recursive2.gno @@ -18,4 +18,4 @@ func main() { } // Error: -// Cyclic dependency detected: C(File: files/recursive2.gno, Line: 11) -> A(File: files/recursive2.gno, Line: 3) -> B(File: files/recursive2.gno, Line: 7) -> C(File: files/recursive2.gno, Line: 11) +// loop in variable initialization: dependency trail [B C A] circularly depends on B diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno index 9ee98226a79..8cbbd9ae67d 100644 --- a/gnovm/tests/files/recursive5.gno +++ b/gnovm/tests/files/recursive5.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive5.gno, Line: 3) -> S(File: files/recursive5.gno, Line: 3) +// loop in variable initialization: dependency trail [S] circularly depends on S From 992d0d602f76f372c82e978ebe601e43e9271c62 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Thu, 16 May 2024 00:24:25 +0800 Subject: [PATCH 11/35] revert --- gnovm/pkg/gnolang/machine.go | 6 +++--- gnovm/pkg/gnolang/preprocess.go | 2 +- gnovm/tests/debug/recursive1.gno | 13 ------------- 3 files changed, 4 insertions(+), 17 deletions(-) delete mode 100644 gnovm/tests/debug/recursive1.gno diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index e59b241316e..1875bca71f5 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -566,15 +566,15 @@ func (m *Machine) runFiles(fns ...*FileNode) { } } } + // get fileblock of fn. + // fb := pv.GetFileBlock(nil, fn.Name) + // get dependencies of decl, struct decl as a special case. if isStructDecl { // find dependent name in same pkg findStructDeclDependentNames(m.Store, fn, decl, deps, pn.PkgPath) } else { findDependentNames(decl, deps) } - // get fileblock of fn. - // fb := pv.GetFileBlock(nil, fn.Name) - // get dependencies of decl. for dep := range deps { // if dep already defined as import, skip. if _, ok := fn.GetLocalIndex(dep); ok { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 9e479f4d317..3b0270b5f7b 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -14,6 +14,7 @@ import ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { + // First, initialize all file nodes and connect to package node for _, fn := range fset.Files { SetNodeLocations(pn.PkgPath, string(fn.Name), fn) fn.InitStaticBlock(fn, pn) @@ -130,7 +131,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // if n is file node, set node locations recursively. if fn, ok := n.(*FileNode); ok { - //fmt.Println("---is file node, fn: ", fn) pkgPath := ctx.(*PackageNode).PkgPath fileName := string(fn.Name) SetNodeLocations(pkgPath, fileName, fn) diff --git a/gnovm/tests/debug/recursive1.gno b/gnovm/tests/debug/recursive1.gno deleted file mode 100644 index 61c96ad72d0..00000000000 --- a/gnovm/tests/debug/recursive1.gno +++ /dev/null @@ -1,13 +0,0 @@ -package main - -type S struct { - T S -} - -func main() { - var a, b S - println(a == b) -} - -// Error: -// Cyclic dependency detected: S(File: debug/self_refer_struct.gno, Line: 3) -> S(File: debug/self_refer_struct.gno, Line: 3) From 24791a503c579ead16fd031bb4b78a59639869d7 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Thu, 16 May 2024 00:31:23 +0800 Subject: [PATCH 12/35] comments --- gnovm/pkg/gnolang/machine.go | 4 +++- gnovm/pkg/gnolang/preprocess.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 1875bca71f5..a6b4c2b1e9f 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -568,7 +568,9 @@ func (m *Machine) runFiles(fns ...*FileNode) { } // get fileblock of fn. // fb := pv.GetFileBlock(nil, fn.Name) - // get dependencies of decl, struct decl as a special case. + // Obtain dependencies of the declaration. For struct declarations, + // this is treated as a special case where we need to search the fields + // to identify cyclic references. if isStructDecl { // find dependent name in same pkg findStructDeclDependentNames(m.Store, fn, decl, deps, pn.PkgPath) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 3b0270b5f7b..684d13b3ba1 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -14,7 +14,7 @@ import ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { - // First, initialize all file nodes and connect to package node + // First, initialize all file nodes and connect to package node. for _, fn := range fset.Files { SetNodeLocations(pn.PkgPath, string(fn.Name), fn) fn.InitStaticBlock(fn, pn) From 0442b2ac9d847d22f4c3780885c1864c8c81ac31 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Thu, 16 May 2024 21:38:32 +0800 Subject: [PATCH 13/35] fixup --- gnovm/pkg/gnolang/preprocess.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 684d13b3ba1..68c94bcd7d2 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1915,9 +1915,9 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } } } - // TODO make note of constance in static block for - // future use, or consider "const paths". set as - // preprocessed. + // TODO make note of constance in static block for + // future use, or consider "const paths". set as + // preprocessed. // TRANS_LEAVE ----------------------- case *TypeDecl: @@ -3547,8 +3547,8 @@ func findStructDeclDependentNames(store Store, last BlockNode, n Node, dst map[N for _, f := range st.Fields { ft := evalStaticType(store, last, f.Type) if dt, ok := ft.(*DeclaredType); ok { - if _, ok := dt.Base.(*StructType); ok { - if dt.PkgPath == pkgPath { + if dt.PkgPath == pkgPath { + if _, ok := dt.Base.(*StructType); ok { dst[dt.Name] = struct{}{} } } From 2ef06ab57e7f2440ac6c1d71eb0ef8ef670e7806 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Fri, 28 Jun 2024 18:32:39 +0800 Subject: [PATCH 14/35] delete txtar test --- .../cmd/gnoland/testdata/decl_recursive.txtar | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 gno.land/cmd/gnoland/testdata/decl_recursive.txtar diff --git a/gno.land/cmd/gnoland/testdata/decl_recursive.txtar b/gno.land/cmd/gnoland/testdata/decl_recursive.txtar deleted file mode 100644 index e9ac2761c03..00000000000 --- a/gno.land/cmd/gnoland/testdata/decl_recursive.txtar +++ /dev/null @@ -1,32 +0,0 @@ -# test for add package - -## start a new node -gnoland start - -## add hello.gno package located in $WORK directory as gno.land/r/decl -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/decl -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 - -stderr '"gnokey" error: --= Error =--\nData: loop in variable initialization: dependency trail' - --- decl.gno -- -package decl - -type A struct { - X B -} - -func Cmp() { - var p, q A - println(p == q) -} - --- decl2.gno -- -package decl - -type B struct { - X C -} - -type C struct { - X A -} From a5129a877de6d5fb19b19b0d577b1ff0a49938a3 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Fri, 28 Jun 2024 22:44:06 +0800 Subject: [PATCH 15/35] fixup --- gnovm/tests/files/circular_constant.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/tests/files/circular_constant.gno b/gnovm/tests/files/circular_constant.gno index 0f9b35fe8e5..1dea0d3d007 100644 --- a/gnovm/tests/files/circular_constant.gno +++ b/gnovm/tests/files/circular_constant.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/circular_constant.gno:1: constant definition loop with A +// main/files/circular_constant.gno:3: constant definition loop with A From 0469ef5566bc8e5aba51b0df9671645d36cfc01e Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Sun, 30 Jun 2024 14:59:59 +0800 Subject: [PATCH 16/35] re-introduce dependency graph --- .../cmd/gnoland/testdata/decl_recursive.txtar | 32 +++++ gnovm/pkg/gnolang/go2gno.go | 8 +- gnovm/pkg/gnolang/machine.go | 23 +--- gnovm/pkg/gnolang/misc.go | 126 ++++++++++++++++++ gnovm/pkg/gnolang/preprocess.go | 57 ++++---- gnovm/tests/files/recursive1.gno | 2 +- gnovm/tests/files/recursive1b.gno | 12 ++ gnovm/tests/files/recursive1c.gno | 18 +++ gnovm/tests/files/recursive1d.gno | 17 +++ gnovm/tests/files/recursive1e.gno | 13 ++ gnovm/tests/files/recursive2.gno | 2 +- gnovm/tests/files/recursive5.gno | 2 +- 12 files changed, 262 insertions(+), 50 deletions(-) create mode 100644 gno.land/cmd/gnoland/testdata/decl_recursive.txtar create mode 100644 gnovm/tests/files/recursive1b.gno create mode 100644 gnovm/tests/files/recursive1c.gno create mode 100644 gnovm/tests/files/recursive1d.gno create mode 100644 gnovm/tests/files/recursive1e.gno diff --git a/gno.land/cmd/gnoland/testdata/decl_recursive.txtar b/gno.land/cmd/gnoland/testdata/decl_recursive.txtar new file mode 100644 index 00000000000..11feb6f8a2e --- /dev/null +++ b/gno.land/cmd/gnoland/testdata/decl_recursive.txtar @@ -0,0 +1,32 @@ +# test for add package + +## start a new node +gnoland start + +## add hello.gno package located in $WORK directory as gno.land/r/decl +! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/decl -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 + +stderr '"gnokey" error: --= Error =--\nData: Cyclic dependency detected:' + +-- decl.gno -- +package decl + +type A struct { + X B +} + +func Cmp() { + var p, q A + println(p == q) +} + +-- decl2.gno -- +package decl + +type B struct { + X C +} + +type C struct { + X A +} \ No newline at end of file diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index fa958798653..6577b12c659 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -736,11 +736,15 @@ func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { name := toName(s.Name) tipe := toExpr(fs, s.Type) alias := s.Assign != 0 - ds = append(ds, &TypeDecl{ + td := &TypeDecl{ NameExpr: NameExpr{Name: name}, Type: tipe, IsAlias: alias, - }) + } + // collect decl info for debug + posn := fs.Position(gd.Pos()) + td.SetLine(posn.Line) + ds = append(ds, td) case *ast.ValueSpec: if gd.Tok == token.CONST { var names []NameExpr diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index f43c9f20028..864384ea122 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -562,28 +562,11 @@ func (m *Machine) runFiles(fns ...*FileNode) { // recursive function for var declarations. var runDeclarationFor func(fn *FileNode, decl Decl) runDeclarationFor = func(fn *FileNode, decl Decl) { - deps := make(map[Name]struct{}) - - isStructDecl := false - if td, ok := decl.(*TypeDecl); ok { - tdt := evalStaticType(m.Store, fn, td.Type) - if dt, ok := tdt.(*DeclaredType); ok { - if _, ok := dt.Base.(*StructType); ok { - isStructDecl = true - } - } - } // get fileblock of fn. // fb := pv.GetFileBlock(nil, fn.Name) - // Obtain dependencies of the declaration. For struct declarations, - // this is treated as a special case where we need to search the fields - // to identify cyclic references. - if isStructDecl { - // find dependent name in same pkg - findStructDeclDependentNames(m.Store, fn, decl, deps, pn.PkgPath) - } else { - findDependentNames(decl, deps) - } + // get dependencies of decl. + deps := make(map[Name]struct{}) + findDependentNames(decl, deps) for dep := range deps { // if dep already defined as import, skip. if _, ok := fn.GetLocalIndex(dep); ok { diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index a05de8c74aa..7667666159b 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -167,3 +167,129 @@ func DerivePkgAddr(pkgPath string) crypto.Address { // NOTE: must not collide with pubkey addrs. return crypto.AddressFromPreimage([]byte("pkgPath:" + pkgPath)) } + +// circular detection +// DeclNode represents a node in the dependency graph +// used to detect cycle definition of struct in `PredefineFileSet`. +type DeclNode struct { + Name + Line int + Loc Location // file info + Dependencies []*DeclNode +} + +// insertDeclNode inserts a new dependency into the graph +func insertDeclNode(name Name, line int, loc Location, deps ...Name) { + var dep *DeclNode + for _, d := range declGraph { + if d.Name == name { + dep = d + dep.Line = line + dep.Loc = loc + break + } + } + if dep == nil { + dep = &DeclNode{Name: name, Line: line, Loc: loc} + declGraph = append(declGraph, dep) + } + for _, depName := range deps { + var child *DeclNode + for _, d := range declGraph { + if d.Name == depName { + child = d + break + } + } + if child == nil { + child = &DeclNode{Name: depName} + declGraph = append(declGraph, child) + } + dep.Dependencies = append(dep.Dependencies, child) + } +} + +// dumpGraph prints the current declGraph +func dumpGraph() { + fmt.Println("-----------------------dump declGraph begin-------------------------") + for _, node := range declGraph { + fmt.Printf("%s, %d -> ", node.Name, node.Line) + for _, d := range node.Dependencies { + fmt.Printf("%s, %d ", d.Name, d.Line) + } + fmt.Println() + } + fmt.Println("-----------------------dump declGraph done-------------------------") +} + +// assertNoCycle checks if there is a cycle in the declGraph graph +func assertNoCycle() { + defer func() { + declGraph = nil + }() + visited := make(map[Name]bool) + reStack := make(map[Name]bool) + var cycle []*DeclNode + + for _, dep := range declGraph { + if detectCycle(dep, visited, reStack, &cycle) { + cycleNames := make([]string, len(cycle)) + for i, c := range cycle { + cycleNames[i] = fmt.Sprintf("%s(File: %s, Line: %d)", c.Name, c.Loc.File, c.Line) + } + cycleMsg := strings.Join(cycleNames, " -> ") + panic(fmt.Sprintf("Cyclic dependency detected: %s", cycleMsg)) + } + } +} + +// detectCycle detects cycle using DFS traversal +func detectCycle(node *DeclNode, visited, recStack map[Name]bool, cycle *[]*DeclNode) bool { + if visited[node.Name] { // existing visited node are not in cycle, otherwise it wil be elided + return false + } + visited[node.Name] = true + recStack[node.Name] = true + *cycle = append(*cycle, node) + + for _, d := range node.Dependencies { + // check if d is in recStack to form a cycle + if recStack[d.Name] { + for _, n := range *cycle { + if n == d { + startIndex := 0 + for ; (*cycle)[startIndex] != d; startIndex++ { + } + *cycle = append((*cycle)[startIndex:], d) + } + } + return true + } else { + if detectCycle(d, visited, recStack, cycle) { + return true + } + } + } + + delete(recStack, node.Name) + // Backtrack: Remove the last node from the cycle slice and mark as not in recStack + *cycle = (*cycle)[:len(*cycle)-1] + + return false +} + +func checkFieldReference(PkgPath string, t Type, names *[]Name) bool { + switch fdt := t.(type) { + case *DeclaredType: + if PkgPath == fdt.PkgPath { // not cross pkg + *names = append(*names, fdt.Name) + return true + } + case *ArrayType: + return checkFieldReference(PkgPath, fdt.Elem(), names) + default: + return false + } + + return false +} diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index ff7a76fbebb..61fcc05844f 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -18,6 +18,18 @@ const ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { + defer func() { + //fmt.Println("---Predefine file set ending") + // Check for cyclic + if len(declGraph) != 0 { + //dumpGraph() + assertNoCycle() + } + //fmt.Println("---no cycle detected") + if err := recover(); err != nil { + panic(err) + } + }() // First, initialize all file nodes and connect to package node. for _, fn := range fset.Files { SetNodeLocations(pn.PkgPath, string(fn.Name), fn) @@ -99,6 +111,9 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { } } +// declGraph represents a slice of declGraph +var declGraph []*DeclNode + // This counter ensures (during testing) that certain functions // (like ConvertUntypedTo() for bigints and strings) // are only called during the preprocessing stage. @@ -1970,6 +1985,21 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *StructType: *dst = *(tmp.(*StructType)) case *DeclaredType: + if st, ok := tmp.(*StructType); ok { + // check if fields contains declaredType + maybeRecursive := false + names := make([]Name, 0) + + for _, f := range st.Fields { + maybeRecursive = checkFieldReference(st.PkgPath, f.Type, &names) + } + if maybeRecursive { + for _, name := range names { + line := n.GetLine() + insertDeclNode(dst.Name, line, last.GetLocation(), name) + } + } + } // if store has this type, use that. tid := DeclaredTypeID(lastpn.PkgPath, n.Name) exists := false @@ -2845,6 +2875,7 @@ func checkIntegerKind(xt Type) { // preprocess-able before other file-level declarations are // preprocessed). func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { + //fmt.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { // before re-throwing the error, append location information to message. @@ -3140,7 +3171,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { last2.Define(d.Name, asValue(t)) d.Path = last.GetPathForName(store, d.Name) } - // after predefinitions, return any undefined dependencies. + // after predefinitions, return any undefined declGraph. un = findUndefined(store, last, d.Type) if un != "" { return @@ -3423,30 +3454,6 @@ func countNumArgs(store Store, last BlockNode, n *CallExpr) (numArgs int) { } } -func findStructDeclDependentNames(store Store, last BlockNode, n Node, dst map[Name]struct{}, pkgPath string) { - switch cn := n.(type) { - case *constTypeExpr: - if st, ok := cn.Source.(*StructTypeExpr); ok { - for _, f := range st.Fields { - ft := evalStaticType(store, last, f.Type) - if dt, ok := ft.(*DeclaredType); ok { - if dt.PkgPath == pkgPath { - if _, ok := dt.Base.(*StructType); ok { - dst[dt.Name] = struct{}{} - } - } - } - } - } - case *TypeDecl: - findStructDeclDependentNames(store, last, cn.Type, dst, pkgPath) - default: - panic(fmt.Sprintf( - "unexpected node: %v (%v)", - n, reflect.TypeOf(n))) - } -} - // This is to be run *after* preprocessing is done, // to determine the order of var decl execution // (which may include functions which may refer to package vars). diff --git a/gnovm/tests/files/recursive1.gno b/gnovm/tests/files/recursive1.gno index a51643e143f..36df2e4d583 100644 --- a/gnovm/tests/files/recursive1.gno +++ b/gnovm/tests/files/recursive1.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// loop in variable initialization: dependency trail [S] circularly depends on S +// Cyclic dependency detected: S(File: files/recursive1.gno, Line: 3) -> S(File: files/recursive1.gno, Line: 3) diff --git a/gnovm/tests/files/recursive1b.gno b/gnovm/tests/files/recursive1b.gno new file mode 100644 index 00000000000..af7523e49ad --- /dev/null +++ b/gnovm/tests/files/recursive1b.gno @@ -0,0 +1,12 @@ +package main + +type S struct { + T S +} + +func main() { + var a, b S +} + +// Error: +// Cyclic dependency detected: S(File: files/recursive1b.gno, Line: 3) -> S(File: files/recursive1b.gno, Line: 3) diff --git a/gnovm/tests/files/recursive1c.gno b/gnovm/tests/files/recursive1c.gno new file mode 100644 index 00000000000..b78ceb92eec --- /dev/null +++ b/gnovm/tests/files/recursive1c.gno @@ -0,0 +1,18 @@ +package main + +import "fmt" + +type S struct { + A [2][2]S +} + +func main() { + var a, b S + + //println(a == b) + fmt.Println(a) + fmt.Println(b) +} + +// Error: +// Cyclic dependency detected: S(File: files/recursive1c.gno, Line: 5) -> S(File: files/recursive1c.gno, Line: 5) diff --git a/gnovm/tests/files/recursive1d.gno b/gnovm/tests/files/recursive1d.gno new file mode 100644 index 00000000000..d6c1eaffe4d --- /dev/null +++ b/gnovm/tests/files/recursive1d.gno @@ -0,0 +1,17 @@ +package main + +import "fmt" + +type S struct { + A [2]S +} + +func main() { + var a, b S + + fmt.Println(a) + fmt.Println(b) +} + +// Error: +// Cyclic dependency detected: S(File: files/recursive1d.gno, Line: 5) -> S(File: files/recursive1d.gno, Line: 5) diff --git a/gnovm/tests/files/recursive1e.gno b/gnovm/tests/files/recursive1e.gno new file mode 100644 index 00000000000..6d1636ba9f3 --- /dev/null +++ b/gnovm/tests/files/recursive1e.gno @@ -0,0 +1,13 @@ +package main + +type S struct { + A [2][]S +} + +func main() { + var a, b S + println(a) +} + +// Output: +// (struct{(array[(nil []main.S),(nil []main.S)] [2][]main.S)} main.S) diff --git a/gnovm/tests/files/recursive2.gno b/gnovm/tests/files/recursive2.gno index 3b759364fbe..cdf0f7c6587 100644 --- a/gnovm/tests/files/recursive2.gno +++ b/gnovm/tests/files/recursive2.gno @@ -18,4 +18,4 @@ func main() { } // Error: -// loop in variable initialization: dependency trail [B C A] circularly depends on B +// Cyclic dependency detected: C(File: files/recursive2.gno, Line: 11) -> A(File: files/recursive2.gno, Line: 3) -> B(File: files/recursive2.gno, Line: 7) -> C(File: files/recursive2.gno, Line: 11) diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno index 8cbbd9ae67d..9ee98226a79 100644 --- a/gnovm/tests/files/recursive5.gno +++ b/gnovm/tests/files/recursive5.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// loop in variable initialization: dependency trail [S] circularly depends on S +// Cyclic dependency detected: S(File: files/recursive5.gno, Line: 3) -> S(File: files/recursive5.gno, Line: 3) From ce21b4d58d0304ea88d530557174f5220b329bff Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Sun, 30 Jun 2024 15:04:56 +0800 Subject: [PATCH 17/35] fixup --- gnovm/pkg/gnolang/misc.go | 13 ------------- gnovm/pkg/gnolang/preprocess.go | 5 +---- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index 7667666159b..430261ac86b 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -209,19 +209,6 @@ func insertDeclNode(name Name, line int, loc Location, deps ...Name) { } } -// dumpGraph prints the current declGraph -func dumpGraph() { - fmt.Println("-----------------------dump declGraph begin-------------------------") - for _, node := range declGraph { - fmt.Printf("%s, %d -> ", node.Name, node.Line) - for _, d := range node.Dependencies { - fmt.Printf("%s, %d ", d.Name, d.Line) - } - fmt.Println() - } - fmt.Println("-----------------------dump declGraph done-------------------------") -} - // assertNoCycle checks if there is a cycle in the declGraph graph func assertNoCycle() { defer func() { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 61fcc05844f..c9d174c59f9 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -19,13 +19,10 @@ const ( // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { defer func() { - //fmt.Println("---Predefine file set ending") // Check for cyclic if len(declGraph) != 0 { - //dumpGraph() assertNoCycle() } - //fmt.Println("---no cycle detected") if err := recover(); err != nil { panic(err) } @@ -2875,7 +2872,7 @@ func checkIntegerKind(xt Type) { // preprocess-able before other file-level declarations are // preprocessed). func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { - //fmt.Println("---predefine now, d.line", d.GetLine()) + // fmt.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { // before re-throwing the error, append location information to message. From 954fb82c029753739c5d1ede989ffbe214fb6446 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Sun, 30 Jun 2024 15:14:34 +0800 Subject: [PATCH 18/35] fixup --- .../cmd/gnoland/testdata/decl_recursive.txtar | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 gno.land/cmd/gnoland/testdata/decl_recursive.txtar diff --git a/gno.land/cmd/gnoland/testdata/decl_recursive.txtar b/gno.land/cmd/gnoland/testdata/decl_recursive.txtar deleted file mode 100644 index 11feb6f8a2e..00000000000 --- a/gno.land/cmd/gnoland/testdata/decl_recursive.txtar +++ /dev/null @@ -1,32 +0,0 @@ -# test for add package - -## start a new node -gnoland start - -## add hello.gno package located in $WORK directory as gno.land/r/decl -! gnokey maketx addpkg -pkgdir $WORK -pkgpath gno.land/r/decl -gas-fee 1000000ugnot -gas-wanted 2000000 -broadcast -chainid=tendermint_test test1 - -stderr '"gnokey" error: --= Error =--\nData: Cyclic dependency detected:' - --- decl.gno -- -package decl - -type A struct { - X B -} - -func Cmp() { - var p, q A - println(p == q) -} - --- decl2.gno -- -package decl - -type B struct { - X C -} - -type C struct { - X A -} \ No newline at end of file From da4fec127a9d1ccd64ca3dca5c8cdb5cf933c127 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Sun, 30 Jun 2024 15:53:22 +0800 Subject: [PATCH 19/35] test --- gnovm/pkg/gnolang/debugger_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index 3f2f58b9709..8152ff184cb 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -119,7 +119,7 @@ func TestDebug(t *testing.T) { {in: "p \"xxxx\"\n", out: `("xxxx" string)`}, {in: "si\n", out: "sample.gno:14"}, {in: "s\ns\n", out: `=> 14: var global = "test"`}, - {in: "s\n\n", out: "=> 33: num := 5"}, + //{in: "s\n\n", out: "=> 33: num := 5"}, {in: "foo", out: "command not available: foo"}, {in: "\n\n", out: "dbg> "}, {in: "#\n", out: "dbg> "}, From c8fda764761d7ad4e23069179dc03653a9fbe0f0 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Sun, 30 Jun 2024 16:43:57 +0800 Subject: [PATCH 20/35] fix test, resolve conflicts with debugger on line number --- gnovm/pkg/gnolang/debugger_test.go | 2 +- gnovm/pkg/gnolang/go2gno.go | 8 ++------ gnovm/pkg/gnolang/misc.go | 8 +++----- gnovm/pkg/gnolang/preprocess.go | 3 +-- gnovm/tests/files/recursive1.gno | 2 +- gnovm/tests/files/recursive1b.gno | 2 +- gnovm/tests/files/recursive1c.gno | 2 +- gnovm/tests/files/recursive1d.gno | 2 +- gnovm/tests/files/recursive2.gno | 2 +- gnovm/tests/files/recursive5.gno | 2 +- 10 files changed, 13 insertions(+), 20 deletions(-) diff --git a/gnovm/pkg/gnolang/debugger_test.go b/gnovm/pkg/gnolang/debugger_test.go index 8152ff184cb..3f2f58b9709 100644 --- a/gnovm/pkg/gnolang/debugger_test.go +++ b/gnovm/pkg/gnolang/debugger_test.go @@ -119,7 +119,7 @@ func TestDebug(t *testing.T) { {in: "p \"xxxx\"\n", out: `("xxxx" string)`}, {in: "si\n", out: "sample.gno:14"}, {in: "s\ns\n", out: `=> 14: var global = "test"`}, - //{in: "s\n\n", out: "=> 33: num := 5"}, + {in: "s\n\n", out: "=> 33: num := 5"}, {in: "foo", out: "command not available: foo"}, {in: "\n\n", out: "dbg> "}, {in: "#\n", out: "dbg> "}, diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index 6577b12c659..fa958798653 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -736,15 +736,11 @@ func toDecls(fs *token.FileSet, gd *ast.GenDecl) (ds Decls) { name := toName(s.Name) tipe := toExpr(fs, s.Type) alias := s.Assign != 0 - td := &TypeDecl{ + ds = append(ds, &TypeDecl{ NameExpr: NameExpr{Name: name}, Type: tipe, IsAlias: alias, - } - // collect decl info for debug - posn := fs.Position(gd.Pos()) - td.SetLine(posn.Line) - ds = append(ds, td) + }) case *ast.ValueSpec: if gd.Tok == token.CONST { var names []NameExpr diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index 430261ac86b..cf55c6156e9 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -173,24 +173,22 @@ func DerivePkgAddr(pkgPath string) crypto.Address { // used to detect cycle definition of struct in `PredefineFileSet`. type DeclNode struct { Name - Line int Loc Location // file info Dependencies []*DeclNode } // insertDeclNode inserts a new dependency into the graph -func insertDeclNode(name Name, line int, loc Location, deps ...Name) { +func insertDeclNode(name Name, loc Location, deps ...Name) { var dep *DeclNode for _, d := range declGraph { if d.Name == name { dep = d - dep.Line = line dep.Loc = loc break } } if dep == nil { - dep = &DeclNode{Name: name, Line: line, Loc: loc} + dep = &DeclNode{Name: name, Loc: loc} declGraph = append(declGraph, dep) } for _, depName := range deps { @@ -222,7 +220,7 @@ func assertNoCycle() { if detectCycle(dep, visited, reStack, &cycle) { cycleNames := make([]string, len(cycle)) for i, c := range cycle { - cycleNames[i] = fmt.Sprintf("%s(File: %s, Line: %d)", c.Name, c.Loc.File, c.Line) + cycleNames[i] = fmt.Sprintf("%s(File: %s)", c.Name, c.Loc.File) } cycleMsg := strings.Join(cycleNames, " -> ") panic(fmt.Sprintf("Cyclic dependency detected: %s", cycleMsg)) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index c9d174c59f9..4d3421279c8 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1992,8 +1992,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } if maybeRecursive { for _, name := range names { - line := n.GetLine() - insertDeclNode(dst.Name, line, last.GetLocation(), name) + insertDeclNode(dst.Name, last.GetLocation(), name) } } } diff --git a/gnovm/tests/files/recursive1.gno b/gnovm/tests/files/recursive1.gno index 36df2e4d583..57da1523c8d 100644 --- a/gnovm/tests/files/recursive1.gno +++ b/gnovm/tests/files/recursive1.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive1.gno, Line: 3) -> S(File: files/recursive1.gno, Line: 3) +// Cyclic dependency detected: S(File: files/recursive1.gno) -> S(File: files/recursive1.gno) diff --git a/gnovm/tests/files/recursive1b.gno b/gnovm/tests/files/recursive1b.gno index af7523e49ad..2a62a2c29be 100644 --- a/gnovm/tests/files/recursive1b.gno +++ b/gnovm/tests/files/recursive1b.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive1b.gno, Line: 3) -> S(File: files/recursive1b.gno, Line: 3) +// Cyclic dependency detected: S(File: files/recursive1b.gno) -> S(File: files/recursive1b.gno) diff --git a/gnovm/tests/files/recursive1c.gno b/gnovm/tests/files/recursive1c.gno index b78ceb92eec..ee2657017d0 100644 --- a/gnovm/tests/files/recursive1c.gno +++ b/gnovm/tests/files/recursive1c.gno @@ -15,4 +15,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive1c.gno, Line: 5) -> S(File: files/recursive1c.gno, Line: 5) +// Cyclic dependency detected: S(File: files/recursive1c.gno) -> S(File: files/recursive1c.gno) diff --git a/gnovm/tests/files/recursive1d.gno b/gnovm/tests/files/recursive1d.gno index d6c1eaffe4d..ab2f2670ab8 100644 --- a/gnovm/tests/files/recursive1d.gno +++ b/gnovm/tests/files/recursive1d.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive1d.gno, Line: 5) -> S(File: files/recursive1d.gno, Line: 5) +// Cyclic dependency detected: S(File: files/recursive1d.gno) -> S(File: files/recursive1d.gno) diff --git a/gnovm/tests/files/recursive2.gno b/gnovm/tests/files/recursive2.gno index cdf0f7c6587..67a8df0a477 100644 --- a/gnovm/tests/files/recursive2.gno +++ b/gnovm/tests/files/recursive2.gno @@ -18,4 +18,4 @@ func main() { } // Error: -// Cyclic dependency detected: C(File: files/recursive2.gno, Line: 11) -> A(File: files/recursive2.gno, Line: 3) -> B(File: files/recursive2.gno, Line: 7) -> C(File: files/recursive2.gno, Line: 11) +// Cyclic dependency detected: C(File: files/recursive2.gno) -> A(File: files/recursive2.gno) -> B(File: files/recursive2.gno) -> C(File: files/recursive2.gno) diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno index 9ee98226a79..58e6b566842 100644 --- a/gnovm/tests/files/recursive5.gno +++ b/gnovm/tests/files/recursive5.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive5.gno, Line: 3) -> S(File: files/recursive5.gno, Line: 3) +// Cyclic dependency detected: S(File: files/recursive5.gno) -> S(File: files/recursive5.gno) From ef120b8f9fe1b161d9e5065c477fc786a790419b Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Tue, 9 Jul 2024 09:52:33 -0500 Subject: [PATCH 21/35] save --- gnovm/1.gno | 12 ++ gnovm/1a.gno | 9 ++ gnovm/1b.gno | 10 ++ gnovm/2.gno | 8 ++ gnovm/3.gno | 12 ++ gnovm/pkg/gnolang/machine.go | 2 + gnovm/pkg/gnolang/misc.go | 35 +++++- gnovm/pkg/gnolang/nodes.go | 2 + gnovm/pkg/gnolang/preprocess.go | 216 +++++++++++++++++++++++--------- gnovm/pkg/gnolang/type_check.go | 1 + 10 files changed, 239 insertions(+), 68 deletions(-) create mode 100644 gnovm/1.gno create mode 100644 gnovm/1a.gno create mode 100644 gnovm/1b.gno create mode 100644 gnovm/2.gno create mode 100644 gnovm/3.gno diff --git a/gnovm/1.gno b/gnovm/1.gno new file mode 100644 index 00000000000..8c3eabd3c95 --- /dev/null +++ b/gnovm/1.gno @@ -0,0 +1,12 @@ +package main + +const a = b + 1 +const b = a + +func main() { + println(a) + println(b) +} + +// Error: +// main/1.gno:3: 2 constant definition loop with a diff --git a/gnovm/1a.gno b/gnovm/1a.gno new file mode 100644 index 00000000000..ec61ba94d9d --- /dev/null +++ b/gnovm/1a.gno @@ -0,0 +1,9 @@ +package main + +func main() { + var a = b + 1 + var b = a + + println(a) + println(b) +} diff --git a/gnovm/1b.gno b/gnovm/1b.gno new file mode 100644 index 00000000000..ff03556049c --- /dev/null +++ b/gnovm/1b.gno @@ -0,0 +1,10 @@ +package main + +func main() { + + const a = b + 1 + const b = a + + println(a) + println(b) +} diff --git a/gnovm/2.gno b/gnovm/2.gno new file mode 100644 index 00000000000..081a16c0045 --- /dev/null +++ b/gnovm/2.gno @@ -0,0 +1,8 @@ +package main + +var a int + +func main() { + a = 1 + println(a) +} diff --git a/gnovm/3.gno b/gnovm/3.gno new file mode 100644 index 00000000000..8429c40b55a --- /dev/null +++ b/gnovm/3.gno @@ -0,0 +1,12 @@ +package main + + +func main() { + type S struct { + T S + } + var a, b S + println(a == b) +} + + diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 864384ea122..02518ebc6cb 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -618,8 +618,10 @@ func (m *Machine) runFiles(fns ...*FileNode) { // order and depend on other files. // Run declarations. + // XXX, is this only for global vars for _, fn := range fns { for _, decl := range fn.Decls { + fmt.Println("---machine runDeclareFor, decl: ", decl) runDeclarationFor(fn, decl) } } diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index cf55c6156e9..d0d452863dd 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -177,10 +177,28 @@ type DeclNode struct { Dependencies []*DeclNode } +// dumpGraph prints the current declGraph +func dumpGraph(declGraph []*DeclNode) { + fmt.Println("-----------------------dump declGraph begin-------------------------") + for _, node := range declGraph { + fmt.Printf("%s -> ", node.Name) + for _, d := range node.Dependencies { + fmt.Printf("%s: ", d.Name) + } + fmt.Println() + } + fmt.Println("-----------------------dump declGraph done-------------------------") +} + // insertDeclNode inserts a new dependency into the graph -func insertDeclNode(name Name, loc Location, deps ...Name) { +func insertDeclNode(declGraph *[]*DeclNode, name Name, loc Location, deps ...Name) { + fmt.Println("---insertDeclNode, name: ", name) + for _, d := range *declGraph { + fmt.Println("---insertDeclNode, declGraph: ", *d) + } + var dep *DeclNode - for _, d := range declGraph { + for _, d := range *declGraph { if d.Name == name { dep = d dep.Loc = loc @@ -189,11 +207,11 @@ func insertDeclNode(name Name, loc Location, deps ...Name) { } if dep == nil { dep = &DeclNode{Name: name, Loc: loc} - declGraph = append(declGraph, dep) + *declGraph = append(*declGraph, dep) } for _, depName := range deps { var child *DeclNode - for _, d := range declGraph { + for _, d := range *declGraph { if d.Name == depName { child = d break @@ -201,14 +219,19 @@ func insertDeclNode(name Name, loc Location, deps ...Name) { } if child == nil { child = &DeclNode{Name: depName} - declGraph = append(declGraph, child) + *declGraph = append(*declGraph, child) } dep.Dependencies = append(dep.Dependencies, child) } + + dumpGraph(*declGraph) } // assertNoCycle checks if there is a cycle in the declGraph graph -func assertNoCycle() { +func assertNoCycle(declGraph []*DeclNode) { + for _, d := range declGraph { + fmt.Println("---assertNoCycle, declGraph: ", *d) + } defer func() { declGraph = nil }() diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 2897fdd5306..399b3a2867a 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1772,6 +1772,8 @@ func (sb *StaticBlock) Predefine(isConst bool, n Name) { // The declared type st may not be the same as the static tv; // e.g. var x MyInterface = MyStruct{}. func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { + fmt.Println("---Define2, n: ", n) + if debug { debug.Printf( "StaticBlock.Define2(%v, %s, %v, %v)\n", diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 4d3421279c8..fbf02896a2b 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -18,11 +18,16 @@ const ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { + var declGraph []*DeclNode + loopfindr := make(map[Name]struct{}) + + fmt.Println("---predefineFileSet start") defer func() { + fmt.Println("---predefineFileSet end") // Check for cyclic - if len(declGraph) != 0 { - assertNoCycle() - } + //if len(declGraph) != 0 { + // assertNoCycle() + //} if err := recover(); err != nil { panic(err) } @@ -49,7 +54,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // dependent) } else { // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d) + d2, _ := predefineNow(store, fn, d, &declGraph, &loopfindr) fn.Decls[i] = d2 } } @@ -58,16 +63,19 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // Predefine all type decls decls. for _, fn := range fset.Files { for i := 0; i < len(fn.Decls); i++ { + fmt.Println("---PredefineFileSet, len of decls: ", len(fn.Decls)) + fmt.Println("---PredefineFileSet, fn.Decls: ", fn.Decls) d := fn.Decls[i] switch d.(type) { case *TypeDecl: + fmt.Println("---predefineFileSet, type decl, d: ", d) if d.GetAttribute(ATTR_PREDEFINED) == true { // skip declarations already predefined // (e.g. through recursion for a // dependent) } else { // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d) + d2, _ := predefineNow(store, fn, d, &declGraph, &loopfindr) fn.Decls[i] = d2 } } @@ -85,7 +93,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // dependent) } else { // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d) + d2, _ := predefineNow(store, fn, d, &declGraph, &loopfindr) fn.Decls[i] = d2 } } @@ -101,16 +109,13 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // through recursion for a dependent) } else { // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d) + d2, _ := predefineNow(store, fn, d, &declGraph, &loopfindr) fn.Decls[i] = d2 } } } } -// declGraph represents a slice of declGraph -var declGraph []*DeclNode - // This counter ensures (during testing) that certain functions // (like ConvertUntypedTo() for bigints and strings) // are only called during the preprocessing stage. @@ -152,6 +157,12 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { SetNodeLocations(pkgPath, fileName, fn) } + fmt.Println("---preprocess, new declGraph") + // declGraph represents a slice of declGraph + var declGraph []*DeclNode + + loopfindr := make(map[Name]struct{}) + // create stack of BlockNodes. var stack []BlockNode = make([]BlockNode, 0, 32) var last BlockNode = ctx @@ -237,7 +248,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // (e.g. through recursion for a dependent) } else { // recursively predefine dependencies. - d2, ppd := predefineNow(store, last, n.(Decl)) + d2, ppd := predefineNow(store, last, n.(Decl), &declGraph, &loopfindr) if ppd { return d2, TRANS_SKIP } else { @@ -536,7 +547,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // recursively predefine // dependencies. - d2, _ := predefineNow(store, n, d) + d2, _ := predefineNow(store, n, d, &declGraph, &loopfindr) n.Decls[i] = d2 } } @@ -553,7 +564,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // recursively predefine // dependencies. - d2, _ := predefineNow(store, n, d) + d2, _ := predefineNow(store, n, d, &declGraph, &loopfindr) n.Decls[i] = d2 } } @@ -570,7 +581,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // recursively predefine // dependencies. - d2, _ := predefineNow(store, n, d) + d2, _ := predefineNow(store, n, d, &declGraph, &loopfindr) n.Decls[i] = d2 } } @@ -586,7 +597,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // recursively predefine // dependencies. - d2, _ := predefineNow(store, n, d) + d2, _ := predefineNow(store, n, d, &declGraph, &loopfindr) n.Decls[i] = d2 } } @@ -1610,7 +1621,10 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *StructTypeExpr: + fmt.Println("---trans_leave, StructTypeExpr") evalStaticType(store, last, n) + // TODO: maxwell can we do something here? + //panic("---!!!") // TRANS_LEAVE ----------------------- case *AssignStmt: @@ -1960,6 +1974,9 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *TypeDecl: + fmt.Println("---trans_leave, TypeDecl") + + // TODO: maxwell, do somthing here? // Construct new Type, where any recursive // references refer to the old Type declared // during *TypeDecl:ENTER. Then, copy over the @@ -1982,20 +1999,22 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *StructType: *dst = *(tmp.(*StructType)) case *DeclaredType: - if st, ok := tmp.(*StructType); ok { - // check if fields contains declaredType - maybeRecursive := false - names := make([]Name, 0) - - for _, f := range st.Fields { - maybeRecursive = checkFieldReference(st.PkgPath, f.Type, &names) - } - if maybeRecursive { - for _, name := range names { - insertDeclNode(dst.Name, last.GetLocation(), name) - } - } - } + //if st, ok := tmp.(*StructType); ok { + // // check if fields contains declaredType + // maybeRecursive := false + // names := make([]Name, 0) + // + // for _, f := range st.Fields { + // maybeRecursive = checkFieldReference(st.PkgPath, f.Type, &names) + // } + // if maybeRecursive { + // for _, name := range names { + // insertDeclNode(&declGraph, dst.Name, last.GetLocation(), name) + // } + // } + //} + //// do assert + //assertNoCycle(declGraph) // if store has this type, use that. tid := DeclaredTypeID(lastpn.PkgPath, n.Name) exists := false @@ -2609,19 +2628,23 @@ func findUndefined(store Store, last BlockNode, x Expr) (un Name) { return findUndefined2(store, last, x, nil) } +// TODO: add helpers for type decl func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { + fmt.Println("---findUndefined2, x, t: ", x, t) if x == nil { return } switch cx := x.(type) { case *NameExpr: if tv := last.GetValueRef(store, cx.Name); tv != nil { + fmt.Println("---1") return } if _, ok := UverseNode().GetLocalIndex(cx.Name); ok { // XXX NOTE even if the name is shadowed by a file // level declaration, it is fine to return here as it // will be predefined later. + fmt.Println("---2") return } return cx.Name @@ -2639,30 +2662,31 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { case *SelectorExpr: return findUndefined(store, last, cx.X) case *SliceExpr: - un = findUndefined(store, last, cx.X) - if un != "" { - return - } - if cx.Low != nil { - un = findUndefined(store, last, cx.Low) - if un != "" { - return - } - } - if cx.High != nil { - un = findUndefined(store, last, cx.High) - if un != "" { - return - } - } - if cx.Max != nil { - un = findUndefined(store, last, cx.Max) - if un != "" { - return - } - } + //un = findUndefined(store, last, cx.X) + //if un != "" { + // return + //} + //if cx.Low != nil { + // un = findUndefined(store, last, cx.Low) + // if un != "" { + // return + // } + //} + //if cx.High != nil { + // un = findUndefined(store, last, cx.High) + // if un != "" { + // return + // } + //} + //if cx.Max != nil { + // un = findUndefined(store, last, cx.Max) + // if un != "" { + // return + // } + //} case *StarExpr: return findUndefined(store, last, cx.X) + return case *RefExpr: return findUndefined(store, last, cx.X) case *TypeAssertExpr: @@ -2870,8 +2894,8 @@ func checkIntegerKind(xt Type) { // preprocesses function declarations (which may not be completely // preprocess-able before other file-level declarations are // preprocessed). -func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { - // fmt.Println("---predefine now, d.line", d.GetLine()) +func predefineNow(store Store, last BlockNode, d Decl, declGraph *[]*DeclNode, loopfinder *map[Name]struct{}) (Decl, bool) { + fmt.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { // before re-throwing the error, append location information to message. @@ -2889,10 +2913,11 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { } }() m := make(map[Name]struct{}) - return predefineNow2(store, last, d, m) + return predefineNow2(store, last, d, m, declGraph, loopfinder) } -func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (Decl, bool) { +func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}, declGraph *[]*DeclNode, loopfinder *map[Name]struct{}) (Decl, bool) { + fmt.Println("---predefineNow2, d: ", d) pkg := packageOf(last) // pre-register d.GetName() to detect circular definition. for _, dn := range d.GetDeclNames() { @@ -2905,6 +2930,8 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De // recursively predefine dependencies. for { un := tryPredefine(store, last, d) + fmt.Printf("---un: %s after tryPredefine d: %v \n", un, d) + fmt.Println("---m: ", m) if un != "" { // check circularity. if _, ok := m[un]; ok { @@ -2917,11 +2944,12 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De panic("all types from files in file-set should have already been predefined") } // predefine dependency (recursive). - *decl, _ = predefineNow2(store, file, *decl, m) + *decl, _ = predefineNow2(store, file, *decl, m, declGraph, loopfinder) } else { break } } + // after recursive define switch cd := d.(type) { case *FuncDecl: // *FuncValue/*FuncType is mostly empty still; here @@ -3009,7 +3037,57 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De case *ValueDecl: return Preprocess(store, last, cd).(Decl), true case *TypeDecl: - return Preprocess(store, last, cd).(Decl), true + fmt.Println("---predefineNow2, loopfinder: ", loopfinder) + + fmt.Println("---predefineNow2, cd: ", cd) + fmt.Println("---predefineNow2, cd.Type: ", cd.Type) + fmt.Println("---predefineNow2, m: ", m) + d := Preprocess(store, last, cd).(Decl) + fmt.Println("---predefineNow2, d: ", d) + + td := d.(*TypeDecl) + fmt.Println("---td: ", td) + fmt.Println("---td.Type: ", td.Type) + + tmp := evalStaticType(store, last, cd.Type) + dst := last.GetValueRef(store, cd.Name).GetType() + + fmt.Println("---dst: ", dst) + switch dst := dst.(type) { + case *DeclaredType: + fmt.Println("---declared type, tmp: ", tmp, reflect.TypeOf(tmp)) + if dt, ok := tmp.(*DeclaredType); ok { + fmt.Println("---dt: ", dt, dt.Name, dt.Base) + if st, ok := dt.Base.(*StructType); ok { + fmt.Println("---struct type") + // check if fields contains declaredType + maybeRecursive := false + names := make([]Name, 0) + + for _, f := range st.Fields { + maybeRecursive = checkFieldReference(st.PkgPath, f.Type, &names) + } + if maybeRecursive { + for _, name := range names { + fmt.Println("---name: ", name) + //if _, ok := (*loopfinder)[dst.Name]; ok { + // panic(fmt.Sprintf("loop definition: %v ", *loopfinder)) + //} + + (*loopfinder)[dst.Name] = struct{}{} + fmt.Println("---after insert, loopfinder: ", *loopfinder) + + insertDeclNode(declGraph, dst.Name, last.GetLocation(), name) + } + } + } + } + // do assert + assertNoCycle(*declGraph) + } + + return td, true + //return Preprocess(store, last, cd).(Decl), true default: return d, false } @@ -3025,6 +3103,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De // must be called for name declarations within (non-file, // non-package) stmt bodies. func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { + fmt.Println("---tryPredefine, d: ", d) if d.GetAttribute(ATTR_PREDEFINED) == true { panic("decl node already predefined!") } @@ -3063,13 +3142,18 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { }) d.Path = last.GetPathForName(store, d.Name) case *ValueDecl: + fmt.Println("---value decl") un = findUndefined(store, last, d.Type) if un != "" { + fmt.Println("---return 1") return } for _, vx := range d.Values { + fmt.Println("---vx: ", vx) un = findUndefined(store, last, vx) + fmt.Println("---un: ", un) if un != "" { + fmt.Println("---return 2") return } } @@ -3084,6 +3168,13 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } } case *TypeDecl: + fmt.Println("---typeDecl") + // XXX, before predefine + un = findUndefined(store, last, d.Type) + fmt.Println("---un for type decl: ", un) + if un != "" { + return + } // before looking for dependencies, predefine empty type. last2 := skipFile(last) _, ok := last2.GetLocalIndex(d.Name) @@ -3167,11 +3258,12 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { last2.Define(d.Name, asValue(t)) d.Path = last.GetPathForName(store, d.Name) } - // after predefinitions, return any undefined declGraph. - un = findUndefined(store, last, d.Type) - if un != "" { - return - } + //// after predefinitions, return any undefined declGraph. + //un = findUndefined(store, last, d.Type) + //fmt.Println("---un for type decl: ", un) + //if un != "" { + // return + //} case *FuncDecl: un = findUndefined(store, last, &d.Type) if un != "" { diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 870eb10b690..4b4e078f478 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -137,6 +137,7 @@ func isNumericOrString(t Type) bool { // =========================================================== func assertComparable(xt, dt Type) { + fmt.Println("---assertComparable...") switch baseOf(dt).(type) { case *SliceType, *FuncType, *MapType: if xt != nil { From 20148ea25ed9bbc150f4285d87ce990b478174e2 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Thu, 25 Jul 2024 23:40:49 +0800 Subject: [PATCH 22/35] save --- gnovm/pkg/gnolang/misc.go | 51 ++++ gnovm/pkg/gnolang/preprocess.go | 398 +++++++++++++++++++++++------- gnovm/tests/files/recursive1.gno | 2 +- gnovm/tests/files/recursive12.gno | 15 ++ gnovm/tests/files/recursive1b.gno | 2 +- gnovm/tests/files/recursive1c.gno | 2 +- gnovm/tests/files/recursive1d.gno | 2 +- gnovm/tests/files/recursive1f.gno | 13 + gnovm/tests/files/recursive2.gno | 2 +- gnovm/tests/files/recursive2b.gno | 21 ++ gnovm/tests/files/recursive2c.gno | 21 ++ gnovm/tests/files/recursive5.gno | 2 +- 12 files changed, 442 insertions(+), 89 deletions(-) create mode 100644 gnovm/tests/files/recursive12.gno create mode 100644 gnovm/tests/files/recursive1f.gno create mode 100644 gnovm/tests/files/recursive2b.gno create mode 100644 gnovm/tests/files/recursive2c.gno diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index d0d452863dd..bee147b5da3 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -301,3 +301,54 @@ func checkFieldReference(PkgPath string, t Type, names *[]Name) bool { return false } + +// -----------------dependency graph-------------------- +type Element struct { + name Name + indirect bool +} + +type Graph struct { + nodes *[]*Element +} + +func NewGraph() *Graph { + return &Graph{ + nodes: &[]*Element{}, + } +} + +func (g *Graph) AddNode(name Name, indirect bool) { + fmt.Printf("---addNode, name: %v, indirect: %v \n", name, indirect) + *(g.nodes) = append(*(g.nodes), &Element{name: name, indirect: indirect}) +} + +func (g *Graph) PopNode() { + fmt.Println("---pop node") + *(g.nodes) = (*g.nodes)[:len(*(g.nodes))-1] +} + +func (g *Graph) checkCycle(name Name) bool { + fmt.Println("---checkCycle, name: ", name) + // if indirect exists, no cycle + for _, n := range *(g.nodes) { + if n.indirect { + return false + } + } + // no indirect, and exists, cycle + for _, n := range *(g.nodes) { + if name == n.name { + return true + } + } + return false +} + +func (g *Graph) dump() { + for i, n := range *(g.nodes) { + fmt.Printf("---g.nodes[%d] is %+v \n", i, n) + } +} + +// -----------------dependency graph-------------------- diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index fbf02896a2b..a920f8c7603 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -18,8 +18,8 @@ const ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { - var declGraph []*DeclNode - loopfindr := make(map[Name]struct{}) + //var declGraph []*DeclNode + //loopfindr := make(map[Name]struct{}) fmt.Println("---predefineFileSet start") defer func() { @@ -54,7 +54,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // dependent) } else { // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d, &declGraph, &loopfindr) + d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } } @@ -75,7 +75,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // dependent) } else { // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d, &declGraph, &loopfindr) + d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } } @@ -93,7 +93,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // dependent) } else { // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d, &declGraph, &loopfindr) + d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } } @@ -109,7 +109,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // through recursion for a dependent) } else { // recursively predefine dependencies. - d2, _ := predefineNow(store, fn, d, &declGraph, &loopfindr) + d2, _ := predefineNow(store, fn, d) fn.Decls[i] = d2 } } @@ -157,11 +157,11 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { SetNodeLocations(pkgPath, fileName, fn) } - fmt.Println("---preprocess, new declGraph") + //fmt.Println("---preprocess, new declGraph") // declGraph represents a slice of declGraph - var declGraph []*DeclNode + //var declGraph []*DeclNode - loopfindr := make(map[Name]struct{}) + //loopfindr := make(map[Name]struct{}) // create stack of BlockNodes. var stack []BlockNode = make([]BlockNode, 0, 32) @@ -248,7 +248,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // (e.g. through recursion for a dependent) } else { // recursively predefine dependencies. - d2, ppd := predefineNow(store, last, n.(Decl), &declGraph, &loopfindr) + d2, ppd := predefineNow(store, last, n.(Decl)) if ppd { return d2, TRANS_SKIP } else { @@ -547,7 +547,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // recursively predefine // dependencies. - d2, _ := predefineNow(store, n, d, &declGraph, &loopfindr) + d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } } @@ -564,7 +564,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // recursively predefine // dependencies. - d2, _ := predefineNow(store, n, d, &declGraph, &loopfindr) + d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } } @@ -581,7 +581,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // recursively predefine // dependencies. - d2, _ := predefineNow(store, n, d, &declGraph, &loopfindr) + d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } } @@ -597,7 +597,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { } else { // recursively predefine // dependencies. - d2, _ := predefineNow(store, n, d, &declGraph, &loopfindr) + d2, _ := predefineNow(store, n, d) n.Decls[i] = d2 } } @@ -2616,6 +2616,119 @@ func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { } } +func findTypeDeclDependency(store Store, last BlockNode, x Expr, g *Graph, indirect bool) { + fmt.Printf("---findDependency, x: %v, indirect: %v, type of x: %v \n", x, indirect, reflect.TypeOf(x)) + if x == nil { + return + } + + var lastX Expr + defer func() { + fmt.Println("------defer call, before pop--->>>") + g.dump() + if _, ok := lastX.(*NameExpr); ok { + g.PopNode() + } + fmt.Println("------defer call, after pop---<<<") + g.dump() + }() + + switch cx := x.(type) { + case *NameExpr: + fmt.Println("---found name expr, cx: ", cx) + // check cycle before add node + if indirect { + // not check + } else { + if g.checkCycle(cx.Name) { + panic(fmt.Sprintf("type define cycle with name: %s", cx.Name)) + } + } + // add node + g.AddNode(cx.Name, indirect) + lastX = cx + return + case *BasicLitExpr: + return + case *BinaryExpr: + findTypeDeclDependency(store, last, cx.Left, g, indirect) + findTypeDeclDependency(store, last, cx.Right, g, indirect) + case *SelectorExpr: + findTypeDeclDependency(store, last, cx.X, g, indirect) + case *SliceExpr: + findTypeDeclDependency(store, last, cx.X, g, indirect) + if cx.Low != nil { + findTypeDeclDependency(store, last, cx.Low, g, indirect) + } + if cx.High != nil { + findTypeDeclDependency(store, last, cx.High, g, indirect) + } + if cx.Max != nil { + findTypeDeclDependency(store, last, cx.Max, g, indirect) + } + case *StarExpr: + fmt.Println("---starExpr") + findTypeDeclDependency(store, last, cx.X, g, true) + case *RefExpr: + findTypeDeclDependency(store, last, cx.X, g, indirect) + case *TypeAssertExpr: + findTypeDeclDependency(store, last, cx.X, g, indirect) + findTypeDeclDependency(store, last, cx.Type, g, indirect) + case *UnaryExpr: + findTypeDeclDependency(store, last, cx.X, g, indirect) + case *FuncLitExpr: + findTypeDeclDependency(store, last, &cx.Type, g, indirect) + case *FieldTypeExpr: + findTypeDeclDependency(store, last, cx.Type, g, indirect) + case *ArrayTypeExpr: + if cx.Len != nil { + findTypeDeclDependency(store, last, cx.Len, g, indirect) + } + findTypeDeclDependency(store, last, cx.Elt, g, indirect) + case *SliceTypeExpr: + findTypeDeclDependency(store, last, cx.Elt, g, indirect) + case *InterfaceTypeExpr: + for i := range cx.Methods { + findTypeDeclDependency(store, last, &cx.Methods[i], g, indirect) + } + case *ChanTypeExpr: + findTypeDeclDependency(store, last, cx.Value, g, indirect) + case *FuncTypeExpr: + for i := range cx.Params { + findTypeDeclDependency(store, last, &cx.Params[i], g, indirect) + } + for i := range cx.Results { + findTypeDeclDependency(store, last, &cx.Results[i], g, indirect) + } + case *MapTypeExpr: + findTypeDeclDependency(store, last, cx.Key, g, indirect) + findTypeDeclDependency(store, last, cx.Value, g, indirect) + case *StructTypeExpr: + for i := range cx.Fields { + findTypeDeclDependency(store, last, &cx.Fields[i], g, indirect) + } + case *MaybeNativeTypeExpr: + findTypeDeclDependency(store, last, cx.Type, g, indirect) + case *CallExpr: + findTypeDeclDependency(store, last, cx.Func, g, indirect) + for i := range cx.Args { + findTypeDeclDependency(store, last, cx.Args[i], g, indirect) + } + case *IndexExpr: + findTypeDeclDependency(store, last, cx.X, g, indirect) + findTypeDeclDependency(store, last, cx.Index, g, indirect) + case *constTypeExpr: + return + case *ConstExpr: + return + default: + panic(fmt.Sprintf( + "unexpected expr: %v (%v)", + x, reflect.TypeOf(x))) + } + return +} + // Returns any names not yet defined nor predefined in expr. These happen // upon transcribe:enter from the top, so value paths cannot be used. If no // names are un and x is TypeExpr, evalStaticType(store,last, x) must not @@ -2894,7 +3007,7 @@ func checkIntegerKind(xt Type) { // preprocesses function declarations (which may not be completely // preprocess-able before other file-level declarations are // preprocessed). -func predefineNow(store Store, last BlockNode, d Decl, declGraph *[]*DeclNode, loopfinder *map[Name]struct{}) (Decl, bool) { +func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { fmt.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { @@ -2912,11 +3025,13 @@ func predefineNow(store Store, last BlockNode, d Decl, declGraph *[]*DeclNode, l } } }() - m := make(map[Name]struct{}) - return predefineNow2(store, last, d, m, declGraph, loopfinder) + // new dependency graph + g := NewGraph() + //m := make(map[Name]struct{ direct bool }) + return predefineNow2(store, last, d, g) } -func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}, declGraph *[]*DeclNode, loopfinder *map[Name]struct{}) (Decl, bool) { +func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { fmt.Println("---predefineNow2, d: ", d) pkg := packageOf(last) // pre-register d.GetName() to detect circular definition. @@ -2925,16 +3040,28 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}, dec panic(fmt.Sprintf( "builtin identifiers cannot be shadowed: %s", dn)) } - m[dn] = struct{}{} } + + // check type decl cycle + if td, ok := d.(*TypeDecl); ok { + fmt.Println("---type decl, find dependency and assert no cycle, td: ", td) + fmt.Println("==============check cycle start==================") + g.dump() + + g.AddNode(td.Name, false) + // recursively check + findTypeDeclDependency(store, last, td.Type, g, false) + fmt.Println("==============check cycle complete!!!==================") + } + // recursively predefine dependencies. for { + fmt.Println("---for loop start, recursively define ") + // type decl un := tryPredefine(store, last, d) - fmt.Printf("---un: %s after tryPredefine d: %v \n", un, d) - fmt.Println("---m: ", m) if un != "" { // check circularity. - if _, ok := m[un]; ok { + if g.checkCycle(un) { panic(fmt.Sprintf("constant definition loop with %s", un)) } // look up dependency declaration from fileset. @@ -2944,11 +3071,12 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}, dec panic("all types from files in file-set should have already been predefined") } // predefine dependency (recursive). - *decl, _ = predefineNow2(store, file, *decl, m, declGraph, loopfinder) + *decl, _ = predefineNow2(store, file, *decl, g) } else { break } } + fmt.Println("---after for loop") // after recursive define switch cd := d.(type) { case *FuncDecl: @@ -3037,60 +3165,179 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}, dec case *ValueDecl: return Preprocess(store, last, cd).(Decl), true case *TypeDecl: - fmt.Println("---predefineNow2, loopfinder: ", loopfinder) - - fmt.Println("---predefineNow2, cd: ", cd) - fmt.Println("---predefineNow2, cd.Type: ", cd.Type) - fmt.Println("---predefineNow2, m: ", m) + //fmt.Println("---predefineNow2, loopfinder: ", loopfinder) + //fmt.Println("---predefineNow2, cd: ", cd) + //fmt.Println("---predefineNow2, cd.Type: ", cd.Type) + fmt.Println("---going to preprocess type decl") d := Preprocess(store, last, cd).(Decl) - fmt.Println("---predefineNow2, d: ", d) + fmt.Println("---DONE preprocess type decl!") + //fmt.Println("---predefineNow2, d: ", d) td := d.(*TypeDecl) - fmt.Println("---td: ", td) - fmt.Println("---td.Type: ", td.Type) + //fmt.Println("---td: ", td) + //fmt.Println("---td.Type: ", td.Type) + + //tmp := evalStaticType(store, last, cd.Type) + //dst := last.GetValueRef(store, cd.Name).GetType() + + //fmt.Println("---dst: ", dst) + //switch dst := dst.(type) { + //case *DeclaredType: + // fmt.Println("---declared type, tmp: ", tmp, reflect.TypeOf(tmp)) + // if dt, ok := tmp.(*DeclaredType); ok { + // fmt.Println("---dt: ", dt, dt.Name, dt.Base) + // if st, ok := dt.Base.(*StructType); ok { + // fmt.Println("---struct type") + // // check if fields contains declaredType + // maybeRecursive := false + // names := make([]Name, 0) + // + // for _, f := range st.Fields { + // maybeRecursive = checkFieldReference(st.PkgPath, f.Type, &names) + // } + // if maybeRecursive { + // for _, name := range names { + // fmt.Println("---name: ", name) + // //if _, ok := (*loopfinder)[dst.Name]; ok { + // // panic(fmt.Sprintf("loop definition: %v ", *loopfinder)) + // //} + // + // (*loopfinder)[dst.Name] = struct{}{} + // fmt.Println("---after insert, loopfinder: ", *loopfinder) + // + // insertDeclNode(declGraph, dst.Name, last.GetLocation(), name) + // } + // } + // } + // } + // do assert + //assertNoCycle(*declGraph) + //} - tmp := evalStaticType(store, last, cd.Type) - dst := last.GetValueRef(store, cd.Name).GetType() + return td, true + //return Preprocess(store, last, cd).(Decl), true + default: + return d, false + } +} - fmt.Println("---dst: ", dst) - switch dst := dst.(type) { - case *DeclaredType: - fmt.Println("---declared type, tmp: ", tmp, reflect.TypeOf(tmp)) - if dt, ok := tmp.(*DeclaredType); ok { - fmt.Println("---dt: ", dt, dt.Name, dt.Base) - if st, ok := dt.Base.(*StructType); ok { - fmt.Println("---struct type") - // check if fields contains declaredType - maybeRecursive := false - names := make([]Name, 0) +func tryPredefineType(store Store, last BlockNode, d Decl) (un Name, indirect bool, undefined bool, complete bool) { + fmt.Println("---tryPredefine Type, d: ", d) - for _, f := range st.Fields { - maybeRecursive = checkFieldReference(st.PkgPath, f.Type, &names) - } - if maybeRecursive { - for _, name := range names { - fmt.Println("---name: ", name) - //if _, ok := (*loopfinder)[dst.Name]; ok { - // panic(fmt.Sprintf("loop definition: %v ", *loopfinder)) - //} + if d.GetAttribute(ATTR_PREDEFINED) == true { + panic(fmt.Sprintf("decl node %v already predefined!", d)) + } else { + fmt.Printf("---%v not predefined: \n", d) + } - (*loopfinder)[dst.Name] = struct{}{} - fmt.Println("---after insert, loopfinder: ", *loopfinder) + // If un is blank, it means the predefine succeeded. + defer func() { + if un == "" { + d.SetAttribute(ATTR_PREDEFINED, true) + complete = true + } + }() - insertDeclNode(declGraph, dst.Name, last.GetLocation(), name) + // NOTE: These happen upon enter from the top, + // so value paths cannot be used here. + switch d := d.(type) { + case *TypeDecl: + last2 := skipFile(last) + _, ok := last2.GetLocalIndex(d.Name) + if !ok { + // construct empty t type + var t Type + switch tx := d.Type.(type) { + case *FuncTypeExpr: + t = &FuncType{} + case *ArrayTypeExpr: + t = &ArrayType{} + case *SliceTypeExpr: + t = &SliceType{} + case *InterfaceTypeExpr: + t = &InterfaceType{} + case *ChanTypeExpr: + t = &ChanType{} + case *MapTypeExpr: + t = &MapType{} + case *StructTypeExpr: + t = &StructType{} + case *StarExpr: + t = &PointerType{} + case *NameExpr: + if tv := last.GetValueRef(store, tx.Name); tv != nil { + // (file) block name + t = tv.GetType() + if dt, ok := t.(*DeclaredType); ok { + if !dt.sealed { + // predefineNow preprocessed dependent types. + panic("should not happen") } + } else { + // all names are declared types. + panic("should not happen") } + } else if idx, ok := UverseNode().GetLocalIndex(tx.Name); ok { + // uverse name + path := NewValuePathUverse(idx, tx.Name) + tv := Uverse().GetValueAt(nil, path) + t = tv.GetType() + } else { + // yet undefined + un = tx.Name + return + } + case *SelectorExpr: + // get package value. + un = findUndefined(store, last, tx.X) + if un != "" { + return + } + pkgName := tx.X.(*NameExpr).Name + tv := last.GetValueRef(store, pkgName) + pv, ok := tv.V.(*PackageValue) + if !ok { + panic(fmt.Sprintf( + "unknown package name %s in %s", + pkgName, + tx.String(), + )) } + // check package node for name. + pn := pv.GetPackageNode(store) + tx.Path = pn.GetPathForName(store, tx.Sel) + ptr := pv.GetBlock(store).GetPointerTo(store, tx.Path) + t = ptr.TV.T + default: + panic(fmt.Sprintf( + "unexpected type declaration type %v", + reflect.TypeOf(d.Type))) + } + if d.IsAlias { + // use t directly. + } else { + // create new declared type. + pn := packageOf(last) + t = declareWith(pn.PkgPath, d.Name, t) } - // do assert - assertNoCycle(*declGraph) + // fill in later. + last2.Define(d.Name, asValue(t)) + d.Path = last.GetPathForName(store, d.Name) } - return td, true - //return Preprocess(store, last, cd).(Decl), true - default: - return d, false + //name, isIndirect, isUndefined, done := findTypeDeclDependency(store, last, d.Type) + //fmt.Printf("after findTypeDeclDependency, name: %v, isIndirect: %v, isUndefined: %v \n", name, isIndirect, isUndefined) + //fmt.Println("---done: ", done) + // + //if name != "" { + // un = name + // indirect = isIndirect + // undefined = isUndefined + // complete = done + // return + //} } + return "", false, false, complete } // If a dependent name is not yet defined, that name is @@ -3103,18 +3350,15 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}, dec // must be called for name declarations within (non-file, // non-package) stmt bodies. func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { - fmt.Println("---tryPredefine, d: ", d) if d.GetAttribute(ATTR_PREDEFINED) == true { panic("decl node already predefined!") } - // If un is blank, it means the predefine succeeded. defer func() { if un == "" { d.SetAttribute(ATTR_PREDEFINED, true) } }() - // NOTE: These happen upon enter from the top, // so value paths cannot be used here. switch d := d.(type) { @@ -3142,18 +3386,13 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { }) d.Path = last.GetPathForName(store, d.Name) case *ValueDecl: - fmt.Println("---value decl") un = findUndefined(store, last, d.Type) if un != "" { - fmt.Println("---return 1") return } for _, vx := range d.Values { - fmt.Println("---vx: ", vx) un = findUndefined(store, last, vx) - fmt.Println("---un: ", un) if un != "" { - fmt.Println("---return 2") return } } @@ -3168,13 +3407,6 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } } case *TypeDecl: - fmt.Println("---typeDecl") - // XXX, before predefine - un = findUndefined(store, last, d.Type) - fmt.Println("---un for type decl: ", un) - if un != "" { - return - } // before looking for dependencies, predefine empty type. last2 := skipFile(last) _, ok := last2.GetLocalIndex(d.Name) @@ -3258,12 +3490,12 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { last2.Define(d.Name, asValue(t)) d.Path = last.GetPathForName(store, d.Name) } - //// after predefinitions, return any undefined declGraph. - //un = findUndefined(store, last, d.Type) - //fmt.Println("---un for type decl: ", un) - //if un != "" { - // return - //} + // after predefinitions, return any undefined dependencies. + // after predefinitions, return any undefined declGraph. + un = findUndefined(store, last, d.Type) + if un != "" { + return + } case *FuncDecl: un = findUndefined(store, last, &d.Type) if un != "" { diff --git a/gnovm/tests/files/recursive1.gno b/gnovm/tests/files/recursive1.gno index 57da1523c8d..11a7d61d9a2 100644 --- a/gnovm/tests/files/recursive1.gno +++ b/gnovm/tests/files/recursive1.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive1.gno) -> S(File: files/recursive1.gno) +// main/files/recursive1.gno:1: type define cycle with name: S diff --git a/gnovm/tests/files/recursive12.gno b/gnovm/tests/files/recursive12.gno new file mode 100644 index 00000000000..e742dc1b9dc --- /dev/null +++ b/gnovm/tests/files/recursive12.gno @@ -0,0 +1,15 @@ +package main + +type S struct { + T *S + B Integer +} + +type Integer int + +func main() { + var a, b S + println(a == b) +} + +// Error: diff --git a/gnovm/tests/files/recursive1b.gno b/gnovm/tests/files/recursive1b.gno index 2a62a2c29be..1cb4fb37905 100644 --- a/gnovm/tests/files/recursive1b.gno +++ b/gnovm/tests/files/recursive1b.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive1b.gno) -> S(File: files/recursive1b.gno) +// main/files/recursive1b.gno:1: type definition loop with S diff --git a/gnovm/tests/files/recursive1c.gno b/gnovm/tests/files/recursive1c.gno index ee2657017d0..6e0273741a3 100644 --- a/gnovm/tests/files/recursive1c.gno +++ b/gnovm/tests/files/recursive1c.gno @@ -15,4 +15,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive1c.gno) -> S(File: files/recursive1c.gno) +// main/files/recursive1c.gno:1: type definition loop with S diff --git a/gnovm/tests/files/recursive1d.gno b/gnovm/tests/files/recursive1d.gno index ab2f2670ab8..278d875440f 100644 --- a/gnovm/tests/files/recursive1d.gno +++ b/gnovm/tests/files/recursive1d.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive1d.gno) -> S(File: files/recursive1d.gno) +// main/files/recursive1d.gno:1: type definition loop with S diff --git a/gnovm/tests/files/recursive1f.gno b/gnovm/tests/files/recursive1f.gno new file mode 100644 index 00000000000..211b07be0ef --- /dev/null +++ b/gnovm/tests/files/recursive1f.gno @@ -0,0 +1,13 @@ +package main + +func main() { + type S struct { + T S + } + + var a, b S + println(a == b) +} + +// Error: +// main/files/recursive1f.gno:3: type define cycle with name: S diff --git a/gnovm/tests/files/recursive2.gno b/gnovm/tests/files/recursive2.gno index 67a8df0a477..4815ff6090e 100644 --- a/gnovm/tests/files/recursive2.gno +++ b/gnovm/tests/files/recursive2.gno @@ -18,4 +18,4 @@ func main() { } // Error: -// Cyclic dependency detected: C(File: files/recursive2.gno) -> A(File: files/recursive2.gno) -> B(File: files/recursive2.gno) -> C(File: files/recursive2.gno) +// main/files/recursive2.gno:1: type define cycle with name: A diff --git a/gnovm/tests/files/recursive2b.gno b/gnovm/tests/files/recursive2b.gno new file mode 100644 index 00000000000..92d633cdda1 --- /dev/null +++ b/gnovm/tests/files/recursive2b.gno @@ -0,0 +1,21 @@ +package main + +type A struct { + X B +} + +type B struct { + X C +} + +type C struct { + X *A +} + +func main() { + var p, q A + println(p == q) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive2c.gno b/gnovm/tests/files/recursive2c.gno new file mode 100644 index 00000000000..9060577ba1b --- /dev/null +++ b/gnovm/tests/files/recursive2c.gno @@ -0,0 +1,21 @@ +package main + +func main() { + type A struct { + X B + } + + type B struct { + X C + } + + type C struct { + X A + } + + var p, q A + println(p == q) +} + +// Error: +// main/files/recursive2c.gno:3: name B not defined in fileset with files [files/recursive2c.gno] diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno index 58e6b566842..0430b0b59ad 100644 --- a/gnovm/tests/files/recursive5.gno +++ b/gnovm/tests/files/recursive5.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// Cyclic dependency detected: S(File: files/recursive5.gno) -> S(File: files/recursive5.gno) +// main/files/recursive5.gno:1: type definition loop with S From 60ab538768732dbe869acedad9e2aabfb727d3d2 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Fri, 26 Jul 2024 12:17:34 +0800 Subject: [PATCH 23/35] save --- gnovm/pkg/gnolang/preprocess.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index a920f8c7603..770b99ed917 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2641,6 +2641,7 @@ func findTypeDeclDependency(store Store, last BlockNode, x Expr, g *Graph, indir // not check } else { if g.checkCycle(cx.Name) { + fmt.Println("!!!!!!found circle") panic(fmt.Sprintf("type define cycle with name: %s", cx.Name)) } } @@ -2686,10 +2687,10 @@ func findTypeDeclDependency(store Store, last BlockNode, x Expr, g *Graph, indir } findTypeDeclDependency(store, last, cx.Elt, g, indirect) case *SliceTypeExpr: - findTypeDeclDependency(store, last, cx.Elt, g, indirect) + findTypeDeclDependency(store, last, cx.Elt, g, true) case *InterfaceTypeExpr: for i := range cx.Methods { - findTypeDeclDependency(store, last, &cx.Methods[i], g, indirect) + findTypeDeclDependency(store, last, &cx.Methods[i], g, true) } case *ChanTypeExpr: findTypeDeclDependency(store, last, cx.Value, g, indirect) From fd8bbc60101934a69c99d804602fb18643f872a5 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Mon, 29 Jul 2024 11:02:13 +0800 Subject: [PATCH 24/35] new way fix --- gnovm/pkg/gnolang/machine.go | 6 +- gnovm/pkg/gnolang/misc.go | 30 ++++++---- gnovm/pkg/gnolang/nodes.go | 2 +- gnovm/pkg/gnolang/preprocess.go | 99 ++++++++++++++++--------------- gnovm/pkg/gnolang/type_check.go | 2 +- gnovm/tests/files/recursive1b.gno | 2 +- gnovm/tests/files/recursive1c.gno | 2 +- gnovm/tests/files/recursive1d.gno | 2 +- gnovm/tests/files/recursive2d.gno | 21 +++++++ gnovm/tests/files/recursive5.gno | 2 +- gnovm/tests/files/recursive7.gno | 5 ++ gnovm/tests/files/recursive7a.gno | 8 +++ gnovm/tests/files/recursive8.gno | 8 +++ 13 files changed, 120 insertions(+), 69 deletions(-) create mode 100644 gnovm/tests/files/recursive2d.gno create mode 100644 gnovm/tests/files/recursive7.gno create mode 100644 gnovm/tests/files/recursive7a.gno create mode 100644 gnovm/tests/files/recursive8.gno diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 02518ebc6cb..768cd239f12 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -353,7 +353,7 @@ func destar(x Expr) Expr { func (m *Machine) TestMemPackage(t *testing.T, memPkg *std.MemPackage) { defer m.injectLocOnPanic() DisableDebug() - fmt.Println("DEBUG DISABLED (FOR TEST DEPENDENCIES INIT)") + debug.Println("DEBUG DISABLED (FOR TEST DEPENDENCIES INIT)") // parse test files. tfiles, itfiles := ParseMemPackageTests(memPkg) { // first, tfiles which run in the same package. @@ -379,7 +379,7 @@ func (m *Machine) TestMemPackage(t *testing.T, memPkg *std.MemPackage) { m.RunFiles(itfiles.Files...) pn.PrepareNewValues(pv) EnableDebug() - fmt.Println("DEBUG ENABLED") + debug.Println("DEBUG ENABLED") for i := 0; i < len(pvBlock.Values); i++ { tv := pvBlock.Values[i] m.TestFunc(t, tv) @@ -621,7 +621,7 @@ func (m *Machine) runFiles(fns ...*FileNode) { // XXX, is this only for global vars for _, fn := range fns { for _, decl := range fn.Decls { - fmt.Println("---machine runDeclareFor, decl: ", decl) + debug.Println("---machine runDeclareFor, decl: ", decl) runDeclarationFor(fn, decl) } } diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index bee147b5da3..339f35e27e7 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -179,22 +179,22 @@ type DeclNode struct { // dumpGraph prints the current declGraph func dumpGraph(declGraph []*DeclNode) { - fmt.Println("-----------------------dump declGraph begin-------------------------") + debug.Println("-----------------------dump declGraph begin-------------------------") for _, node := range declGraph { - fmt.Printf("%s -> ", node.Name) + debug.Printf("%s -> ", node.Name) for _, d := range node.Dependencies { - fmt.Printf("%s: ", d.Name) + debug.Printf("%s: ", d.Name) } - fmt.Println() + debug.Println() } - fmt.Println("-----------------------dump declGraph done-------------------------") + debug.Println("-----------------------dump declGraph done-------------------------") } // insertDeclNode inserts a new dependency into the graph func insertDeclNode(declGraph *[]*DeclNode, name Name, loc Location, deps ...Name) { - fmt.Println("---insertDeclNode, name: ", name) + debug.Println("---insertDeclNode, name: ", name) for _, d := range *declGraph { - fmt.Println("---insertDeclNode, declGraph: ", *d) + debug.Println("---insertDeclNode, declGraph: ", *d) } var dep *DeclNode @@ -230,7 +230,7 @@ func insertDeclNode(declGraph *[]*DeclNode, name Name, loc Location, deps ...Nam // assertNoCycle checks if there is a cycle in the declGraph graph func assertNoCycle(declGraph []*DeclNode) { for _, d := range declGraph { - fmt.Println("---assertNoCycle, declGraph: ", *d) + debug.Println("---assertNoCycle, declGraph: ", *d) } defer func() { declGraph = nil @@ -319,17 +319,20 @@ func NewGraph() *Graph { } func (g *Graph) AddNode(name Name, indirect bool) { - fmt.Printf("---addNode, name: %v, indirect: %v \n", name, indirect) + debug.Printf("---addNode, name: %v, indirect: %v \n", name, indirect) *(g.nodes) = append(*(g.nodes), &Element{name: name, indirect: indirect}) } -func (g *Graph) PopNode() { - fmt.Println("---pop node") +func (g *Graph) PopNode() *Element { + debug.Println("---pop node") + e := (*g.nodes)[len(*g.nodes)-1] *(g.nodes) = (*g.nodes)[:len(*(g.nodes))-1] + return e } func (g *Graph) checkCycle(name Name) bool { - fmt.Println("---checkCycle, name: ", name) + debug.Println("---checkCycle, name: ", name) + g.dump() // if indirect exists, no cycle for _, n := range *(g.nodes) { if n.indirect { @@ -346,8 +349,9 @@ func (g *Graph) checkCycle(name Name) bool { } func (g *Graph) dump() { + debug.Println("---len of stack: ", len(*g.nodes)) for i, n := range *(g.nodes) { - fmt.Printf("---g.nodes[%d] is %+v \n", i, n) + debug.Printf("---g.nodes[%d] is %+v \n", i, n) } } diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 399b3a2867a..4029b9c56e4 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1772,7 +1772,7 @@ func (sb *StaticBlock) Predefine(isConst bool, n Name) { // The declared type st may not be the same as the static tv; // e.g. var x MyInterface = MyStruct{}. func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { - fmt.Println("---Define2, n: ", n) + //fmt.Println("---Define2, n: ", n) if debug { debug.Printf( diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 770b99ed917..bd2e77861bc 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -21,9 +21,9 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { //var declGraph []*DeclNode //loopfindr := make(map[Name]struct{}) - fmt.Println("---predefineFileSet start") + debug.Println("---predefineFileSet start") defer func() { - fmt.Println("---predefineFileSet end") + debug.Println("---predefineFileSet end") // Check for cyclic //if len(declGraph) != 0 { // assertNoCycle() @@ -63,12 +63,12 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // Predefine all type decls decls. for _, fn := range fset.Files { for i := 0; i < len(fn.Decls); i++ { - fmt.Println("---PredefineFileSet, len of decls: ", len(fn.Decls)) - fmt.Println("---PredefineFileSet, fn.Decls: ", fn.Decls) + debug.Println("---PredefineFileSet, len of decls: ", len(fn.Decls)) + debug.Println("---PredefineFileSet, fn.Decls: ", fn.Decls) d := fn.Decls[i] switch d.(type) { case *TypeDecl: - fmt.Println("---predefineFileSet, type decl, d: ", d) + debug.Println("---predefineFileSet, type decl, d: ", d) if d.GetAttribute(ATTR_PREDEFINED) == true { // skip declarations already predefined // (e.g. through recursion for a @@ -157,7 +157,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { SetNodeLocations(pkgPath, fileName, fn) } - //fmt.Println("---preprocess, new declGraph") + //debug.Println("---preprocess, new declGraph") // declGraph represents a slice of declGraph //var declGraph []*DeclNode @@ -1621,7 +1621,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *StructTypeExpr: - fmt.Println("---trans_leave, StructTypeExpr") + debug.Println("---trans_leave, StructTypeExpr") evalStaticType(store, last, n) // TODO: maxwell can we do something here? //panic("---!!!") @@ -1974,7 +1974,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *TypeDecl: - fmt.Println("---trans_leave, TypeDecl") + debug.Println("---trans_leave, TypeDecl") // TODO: maxwell, do somthing here? // Construct new Type, where any recursive @@ -2617,31 +2617,35 @@ func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { } func findTypeDeclDependency(store Store, last BlockNode, x Expr, g *Graph, indirect bool) { - fmt.Printf("---findDependency, x: %v, indirect: %v, type of x: %v \n", x, indirect, reflect.TypeOf(x)) + debug.Printf("---findDependency, x: %v, indirect: %v, type of x: %v \n", x, indirect, reflect.TypeOf(x)) if x == nil { return } var lastX Expr defer func() { - fmt.Println("------defer call, before pop--->>>") + debug.Println(">>>>>>------defer call, before pop--->>>>>>") g.dump() if _, ok := lastX.(*NameExpr); ok { - g.PopNode() + e := g.PopNode() + if e.indirect { // clear all element + debug.Println("---indirect, clear stack") + g.nodes = &[]*Element{} + } } - fmt.Println("------defer call, after pop---<<<") + debug.Println("<<<<<<------defer call, after pop---<<<<<<") g.dump() }() switch cx := x.(type) { case *NameExpr: - fmt.Println("---found name expr, cx: ", cx) + debug.Println("---found name expr, cx: ", cx) // check cycle before add node if indirect { // not check } else { if g.checkCycle(cx.Name) { - fmt.Println("!!!!!!found circle") + debug.Println("!!!!!!found circle") panic(fmt.Sprintf("type define cycle with name: %s", cx.Name)) } } @@ -2668,7 +2672,7 @@ func findTypeDeclDependency(store Store, last BlockNode, x Expr, g *Graph, indir findTypeDeclDependency(store, last, cx.Max, g, indirect) } case *StarExpr: - fmt.Println("---starExpr") + debug.Println("---starExpr") findTypeDeclDependency(store, last, cx.X, g, true) case *RefExpr: findTypeDeclDependency(store, last, cx.X, g, indirect) @@ -2696,14 +2700,14 @@ func findTypeDeclDependency(store Store, last BlockNode, x Expr, g *Graph, indir findTypeDeclDependency(store, last, cx.Value, g, indirect) case *FuncTypeExpr: for i := range cx.Params { - findTypeDeclDependency(store, last, &cx.Params[i], g, indirect) + findTypeDeclDependency(store, last, &cx.Params[i], g, true) } for i := range cx.Results { - findTypeDeclDependency(store, last, &cx.Results[i], g, indirect) + findTypeDeclDependency(store, last, &cx.Results[i], g, true) } case *MapTypeExpr: - findTypeDeclDependency(store, last, cx.Key, g, indirect) - findTypeDeclDependency(store, last, cx.Value, g, indirect) + findTypeDeclDependency(store, last, cx.Key, g, true) + findTypeDeclDependency(store, last, cx.Value, g, true) case *StructTypeExpr: for i := range cx.Fields { findTypeDeclDependency(store, last, &cx.Fields[i], g, indirect) @@ -2744,21 +2748,21 @@ func findUndefined(store Store, last BlockNode, x Expr) (un Name) { // TODO: add helpers for type decl func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { - fmt.Println("---findUndefined2, x, t: ", x, t) + debug.Println("---findUndefined2, x, t: ", x, t) if x == nil { return } switch cx := x.(type) { case *NameExpr: if tv := last.GetValueRef(store, cx.Name); tv != nil { - fmt.Println("---1") + debug.Println("---1") return } if _, ok := UverseNode().GetLocalIndex(cx.Name); ok { // XXX NOTE even if the name is shadowed by a file // level declaration, it is fine to return here as it // will be predefined later. - fmt.Println("---2") + debug.Println("---2") return } return cx.Name @@ -3009,7 +3013,7 @@ func checkIntegerKind(xt Type) { // preprocess-able before other file-level declarations are // preprocessed). func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { - fmt.Println("---predefine now, d.line", d.GetLine()) + debug.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { // before re-throwing the error, append location information to message. @@ -3033,7 +3037,7 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { } func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { - fmt.Println("---predefineNow2, d: ", d) + debug.Println("---predefineNow2, d: ", d) pkg := packageOf(last) // pre-register d.GetName() to detect circular definition. for _, dn := range d.GetDeclNames() { @@ -3041,23 +3045,24 @@ func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { panic(fmt.Sprintf( "builtin identifiers cannot be shadowed: %s", dn)) } + g.AddNode(dn, false) } // check type decl cycle if td, ok := d.(*TypeDecl); ok { - fmt.Println("---type decl, find dependency and assert no cycle, td: ", td) - fmt.Println("==============check cycle start==================") + debug.Println("---type decl, find dependency and assert no cycle, td: ", td) + debug.Println("==============check cycle start==================") g.dump() g.AddNode(td.Name, false) // recursively check findTypeDeclDependency(store, last, td.Type, g, false) - fmt.Println("==============check cycle complete!!!==================") + debug.Println("==============check cycle complete!!!==================") } // recursively predefine dependencies. for { - fmt.Println("---for loop start, recursively define ") + debug.Println("---for loop start, recursively define ") // type decl un := tryPredefine(store, last, d) if un != "" { @@ -3077,7 +3082,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { break } } - fmt.Println("---after for loop") + debug.Println("---after for loop") // after recursive define switch cd := d.(type) { case *FuncDecl: @@ -3166,29 +3171,29 @@ func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { case *ValueDecl: return Preprocess(store, last, cd).(Decl), true case *TypeDecl: - //fmt.Println("---predefineNow2, loopfinder: ", loopfinder) - //fmt.Println("---predefineNow2, cd: ", cd) - //fmt.Println("---predefineNow2, cd.Type: ", cd.Type) - fmt.Println("---going to preprocess type decl") + //debug.Println("---predefineNow2, loopfinder: ", loopfinder) + //debug.Println("---predefineNow2, cd: ", cd) + //debug.Println("---predefineNow2, cd.Type: ", cd.Type) + debug.Println("---going to preprocess type decl") d := Preprocess(store, last, cd).(Decl) - fmt.Println("---DONE preprocess type decl!") - //fmt.Println("---predefineNow2, d: ", d) + debug.Println("---DONE preprocess type decl!") + //debug.Println("---predefineNow2, d: ", d) td := d.(*TypeDecl) - //fmt.Println("---td: ", td) - //fmt.Println("---td.Type: ", td.Type) + //debug.Println("---td: ", td) + //debug.Println("---td.Type: ", td.Type) //tmp := evalStaticType(store, last, cd.Type) //dst := last.GetValueRef(store, cd.Name).GetType() - //fmt.Println("---dst: ", dst) + //debug.Println("---dst: ", dst) //switch dst := dst.(type) { //case *DeclaredType: - // fmt.Println("---declared type, tmp: ", tmp, reflect.TypeOf(tmp)) + // debug.Println("---declared type, tmp: ", tmp, reflect.TypeOf(tmp)) // if dt, ok := tmp.(*DeclaredType); ok { - // fmt.Println("---dt: ", dt, dt.Name, dt.Base) + // debug.Println("---dt: ", dt, dt.Name, dt.Base) // if st, ok := dt.Base.(*StructType); ok { - // fmt.Println("---struct type") + // debug.Println("---struct type") // // check if fields contains declaredType // maybeRecursive := false // names := make([]Name, 0) @@ -3198,13 +3203,13 @@ func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { // } // if maybeRecursive { // for _, name := range names { - // fmt.Println("---name: ", name) + // debug.Println("---name: ", name) // //if _, ok := (*loopfinder)[dst.Name]; ok { // // panic(fmt.Sprintf("loop definition: %v ", *loopfinder)) // //} // // (*loopfinder)[dst.Name] = struct{}{} - // fmt.Println("---after insert, loopfinder: ", *loopfinder) + // debug.Println("---after insert, loopfinder: ", *loopfinder) // // insertDeclNode(declGraph, dst.Name, last.GetLocation(), name) // } @@ -3223,12 +3228,12 @@ func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { } func tryPredefineType(store Store, last BlockNode, d Decl) (un Name, indirect bool, undefined bool, complete bool) { - fmt.Println("---tryPredefine Type, d: ", d) + debug.Println("---tryPredefine Type, d: ", d) if d.GetAttribute(ATTR_PREDEFINED) == true { panic(fmt.Sprintf("decl node %v already predefined!", d)) } else { - fmt.Printf("---%v not predefined: \n", d) + debug.Printf("---%v not predefined: \n", d) } // If un is blank, it means the predefine succeeded. @@ -3327,8 +3332,8 @@ func tryPredefineType(store Store, last BlockNode, d Decl) (un Name, indirect bo } //name, isIndirect, isUndefined, done := findTypeDeclDependency(store, last, d.Type) - //fmt.Printf("after findTypeDeclDependency, name: %v, isIndirect: %v, isUndefined: %v \n", name, isIndirect, isUndefined) - //fmt.Println("---done: ", done) + //debug.Printf("after findTypeDeclDependency, name: %v, isIndirect: %v, isUndefined: %v \n", name, isIndirect, isUndefined) + //debug.Println("---done: ", done) // //if name != "" { // un = name diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 4b4e078f478..4d9cf267aca 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -137,7 +137,7 @@ func isNumericOrString(t Type) bool { // =========================================================== func assertComparable(xt, dt Type) { - fmt.Println("---assertComparable...") + //fmt.Println("---assertComparable...") switch baseOf(dt).(type) { case *SliceType, *FuncType, *MapType: if xt != nil { diff --git a/gnovm/tests/files/recursive1b.gno b/gnovm/tests/files/recursive1b.gno index 1cb4fb37905..4da65e7bbba 100644 --- a/gnovm/tests/files/recursive1b.gno +++ b/gnovm/tests/files/recursive1b.gno @@ -9,4 +9,4 @@ func main() { } // Error: -// main/files/recursive1b.gno:1: type definition loop with S +// main/files/recursive1b.gno:1: type define cycle with name: S diff --git a/gnovm/tests/files/recursive1c.gno b/gnovm/tests/files/recursive1c.gno index 6e0273741a3..db142c3ac2e 100644 --- a/gnovm/tests/files/recursive1c.gno +++ b/gnovm/tests/files/recursive1c.gno @@ -15,4 +15,4 @@ func main() { } // Error: -// main/files/recursive1c.gno:1: type definition loop with S +// main/files/recursive1c.gno:1: type define cycle with name: S diff --git a/gnovm/tests/files/recursive1d.gno b/gnovm/tests/files/recursive1d.gno index 278d875440f..929f0a08aa2 100644 --- a/gnovm/tests/files/recursive1d.gno +++ b/gnovm/tests/files/recursive1d.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// main/files/recursive1d.gno:1: type definition loop with S +// main/files/recursive1d.gno:1: type define cycle with name: S diff --git a/gnovm/tests/files/recursive2d.gno b/gnovm/tests/files/recursive2d.gno new file mode 100644 index 00000000000..b2439ba6259 --- /dev/null +++ b/gnovm/tests/files/recursive2d.gno @@ -0,0 +1,21 @@ +package main + +type A struct { + X *B +} + +type B struct { + X int +} + +type C struct { + X A +} + +func main() { + var p, q A + println(p == q) +} + +// Output: +// true diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno index 0430b0b59ad..8992453a9a8 100644 --- a/gnovm/tests/files/recursive5.gno +++ b/gnovm/tests/files/recursive5.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/recursive5.gno:1: type definition loop with S +// main/files/recursive5.gno:1: type define cycle with name: S diff --git a/gnovm/tests/files/recursive7.gno b/gnovm/tests/files/recursive7.gno new file mode 100644 index 00000000000..4ca7b185a6e --- /dev/null +++ b/gnovm/tests/files/recursive7.gno @@ -0,0 +1,5 @@ +package main + +type S []S + +func main() {} diff --git a/gnovm/tests/files/recursive7a.gno b/gnovm/tests/files/recursive7a.gno new file mode 100644 index 00000000000..09384ff5513 --- /dev/null +++ b/gnovm/tests/files/recursive7a.gno @@ -0,0 +1,8 @@ +package main + +type S [2]S + +func main() {} + +// Error: +// main/files/recursive7a.gno:1: type define cycle with name: S \ No newline at end of file diff --git a/gnovm/tests/files/recursive8.gno b/gnovm/tests/files/recursive8.gno new file mode 100644 index 00000000000..16d42c978ef --- /dev/null +++ b/gnovm/tests/files/recursive8.gno @@ -0,0 +1,8 @@ +package main + +type Int Int + +func main() {} + +// Error: +// main/files/recursive8.gno:1: type define cycle with name: Int From ba68c133044a9b5422be1ce23e34ad90de3160de Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Mon, 29 Jul 2024 21:53:34 +0800 Subject: [PATCH 25/35] clean --- gnovm/1.gno | 12 -- gnovm/1a.gno | 9 - gnovm/1b.gno | 10 - gnovm/2.gno | 8 - gnovm/3.gno | 12 -- gnovm/pkg/gnolang/misc.go | 189 ------------------ gnovm/pkg/gnolang/preprocess.go | 313 ++++++------------------------ gnovm/tests/files/recursive1.gno | 2 +- gnovm/tests/files/recursive12.gno | 15 -- gnovm/tests/files/recursive1b.gno | 10 +- gnovm/tests/files/recursive1c.gno | 3 +- gnovm/tests/files/recursive1d.gno | 2 +- gnovm/tests/files/recursive1f.gno | 2 +- gnovm/tests/files/recursive2.gno | 2 +- gnovm/tests/files/recursive5.gno | 2 +- gnovm/tests/files/recursive7.gno | 7 +- gnovm/tests/files/recursive7a.gno | 2 +- gnovm/tests/files/recursive8.gno | 2 +- 18 files changed, 85 insertions(+), 517 deletions(-) delete mode 100644 gnovm/1.gno delete mode 100644 gnovm/1a.gno delete mode 100644 gnovm/1b.gno delete mode 100644 gnovm/2.gno delete mode 100644 gnovm/3.gno delete mode 100644 gnovm/tests/files/recursive12.gno diff --git a/gnovm/1.gno b/gnovm/1.gno deleted file mode 100644 index 8c3eabd3c95..00000000000 --- a/gnovm/1.gno +++ /dev/null @@ -1,12 +0,0 @@ -package main - -const a = b + 1 -const b = a - -func main() { - println(a) - println(b) -} - -// Error: -// main/1.gno:3: 2 constant definition loop with a diff --git a/gnovm/1a.gno b/gnovm/1a.gno deleted file mode 100644 index ec61ba94d9d..00000000000 --- a/gnovm/1a.gno +++ /dev/null @@ -1,9 +0,0 @@ -package main - -func main() { - var a = b + 1 - var b = a - - println(a) - println(b) -} diff --git a/gnovm/1b.gno b/gnovm/1b.gno deleted file mode 100644 index ff03556049c..00000000000 --- a/gnovm/1b.gno +++ /dev/null @@ -1,10 +0,0 @@ -package main - -func main() { - - const a = b + 1 - const b = a - - println(a) - println(b) -} diff --git a/gnovm/2.gno b/gnovm/2.gno deleted file mode 100644 index 081a16c0045..00000000000 --- a/gnovm/2.gno +++ /dev/null @@ -1,8 +0,0 @@ -package main - -var a int - -func main() { - a = 1 - println(a) -} diff --git a/gnovm/3.gno b/gnovm/3.gno deleted file mode 100644 index 8429c40b55a..00000000000 --- a/gnovm/3.gno +++ /dev/null @@ -1,12 +0,0 @@ -package main - - -func main() { - type S struct { - T S - } - var a, b S - println(a == b) -} - - diff --git a/gnovm/pkg/gnolang/misc.go b/gnovm/pkg/gnolang/misc.go index 339f35e27e7..a05de8c74aa 100644 --- a/gnovm/pkg/gnolang/misc.go +++ b/gnovm/pkg/gnolang/misc.go @@ -167,192 +167,3 @@ func DerivePkgAddr(pkgPath string) crypto.Address { // NOTE: must not collide with pubkey addrs. return crypto.AddressFromPreimage([]byte("pkgPath:" + pkgPath)) } - -// circular detection -// DeclNode represents a node in the dependency graph -// used to detect cycle definition of struct in `PredefineFileSet`. -type DeclNode struct { - Name - Loc Location // file info - Dependencies []*DeclNode -} - -// dumpGraph prints the current declGraph -func dumpGraph(declGraph []*DeclNode) { - debug.Println("-----------------------dump declGraph begin-------------------------") - for _, node := range declGraph { - debug.Printf("%s -> ", node.Name) - for _, d := range node.Dependencies { - debug.Printf("%s: ", d.Name) - } - debug.Println() - } - debug.Println("-----------------------dump declGraph done-------------------------") -} - -// insertDeclNode inserts a new dependency into the graph -func insertDeclNode(declGraph *[]*DeclNode, name Name, loc Location, deps ...Name) { - debug.Println("---insertDeclNode, name: ", name) - for _, d := range *declGraph { - debug.Println("---insertDeclNode, declGraph: ", *d) - } - - var dep *DeclNode - for _, d := range *declGraph { - if d.Name == name { - dep = d - dep.Loc = loc - break - } - } - if dep == nil { - dep = &DeclNode{Name: name, Loc: loc} - *declGraph = append(*declGraph, dep) - } - for _, depName := range deps { - var child *DeclNode - for _, d := range *declGraph { - if d.Name == depName { - child = d - break - } - } - if child == nil { - child = &DeclNode{Name: depName} - *declGraph = append(*declGraph, child) - } - dep.Dependencies = append(dep.Dependencies, child) - } - - dumpGraph(*declGraph) -} - -// assertNoCycle checks if there is a cycle in the declGraph graph -func assertNoCycle(declGraph []*DeclNode) { - for _, d := range declGraph { - debug.Println("---assertNoCycle, declGraph: ", *d) - } - defer func() { - declGraph = nil - }() - visited := make(map[Name]bool) - reStack := make(map[Name]bool) - var cycle []*DeclNode - - for _, dep := range declGraph { - if detectCycle(dep, visited, reStack, &cycle) { - cycleNames := make([]string, len(cycle)) - for i, c := range cycle { - cycleNames[i] = fmt.Sprintf("%s(File: %s)", c.Name, c.Loc.File) - } - cycleMsg := strings.Join(cycleNames, " -> ") - panic(fmt.Sprintf("Cyclic dependency detected: %s", cycleMsg)) - } - } -} - -// detectCycle detects cycle using DFS traversal -func detectCycle(node *DeclNode, visited, recStack map[Name]bool, cycle *[]*DeclNode) bool { - if visited[node.Name] { // existing visited node are not in cycle, otherwise it wil be elided - return false - } - visited[node.Name] = true - recStack[node.Name] = true - *cycle = append(*cycle, node) - - for _, d := range node.Dependencies { - // check if d is in recStack to form a cycle - if recStack[d.Name] { - for _, n := range *cycle { - if n == d { - startIndex := 0 - for ; (*cycle)[startIndex] != d; startIndex++ { - } - *cycle = append((*cycle)[startIndex:], d) - } - } - return true - } else { - if detectCycle(d, visited, recStack, cycle) { - return true - } - } - } - - delete(recStack, node.Name) - // Backtrack: Remove the last node from the cycle slice and mark as not in recStack - *cycle = (*cycle)[:len(*cycle)-1] - - return false -} - -func checkFieldReference(PkgPath string, t Type, names *[]Name) bool { - switch fdt := t.(type) { - case *DeclaredType: - if PkgPath == fdt.PkgPath { // not cross pkg - *names = append(*names, fdt.Name) - return true - } - case *ArrayType: - return checkFieldReference(PkgPath, fdt.Elem(), names) - default: - return false - } - - return false -} - -// -----------------dependency graph-------------------- -type Element struct { - name Name - indirect bool -} - -type Graph struct { - nodes *[]*Element -} - -func NewGraph() *Graph { - return &Graph{ - nodes: &[]*Element{}, - } -} - -func (g *Graph) AddNode(name Name, indirect bool) { - debug.Printf("---addNode, name: %v, indirect: %v \n", name, indirect) - *(g.nodes) = append(*(g.nodes), &Element{name: name, indirect: indirect}) -} - -func (g *Graph) PopNode() *Element { - debug.Println("---pop node") - e := (*g.nodes)[len(*g.nodes)-1] - *(g.nodes) = (*g.nodes)[:len(*(g.nodes))-1] - return e -} - -func (g *Graph) checkCycle(name Name) bool { - debug.Println("---checkCycle, name: ", name) - g.dump() - // if indirect exists, no cycle - for _, n := range *(g.nodes) { - if n.indirect { - return false - } - } - // no indirect, and exists, cycle - for _, n := range *(g.nodes) { - if name == n.name { - return true - } - } - return false -} - -func (g *Graph) dump() { - debug.Println("---len of stack: ", len(*g.nodes)) - for i, n := range *(g.nodes) { - debug.Printf("---g.nodes[%d] is %+v \n", i, n) - } -} - -// -----------------dependency graph-------------------- diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index bd2e77861bc..3218281b57f 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1974,9 +1974,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *TypeDecl: - debug.Println("---trans_leave, TypeDecl") - - // TODO: maxwell, do somthing here? // Construct new Type, where any recursive // references refer to the old Type declared // during *TypeDecl:ENTER. Then, copy over the @@ -1999,22 +1996,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { case *StructType: *dst = *(tmp.(*StructType)) case *DeclaredType: - //if st, ok := tmp.(*StructType); ok { - // // check if fields contains declaredType - // maybeRecursive := false - // names := make([]Name, 0) - // - // for _, f := range st.Fields { - // maybeRecursive = checkFieldReference(st.PkgPath, f.Type, &names) - // } - // if maybeRecursive { - // for _, name := range names { - // insertDeclNode(&declGraph, dst.Name, last.GetLocation(), name) - // } - // } - //} - //// do assert - //assertNoCycle(declGraph) // if store has this type, use that. tid := DeclaredTypeID(lastpn.PkgPath, n.Name) exists := false @@ -2616,7 +2597,11 @@ func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { } } -func findTypeDeclDependency(store Store, last BlockNode, x Expr, g *Graph, indirect bool) { +func findTypeDeclDependency(store Store, last BlockNode, x Expr, stack *[]Name) { + findTypeDeclDependency2(store, last, x, stack, false) +} + +func findTypeDeclDependency2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool) { debug.Printf("---findDependency, x: %v, indirect: %v, type of x: %v \n", x, indirect, reflect.TypeOf(x)) if x == nil { return @@ -2624,104 +2609,105 @@ func findTypeDeclDependency(store Store, last BlockNode, x Expr, g *Graph, indir var lastX Expr defer func() { - debug.Println(">>>>>>------defer call, before pop--->>>>>>") - g.dump() + fmt.Println(">>>>>>------defer call, before pop--->>>>>>") + fmt.Println("---stack: ", *stack) if _, ok := lastX.(*NameExpr); ok { - e := g.PopNode() - if e.indirect { // clear all element - debug.Println("---indirect, clear stack") - g.nodes = &[]*Element{} - } + // pop stack + *stack = (*stack)[:len(*stack)-1] } - debug.Println("<<<<<<------defer call, after pop---<<<<<<") - g.dump() + fmt.Println("<<<<<<------defer call, after pop---<<<<<<") + fmt.Println("---stack: ", *stack) }() switch cx := x.(type) { case *NameExpr: - debug.Println("---found name expr, cx: ", cx) - // check cycle before add node if indirect { // not check + *stack = []Name{} // reset } else { - if g.checkCycle(cx.Name) { - debug.Println("!!!!!!found circle") - panic(fmt.Sprintf("type define cycle with name: %s", cx.Name)) + var msg string + for _, n := range *stack { + if n == cx.Name { + for j := 0; j < len(*stack); j++ { + msg += fmt.Sprintf("%s -> ", (*stack)[j]) + } + msg += string(n) + fmt.Println("---msg: ", msg) + panic(fmt.Sprintf("invalid recursive type: %s", msg)) + } } + *stack = append(*stack, cx.Name) + lastX = cx } - // add node - g.AddNode(cx.Name, indirect) - lastX = cx return case *BasicLitExpr: return case *BinaryExpr: - findTypeDeclDependency(store, last, cx.Left, g, indirect) - findTypeDeclDependency(store, last, cx.Right, g, indirect) + findTypeDeclDependency2(store, last, cx.Left, stack, indirect) + findTypeDeclDependency2(store, last, cx.Right, stack, indirect) case *SelectorExpr: - findTypeDeclDependency(store, last, cx.X, g, indirect) + findTypeDeclDependency2(store, last, cx.X, stack, indirect) case *SliceExpr: - findTypeDeclDependency(store, last, cx.X, g, indirect) + findTypeDeclDependency2(store, last, cx.X, stack, indirect) if cx.Low != nil { - findTypeDeclDependency(store, last, cx.Low, g, indirect) + findTypeDeclDependency2(store, last, cx.Low, stack, indirect) } if cx.High != nil { - findTypeDeclDependency(store, last, cx.High, g, indirect) + findTypeDeclDependency2(store, last, cx.High, stack, indirect) } if cx.Max != nil { - findTypeDeclDependency(store, last, cx.Max, g, indirect) + findTypeDeclDependency2(store, last, cx.Max, stack, indirect) } case *StarExpr: - debug.Println("---starExpr") - findTypeDeclDependency(store, last, cx.X, g, true) + findTypeDeclDependency2(store, last, cx.X, stack, true) case *RefExpr: - findTypeDeclDependency(store, last, cx.X, g, indirect) + findTypeDeclDependency2(store, last, cx.X, stack, indirect) case *TypeAssertExpr: - findTypeDeclDependency(store, last, cx.X, g, indirect) - findTypeDeclDependency(store, last, cx.Type, g, indirect) + findTypeDeclDependency2(store, last, cx.X, stack, indirect) + findTypeDeclDependency2(store, last, cx.Type, stack, indirect) case *UnaryExpr: - findTypeDeclDependency(store, last, cx.X, g, indirect) + findTypeDeclDependency2(store, last, cx.X, stack, indirect) case *FuncLitExpr: - findTypeDeclDependency(store, last, &cx.Type, g, indirect) + findTypeDeclDependency2(store, last, &cx.Type, stack, indirect) case *FieldTypeExpr: - findTypeDeclDependency(store, last, cx.Type, g, indirect) + findTypeDeclDependency2(store, last, cx.Type, stack, indirect) case *ArrayTypeExpr: if cx.Len != nil { - findTypeDeclDependency(store, last, cx.Len, g, indirect) + findTypeDeclDependency2(store, last, cx.Len, stack, indirect) } - findTypeDeclDependency(store, last, cx.Elt, g, indirect) + findTypeDeclDependency2(store, last, cx.Elt, stack, indirect) case *SliceTypeExpr: - findTypeDeclDependency(store, last, cx.Elt, g, true) + findTypeDeclDependency2(store, last, cx.Elt, stack, true) case *InterfaceTypeExpr: for i := range cx.Methods { - findTypeDeclDependency(store, last, &cx.Methods[i], g, true) + findTypeDeclDependency2(store, last, &cx.Methods[i], stack, true) } case *ChanTypeExpr: - findTypeDeclDependency(store, last, cx.Value, g, indirect) + findTypeDeclDependency2(store, last, cx.Value, stack, indirect) case *FuncTypeExpr: for i := range cx.Params { - findTypeDeclDependency(store, last, &cx.Params[i], g, true) + findTypeDeclDependency2(store, last, &cx.Params[i], stack, true) } for i := range cx.Results { - findTypeDeclDependency(store, last, &cx.Results[i], g, true) + findTypeDeclDependency2(store, last, &cx.Results[i], stack, true) } case *MapTypeExpr: - findTypeDeclDependency(store, last, cx.Key, g, true) - findTypeDeclDependency(store, last, cx.Value, g, true) + findTypeDeclDependency2(store, last, cx.Key, stack, true) + findTypeDeclDependency2(store, last, cx.Value, stack, true) case *StructTypeExpr: for i := range cx.Fields { - findTypeDeclDependency(store, last, &cx.Fields[i], g, indirect) + findTypeDeclDependency2(store, last, &cx.Fields[i], stack, indirect) } case *MaybeNativeTypeExpr: - findTypeDeclDependency(store, last, cx.Type, g, indirect) + findTypeDeclDependency2(store, last, cx.Type, stack, indirect) case *CallExpr: - findTypeDeclDependency(store, last, cx.Func, g, indirect) + findTypeDeclDependency2(store, last, cx.Func, stack, indirect) for i := range cx.Args { - findTypeDeclDependency(store, last, cx.Args[i], g, indirect) + findTypeDeclDependency2(store, last, cx.Args[i], stack, indirect) } case *IndexExpr: - findTypeDeclDependency(store, last, cx.X, g, indirect) - findTypeDeclDependency(store, last, cx.Index, g, indirect) + findTypeDeclDependency2(store, last, cx.X, stack, indirect) + findTypeDeclDependency2(store, last, cx.Index, stack, indirect) case *constTypeExpr: return case *ConstExpr: @@ -3030,13 +3016,11 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { } } }() - // new dependency graph - g := NewGraph() - //m := make(map[Name]struct{ direct bool }) - return predefineNow2(store, last, d, g) + stack := &[]Name{} + return predefineNow2(store, last, d, stack) } -func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { +func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bool) { debug.Println("---predefineNow2, d: ", d) pkg := packageOf(last) // pre-register d.GetName() to detect circular definition. @@ -3045,18 +3029,16 @@ func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { panic(fmt.Sprintf( "builtin identifiers cannot be shadowed: %s", dn)) } - g.AddNode(dn, false) + *stack = append(*stack, dn) } // check type decl cycle if td, ok := d.(*TypeDecl); ok { debug.Println("---type decl, find dependency and assert no cycle, td: ", td) debug.Println("==============check cycle start==================") - g.dump() - g.AddNode(td.Name, false) // recursively check - findTypeDeclDependency(store, last, td.Type, g, false) + findTypeDeclDependency(store, last, td.Type, stack) debug.Println("==============check cycle complete!!!==================") } @@ -3067,8 +3049,10 @@ func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { un := tryPredefine(store, last, d) if un != "" { // check circularity. - if g.checkCycle(un) { - panic(fmt.Sprintf("constant definition loop with %s", un)) + for _, n := range *stack { + if n == un { + panic(fmt.Sprintf("constant definition loop with %s", un)) + } } // look up dependency declaration from fileset. file, decl := pkg.FileSet.GetDeclFor(un) @@ -3077,7 +3061,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { panic("all types from files in file-set should have already been predefined") } // predefine dependency (recursive). - *decl, _ = predefineNow2(store, file, *decl, g) + *decl, _ = predefineNow2(store, file, *decl, stack) } else { break } @@ -3171,181 +3155,12 @@ func predefineNow2(store Store, last BlockNode, d Decl, g *Graph) (Decl, bool) { case *ValueDecl: return Preprocess(store, last, cd).(Decl), true case *TypeDecl: - //debug.Println("---predefineNow2, loopfinder: ", loopfinder) - //debug.Println("---predefineNow2, cd: ", cd) - //debug.Println("---predefineNow2, cd.Type: ", cd.Type) - debug.Println("---going to preprocess type decl") - d := Preprocess(store, last, cd).(Decl) - debug.Println("---DONE preprocess type decl!") - //debug.Println("---predefineNow2, d: ", d) - - td := d.(*TypeDecl) - //debug.Println("---td: ", td) - //debug.Println("---td.Type: ", td.Type) - - //tmp := evalStaticType(store, last, cd.Type) - //dst := last.GetValueRef(store, cd.Name).GetType() - - //debug.Println("---dst: ", dst) - //switch dst := dst.(type) { - //case *DeclaredType: - // debug.Println("---declared type, tmp: ", tmp, reflect.TypeOf(tmp)) - // if dt, ok := tmp.(*DeclaredType); ok { - // debug.Println("---dt: ", dt, dt.Name, dt.Base) - // if st, ok := dt.Base.(*StructType); ok { - // debug.Println("---struct type") - // // check if fields contains declaredType - // maybeRecursive := false - // names := make([]Name, 0) - // - // for _, f := range st.Fields { - // maybeRecursive = checkFieldReference(st.PkgPath, f.Type, &names) - // } - // if maybeRecursive { - // for _, name := range names { - // debug.Println("---name: ", name) - // //if _, ok := (*loopfinder)[dst.Name]; ok { - // // panic(fmt.Sprintf("loop definition: %v ", *loopfinder)) - // //} - // - // (*loopfinder)[dst.Name] = struct{}{} - // debug.Println("---after insert, loopfinder: ", *loopfinder) - // - // insertDeclNode(declGraph, dst.Name, last.GetLocation(), name) - // } - // } - // } - // } - // do assert - //assertNoCycle(*declGraph) - //} - - return td, true - //return Preprocess(store, last, cd).(Decl), true + return Preprocess(store, last, cd).(Decl), true default: return d, false } } -func tryPredefineType(store Store, last BlockNode, d Decl) (un Name, indirect bool, undefined bool, complete bool) { - debug.Println("---tryPredefine Type, d: ", d) - - if d.GetAttribute(ATTR_PREDEFINED) == true { - panic(fmt.Sprintf("decl node %v already predefined!", d)) - } else { - debug.Printf("---%v not predefined: \n", d) - } - - // If un is blank, it means the predefine succeeded. - defer func() { - if un == "" { - d.SetAttribute(ATTR_PREDEFINED, true) - complete = true - } - }() - - // NOTE: These happen upon enter from the top, - // so value paths cannot be used here. - switch d := d.(type) { - case *TypeDecl: - last2 := skipFile(last) - _, ok := last2.GetLocalIndex(d.Name) - if !ok { - // construct empty t type - var t Type - switch tx := d.Type.(type) { - case *FuncTypeExpr: - t = &FuncType{} - case *ArrayTypeExpr: - t = &ArrayType{} - case *SliceTypeExpr: - t = &SliceType{} - case *InterfaceTypeExpr: - t = &InterfaceType{} - case *ChanTypeExpr: - t = &ChanType{} - case *MapTypeExpr: - t = &MapType{} - case *StructTypeExpr: - t = &StructType{} - case *StarExpr: - t = &PointerType{} - case *NameExpr: - if tv := last.GetValueRef(store, tx.Name); tv != nil { - // (file) block name - t = tv.GetType() - if dt, ok := t.(*DeclaredType); ok { - if !dt.sealed { - // predefineNow preprocessed dependent types. - panic("should not happen") - } - } else { - // all names are declared types. - panic("should not happen") - } - } else if idx, ok := UverseNode().GetLocalIndex(tx.Name); ok { - // uverse name - path := NewValuePathUverse(idx, tx.Name) - tv := Uverse().GetValueAt(nil, path) - t = tv.GetType() - } else { - // yet undefined - un = tx.Name - return - } - case *SelectorExpr: - // get package value. - un = findUndefined(store, last, tx.X) - if un != "" { - return - } - pkgName := tx.X.(*NameExpr).Name - tv := last.GetValueRef(store, pkgName) - pv, ok := tv.V.(*PackageValue) - if !ok { - panic(fmt.Sprintf( - "unknown package name %s in %s", - pkgName, - tx.String(), - )) - } - // check package node for name. - pn := pv.GetPackageNode(store) - tx.Path = pn.GetPathForName(store, tx.Sel) - ptr := pv.GetBlock(store).GetPointerTo(store, tx.Path) - t = ptr.TV.T - default: - panic(fmt.Sprintf( - "unexpected type declaration type %v", - reflect.TypeOf(d.Type))) - } - if d.IsAlias { - // use t directly. - } else { - // create new declared type. - pn := packageOf(last) - t = declareWith(pn.PkgPath, d.Name, t) - } - // fill in later. - last2.Define(d.Name, asValue(t)) - d.Path = last.GetPathForName(store, d.Name) - } - - //name, isIndirect, isUndefined, done := findTypeDeclDependency(store, last, d.Type) - //debug.Printf("after findTypeDeclDependency, name: %v, isIndirect: %v, isUndefined: %v \n", name, isIndirect, isUndefined) - //debug.Println("---done: ", done) - // - //if name != "" { - // un = name - // indirect = isIndirect - // undefined = isUndefined - // complete = done - // return - //} - } - return "", false, false, complete -} - // If a dependent name is not yet defined, that name is // returned; this return value is used by the caller to // enforce declaration order. If a dependent type is not yet diff --git a/gnovm/tests/files/recursive1.gno b/gnovm/tests/files/recursive1.gno index 11a7d61d9a2..71fddc5db19 100644 --- a/gnovm/tests/files/recursive1.gno +++ b/gnovm/tests/files/recursive1.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/recursive1.gno:1: type define cycle with name: S +// main/files/recursive1.gno:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive12.gno b/gnovm/tests/files/recursive12.gno deleted file mode 100644 index e742dc1b9dc..00000000000 --- a/gnovm/tests/files/recursive12.gno +++ /dev/null @@ -1,15 +0,0 @@ -package main - -type S struct { - T *S - B Integer -} - -type Integer int - -func main() { - var a, b S - println(a == b) -} - -// Error: diff --git a/gnovm/tests/files/recursive1b.gno b/gnovm/tests/files/recursive1b.gno index 4da65e7bbba..2893baf8fca 100644 --- a/gnovm/tests/files/recursive1b.gno +++ b/gnovm/tests/files/recursive1b.gno @@ -1,12 +1,16 @@ package main type S struct { - T S + T *S + B Integer } +type Integer int + func main() { var a, b S + println(a == b) } -// Error: -// main/files/recursive1b.gno:1: type define cycle with name: S +// Output: +// true diff --git a/gnovm/tests/files/recursive1c.gno b/gnovm/tests/files/recursive1c.gno index db142c3ac2e..5315f4103b9 100644 --- a/gnovm/tests/files/recursive1c.gno +++ b/gnovm/tests/files/recursive1c.gno @@ -9,10 +9,9 @@ type S struct { func main() { var a, b S - //println(a == b) fmt.Println(a) fmt.Println(b) } // Error: -// main/files/recursive1c.gno:1: type define cycle with name: S +// main/files/recursive1c.gno:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive1d.gno b/gnovm/tests/files/recursive1d.gno index 929f0a08aa2..fc6afd4e18b 100644 --- a/gnovm/tests/files/recursive1d.gno +++ b/gnovm/tests/files/recursive1d.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// main/files/recursive1d.gno:1: type define cycle with name: S +// main/files/recursive1d.gno:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive1f.gno b/gnovm/tests/files/recursive1f.gno index 211b07be0ef..0658abc942c 100644 --- a/gnovm/tests/files/recursive1f.gno +++ b/gnovm/tests/files/recursive1f.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/recursive1f.gno:3: type define cycle with name: S +// main/files/recursive1f.gno:3: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive2.gno b/gnovm/tests/files/recursive2.gno index 4815ff6090e..f185bcc055f 100644 --- a/gnovm/tests/files/recursive2.gno +++ b/gnovm/tests/files/recursive2.gno @@ -18,4 +18,4 @@ func main() { } // Error: -// main/files/recursive2.gno:1: type define cycle with name: A +// main/files/recursive2.gno:1: invalid recursive type: A -> B -> C -> A diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno index 8992453a9a8..08cc300cd3e 100644 --- a/gnovm/tests/files/recursive5.gno +++ b/gnovm/tests/files/recursive5.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/recursive5.gno:1: type define cycle with name: S +// main/files/recursive5.gno:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive7.gno b/gnovm/tests/files/recursive7.gno index 4ca7b185a6e..9bd8a56995d 100644 --- a/gnovm/tests/files/recursive7.gno +++ b/gnovm/tests/files/recursive7.gno @@ -2,4 +2,9 @@ package main type S []S -func main() {} +func main() { + println("ok") +} + +// Output: +// ok diff --git a/gnovm/tests/files/recursive7a.gno b/gnovm/tests/files/recursive7a.gno index 09384ff5513..6e2c0a60740 100644 --- a/gnovm/tests/files/recursive7a.gno +++ b/gnovm/tests/files/recursive7a.gno @@ -5,4 +5,4 @@ type S [2]S func main() {} // Error: -// main/files/recursive7a.gno:1: type define cycle with name: S \ No newline at end of file +// main/files/recursive7a.gno:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive8.gno b/gnovm/tests/files/recursive8.gno index 16d42c978ef..90d978e7c59 100644 --- a/gnovm/tests/files/recursive8.gno +++ b/gnovm/tests/files/recursive8.gno @@ -5,4 +5,4 @@ type Int Int func main() {} // Error: -// main/files/recursive8.gno:1: type define cycle with name: Int +// main/files/recursive8.gno:1: invalid recursive type: Int -> Int From 0116a43bb2166361ec42bff591561fcb4ec14c15 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Mon, 29 Jul 2024 22:17:15 +0800 Subject: [PATCH 26/35] clean --- gnovm/pkg/gnolang/machine.go | 6 +- gnovm/pkg/gnolang/nodes.go | 2 - gnovm/pkg/gnolang/preprocess.go | 158 ++++++++++++-------------------- gnovm/pkg/gnolang/type_check.go | 1 - 4 files changed, 60 insertions(+), 107 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 768cd239f12..864384ea122 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -353,7 +353,7 @@ func destar(x Expr) Expr { func (m *Machine) TestMemPackage(t *testing.T, memPkg *std.MemPackage) { defer m.injectLocOnPanic() DisableDebug() - debug.Println("DEBUG DISABLED (FOR TEST DEPENDENCIES INIT)") + fmt.Println("DEBUG DISABLED (FOR TEST DEPENDENCIES INIT)") // parse test files. tfiles, itfiles := ParseMemPackageTests(memPkg) { // first, tfiles which run in the same package. @@ -379,7 +379,7 @@ func (m *Machine) TestMemPackage(t *testing.T, memPkg *std.MemPackage) { m.RunFiles(itfiles.Files...) pn.PrepareNewValues(pv) EnableDebug() - debug.Println("DEBUG ENABLED") + fmt.Println("DEBUG ENABLED") for i := 0; i < len(pvBlock.Values); i++ { tv := pvBlock.Values[i] m.TestFunc(t, tv) @@ -618,10 +618,8 @@ func (m *Machine) runFiles(fns ...*FileNode) { // order and depend on other files. // Run declarations. - // XXX, is this only for global vars for _, fn := range fns { for _, decl := range fn.Decls { - debug.Println("---machine runDeclareFor, decl: ", decl) runDeclarationFor(fn, decl) } } diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 4029b9c56e4..2897fdd5306 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1772,8 +1772,6 @@ func (sb *StaticBlock) Predefine(isConst bool, n Name) { // The declared type st may not be the same as the static tv; // e.g. var x MyInterface = MyStruct{}. func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { - //fmt.Println("---Define2, n: ", n) - if debug { debug.Printf( "StaticBlock.Define2(%v, %s, %v, %v)\n", diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 3218281b57f..b61d7e67bea 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -18,20 +18,6 @@ const ( // Anything predefined or preprocessed here get skipped during the Preprocess // phase. func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { - //var declGraph []*DeclNode - //loopfindr := make(map[Name]struct{}) - - debug.Println("---predefineFileSet start") - defer func() { - debug.Println("---predefineFileSet end") - // Check for cyclic - //if len(declGraph) != 0 { - // assertNoCycle() - //} - if err := recover(); err != nil { - panic(err) - } - }() // First, initialize all file nodes and connect to package node. for _, fn := range fset.Files { SetNodeLocations(pn.PkgPath, string(fn.Name), fn) @@ -63,12 +49,9 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { // Predefine all type decls decls. for _, fn := range fset.Files { for i := 0; i < len(fn.Decls); i++ { - debug.Println("---PredefineFileSet, len of decls: ", len(fn.Decls)) - debug.Println("---PredefineFileSet, fn.Decls: ", fn.Decls) d := fn.Decls[i] switch d.(type) { case *TypeDecl: - debug.Println("---predefineFileSet, type decl, d: ", d) if d.GetAttribute(ATTR_PREDEFINED) == true { // skip declarations already predefined // (e.g. through recursion for a @@ -157,12 +140,6 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { SetNodeLocations(pkgPath, fileName, fn) } - //debug.Println("---preprocess, new declGraph") - // declGraph represents a slice of declGraph - //var declGraph []*DeclNode - - //loopfindr := make(map[Name]struct{}) - // create stack of BlockNodes. var stack []BlockNode = make([]BlockNode, 0, 32) var last BlockNode = ctx @@ -1621,11 +1598,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *StructTypeExpr: - debug.Println("---trans_leave, StructTypeExpr") evalStaticType(store, last, n) - // TODO: maxwell can we do something here? - //panic("---!!!") - // TRANS_LEAVE ----------------------- case *AssignStmt: n.AssertCompatible(store, last) @@ -2597,26 +2570,22 @@ func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { } } -func findTypeDeclDependency(store Store, last BlockNode, x Expr, stack *[]Name) { - findTypeDeclDependency2(store, last, x, stack, false) +func assertTypeDeclNoCycle(store Store, last BlockNode, x Expr, stack *[]Name) { + assertTypeDeclNoCycle2(store, last, x, stack, false) } -func findTypeDeclDependency2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool) { - debug.Printf("---findDependency, x: %v, indirect: %v, type of x: %v \n", x, indirect, reflect.TypeOf(x)) +func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool) { if x == nil { + // XXX, should not happen? return } var lastX Expr defer func() { - fmt.Println(">>>>>>------defer call, before pop--->>>>>>") - fmt.Println("---stack: ", *stack) if _, ok := lastX.(*NameExpr); ok { // pop stack *stack = (*stack)[:len(*stack)-1] } - fmt.Println("<<<<<<------defer call, after pop---<<<<<<") - fmt.Println("---stack: ", *stack) }() switch cx := x.(type) { @@ -2632,7 +2601,6 @@ func findTypeDeclDependency2(store Store, last BlockNode, x Expr, stack *[]Name, msg += fmt.Sprintf("%s -> ", (*stack)[j]) } msg += string(n) - fmt.Println("---msg: ", msg) panic(fmt.Sprintf("invalid recursive type: %s", msg)) } } @@ -2643,71 +2611,71 @@ func findTypeDeclDependency2(store Store, last BlockNode, x Expr, stack *[]Name, case *BasicLitExpr: return case *BinaryExpr: - findTypeDeclDependency2(store, last, cx.Left, stack, indirect) - findTypeDeclDependency2(store, last, cx.Right, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Left, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Right, stack, indirect) case *SelectorExpr: - findTypeDeclDependency2(store, last, cx.X, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) case *SliceExpr: - findTypeDeclDependency2(store, last, cx.X, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) if cx.Low != nil { - findTypeDeclDependency2(store, last, cx.Low, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Low, stack, indirect) } if cx.High != nil { - findTypeDeclDependency2(store, last, cx.High, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.High, stack, indirect) } if cx.Max != nil { - findTypeDeclDependency2(store, last, cx.Max, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Max, stack, indirect) } case *StarExpr: - findTypeDeclDependency2(store, last, cx.X, stack, true) + assertTypeDeclNoCycle2(store, last, cx.X, stack, true) case *RefExpr: - findTypeDeclDependency2(store, last, cx.X, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) case *TypeAssertExpr: - findTypeDeclDependency2(store, last, cx.X, stack, indirect) - findTypeDeclDependency2(store, last, cx.Type, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect) case *UnaryExpr: - findTypeDeclDependency2(store, last, cx.X, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) case *FuncLitExpr: - findTypeDeclDependency2(store, last, &cx.Type, stack, indirect) + assertTypeDeclNoCycle2(store, last, &cx.Type, stack, indirect) case *FieldTypeExpr: - findTypeDeclDependency2(store, last, cx.Type, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect) case *ArrayTypeExpr: if cx.Len != nil { - findTypeDeclDependency2(store, last, cx.Len, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Len, stack, indirect) } - findTypeDeclDependency2(store, last, cx.Elt, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Elt, stack, indirect) case *SliceTypeExpr: - findTypeDeclDependency2(store, last, cx.Elt, stack, true) + assertTypeDeclNoCycle2(store, last, cx.Elt, stack, true) case *InterfaceTypeExpr: for i := range cx.Methods { - findTypeDeclDependency2(store, last, &cx.Methods[i], stack, true) + assertTypeDeclNoCycle2(store, last, &cx.Methods[i], stack, true) } case *ChanTypeExpr: - findTypeDeclDependency2(store, last, cx.Value, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Value, stack, indirect) case *FuncTypeExpr: for i := range cx.Params { - findTypeDeclDependency2(store, last, &cx.Params[i], stack, true) + assertTypeDeclNoCycle2(store, last, &cx.Params[i], stack, true) } for i := range cx.Results { - findTypeDeclDependency2(store, last, &cx.Results[i], stack, true) + assertTypeDeclNoCycle2(store, last, &cx.Results[i], stack, true) } case *MapTypeExpr: - findTypeDeclDependency2(store, last, cx.Key, stack, true) - findTypeDeclDependency2(store, last, cx.Value, stack, true) + assertTypeDeclNoCycle2(store, last, cx.Key, stack, true) + assertTypeDeclNoCycle2(store, last, cx.Value, stack, true) case *StructTypeExpr: for i := range cx.Fields { - findTypeDeclDependency2(store, last, &cx.Fields[i], stack, indirect) + assertTypeDeclNoCycle2(store, last, &cx.Fields[i], stack, indirect) } case *MaybeNativeTypeExpr: - findTypeDeclDependency2(store, last, cx.Type, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect) case *CallExpr: - findTypeDeclDependency2(store, last, cx.Func, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Func, stack, indirect) for i := range cx.Args { - findTypeDeclDependency2(store, last, cx.Args[i], stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Args[i], stack, indirect) } case *IndexExpr: - findTypeDeclDependency2(store, last, cx.X, stack, indirect) - findTypeDeclDependency2(store, last, cx.Index, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Index, stack, indirect) case *constTypeExpr: return case *ConstExpr: @@ -2734,21 +2702,18 @@ func findUndefined(store Store, last BlockNode, x Expr) (un Name) { // TODO: add helpers for type decl func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { - debug.Println("---findUndefined2, x, t: ", x, t) if x == nil { return } switch cx := x.(type) { case *NameExpr: if tv := last.GetValueRef(store, cx.Name); tv != nil { - debug.Println("---1") return } if _, ok := UverseNode().GetLocalIndex(cx.Name); ok { // XXX NOTE even if the name is shadowed by a file // level declaration, it is fine to return here as it // will be predefined later. - debug.Println("---2") return } return cx.Name @@ -2766,28 +2731,28 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { case *SelectorExpr: return findUndefined(store, last, cx.X) case *SliceExpr: - //un = findUndefined(store, last, cx.X) - //if un != "" { - // return - //} - //if cx.Low != nil { - // un = findUndefined(store, last, cx.Low) - // if un != "" { - // return - // } - //} - //if cx.High != nil { - // un = findUndefined(store, last, cx.High) - // if un != "" { - // return - // } - //} - //if cx.Max != nil { - // un = findUndefined(store, last, cx.Max) - // if un != "" { - // return - // } - //} + un = findUndefined(store, last, cx.X) + if un != "" { + return + } + if cx.Low != nil { + un = findUndefined(store, last, cx.Low) + if un != "" { + return + } + } + if cx.High != nil { + un = findUndefined(store, last, cx.High) + if un != "" { + return + } + } + if cx.Max != nil { + un = findUndefined(store, last, cx.Max) + if un != "" { + return + } + } case *StarExpr: return findUndefined(store, last, cx.X) return @@ -2999,7 +2964,6 @@ func checkIntegerKind(xt Type) { // preprocess-able before other file-level declarations are // preprocessed). func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { - debug.Println("---predefine now, d.line", d.GetLine()) defer func() { if r := recover(); r != nil { // before re-throwing the error, append location information to message. @@ -3021,7 +2985,6 @@ func predefineNow(store Store, last BlockNode, d Decl) (Decl, bool) { } func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bool) { - debug.Println("---predefineNow2, d: ", d) pkg := packageOf(last) // pre-register d.GetName() to detect circular definition. for _, dn := range d.GetDeclNames() { @@ -3034,17 +2997,12 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo // check type decl cycle if td, ok := d.(*TypeDecl); ok { - debug.Println("---type decl, find dependency and assert no cycle, td: ", td) - debug.Println("==============check cycle start==================") - // recursively check - findTypeDeclDependency(store, last, td.Type, stack) - debug.Println("==============check cycle complete!!!==================") + assertTypeDeclNoCycle(store, last, td.Type, stack) } // recursively predefine dependencies. for { - debug.Println("---for loop start, recursively define ") // type decl un := tryPredefine(store, last, d) if un != "" { @@ -3066,7 +3024,6 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo break } } - debug.Println("---after for loop") // after recursive define switch cd := d.(type) { case *FuncDecl: @@ -3174,12 +3131,14 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { if d.GetAttribute(ATTR_PREDEFINED) == true { panic("decl node already predefined!") } + // If un is blank, it means the predefine succeeded. defer func() { if un == "" { d.SetAttribute(ATTR_PREDEFINED, true) } }() + // NOTE: These happen upon enter from the top, // so value paths cannot be used here. switch d := d.(type) { @@ -3312,7 +3271,6 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { d.Path = last.GetPathForName(store, d.Name) } // after predefinitions, return any undefined dependencies. - // after predefinitions, return any undefined declGraph. un = findUndefined(store, last, d.Type) if un != "" { return diff --git a/gnovm/pkg/gnolang/type_check.go b/gnovm/pkg/gnolang/type_check.go index 4d9cf267aca..870eb10b690 100644 --- a/gnovm/pkg/gnolang/type_check.go +++ b/gnovm/pkg/gnolang/type_check.go @@ -137,7 +137,6 @@ func isNumericOrString(t Type) bool { // =========================================================== func assertComparable(xt, dt Type) { - //fmt.Println("---assertComparable...") switch baseOf(dt).(type) { case *SliceType, *FuncType, *MapType: if xt != nil { From 51deee2e9ac81c2ba624033ab408a3e62458cdc4 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Mon, 29 Jul 2024 22:21:33 +0800 Subject: [PATCH 27/35] clean --- gnovm/pkg/gnolang/preprocess.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index b61d7e67bea..25a5c913625 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -1599,6 +1599,7 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { // TRANS_LEAVE ----------------------- case *StructTypeExpr: evalStaticType(store, last, n) + // TRANS_LEAVE ----------------------- case *AssignStmt: n.AssertCompatible(store, last) @@ -2700,7 +2701,6 @@ func findUndefined(store Store, last BlockNode, x Expr) (un Name) { return findUndefined2(store, last, x, nil) } -// TODO: add helpers for type decl func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { if x == nil { return @@ -3003,7 +3003,6 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo // recursively predefine dependencies. for { - // type decl un := tryPredefine(store, last, d) if un != "" { // check circularity. @@ -3024,7 +3023,6 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo break } } - // after recursive define switch cd := d.(type) { case *FuncDecl: // *FuncValue/*FuncType is mostly empty still; here From caf382ca9e59b34f925aee55913be105ce3d7bfd Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Mon, 5 Aug 2024 18:48:18 +0800 Subject: [PATCH 28/35] fixup --- gnovm/pkg/gnolang/preprocess.go | 1 + 1 file changed, 1 insertion(+) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 25a5c913625..3065250f841 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2578,6 +2578,7 @@ func assertTypeDeclNoCycle(store Store, last BlockNode, x Expr, stack *[]Name) { func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool) { if x == nil { // XXX, should not happen? + panic("should not happen") return } From a9282ba726dc8bebaf80f8c8097b296fbb46babd Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Mon, 5 Aug 2024 22:40:28 +0800 Subject: [PATCH 29/35] simplify --- gnovm/pkg/gnolang/preprocess.go | 44 +------------------------------ gnovm/tests/files/recursive4a.gno | 9 +++++++ 2 files changed, 10 insertions(+), 43 deletions(-) create mode 100644 gnovm/tests/files/recursive4a.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 3065250f841..f3464ec97c8 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2577,9 +2577,7 @@ func assertTypeDeclNoCycle(store Store, last BlockNode, x Expr, stack *[]Name) { func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool) { if x == nil { - // XXX, should not happen? panic("should not happen") - return } var lastX Expr @@ -2610,35 +2608,10 @@ func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, lastX = cx } return - case *BasicLitExpr: - return - case *BinaryExpr: - assertTypeDeclNoCycle2(store, last, cx.Left, stack, indirect) - assertTypeDeclNoCycle2(store, last, cx.Right, stack, indirect) case *SelectorExpr: assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) - case *SliceExpr: - assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) - if cx.Low != nil { - assertTypeDeclNoCycle2(store, last, cx.Low, stack, indirect) - } - if cx.High != nil { - assertTypeDeclNoCycle2(store, last, cx.High, stack, indirect) - } - if cx.Max != nil { - assertTypeDeclNoCycle2(store, last, cx.Max, stack, indirect) - } case *StarExpr: assertTypeDeclNoCycle2(store, last, cx.X, stack, true) - case *RefExpr: - assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) - case *TypeAssertExpr: - assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) - assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect) - case *UnaryExpr: - assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) - case *FuncLitExpr: - assertTypeDeclNoCycle2(store, last, &cx.Type, stack, indirect) case *FieldTypeExpr: assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect) case *ArrayTypeExpr: @@ -2653,7 +2626,7 @@ func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, assertTypeDeclNoCycle2(store, last, &cx.Methods[i], stack, true) } case *ChanTypeExpr: - assertTypeDeclNoCycle2(store, last, cx.Value, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Value, stack, true) case *FuncTypeExpr: for i := range cx.Params { assertTypeDeclNoCycle2(store, last, &cx.Params[i], stack, true) @@ -2668,24 +2641,9 @@ func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, for i := range cx.Fields { assertTypeDeclNoCycle2(store, last, &cx.Fields[i], stack, indirect) } - case *MaybeNativeTypeExpr: - assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect) - case *CallExpr: - assertTypeDeclNoCycle2(store, last, cx.Func, stack, indirect) - for i := range cx.Args { - assertTypeDeclNoCycle2(store, last, cx.Args[i], stack, indirect) - } - case *IndexExpr: - assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) - assertTypeDeclNoCycle2(store, last, cx.Index, stack, indirect) case *constTypeExpr: return - case *ConstExpr: - return default: - panic(fmt.Sprintf( - "unexpected expr: %v (%v)", - x, reflect.TypeOf(x))) } return } diff --git a/gnovm/tests/files/recursive4a.gno b/gnovm/tests/files/recursive4a.gno new file mode 100644 index 00000000000..a3567e56b6a --- /dev/null +++ b/gnovm/tests/files/recursive4a.gno @@ -0,0 +1,9 @@ +package main + +type time time.Duration + +func main() { +} + +// Error: +// main/files/recursive4a.gno:1: invalid recursive type: time -> time From f044f97f970b72ef892c67d488b4ca295cf755bf Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Mon, 5 Aug 2024 22:47:49 +0800 Subject: [PATCH 30/35] fixup --- gnovm/pkg/gnolang/preprocess.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index f3464ec97c8..95c0b7a53e6 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2714,7 +2714,6 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { } case *StarExpr: return findUndefined(store, last, cx.X) - return case *RefExpr: return findUndefined(store, last, cx.X) case *TypeAssertExpr: From 43c451c5c4519a43f91c6e1711feaef47054ae06 Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Tue, 6 Aug 2024 08:57:56 +0800 Subject: [PATCH 31/35] fixup --- gnovm/pkg/gnolang/preprocess.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 95c0b7a53e6..42c80b0bbb0 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2591,8 +2591,8 @@ func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, switch cx := x.(type) { case *NameExpr: if indirect { - // not check - *stack = []Name{} // reset + // clear current stack + *stack = (*stack)[:0] } else { var msg string for _, n := range *stack { From ca4a14b3ab4685f4405be6294aec01b81c74f32b Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Tue, 6 Aug 2024 09:02:44 +0800 Subject: [PATCH 32/35] sync location --- gnovm/tests/files/circular_constant.gno | 2 +- gnovm/tests/files/recursive1.gno | 2 +- gnovm/tests/files/recursive1c.gno | 2 +- gnovm/tests/files/recursive1d.gno | 2 +- gnovm/tests/files/recursive1f.gno | 2 +- gnovm/tests/files/recursive2.gno | 2 +- gnovm/tests/files/recursive2c.gno | 2 +- gnovm/tests/files/recursive4a.gno | 2 +- gnovm/tests/files/recursive5.gno | 2 +- gnovm/tests/files/recursive7a.gno | 2 +- gnovm/tests/files/recursive8.gno | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gnovm/tests/files/circular_constant.gno b/gnovm/tests/files/circular_constant.gno index 1dea0d3d007..ff25da7428d 100644 --- a/gnovm/tests/files/circular_constant.gno +++ b/gnovm/tests/files/circular_constant.gno @@ -7,4 +7,4 @@ func main() { } // Error: -// main/files/circular_constant.gno:3: constant definition loop with A +// main/files/circular_constant.gno:3:7: constant definition loop with A diff --git a/gnovm/tests/files/recursive1.gno b/gnovm/tests/files/recursive1.gno index 71fddc5db19..8279e247d84 100644 --- a/gnovm/tests/files/recursive1.gno +++ b/gnovm/tests/files/recursive1.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/recursive1.gno:1: invalid recursive type: S -> S +// main/files/recursive1.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive1c.gno b/gnovm/tests/files/recursive1c.gno index 5315f4103b9..7797f375027 100644 --- a/gnovm/tests/files/recursive1c.gno +++ b/gnovm/tests/files/recursive1c.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// main/files/recursive1c.gno:1: invalid recursive type: S -> S +// main/files/recursive1c.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive1d.gno b/gnovm/tests/files/recursive1d.gno index fc6afd4e18b..22bf172b5ac 100644 --- a/gnovm/tests/files/recursive1d.gno +++ b/gnovm/tests/files/recursive1d.gno @@ -14,4 +14,4 @@ func main() { } // Error: -// main/files/recursive1d.gno:1: invalid recursive type: S -> S +// main/files/recursive1d.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive1f.gno b/gnovm/tests/files/recursive1f.gno index 0658abc942c..81fe2a5699c 100644 --- a/gnovm/tests/files/recursive1f.gno +++ b/gnovm/tests/files/recursive1f.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/recursive1f.gno:3: invalid recursive type: S -> S +// main/files/recursive1f.gno:3:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive2.gno b/gnovm/tests/files/recursive2.gno index f185bcc055f..4ed86f03d58 100644 --- a/gnovm/tests/files/recursive2.gno +++ b/gnovm/tests/files/recursive2.gno @@ -18,4 +18,4 @@ func main() { } // Error: -// main/files/recursive2.gno:1: invalid recursive type: A -> B -> C -> A +// main/files/recursive2.gno:1:1: invalid recursive type: A -> B -> C -> A diff --git a/gnovm/tests/files/recursive2c.gno b/gnovm/tests/files/recursive2c.gno index 9060577ba1b..3b5c27ed8ea 100644 --- a/gnovm/tests/files/recursive2c.gno +++ b/gnovm/tests/files/recursive2c.gno @@ -18,4 +18,4 @@ func main() { } // Error: -// main/files/recursive2c.gno:3: name B not defined in fileset with files [files/recursive2c.gno] +// main/files/recursive2c.gno:3:1: name B not defined in fileset with files [files/recursive2c.gno] diff --git a/gnovm/tests/files/recursive4a.gno b/gnovm/tests/files/recursive4a.gno index a3567e56b6a..8b4d13b4785 100644 --- a/gnovm/tests/files/recursive4a.gno +++ b/gnovm/tests/files/recursive4a.gno @@ -6,4 +6,4 @@ func main() { } // Error: -// main/files/recursive4a.gno:1: invalid recursive type: time -> time +// main/files/recursive4a.gno:1:1: invalid recursive type: time -> time diff --git a/gnovm/tests/files/recursive5.gno b/gnovm/tests/files/recursive5.gno index 08cc300cd3e..1c2fbd89fb8 100644 --- a/gnovm/tests/files/recursive5.gno +++ b/gnovm/tests/files/recursive5.gno @@ -10,4 +10,4 @@ func main() { } // Error: -// main/files/recursive5.gno:1: invalid recursive type: S -> S +// main/files/recursive5.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive7a.gno b/gnovm/tests/files/recursive7a.gno index 6e2c0a60740..b3c57516f13 100644 --- a/gnovm/tests/files/recursive7a.gno +++ b/gnovm/tests/files/recursive7a.gno @@ -5,4 +5,4 @@ type S [2]S func main() {} // Error: -// main/files/recursive7a.gno:1: invalid recursive type: S -> S +// main/files/recursive7a.gno:1:1: invalid recursive type: S -> S diff --git a/gnovm/tests/files/recursive8.gno b/gnovm/tests/files/recursive8.gno index 90d978e7c59..1f9325ae35c 100644 --- a/gnovm/tests/files/recursive8.gno +++ b/gnovm/tests/files/recursive8.gno @@ -5,4 +5,4 @@ type Int Int func main() {} // Error: -// main/files/recursive8.gno:1: invalid recursive type: Int -> Int +// main/files/recursive8.gno:1:1: invalid recursive type: Int -> Int From 64fb6852ca6c3cb838e129fe6481475b7de41aed Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Tue, 6 Aug 2024 16:03:20 +0800 Subject: [PATCH 33/35] check type alias --- gnovm/pkg/gnolang/preprocess.go | 67 ++++++++++++++++++------------- gnovm/tests/files/recursive6a.gno | 12 ++++++ gnovm/tests/files/recursive9.gno | 8 ++++ gnovm/tests/files/recursive9a.gno | 8 ++++ gnovm/tests/files/recursive9b.gno | 8 ++++ gnovm/tests/files/recursive9c.gno | 8 ++++ gnovm/tests/files/recursive9d.gno | 10 +++++ 7 files changed, 92 insertions(+), 29 deletions(-) create mode 100644 gnovm/tests/files/recursive6a.gno create mode 100644 gnovm/tests/files/recursive9.gno create mode 100644 gnovm/tests/files/recursive9a.gno create mode 100644 gnovm/tests/files/recursive9b.gno create mode 100644 gnovm/tests/files/recursive9c.gno create mode 100644 gnovm/tests/files/recursive9d.gno diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 2274862c4ab..6d0da5328ae 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2906,11 +2906,11 @@ func convertConst(store Store, last BlockNode, cx *ConstExpr, t Type) { } } -func assertTypeDeclNoCycle(store Store, last BlockNode, x Expr, stack *[]Name) { - assertTypeDeclNoCycle2(store, last, x, stack, false) +func assertTypeDeclNoCycle(store Store, last BlockNode, td *TypeDecl, stack *[]Name) { + assertTypeDeclNoCycle2(store, last, td.Type, stack, false, td.IsAlias) } -func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool) { +func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool, isAlias bool) { if x == nil { panic("should not happen") } @@ -2925,59 +2925,68 @@ func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, switch cx := x.(type) { case *NameExpr: - if indirect { - // clear current stack - *stack = (*stack)[:0] - } else { - var msg string + var msg string + + // Function to build the error message + buildMessage := func() string { + for j := 0; j < len(*stack); j++ { + msg += fmt.Sprintf("%s -> ", (*stack)[j]) + } + return msg + string(cx.Name) // Append the current name last + } + + // Check for existence of cx.Name in stack + findCycle := func() { for _, n := range *stack { if n == cx.Name { - for j := 0; j < len(*stack); j++ { - msg += fmt.Sprintf("%s -> ", (*stack)[j]) - } - msg += string(n) + msg = buildMessage() panic(fmt.Sprintf("invalid recursive type: %s", msg)) } } + } + + if indirect && !isAlias { + *stack = (*stack)[:0] + } else { + findCycle() *stack = append(*stack, cx.Name) - lastX = cx + lastX = cx // Assuming lastX is declared somewhere in this context } + return case *SelectorExpr: - assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.X, stack, indirect, isAlias) case *StarExpr: - assertTypeDeclNoCycle2(store, last, cx.X, stack, true) + assertTypeDeclNoCycle2(store, last, cx.X, stack, true, isAlias) case *FieldTypeExpr: - assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Type, stack, indirect, isAlias) case *ArrayTypeExpr: if cx.Len != nil { - assertTypeDeclNoCycle2(store, last, cx.Len, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Len, stack, indirect, isAlias) } - assertTypeDeclNoCycle2(store, last, cx.Elt, stack, indirect) + assertTypeDeclNoCycle2(store, last, cx.Elt, stack, indirect, isAlias) case *SliceTypeExpr: - assertTypeDeclNoCycle2(store, last, cx.Elt, stack, true) + assertTypeDeclNoCycle2(store, last, cx.Elt, stack, true, isAlias) case *InterfaceTypeExpr: for i := range cx.Methods { - assertTypeDeclNoCycle2(store, last, &cx.Methods[i], stack, true) + assertTypeDeclNoCycle2(store, last, &cx.Methods[i], stack, indirect, isAlias) } case *ChanTypeExpr: - assertTypeDeclNoCycle2(store, last, cx.Value, stack, true) + assertTypeDeclNoCycle2(store, last, cx.Value, stack, true, isAlias) case *FuncTypeExpr: for i := range cx.Params { - assertTypeDeclNoCycle2(store, last, &cx.Params[i], stack, true) + assertTypeDeclNoCycle2(store, last, &cx.Params[i], stack, true, isAlias) } for i := range cx.Results { - assertTypeDeclNoCycle2(store, last, &cx.Results[i], stack, true) + assertTypeDeclNoCycle2(store, last, &cx.Results[i], stack, true, isAlias) } case *MapTypeExpr: - assertTypeDeclNoCycle2(store, last, cx.Key, stack, true) - assertTypeDeclNoCycle2(store, last, cx.Value, stack, true) + assertTypeDeclNoCycle2(store, last, cx.Key, stack, true, isAlias) + assertTypeDeclNoCycle2(store, last, cx.Value, stack, true, isAlias) case *StructTypeExpr: for i := range cx.Fields { - assertTypeDeclNoCycle2(store, last, &cx.Fields[i], stack, indirect) + assertTypeDeclNoCycle2(store, last, &cx.Fields[i], stack, indirect, isAlias) } - case *constTypeExpr: - return default: } return @@ -3292,7 +3301,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo // check type decl cycle if td, ok := d.(*TypeDecl); ok { // recursively check - assertTypeDeclNoCycle(store, last, td.Type, stack) + assertTypeDeclNoCycle(store, last, td, stack) } // recursively predefine dependencies. diff --git a/gnovm/tests/files/recursive6a.gno b/gnovm/tests/files/recursive6a.gno new file mode 100644 index 00000000000..8123fc626a5 --- /dev/null +++ b/gnovm/tests/files/recursive6a.gno @@ -0,0 +1,12 @@ +package main + +type SelfReferencing interface { + SelfReferencing +} + +func main() { + println("ok") +} + +// Error: +// main/files/recursive6a.gno:1:1: invalid recursive type: SelfReferencing -> SelfReferencing diff --git a/gnovm/tests/files/recursive9.gno b/gnovm/tests/files/recursive9.gno new file mode 100644 index 00000000000..8181be55d33 --- /dev/null +++ b/gnovm/tests/files/recursive9.gno @@ -0,0 +1,8 @@ +package main + +type Int = Int + +func main() {} + +// Error: +// main/files/recursive9.gno:1:1: invalid recursive type: Int -> Int diff --git a/gnovm/tests/files/recursive9a.gno b/gnovm/tests/files/recursive9a.gno new file mode 100644 index 00000000000..b96efa090e4 --- /dev/null +++ b/gnovm/tests/files/recursive9a.gno @@ -0,0 +1,8 @@ +package main + +type Int = *Int + +func main() {} + +// Error: +// main/files/recursive9a.gno:1:1: invalid recursive type: Int -> Int \ No newline at end of file diff --git a/gnovm/tests/files/recursive9b.gno b/gnovm/tests/files/recursive9b.gno new file mode 100644 index 00000000000..e033349d597 --- /dev/null +++ b/gnovm/tests/files/recursive9b.gno @@ -0,0 +1,8 @@ +package main + +type Int = func() Int + +func main() {} + +// Error: +// main/files/recursive9b.gno:1:1: invalid recursive type: Int -> Int \ No newline at end of file diff --git a/gnovm/tests/files/recursive9c.gno b/gnovm/tests/files/recursive9c.gno new file mode 100644 index 00000000000..ad865978920 --- /dev/null +++ b/gnovm/tests/files/recursive9c.gno @@ -0,0 +1,8 @@ +package main + +type Int = []Int + +func main() {} + +// Error: +// main/files/recursive9c.gno:1:1: invalid recursive type: Int -> Int diff --git a/gnovm/tests/files/recursive9d.gno b/gnovm/tests/files/recursive9d.gno new file mode 100644 index 00000000000..ae7310ede0f --- /dev/null +++ b/gnovm/tests/files/recursive9d.gno @@ -0,0 +1,10 @@ +package main + +type S = struct { + *S +} + +func main() {} + +// Error: +// main/files/recursive9d.gno:1:1: invalid recursive type: S -> S From 559564d02e806a8427523c477bb9174527d0d0cd Mon Sep 17 00:00:00 2001 From: ltzmaxwell Date: Thu, 8 Aug 2024 10:36:10 +0800 Subject: [PATCH 34/35] Update gnovm/pkg/gnolang/preprocess.go Co-authored-by: deelawn --- gnovm/pkg/gnolang/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 6d0da5328ae..47a9dbd8fce 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2912,7 +2912,7 @@ func assertTypeDeclNoCycle(store Store, last BlockNode, td *TypeDecl, stack *[]N func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, indirect bool, isAlias bool) { if x == nil { - panic("should not happen") + panic("unexpected nil expression when checking for type declaration cycles") } var lastX Expr From 9e76e22b1676a3b012698e5ba44abfe07191fbda Mon Sep 17 00:00:00 2001 From: ltzMaxwell Date: Thu, 15 Aug 2024 15:51:22 +0800 Subject: [PATCH 35/35] clean --- gnovm/pkg/gnolang/preprocess.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 6d0da5328ae..f378aeccfb9 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2950,7 +2950,7 @@ func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, } else { findCycle() *stack = append(*stack, cx.Name) - lastX = cx // Assuming lastX is declared somewhere in this context + lastX = cx } return