playground: recognize // GOEXPERIMENT=foo lines at top of source file
We sometimes add features we want people to play with to the Go toolchain
using GOEXPERIMENT flags. Provide access to these on the playground by
looking for // GOEXPERIMENT=foo lines at the top of source files.
In particular, this will let people add // GOEXPERIMENT=loopvar and get
the loop variable semantics.
Change-Id: I4335ab72f2bf417148d18508f2233597d745e847
Reviewed-on: https://go-review.googlesource.com/c/playground/+/511640
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Russ Cox <rsc@golang.org>
diff --git a/sandbox.go b/sandbox.go
index 346b47b..4f3ed9d 100644
--- a/sandbox.go
+++ b/sandbox.go
@@ -171,6 +171,39 @@
return fmt.Sprintf("%s-%s-%x", prefix, runtime.Version(), h.Sum(nil))
}
+// experiments returns the experiments listed in // GOEXPERIMENT=xxx comments
+// at the top of src.
+func experiments(src string) []string {
+ var exp []string
+ for src != "" {
+ line := src
+ src = ""
+ if i := strings.Index(line, "\n"); i >= 0 {
+ line, src = line[:i], line[i+1:]
+ }
+ line = strings.TrimSpace(line)
+ if line == "" {
+ continue
+ }
+ if !strings.HasPrefix(line, "//") {
+ break
+ }
+ line = strings.TrimSpace(strings.TrimPrefix(line, "//"))
+ if !strings.HasPrefix(line, "GOEXPERIMENT") {
+ continue
+ }
+ line = strings.TrimSpace(strings.TrimPrefix(line, "GOEXPERIMENT"))
+ if !strings.HasPrefix(line, "=") {
+ continue
+ }
+ line = strings.TrimSpace(strings.TrimPrefix(line, "="))
+ if line != "" {
+ exp = append(exp, line)
+ }
+ }
+ return exp
+}
+
// isTestFunc tells whether fn has the type of a testing, or fuzz function, or a TestMain func.
func isTestFunc(fn *ast.FuncDecl) bool {
if fn.Type.Results != nil && len(fn.Type.Results.List) > 0 ||
@@ -371,6 +404,7 @@
files.AddFile("go.mod", []byte("module play\n"))
}
+ var exp []string
for f, src := range files.m {
// Before multi-file support we required that the
// program be in package main, so continue to do that
@@ -382,6 +416,7 @@
if err == nil && f.Name.Name != "main" {
return &buildResult{errorMessage: "package name must be main"}, nil
}
+ exp = append(exp, experiments(string(src))...)
}
in := filepath.Join(tmpDir, f)
@@ -421,6 +456,7 @@
cmd.Env = []string{"GOOS=linux", "GOARCH=amd64", "GOROOT=/usr/local/go-faketime"}
cmd.Env = append(cmd.Env, "GOCACHE="+goCache)
cmd.Env = append(cmd.Env, "CGO_ENABLED=0")
+ cmd.Env = append(cmd.Env, "GOEXPERIMENT="+strings.Join(exp, ","))
// Create a GOPATH just for modules to be downloaded
// into GOPATH/pkg/mod.
cmd.Args = append(cmd.Args, "-modcacherw")
diff --git a/sandbox_test.go b/sandbox_test.go
index de390bf..6130936 100644
--- a/sandbox_test.go
+++ b/sandbox_test.go
@@ -14,6 +14,28 @@
"testing"
)
+// TestExperiments tests that experiment lines are recognized.
+func TestExperiments(t *testing.T) {
+ var tests = []struct {
+ src string
+ exp []string
+ }{
+ {"//GOEXPERIMENT=active\n\npackage main", []string{"active"}},
+ {" // GOEXPERIMENT= active \n\npackage main", []string{"active"}},
+ {" // GOEXPERIMENT= active \n\npackage main", []string{"active"}},
+ {" // GOEXPERIMENT = active \n\npackage main", []string{"active"}},
+ {"//GOEXPERIMENT=foo\n\n// GOEXPERIMENT=bar\n\npackage main", []string{"foo", "bar"}},
+ {"/* hello world */\n// GOEXPERIMENT=ignored\n", nil},
+ {"package main\n// GOEXPERIMENT=ignored\n", nil},
+ }
+
+ for _, tt := range tests {
+ if exp := experiments(tt.src); !reflect.DeepEqual(exp, tt.exp) {
+ t.Errorf("experiments(%q) = %q, want %q", tt.src, exp, tt.exp)
+ }
+ }
+}
+
// TestIsTest verifies that the isTest helper function matches
// exactly (and only) the names of functions recognized as tests.
func TestIsTest(t *testing.T) {