// in source order. Variables without an initialization expression do not
// appear in this list.
InitOrder []*Initializer
+
+ // FileVersions maps a file's position base to the file's Go version.
+ // If the file doesn't specify a version and Config.GoVersion is not
+ // given, the reported version is the zero version (Major, Minor = 0, 0).
+ FileVersions map[*syntax.PosBase]Version
}
func (info *Info) recordTypes() bool {
return buf.String()
}
+// A Version represents a released Go version.
+type Version struct {
+ Major int
+ Minor int
+}
+
// Check type-checks a package and returns the resulting package object and
// the first error if any. Additionally, if info != nil, Check populates each
// of the non-nil maps in the Info struct.
t.Errorf("src1: unexpected error: got %v", err)
}
}
+
+func TestFileVersions(t *testing.T) {
+ for _, test := range []struct {
+ moduleVersion string
+ fileVersion string
+ want Version
+ }{
+ {"", "", Version{0, 0}}, // no versions specified
+ {"go1.19", "", Version{1, 19}}, // module version specified
+ {"", "go1.20", Version{0, 0}}, // file upgrade ignored
+ {"go1.19", "go1.20", Version{1, 20}}, // file upgrade permitted
+ {"go1.20", "go1.19", Version{1, 20}}, // file downgrade not permitted
+ {"go1.21", "go1.19", Version{1, 19}}, // file downgrade permitted (module version is >= go1.21)
+ } {
+ var src string
+ if test.fileVersion != "" {
+ src = "//go:build " + test.fileVersion + "\n"
+ }
+ src += "package p"
+
+ conf := Config{GoVersion: test.moduleVersion}
+ versions := make(map[*syntax.PosBase]Version)
+ var info Info
+ info.FileVersions = versions
+ mustTypecheck(src, &conf, &info)
+
+ n := 0
+ for _, v := range info.FileVersions {
+ want := test.want
+ if v.Major != want.Major || v.Minor != want.Minor {
+ t.Errorf("%q: unexpected file version: got %v, want %v", src, v, want)
+ }
+ n++
+ }
+ if n != 1 {
+ t.Errorf("%q: incorrect number of map entries: got %d", src, n)
+ }
+ }
+}
}
for _, file := range check.files {
+ fbase := base(file.Pos()) // fbase may be nil for tests
+ check.recordFileVersion(fbase, check.version) // record package version (possibly zero version)
v, _ := parseGoVersion(file.GoVersion)
if v.major > 0 {
if v.equal(check.version) {
if check.posVers == nil {
check.posVers = make(map[*syntax.PosBase]version)
}
- check.posVers[base(file.Pos())] = v
+ check.posVers[fbase] = v
+ check.recordFileVersion(fbase, v) // overwrite package version
}
}
}
m[node] = scope
}
}
+
+func (check *Checker) recordFileVersion(fbase *syntax.PosBase, v version) {
+ if m := check.FileVersions; m != nil {
+ m[fbase] = Version{v.major, v.minor}
+ }
+}
// in source order. Variables without an initialization expression do not
// appear in this list.
InitOrder []*Initializer
+
+ // _FileVersions maps a file to the file's Go version.
+ // If the file doesn't specify a version and Config.GoVersion is not
+ // given, the reported version is the zero version (Major, Minor = 0, 0).
+ _FileVersions map[*token.File]_Version
}
func (info *Info) recordTypes() bool {
return buf.String()
}
+// A _Version represents a released Go version.
+type _Version struct {
+ _Major int
+ _Minor int
+}
+
// Check type-checks a package and returns the resulting package object and
// the first error if any. Additionally, if info != nil, Check populates each
// of the non-nil maps in the Info struct.
t.Errorf("src1: unexpected error: got %v", err)
}
}
+
+func TestFileVersions(t *testing.T) {
+ for _, test := range []struct {
+ moduleVersion string
+ fileVersion string
+ want Version
+ }{
+ {"", "", Version{0, 0}}, // no versions specified
+ {"go1.19", "", Version{1, 19}}, // module version specified
+ {"", "go1.20", Version{0, 0}}, // file upgrade ignored
+ {"go1.19", "go1.20", Version{1, 20}}, // file upgrade permitted
+ {"go1.20", "go1.19", Version{1, 20}}, // file downgrade not permitted
+ {"go1.21", "go1.19", Version{1, 19}}, // file downgrade permitted (module version is >= go1.21)
+ } {
+ var src string
+ if test.fileVersion != "" {
+ src = "//go:build " + test.fileVersion + "\n"
+ }
+ src += "package p"
+
+ conf := Config{GoVersion: test.moduleVersion}
+ versions := make(map[*token.File]Version)
+ var info Info
+ *_FileVersionsAddr(&info) = versions
+ mustTypecheck(src, &conf, &info)
+
+ n := 0
+ for _, v := range versions {
+ want := test.want
+ if v.Major != want.Major || v.Minor != want.Minor {
+ t.Errorf("%q: unexpected file version: got %v, want %v", src, v, want)
+ }
+ n++
+ }
+ if n != 1 {
+ t.Errorf("%q: incorrect number of map entries: got %d", src, n)
+ }
+ }
+}
+
+// Version must match types._Version exactly.
+// TODO(gri) remove this declaration once types.Version is exported.
+type Version struct {
+ Major int
+ Minor int
+}
+
+// _FileVersionsAddr(conf) returns the address of the field info._FileVersions.
+func _FileVersionsAddr(info *Info) *map[*token.File]Version {
+ v := reflect.Indirect(reflect.ValueOf(info))
+ return (*map[*token.File]Version)(v.FieldByName("_FileVersions").Addr().UnsafePointer())
+}
}
for _, file := range check.files {
+ tfile := check.fset.File(file.FileStart)
+ check.recordFileVersion(tfile, check.version) // record package version (possibly zero version)
v, _ := parseGoVersion(file.GoVersion)
if v.major > 0 {
if v.equal(check.version) {
if check.posVers == nil {
check.posVers = make(map[*token.File]version)
}
- check.posVers[check.fset.File(file.FileStart)] = v
+ check.posVers[tfile] = v
+ check.recordFileVersion(tfile, v) // overwrite package version
}
}
}
m[node] = scope
}
}
+
+func (check *Checker) recordFileVersion(tfile *token.File, v version) {
+ if m := check._FileVersions; m != nil {
+ m[tfile] = _Version{v.major, v.minor}
+ }
+}