From 2e9f0817f070a3979afc580c740670690acab672 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 24 Oct 2018 15:49:32 -0700 Subject: [PATCH] cmd/compile: add -lang flag to specify language version The default language version is the current one. For testing purposes, added a check that type aliases require version go1.9. There is no consistent support for changes made before 1.12. Updates #28221 Change-Id: Ia1ef63fff911d5fd29ef79d5fa4e20cfd945feb7 Reviewed-on: https://go-review.googlesource.com/c/144340 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Robert Griesemer --- src/cmd/compile/doc.go | 3 ++ src/cmd/compile/internal/gc/lang_test.go | 59 ++++++++++++++++++++ src/cmd/compile/internal/gc/main.go | 68 ++++++++++++++++++++++++ src/cmd/compile/internal/gc/noder.go | 7 ++- src/go/build/build.go | 2 + 5 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 src/cmd/compile/internal/gc/lang_test.go diff --git a/src/cmd/compile/doc.go b/src/cmd/compile/doc.go index bce03fc40fe224..e2a19d98c0be51 100644 --- a/src/cmd/compile/doc.go +++ b/src/cmd/compile/doc.go @@ -64,6 +64,9 @@ Flags: instead of $GOROOT/pkg/$GOOS_$GOARCH. -l Disable inlining. + -lang version + Set language version to compile, as in -lang=go1.12. + Default is current version. -largemodel Generate code that assumes a large memory model. -linkobj file diff --git a/src/cmd/compile/internal/gc/lang_test.go b/src/cmd/compile/internal/gc/lang_test.go new file mode 100644 index 00000000000000..b225f03a1de1f2 --- /dev/null +++ b/src/cmd/compile/internal/gc/lang_test.go @@ -0,0 +1,59 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gc + +import ( + "internal/testenv" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "testing" +) + +const aliasSrc = ` +package x + +type T = int +` + +func TestInvalidLang(t *testing.T) { + t.Parallel() + + testenv.MustHaveGoBuild(t) + + dir, err := ioutil.TempDir("", "TestInvalidLang") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + + src := filepath.Join(dir, "alias.go") + if err := ioutil.WriteFile(src, []byte(aliasSrc), 0644); err != nil { + t.Fatal(err) + } + + outfile := filepath.Join(dir, "alias.o") + + if testLang(t, "go9.99", src, outfile) == nil { + t.Error("compilation with -lang=go9.99 succeeded unexpectedly") + } + + if testLang(t, "go1.8", src, outfile) == nil { + t.Error("compilation with -lang=go1.8 succeeded unexpectedly") + } + + if err := testLang(t, "go1.9", src, outfile); err != nil { + t.Errorf("compilation with -lang=go1.9 failed unexpectedly: %v", err) + } +} + +func testLang(t *testing.T, lang, src, outfile string) error { + run := []string{testenv.GoToolPath(t), "tool", "compile", "-lang", lang, "-o", outfile, src} + t.Log(run) + out, err := exec.Command(run[0], run[1:]...).CombinedOutput() + t.Logf("%s", out) + return err +} diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 339e8e08cda175..059bf5d1fceecc 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -19,11 +19,13 @@ import ( "cmd/internal/sys" "flag" "fmt" + "go/build" "io" "io/ioutil" "log" "os" "path" + "regexp" "runtime" "strconv" "strings" @@ -211,6 +213,7 @@ func Main(archInit func(*Arch)) { flag.StringVar(&flag_installsuffix, "installsuffix", "", "set pkg directory `suffix`") objabi.Flagcount("j", "debug runtime-initialized variables", &Debug['j']) objabi.Flagcount("l", "disable inlining", &Debug['l']) + flag.StringVar(&flag_lang, "lang", defaultLang(), "release to compile for") flag.StringVar(&linkobj, "linkobj", "", "write linker-specific object to `file`") objabi.Flagcount("live", "debug liveness analysis", &debuglive) objabi.Flagcount("m", "print optimization decisions", &Debug['m']) @@ -277,6 +280,8 @@ func Main(archInit func(*Arch)) { Exit(2) } + checkLang() + thearch.LinkArch.Init(Ctxt) if outfile == "" { @@ -1304,3 +1309,66 @@ func recordFlags(flags ...string) { Ctxt.Data = append(Ctxt.Data, s) s.P = cmd.Bytes()[1:] } + +// flag_lang is the language version we are compiling for, set by the -lang flag. +var flag_lang string + +// defaultLang returns the default value for the -lang flag. +func defaultLang() string { + tags := build.Default.ReleaseTags + return tags[len(tags)-1] +} + +// goVersionRE is a regular expression that matches the valid +// arguments to the -lang flag. +var goVersionRE = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`) + +// A lang is a language version broken into major and minor numbers. +type lang struct { + major, minor int +} + +// langWant is the desired language version set by the -lang flag. +var langWant lang + +// langSupported reports whether language version major.minor is supported. +func langSupported(major, minor int) bool { + return langWant.major > major || (langWant.major == major && langWant.minor >= minor) +} + +// checkLang verifies that the -lang flag holds a valid value, and +// exits if not. It initializes data used by langSupported. +func checkLang() { + var err error + langWant, err = parseLang(flag_lang) + if err != nil { + log.Fatalf("invalid value %q for -lang: %v", flag_lang, err) + } + + if def := defaultLang(); flag_lang != def { + defVers, err := parseLang(def) + if err != nil { + log.Fatalf("internal error parsing default lang %q: %v", def, err) + } + if langWant.major > defVers.major || (langWant.major == defVers.major && langWant.major > defVers.minor) { + log.Fatalf("invalid value %q for -lang: max known version is %q", flag_lang, def) + } + } +} + +// parseLang parses a -lang option into a langVer. +func parseLang(s string) (lang, error) { + matches := goVersionRE.FindStringSubmatch(s) + if matches == nil { + return lang{}, fmt.Errorf(`should be something like "go1.12"`) + } + major, err := strconv.Atoi(matches[1]) + if err != nil { + return lang{}, err + } + minor, err := strconv.Atoi(matches[2]) + if err != nil { + return lang{}, err + } + return lang{major: major, minor: minor}, nil +} diff --git a/src/cmd/compile/internal/gc/noder.go b/src/cmd/compile/internal/gc/noder.go index ca65c7ccca03fd..8964536ff05d8e 100644 --- a/src/cmd/compile/internal/gc/noder.go +++ b/src/cmd/compile/internal/gc/noder.go @@ -417,8 +417,11 @@ func (p *noder) typeDecl(decl *syntax.TypeDecl) *Node { param.Pragma = 0 } - return p.nod(decl, ODCLTYPE, n, nil) - + nod := p.nod(decl, ODCLTYPE, n, nil) + if param.Alias && !langSupported(1, 9) { + yyerrorl(nod.Pos, "type aliases only supported as of -lang=go1.9") + } + return nod } func (p *noder) declNames(names []*syntax.Name) []*Node { diff --git a/src/go/build/build.go b/src/go/build/build.go index fc8d37789f8ee6..015551d0085228 100644 --- a/src/go/build/build.go +++ b/src/go/build/build.go @@ -45,6 +45,7 @@ type Context struct { // which defaults to the list of Go releases the current release is compatible with. // In addition to the BuildTags and ReleaseTags, build constraints // consider the values of GOARCH and GOOS as satisfied tags. + // The last element in ReleaseTags is assumed to be the current release. BuildTags []string ReleaseTags []string @@ -296,6 +297,7 @@ func defaultContext() Context { // say "+build go1.x", and code that should only be built before Go 1.x // (perhaps it is the stub to use in that case) should say "+build !go1.x". // NOTE: If you add to this list, also update the doc comment in doc.go. + // NOTE: The last element in ReleaseTags should be the current release. const version = 11 // go1.11 for i := 1; i <= version; i++ { c.ReleaseTags = append(c.ReleaseTags, "go1."+strconv.Itoa(i))