Browse Source

loader: make sure Go version is plumbed through

This fixes the new loop variable behavior in Go 1.22.

Specifically:
  * The compiler (actually, the x/tools/go/ssa package) now correctly
    picks up the Go version.
  * If a module doesn't specify the Go version, the current Go version
    (from the `go` tool and standard library) is used. This fixes
    `go run`.
  * The tests in testdata/ that use a separate directory are now
    actually run in that directory. This makes it possible to use a
    go.mod file there.
  * There is a test to make sure old Go modules still work with the old
    Go behavior, even on a newer Go version.
pull/4100/head
Ayke van Laethem 10 months ago
committed by Ron Evans
parent
commit
57f49af726
  1. 2
      compileopts/options.go
  2. 14
      loader/loader.go
  3. 17
      loader/loader_go122.go
  4. 8
      main_test.go
  5. 3
      testdata/go1.22/go.mod
  6. 10
      testdata/go1.22/main.go
  7. 2
      testdata/go1.22/out.txt
  8. 5
      testdata/oldgo/go.mod
  9. 26
      testdata/oldgo/main.go
  10. 1
      testdata/oldgo/out.txt

2
compileopts/options.go

@ -23,6 +23,7 @@ type Options struct {
GOOS string // environment variable GOOS string // environment variable
GOARCH string // environment variable GOARCH string // environment variable
GOARM string // environment variable (only used with GOARCH=arm) GOARM string // environment variable (only used with GOARCH=arm)
Directory string // working dir, leave it unset to use the current working dir
Target string Target string
Opt string Opt string
GC string GC string
@ -48,7 +49,6 @@ type Options struct {
Programmer string Programmer string
OpenOCDCommands []string OpenOCDCommands []string
LLVMFeatures string LLVMFeatures string
Directory string
PrintJSON bool PrintJSON bool
Monitor bool Monitor bool
BaudRate int BaudRate int

14
loader/loader.go

@ -27,6 +27,8 @@ import (
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
) )
var initFileVersions = func(info *types.Info) {}
// Program holds all packages and some metadata about the program as a whole. // Program holds all packages and some metadata about the program as a whole.
type Program struct { type Program struct {
config *compileopts.Config config *compileopts.Config
@ -383,7 +385,19 @@ func (p *Package) Check() error {
// errors out on language features not supported in that particular // errors out on language features not supported in that particular
// version. // version.
checker.GoVersion = "go" + p.Module.GoVersion checker.GoVersion = "go" + p.Module.GoVersion
} else {
// Version is not known, so use the currently installed Go version.
// This is needed for `tinygo run` for example.
// Normally we'd use goenv.GorootVersionString(), but for compatibility
// with Go 1.20 and below we need a version in the form of "go1.12" (no
// patch version).
major, minor, err := goenv.GetGorootVersion()
if err != nil {
return err
}
checker.GoVersion = fmt.Sprintf("go%d.%d", major, minor)
} }
initFileVersions(&p.info)
// Do typechecking of the package. // Do typechecking of the package.
packageName := p.ImportPath packageName := p.ImportPath

17
loader/loader_go122.go

@ -0,0 +1,17 @@
//go:build go1.22
// types.Info.FileVersions was added in Go 1.22, so we can only initialize it
// when built with Go 1.22.
package loader
import (
"go/ast"
"go/types"
)
func init() {
initFileVersions = func(info *types.Info) {
info.FileVersions = make(map[*ast.File]string)
}
}

8
main_test.go

@ -69,6 +69,7 @@ func TestBuild(t *testing.T) {
"json.go", "json.go",
"map.go", "map.go",
"math.go", "math.go",
"oldgo/",
"print.go", "print.go",
"reflect.go", "reflect.go",
"slice.go", "slice.go",
@ -91,7 +92,7 @@ func TestBuild(t *testing.T) {
tests = append(tests, "go1.21.go") tests = append(tests, "go1.21.go")
} }
if minor >= 22 { if minor >= 22 {
tests = append(tests, "go1.22.go") tests = append(tests, "go1.22/")
} }
if *testTarget != "" { if *testTarget != "" {
@ -336,8 +337,11 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
path := TESTDATA + "/" + name path := TESTDATA + "/" + name
// Get the expected output for this test. // Get the expected output for this test.
txtpath := path[:len(path)-3] + ".txt" txtpath := path[:len(path)-3] + ".txt"
pkgName := "./" + path
if path[len(path)-1] == '/' { if path[len(path)-1] == '/' {
txtpath = path + "out.txt" txtpath = path + "out.txt"
options.Directory = path
pkgName = "."
} }
expected, err := os.ReadFile(txtpath) expected, err := os.ReadFile(txtpath)
if err != nil { if err != nil {
@ -351,7 +355,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
// Build the test binary. // Build the test binary.
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
_, err = buildAndRun("./"+path, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { _, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
return cmd.Run() return cmd.Run()
}) })
if err != nil { if err != nil {

3
testdata/go1.22/go.mod

@ -0,0 +1,3 @@
module github.com/tinygo-org/tinygo/testdata/go1.22
go 1.22

10
testdata/go1.22.go → testdata/go1.22/main.go

@ -19,15 +19,13 @@ func testLoopVar() {
f = func() int { return i } f = func() int { return i }
} }
} }
// Prints 1 in Go 1.21, or 0 in Go 1.22. // Variable n is 1 in Go 1.21, or 0 in Go 1.22.
// TODO: this still prints Go 1.21 even in Go 1.22. We probably need to
// specify the Go version somewhere.
n := f() n := f()
if n == 0 { if n == 0 {
println("behaves like Go 1.22") println("loops behave like Go 1.22")
} else if n == 1 { } else if n == 1 {
println("behaves like Go 1.21") println("loops behave like Go 1.21")
} else { } else {
println("unknown behavior") println("unknown loop behavior")
} }
} }

2
testdata/go1.22.txt → testdata/go1.22/out.txt

@ -9,4 +9,4 @@
2 2
1 1
go1.22 has lift-off! go1.22 has lift-off!
behaves like Go 1.21 loops behave like Go 1.22

5
testdata/oldgo/go.mod

@ -0,0 +1,5 @@
module github.com/tinygo-org/tinygo/testdata/oldgo
// Go version doesn't matter much, as long as it's old.
go 1.15

26
testdata/oldgo/main.go

@ -0,0 +1,26 @@
package main
// This package verifies that the Go language version is correctly picked up
// from the go.mod file.
func main() {
testLoopVar()
}
func testLoopVar() {
var f func() int
for i := 0; i < 1; i++ {
if i == 0 {
f = func() int { return i }
}
}
// Variable n is 1 in Go 1.21, or 0 in Go 1.22.
n := f()
if n == 0 {
println("loops behave like Go 1.22")
} else if n == 1 {
println("loops behave like Go 1.21")
} else {
println("unknown loop behavior")
}
}

1
testdata/oldgo/out.txt

@ -0,0 +1 @@
loops behave like Go 1.21
Loading…
Cancel
Save