diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index b85d1ac7026..0f1f4388623 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -157,6 +157,7 @@ const ( ATTR_LOOP_USES GnoAttribute = "ATTR_LOOP_USES" // []Name loop defines actually used. ATTR_SHIFT_RHS GnoAttribute = "ATTR_SHIFT_RHS" ATTR_LAST_BLOCK_STMT GnoAttribute = "ATTR_LAST_BLOCK_STMT" + ATTR_GLOBAL GnoAttribute = "ATTR_GLOBAL" ) type Attributes struct { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index ffa0f518331..96e09642cd8 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -129,6 +129,7 @@ func PredefineFileSet(store Store, pn *PackageNode, fset *FileSet) { continue } + d.SetAttribute(ATTR_GLOBAL, true) // recursively predefine dependencies. d2, _ := predefineNow(store, fn, d) @@ -516,6 +517,17 @@ func preprocess1(store Store, ctx BlockNode, n Node) Node { if cd, ok := d.(*ValueDecl); ok { checkValDefineMismatch(cd) } + + isGlobal := true + + for i := len(ns) - 1; i > 0; i-- { + if _, ok := ns[i].(*FuncDecl); ok { + isGlobal = false + } + } + + d.SetAttribute(ATTR_GLOBAL, isGlobal) + // recursively predefine dependencies. d2, ppd := predefineNow(store, last, d) if ppd { @@ -3779,16 +3791,486 @@ func assertTypeDeclNoCycle2(store Store, last BlockNode, x Expr, stack *[]Name, // type expressions, which must get preprocessed for inner // composite type eliding to work. func findUndefined(store Store, last BlockNode, x Expr) (un Name) { - return findUndefined2(store, last, x, nil) + return findUndefined2(store, last, x, nil, true) +} + +// finds the next undefined identifier and returns it if it is global +func findUndefined2SkipLocals(store Store, last BlockNode, x Expr, t Type) Name { + name := findUndefinedGlobal(store, last, x, t) + + if name == "" { + return "" + } + + existsLocal := func(name Name, bn BlockNode) bool { + curr := bn + for { + currNames := curr.GetBlockNames() + + for _, currName := range currNames { + if currName == name { + return true + } + } + + newcurr := bn.GetStaticBlock().GetParentNode(store) + + if curr == newcurr { + return false + } + + curr = newcurr + + if curr == nil { + return false + } + + _, isFile := curr.(*FileNode) + + if isFile { + return false + } + } + } + + pkg := packageOf(last) + + if _, _, ok := pkg.FileSet.GetDeclForSafe(name); !ok { + return "" + } + + isLocal := existsLocal(name, last) + + if isLocal { + return "" + } + + return name +} + +func findUndefinedStmt(store Store, last BlockNode, stmt Stmt, t Type) Name { + switch s := stmt.(type) { + case *TypeDecl: + un := findUndefined2SkipLocals(store, last, s.Type, t) + + if un != "" { + return un + } + case *ValueDecl: + un := findUndefined2SkipLocals(store, last, s.Type, t) + + if un != "" { + return un + } + for _, rh := range s.Values { + un := findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + case *DeclStmt: + for _, rh := range s.Body { + un := findUndefinedStmt(store, last, rh, t) + + if un != "" { + return un + } + } + case *IncDecStmt: + un := findUndefined2SkipLocals(store, last, s.X, t) + + if un != "" { + return un + } + case *PanicStmt: + un := findUndefined2SkipLocals(store, last, s.Exception, t) + + if un != "" { + return un + } + case *BlockStmt: + for _, rh := range s.Body { + un := findUndefinedStmt(store, s, rh, t) + + if un != "" { + return un + } + } + case *DeferStmt: + un := findUndefined2SkipLocals(store, last, s.Call.Func, t) + + if un != "" { + return un + } + + for _, rh := range s.Call.Args { + un = findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + case *SwitchStmt: + un := findUndefined2SkipLocals(store, last, s.X, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, last, s.Init, t) + if un != "" { + return un + } + + for _, b := range s.Clauses { + b := b + un = findUndefinedStmt(store, s, &b, t) + + if un != "" { + return un + } + } + case *SwitchClauseStmt: + for _, rh := range s.Cases { + un := findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + + if un != "" { + return un + } + } + + case *ExprStmt: + return findUndefined2SkipLocals(store, last, s.X, t) + case *AssignStmt: + for _, rh := range s.Rhs { + un := findUndefined2SkipLocals(store, last, rh, t) + + if un != "" { + return un + } + } + case *IfStmt: + un := findUndefinedStmt(store, last, s.Init, t) + if un != "" { + return un + } + + un = findUndefined2SkipLocals(store, last, s.Cond, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, last, &s.Else, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, last, &s.Then, t) + if un != "" { + return un + } + case *IfCaseStmt: + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + + if un != "" { + return un + } + } + case *ReturnStmt: + for _, b := range s.Results { + un := findUndefined2SkipLocals(store, last, b, t) + if un != "" { + return un + } + } + case *RangeStmt: + un := findUndefined2SkipLocals(store, last, s.X, t) + if un != "" { + return un + } + + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + if un != "" { + return un + } + } + case *ForStmt: + un := findUndefinedStmt(store, s, s.Init, t) + if un != "" { + return un + } + + un = findUndefined2SkipLocals(store, s, s.Cond, t) + if un != "" { + return un + } + + un = findUndefinedStmt(store, s, s.Post, t) + if un != "" { + return un + } + + for _, b := range s.Body { + un := findUndefinedStmt(store, last, b, t) + if un != "" { + return un + } + } + case *BranchStmt: + case nil: + return "" + default: + panic(fmt.Sprintf("findUndefinedStmt: %T not supported", s)) + } + return "" +} + +func getGlobalValueRef(sb BlockNode, store Store, n Name) *TypedValue { + sbb := sb.GetStaticBlock() + idx, ok := sb.GetLocalIndex(n) + bb := &sb.GetStaticBlock().Block + bp := sb.GetParentNode(store) + + for { + if ok && sbb.Types[idx] != nil && (bp == nil || bp.GetParentNode(store) == nil) { + return bb.GetPointerToInt(store, int(idx)).TV + } else if bp != nil { + idx, ok = bp.GetLocalIndex(n) + sbb = bp.GetStaticBlock() + bb = sbb.GetBlock() + bp = bp.GetParentNode(store) + } else { + return nil + } + } +} + +func findUndefinedGlobal(store Store, last BlockNode, x Expr, t Type) (un Name) { + if x == nil { + return + } + switch cx := x.(type) { + case *NameExpr: + if tv := getGlobalValueRef(last, store, cx.Name); tv != nil { + 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. + return + } + + return cx.Name + case *BasicLitExpr: + return + case *BinaryExpr: + un = findUndefinedGlobal(store, last, cx.Left, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, cx.Right, nil) + if un != "" { + return + } + case *SelectorExpr: + return findUndefinedGlobal(store, last, cx.X, nil) + case *SliceExpr: + un = findUndefinedGlobal(store, last, cx.X, nil) + if un != "" { + return + } + if cx.Low != nil { + un = findUndefinedGlobal(store, last, cx.Low, nil) + if un != "" { + return + } + } + if cx.High != nil { + un = findUndefinedGlobal(store, last, cx.High, nil) + if un != "" { + return + } + } + if cx.Max != nil { + un = findUndefinedGlobal(store, last, cx.Max, nil) + if un != "" { + return + } + } + case *StarExpr: + return findUndefinedGlobal(store, last, cx.X, nil) + case *RefExpr: + return findUndefinedGlobal(store, last, cx.X, nil) + case *TypeAssertExpr: + un = findUndefinedGlobal(store, last, cx.X, nil) + if un != "" { + return + } + return findUndefinedGlobal(store, last, cx.Type, nil) + case *UnaryExpr: + return findUndefinedGlobal(store, last, cx.X, nil) + case *CompositeLitExpr: + var ct Type + if cx.Type == nil { + if t == nil { + panic("cannot elide unknown composite type") + } + ct = t + cx.Type = constType(cx, t) + } else { + un = findUndefinedGlobal(store, last, cx.Type, nil) + if un != "" { + return + } + // preprocess now for eliding purposes. + // TODO recursive preprocessing here is hacky, find a better + // way. This cannot be done asynchronously, cuz undefined + // names ought to be returned immediately to let the caller + // predefine it. + cx.Type = Preprocess(store, last, cx.Type).(Expr) // recursive + ct = evalStaticType(store, last, cx.Type) + // elide composite lit element (nested) composite types. + elideCompositeElements(cx, ct) + } + switch ct.Kind() { + case ArrayKind, SliceKind, MapKind: + for _, kvx := range cx.Elts { + un = findUndefinedGlobal(store, last, kvx.Key, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, kvx.Value, ct.Elem()) + if un != "" { + return + } + } + case StructKind: + for _, kvx := range cx.Elts { + un = findUndefinedGlobal(store, last, kvx.Value, nil) + if un != "" { + return + } + } + default: + panic(fmt.Sprintf( + "unexpected composite lit type %s", + ct.String())) + } + case *FuncLitExpr: + for _, stmt := range cx.Body { + un = findUndefinedStmt(store, cx, stmt, t) + + if un != "" { + return + } + } + return findUndefinedGlobal(store, last, &cx.Type, nil) + case *FieldTypeExpr: + return findUndefinedGlobal(store, last, cx.Type, nil) + case *ArrayTypeExpr: + if cx.Len != nil { + un = findUndefinedGlobal(store, last, cx.Len, nil) + if un != "" { + return + } + } + return findUndefinedGlobal(store, last, cx.Elt, nil) + case *SliceTypeExpr: + return findUndefinedGlobal(store, last, cx.Elt, nil) + case *InterfaceTypeExpr: + for i := range cx.Methods { + un = findUndefinedGlobal(store, last, &cx.Methods[i], nil) + if un != "" { + return + } + } + case *ChanTypeExpr: + return findUndefinedGlobal(store, last, cx.Value, nil) + case *FuncTypeExpr: + for i := range cx.Params { + un = findUndefinedGlobal(store, last, &cx.Params[i], nil) + if un != "" { + return + } + } + for i := range cx.Results { + un = findUndefinedGlobal(store, last, &cx.Results[i], nil) + if un != "" { + return + } + } + case *MapTypeExpr: + un = findUndefinedGlobal(store, last, cx.Key, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, cx.Value, nil) + if un != "" { + return + } + case *StructTypeExpr: + for i := range cx.Fields { + un = findUndefinedGlobal(store, last, &cx.Fields[i], nil) + if un != "" { + return + } + } + case *MaybeNativeTypeExpr: + un = findUndefinedGlobal(store, last, cx.Type, nil) + if un != "" { + return + } + case *CallExpr: + un = findUndefinedGlobal(store, last, cx.Func, nil) + if un != "" { + return + } + for i := range cx.Args { + un = findUndefinedGlobal(store, last, cx.Args[i], nil) + if un != "" { + return + } + } + case *IndexExpr: + un = findUndefinedGlobal(store, last, cx.X, nil) + if un != "" { + return + } + un = findUndefinedGlobal(store, last, cx.Index, nil) + if un != "" { + return + } + case *constTypeExpr: + return + case *ConstExpr: + return + default: + panic(fmt.Sprintf( + "unexpected expr: %v (%v)", + x, reflect.TypeOf(x))) + } + return } -func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { +func findUndefined2(store Store, last BlockNode, x Expr, t Type, skipPredefined bool) (un Name) { if x == nil { return } switch cx := x.(type) { case *NameExpr: - if tv := last.GetValueRef(store, cx.Name, true); tv != nil { + if tv := last.GetValueRef(store, cx.Name, skipPredefined); tv != nil { return } if _, ok := UverseNode().GetLocalIndex(cx.Name); ok { @@ -3801,51 +4283,51 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { case *BasicLitExpr: return case *BinaryExpr: - un = findUndefined(store, last, cx.Left) + un = findUndefined2(store, last, cx.Left, nil, skipPredefined) if un != "" { return } - un = findUndefined(store, last, cx.Right) + un = findUndefined2(store, last, cx.Right, nil, skipPredefined) if un != "" { return } case *SelectorExpr: - return findUndefined(store, last, cx.X) + return findUndefined2(store, last, cx.X, nil, skipPredefined) case *SliceExpr: - un = findUndefined(store, last, cx.X) + un = findUndefined2(store, last, cx.X, nil, skipPredefined) if un != "" { return } if cx.Low != nil { - un = findUndefined(store, last, cx.Low) + un = findUndefined2(store, last, cx.Low, nil, skipPredefined) if un != "" { return } } if cx.High != nil { - un = findUndefined(store, last, cx.High) + un = findUndefined2(store, last, cx.High, nil, skipPredefined) if un != "" { return } } if cx.Max != nil { - un = findUndefined(store, last, cx.Max) + un = findUndefined2(store, last, cx.Max, nil, skipPredefined) if un != "" { return } } case *StarExpr: - return findUndefined(store, last, cx.X) + return findUndefined2(store, last, cx.X, nil, skipPredefined) case *RefExpr: - return findUndefined(store, last, cx.X) + return findUndefined2(store, last, cx.X, nil, skipPredefined) case *TypeAssertExpr: - un = findUndefined(store, last, cx.X) + un = findUndefined2(store, last, cx.X, nil, skipPredefined) if un != "" { return } - return findUndefined(store, last, cx.Type) + return findUndefined2(store, last, cx.Type, nil, skipPredefined) case *UnaryExpr: - return findUndefined(store, last, cx.X) + return findUndefined2(store, last, cx.X, nil, skipPredefined) case *CompositeLitExpr: var ct Type if cx.Type == nil { @@ -3855,7 +4337,7 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { ct = t cx.Type = constType(cx, t) } else { - un = findUndefined(store, last, cx.Type) + un = findUndefined2(store, last, cx.Type, nil, skipPredefined) if un != "" { return } @@ -3872,18 +4354,18 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { switch ct.Kind() { case ArrayKind, SliceKind, MapKind: for _, kvx := range cx.Elts { - un = findUndefined(store, last, kvx.Key) + un = findUndefined2(store, last, kvx.Key, nil, skipPredefined) if un != "" { return } - un = findUndefined2(store, last, kvx.Value, ct.Elem()) + un = findUndefined2(store, last, kvx.Value, ct.Elem(), skipPredefined) if un != "" { return } } case StructKind: for _, kvx := range cx.Elts { - un = findUndefined(store, last, kvx.Value) + un = findUndefined2(store, last, kvx.Value, nil, skipPredefined) if un != "" { return } @@ -3894,43 +4376,53 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { ct.String())) } case *FuncLitExpr: - return findUndefined(store, last, &cx.Type) + if cx.GetAttribute(ATTR_GLOBAL) == true { + for _, stmt := range cx.Body { + un = findUndefinedStmt(store, cx, stmt, t) + + if un != "" { + return + } + } + } + + return findUndefined2(store, last, &cx.Type, nil, skipPredefined) case *FieldTypeExpr: - return findUndefined(store, last, cx.Type) + return findUndefined2(store, last, cx.Type, nil, skipPredefined) case *ArrayTypeExpr: if cx.Len != nil { - un = findUndefined(store, last, cx.Len) + un = findUndefined2(store, last, cx.Len, nil, skipPredefined) if un != "" { return } } - return findUndefined(store, last, cx.Elt) + return findUndefined2(store, last, cx.Elt, nil, skipPredefined) case *SliceTypeExpr: - return findUndefined(store, last, cx.Elt) + return findUndefined2(store, last, cx.Elt, nil, skipPredefined) case *InterfaceTypeExpr: for i := range cx.Methods { - un = findUndefined(store, last, &cx.Methods[i]) + un = findUndefined2(store, last, &cx.Methods[i], nil, skipPredefined) if un != "" { return } } case *ChanTypeExpr: - return findUndefined(store, last, cx.Value) + return findUndefined2(store, last, cx.Value, nil, skipPredefined) case *FuncTypeExpr: for i := range cx.Params { - un = findUndefined(store, last, &cx.Params[i]) + un = findUndefined2(store, last, &cx.Params[i], nil, skipPredefined) if un != "" { return } } for i := range cx.Results { - un = findUndefined(store, last, &cx.Results[i]) + un = findUndefined2(store, last, &cx.Results[i], nil, skipPredefined) if un != "" { return } } case *MapTypeExpr: - un = findUndefined(store, last, cx.Key) + un = findUndefined2(store, last, cx.Key, nil, skipPredefined) if un != "" { return } @@ -3940,33 +4432,34 @@ func findUndefined2(store Store, last BlockNode, x Expr, t Type) (un Name) { } case *StructTypeExpr: for i := range cx.Fields { - un = findUndefined(store, last, &cx.Fields[i]) + un = findUndefined2(store, last, &cx.Fields[i], nil, skipPredefined) if un != "" { return } } case *MaybeNativeTypeExpr: - un = findUndefined(store, last, cx.Type) + un = findUndefined2(store, last, cx.Type, nil, skipPredefined) if un != "" { return } case *CallExpr: - un = findUndefined(store, last, cx.Func) + cx.Func.SetAttribute(ATTR_GLOBAL, cx.GetAttribute(ATTR_GLOBAL)) + un = findUndefined2(store, last, cx.Func, nil, skipPredefined) if un != "" { return } for i := range cx.Args { - un = findUndefined(store, last, cx.Args[i]) + un = findUndefined2(store, last, cx.Args[i], nil, skipPredefined) if un != "" { return } } case *IndexExpr: - un = findUndefined(store, last, cx.X) + un = findUndefined2(store, last, cx.X, nil, skipPredefined) if un != "" { return } - un = findUndefined(store, last, cx.Index) + un = findUndefined2(store, last, cx.Index, nil, skipPredefined) if un != "" { return } @@ -4082,8 +4575,12 @@ func predefineNow2(store Store, last BlockNode, d Decl, stack *[]Name) (Decl, bo if !file.IsInitialized() { panic("all types from files in file-set should have already been predefined") } + + declaration := *decl + declaration.SetAttribute(ATTR_GLOBAL, true) + // predefine dependency (recursive). - *decl, _ = predefineNow2(store, file, *decl, stack) + *decl, _ = predefineNow2(store, file, declaration, stack) } else { break } @@ -4241,6 +4738,7 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { return } for _, vx := range d.Values { + vx.SetAttribute(ATTR_GLOBAL, d.GetAttribute(ATTR_GLOBAL)) un = findUndefined(store, last, vx) if un != "" { return diff --git a/gnovm/tests/files/closure.gno b/gnovm/tests/files/closure.gno new file mode 100644 index 00000000000..ea05c025954 --- /dev/null +++ b/gnovm/tests/files/closure.gno @@ -0,0 +1,16 @@ +package main + +func main() { + +} + +var a = func() { + b() +} + +var b = func() { + a() +} + +// Error: +// main/files/closure.gno:7:5: constant definition loop with a diff --git a/gnovm/tests/files/var27.gno b/gnovm/tests/files/var27.gno new file mode 100644 index 00000000000..65ab9307b9b --- /dev/null +++ b/gnovm/tests/files/var27.gno @@ -0,0 +1,80 @@ +package main + +func main() {} + +var myDep string + +var myVar1 = func() { + a := myDep1 +} + +var myDep1 string + +var myVar2 = func() { + aaa := "" + + switch myDep { + case aaa: + println(myDep2) + } +} + +var myDep2 string + +var myVar3 = func() { + for _, c := range myDep3 { + println(c) + } +} + +var myDep3 string + +var v1 = func() int { + v2 := 11 + return v2 +}() + +var v2 = func() int { + return v1 +}() + +var v3 = func() int { + return func() int { + v4 := 11 + return v4 + }() +}() + +var v4 = func() int { + return v3 +}() + +var v5 = func() int { + v6 := 11 + return func() int { + return v6 + }() +}() + +var v6 = func() int { + return v5 +}() + +var other = func() { + if true { + something := 2 + print(something) // 2 + } else { + print(something) // a string, but single shared 'st' masks the outer/global reference. + } +} +var something = "a string" + +var other1 = func() { + if true { + something1 := 2 + print(something1) // 2 + } + print(something1) // a string, but single shared 'st' masks the outer/global reference. +} +var something1 = "a string" \ No newline at end of file diff --git a/gnovm/tests/files/var28.gno b/gnovm/tests/files/var28.gno new file mode 100644 index 00000000000..279f60168a4 --- /dev/null +++ b/gnovm/tests/files/var28.gno @@ -0,0 +1,18 @@ +package main + +func main() {} + +var foo = func() (bool, bool) { + return true, true +} + +var x = func() bool { return a }() +var a, b = foo() + +var a1 = func() int { + type B1 b1 + x := B1(1) + return int(x) +} + +type b1 int \ No newline at end of file