Repository: goplus/gop
Pull request #1053 (2021-12-11 03:52:38)

Release v1.0.36
changes:
- fix: link gop build assets now work on Windows
- fix: reference gop command from gopBinFiles
- support fmt.print => print
- compileIdent: print/println as func (#1048)
- disable pkg.var/const; disable auto property of pkg.println



Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 5052546c..ea06a35a 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -21,6 +21,9 @@ jobs:
       with:
         go-version: ${{ matrix.go-version }}
 
+    - name: Test Go+ installer
+      run: go test -v cmd/make_test.go
+
     - name: Compile gop and related tools
       run: go install ./...
 
diff --git a/README.md b/README.md
index 5fe28f26..d7afc11c 100644
--- a/README.md
+++ b/README.md
@@ -87,10 +87,10 @@ It's for Go, and it's also for Go+.
 
 ## Playground
 
-Go+ Playground based on Docker (recommended):
+Go+ Playground based on Docker:
 * https://play.goplus.org/
 
-Go+ Playground based on GopherJS (currently only available in v0.7.x):
+Go+ Playground based on GopherJS:
 * https://jsplay.goplus.org/
 
 
diff --git a/cl/expr.go b/cl/expr.go
index 67551bce..342afe8e 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -114,10 +114,10 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) *gox.PkgRef {
 	}
 
 	// universe object
-	if (flags&clIdentAllowBuiltin) == 0 && isBuiltin(o) {
-		panic(ctx.newCodeErrorf(ident.Pos(), "use of builtin %s not in function call", name))
-	}
 	if obj := ctx.pkg.Builtin().TryRef(name); obj != nil {
+		if (flags&clIdentAllowBuiltin) == 0 && isBuiltin(o) && !strings.HasPrefix(o.Name(), "print") {
+			panic(ctx.newCodeErrorf(ident.Pos(), "use of builtin %s not in function call", name))
+		}
 		o = obj
 	} else if o == nil {
 		if (clIdentGoto & flags) != 0 {
@@ -144,24 +144,16 @@ func isBuiltin(o types.Object) bool {
 }
 
 func compileMember(ctx *blockCtx, v ast.Node, name string, flags int) error {
-	cb := ctx.cb
-	lhs := (flags & clIdentLHS) != 0
-	kind, err := cb.Member(name, lhs, v)
-	if kind != 0 {
-		return nil
-	}
-	if c := name[0]; c >= 'a' && c <= 'z' {
-		name = string(rune(c)+('A'-'a')) + name[1:]
-		switch kind, _ = cb.Member(name, lhs, v); kind {
-		case gox.MemberMethod:
-			if (flags & clIdentAutoCall) != 0 {
-				cb.Call(0)
-			}
-			return nil
-		case gox.MemberField:
-			return nil
-		}
+	var mflag gox.MemberFlag
+	switch {
+	case (flags & clIdentLHS) != 0:
+		mflag = gox.MemberFlagRef
+	case (flags & clIdentAutoCall) != 0:
+		mflag = gox.MemberFlagAutoProperty
+	default:
+		mflag = gox.MemberFlagMethodAlias
 	}
+	_, err := ctx.cb.Member(name, mflag, v)
 	return err
 }
 
@@ -330,39 +322,46 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, flags int) {
 	}
 }
 
-func pkgRef(at *gox.PkgRef, name string) (o types.Object, canAutoCall bool) {
+func pkgRef(at *gox.PkgRef, name string) (o types.Object, alias bool) {
 	if c := name[0]; c >= 'a' && c <= 'z' {
 		name = string(rune(c)+('A'-'a')) + name[1:]
-		return at.TryRef(name), true
+		if v := at.TryRef(name); v != nil && gox.IsFunc(v.Type()) {
+			return v, true
+		}
+		return
 	}
 	return at.TryRef(name), false
 }
 
-func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident) (o types.Object, canAutoCall bool) {
+func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident) (o types.Object, alias bool) {
 	if pkg != nil {
 		return pkgRef(pkg, x.Name)
 	}
 	for _, at := range ctx.lookups {
-		if o2, canAutoCall2 := pkgRef(at, x.Name); o2 != nil {
+		if o2, alias2 := pkgRef(at, x.Name); o2 != nil {
 			if o != nil {
 				panic(ctx.newCodeErrorf(
 					x.Pos(), "confliction: %s declared both in \"%s\" and \"%s\"",
 					x.Name, at.Types.Path(), pkg.Types.Path()))
 			}
-			pkg, o, canAutoCall = at, o2, canAutoCall2
+			pkg, o, alias = at, o2, alias2
 		}
 	}
 	return
 }
 
 func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags int) bool {
-	if v, canAutoCall := lookupPkgRef(ctx, at, x); v != nil {
+	if v, alias := lookupPkgRef(ctx, at, x); v != nil {
 		cb := ctx.cb
 		if (flags & clIdentLHS) != 0 {
 			cb.VarRef(v, x)
 		} else {
+			autoprop := alias && (flags&clIdentAutoCall) != 0
+			if autoprop && !gox.HasAutoProperty(v.Type()) {
+				return false
+			}
 			cb.Val(v, x)
-			if canAutoCall && (flags&clIdentAutoCall) != 0 && isFunc(v.Type()) {
+			if autoprop {
 				cb.Call(0)
 			}
 		}
@@ -371,14 +370,6 @@ func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags int) bool
 	return false
 }
 
-func isFunc(typ types.Type) bool {
-	switch typ.(type) {
-	case *types.Signature:
-		return true
-	}
-	return false
-}
-
 type fnType struct {
 	params   *types.Tuple
 	n1       int
diff --git a/cl/internal/spx/game.go b/cl/internal/spx/game.go
index f2f8c356..9162dea9 100644
--- a/cl/internal/spx/game.go
+++ b/cl/internal/spx/game.go
@@ -52,6 +52,14 @@ func Sched() {
 func SchedNow() {
 }
 
+func Rand__0(int) int {
+	return 0
+}
+
+func Rand__1(float64) float64 {
+	return 0
+}
+
 var (
 	TestIntValue int
 )
diff --git a/cl/stmt.go b/cl/stmt.go
index 6381083d..d8bc695a 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -86,7 +86,7 @@ func compileStmt(ctx *blockCtx, stmt ast.Stmt) {
 			ctx.cb.Call(0)
 		} else {
 			compileExpr(ctx, v.X)
-			if canAutoCall(v.X) && isFunc(ctx.cb.InternalStack().Get(-1).Type) {
+			if canAutoCall(v.X) && gox.IsFunc(ctx.cb.InternalStack().Get(-1).Type) {
 				ctx.cb.Call(0)
 			}
 		}
diff --git a/cmd/make.go b/cmd/make.go
index d761eef7..7d8506d3 100644
--- a/cmd/make.go
+++ b/cmd/make.go
@@ -27,6 +27,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"runtime"
 	"strings"
 	"time"
 )
@@ -73,8 +74,23 @@ func getGopRoot() string {
 var gopRoot = getGopRoot()
 var initCommandExecuteEnv = os.Environ()
 var commandExecuteEnv = initCommandExecuteEnv
+
+// Always put `gop` command as the first item, as it will be referenced by below code.
 var gopBinFiles = []string{"gop", "gopfmt"}
 
+const (
+	inWindows = (runtime.GOOS == "windows")
+)
+
+func init() {
+	if inWindows {
+		for index, file := range gopBinFiles {
+			file += ".exe"
+			gopBinFiles[index] = file
+		}
+	}
+}
+
 func execCommand(command string, arg ...string) (string, string, error) {
 	var stdout, stderr bytes.Buffer
 	cmd := exec.Command(command, arg...)
@@ -217,6 +233,9 @@ func buildGoplusTools(useGoProxy bool) {
 	}
 	print(buildOutput)
 
+	// Clear gop run cache
+	cleanGopRunCache()
+
 	installPath := linkGoplusToLocalBin()
 
 	println("\nGo+ tools installed successfully!")
@@ -241,7 +260,7 @@ func runTestcases() {
 	os.Chdir(gopRoot)
 
 	coverage := "-coverprofile=coverage.txt"
-	gopCommand := filepath.Join(detectGopBinPath(), "gop")
+	gopCommand := filepath.Join(detectGopBinPath(), gopBinFiles[0])
 	if !checkPathExist(gopCommand, false) {
 		println("Error: Go+ must be installed before running testcases.")
 		os.Exit(1)
@@ -277,6 +296,22 @@ func clean() {
 			log.Fatalln(err)
 		}
 	}
+
+	cleanGopRunCache()
+}
+
+func cleanGopRunCache() {
+	homeDir, _ := os.UserHomeDir()
+	runCacheDir := filepath.Join(homeDir, ".gop", "run")
+	files := []string{"go.mod", "go.sum"}
+	for _, file := range files {
+		fullPath := filepath.Join(runCacheDir, file)
+		if checkPathExist(fullPath, false) {
+			if err := os.Remove(fullPath); err != nil {
+				log.Fatalln(err)
+			}
+		}
+	}
 }
 
 func uninstall() {
diff --git a/go.mod b/go.mod
index 1b56ba14..65b1c556 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/goplus/gop
 go 1.16
 
 require (
-	github.com/goplus/gox v1.8.1
+	github.com/goplus/gox v1.8.7
 	github.com/qiniu/x v1.11.5
 	golang.org/x/mod v0.5.1
 	golang.org/x/tools v0.1.8
diff --git a/go.sum b/go.sum
index d0e06736..e18552a7 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/goplus/gox v1.8.1 h1:nMn6J3Jv6zNHr6Ul2GkUqhLCm6m0tWt8jGpI15iSaEw=
-github.com/goplus/gox v1.8.1/go.mod h1:XWqV75HImeysI8+Q9gAdYyvwlP8jds24xsgYRwTO3JI=
+github.com/goplus/gox v1.8.7 h1:XtUEazI+EMSyN9A0IGbrPe99dpT5lnD1k98HwmVL9Nc=
+github.com/goplus/gox v1.8.7/go.mod h1:XWqV75HImeysI8+Q9gAdYyvwlP8jds24xsgYRwTO3JI=
 github.com/qiniu/x v1.11.5 h1:TYr5cl4g2yoHAZeDK4MTjKF6CMoG+IHlCDvvM5qym6U=
 github.com/qiniu/x v1.11.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
 github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
diff --git a/x/format/format.go b/x/format/format.go
index 112489ef..76212821 100644
--- a/x/format/format.go
+++ b/x/format/format.go
@@ -24,24 +24,26 @@ import (
 
 // -----------------------------------------------------------------------------
 
-var printFuncs = map[string]string{
-	"Errorf":   "errorf",
-	"Print":    "print",
-	"Printf":   "printf",
-	"Println":  "println",
-	"Fprint":   "fprint",
-	"Fprintf":  "fprintf",
-	"Fprintln": "fprintln",
-	"Sprint":   "sprint",
-	"Sprintf":  "sprintf",
-	"Sprintln": "sprintln",
+var printFuncs = [][2]string{
+	{"Errorf", "errorf"},
+	{"Print", "print"},
+	{"Printf", "printf"},
+	{"Println", "println"},
+	{"Fprint", "fprint"},
+	{"Fprintf", "fprintf"},
+	{"Fprintln", "fprintln"},
+	{"Sprint", "sprint"},
+	{"Sprintf", "sprintf"},
+	{"Sprintln", "sprintln"},
 }
 
 func fmtToBuiltin(ctx *importCtx, sel *ast.Ident, ref *ast.Expr) bool {
 	if ctx.pkgPath == "fmt" {
-		if v, ok := printFuncs[sel.Name]; ok {
-			*ref = &ast.Ident{NamePos: sel.NamePos, Name: v}
-			return true
+		for _, fns := range printFuncs {
+			if fns[0] == sel.Name || fns[1] == sel.Name {
+				*ref = &ast.Ident{NamePos: sel.NamePos, Name: fns[1]}
+				return true
+			}
 		}
 	}
 	return false


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 8025f6f0..045c08e6 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -57,12 +57,6 @@ func TestGetTypeName(t *testing.T) {
 	getTypeName(types.NewSlice(types.Typ[types.Int]))
 }
 
-func TestIsFunc(t *testing.T) {
-	if isFunc(nil) {
-		t.Fatal("TestIsFunc failed")
-	}
-}
-
 func TestHandleRecover(t *testing.T) {
 	var ctx pkgCtx
 	ctx.handleRecover("hello")
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 04b20236..032ea2f0 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -203,7 +203,7 @@ func TestSpxMethod(t *testing.T) {
 func onInit() {
 	sched
 	broadcast "msg1"
-	testIntValue = 1
+	TestIntValue = 1
 	x := round(1.2)
 }
 `, `
@@ -301,6 +301,8 @@ var (
 	t   Sound
 )
 
+var x float64 = rand(1.2)
+
 run "hzip://open.qiniu.us/weather/res.zip"
 `, `
 println "Hi"
@@ -311,6 +313,8 @@ import (
 	spx "github.com/goplus/gop/cl/internal/spx"
 )
 
+var x float64 = spx.Rand__1(1.2)
+
 type index struct {
 	*spx.MyGame
 	Kai Kai
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 4fe15af4..d2b7e460 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -148,6 +148,21 @@ func main() {
 `)
 }
 
+func TestAssignPrintln(t *testing.T) {
+	gopClTest(t, `
+p := println
+p "Hello world"
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	p := fmt.Println
+	p("Hello world")
+}
+`)
+}
+
 func TestRedefineBuiltin(t *testing.T) {
 	gopClTest(t, `
 func main() {
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 86138ac3..4906a553 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -171,8 +171,8 @@ var a = 1, 2
 }
 `)
 	codeErrorTest(t,
-		"./bar.gop:2:9: use of builtin len not in function call", `func main() {
-println(len.x)
+		"./bar.gop:2:10: use of builtin len not in function call", `func main() {
+	println(len.x)
 }
 `)
 	codeErrorTest(t,
@@ -215,7 +215,7 @@ func foo(v map[int]bar) {
 }
 `)
 	codeErrorTest(t,
-		"./bar.gop:2:2: use of builtin println not in function call", `func main() {
+		"./bar.gop:2:2: println is not a variable", `func main() {
 	println = "hello"
 }
 `)
@@ -798,3 +798,11 @@ switch n.(type) {
 }
 `)
 }
+
+func TestErrAutoProperty(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:4:11: cannot refer to unexported name fmt.println`, `
+import "fmt"
+
+n, err := fmt.println
+`)
+}
diff --git a/cmd/make_test.go b/cmd/make_test.go
new file mode 100644
index 00000000..b63bb311
--- /dev/null
+++ b/cmd/make_test.go
@@ -0,0 +1,104 @@
+//go:build ignore
+// +build ignore
+
+package make_test
+
+import (
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"testing"
+)
+
+const (
+	inWindows = (runtime.GOOS == "windows")
+)
+
+var script = "all.bash"
+var gopRoot = ""
+var gopBinFiles = []string{"gop", "gopfmt"}
+
+func checkPathExist(path string, isDir bool) bool {
+	stat, err := os.Stat(path)
+	isExists := !os.IsNotExist(err)
+	if isDir {
+		return isExists && stat.IsDir()
+	}
+	return isExists && !stat.IsDir()
+}
+
+func detectGoBinPath() string {
+	goBin, ok := os.LookupEnv("GOBIN")
+	if ok {
+		return goBin
+	}
+
+	goPath, ok := os.LookupEnv("GOPATH")
+	if ok {
+		list := filepath.SplitList(goPath)
+		if len(list) > 0 {
+			// Put in first directory of $GOPATH.
+			return filepath.Join(list[0], "bin")
+		}
+	}
+
+	homeDir, _ := os.UserHomeDir()
+	return filepath.Join(homeDir, "go", "bin")
+}
+
+func init() {
+	pwd, _ := os.Getwd()
+	gopRoot = filepath.Join(pwd, "..")
+	if inWindows {
+		script = "all.bat"
+		for index, file := range gopBinFiles {
+			file += ".exe"
+			gopBinFiles[index] = file
+		}
+	}
+}
+
+func TestAllScript(t *testing.T) {
+	os.Chdir(gopRoot)
+	cmd := exec.Command(filepath.Join(gopRoot, script))
+	if output, err := cmd.CombinedOutput(); err != nil {
+		t.Fatalf("Failed: %v:\nOut: %s\n", err, output)
+	}
+
+	goBinPath := detectGoBinPath()
+
+	for _, file := range gopBinFiles {
+		if !checkPathExist(filepath.Join(gopRoot, "bin", file), false) {
+			t.Fatalf("Failed: %s not found in ./bin directory\n", file)
+		}
+		if !checkPathExist(filepath.Join(goBinPath, file), false) {
+			t.Fatalf("Failed: %s not found in %s/bin directory\n", file, goBinPath)
+		}
+	}
+
+	cmd = exec.Command(filepath.Join(gopRoot, "bin", gopBinFiles[0]), "version")
+	if output, err := cmd.CombinedOutput(); err != nil {
+		t.Fatalf("Failed: %v:\nOut: %s\n", err, output)
+	}
+}
+
+func TestHandleMultiFlags(t *testing.T) {
+	os.Chdir(gopRoot)
+	cmd := exec.Command("go", "run", filepath.Join(gopRoot, "cmd/make.go"), "--install", "--test", "--uninstall")
+	if output, err := cmd.CombinedOutput(); err != nil {
+		t.Fatalf("Failed: %v:\nOut: %s\n", err, output)
+	}
+
+	goBinPath := detectGoBinPath()
+
+	// Uninstall will be the last action to run, test if all build assets being cleared.
+	for _, file := range gopBinFiles {
+		if checkPathExist(filepath.Join(gopRoot, "bin", file), false) {
+			t.Fatalf("Failed: %s found in ./bin directory\n", file)
+		}
+		if checkPathExist(filepath.Join(goBinPath, file), false) {
+			t.Fatalf("Failed: %s found in %s/bin directory\n", file, goBinPath)
+		}
+	}
+}
diff --git a/x/format/gopstyle_test.go b/x/format/gopstyle_test.go
index 5b1e8159..5492477e 100644
--- a/x/format/gopstyle_test.go
+++ b/x/format/gopstyle_test.go
@@ -170,7 +170,7 @@ import (
 
 func f() {
 	fmt.Errorf("%w", errors.New("hello"))
-	fmt.Println("hello")
+	fmt.println "hello"
 }
 `, `import (
 	"errors"
@@ -191,7 +191,7 @@ import "fmt"
 
 func f() {
 	_ = fmt.Errorf("%w", errors.New("hello"))
-	fmt.Println("hello")
+	fmt.println("hello")
 }
 `, `import "errors"