aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/types2/version.go
blob: 7d01b829a95e05342fb6fdf4c784f4b74a153681 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Copyright 2021 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 types2

import (
	"cmd/compile/internal/syntax"
	"errors"
	"fmt"
	"strings"
)

// A version represents a released Go version.
type version struct {
	major, minor int
}

func (v version) String() string {
	return fmt.Sprintf("go%d.%d", v.major, v.minor)
}

func (v version) equal(u version) bool {
	return v.major == u.major && v.minor == u.minor
}

func (v version) before(u version) bool {
	return v.major < u.major || v.major == u.major && v.minor < u.minor
}

func (v version) after(u version) bool {
	return v.major > u.major || v.major == u.major && v.minor > u.minor
}

// Go versions that introduced language changes.
var (
	go0_0  = version{0, 0} // no version specified
	go1_9  = version{1, 9}
	go1_13 = version{1, 13}
	go1_14 = version{1, 14}
	go1_17 = version{1, 17}
	go1_18 = version{1, 18}
	go1_20 = version{1, 20}
	go1_21 = version{1, 21}
)

var errVersionSyntax = errors.New("invalid Go version syntax")

// parseGoVersion parses a Go version string (such as "go1.12")
// and returns the version, or an error. If s is the empty
// string, the version is 0.0.
func parseGoVersion(s string) (v version, err error) {
	if s == "" {
		return
	}
	if !strings.HasPrefix(s, "go") {
		return version{}, errVersionSyntax
	}
	s = s[len("go"):]
	i := 0
	for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
		if i >= 10 || i == 0 && s[i] == '0' {
			return version{}, errVersionSyntax
		}
		v.major = 10*v.major + int(s[i]) - '0'
	}
	if i > 0 && i == len(s) {
		return
	}
	if i == 0 || s[i] != '.' {
		return version{}, errVersionSyntax
	}
	s = s[i+1:]
	if s == "0" {
		// We really should not accept "go1.0",
		// but we didn't reject it from the start
		// and there are now programs that use it.
		// So accept it.
		return
	}
	i = 0
	for ; i < len(s) && '0' <= s[i] && s[i] <= '9'; i++ {
		if i >= 10 || i == 0 && s[i] == '0' {
			return version{}, errVersionSyntax
		}
		v.minor = 10*v.minor + int(s[i]) - '0'
	}
	if i > 0 && i == len(s) {
		return
	}
	return version{}, errVersionSyntax
}

// langCompat reports an error if the representation of a numeric
// literal is not compatible with the current language version.
func (check *Checker) langCompat(lit *syntax.BasicLit) {
	s := lit.Value
	if len(s) <= 2 || check.allowVersion(check.pkg, lit, go1_13) {
		return
	}
	// len(s) > 2
	if strings.Contains(s, "_") {
		check.versionErrorf(lit, go1_13, "underscores in numeric literals")
		return
	}
	if s[0] != '0' {
		return
	}
	radix := s[1]
	if radix == 'b' || radix == 'B' {
		check.versionErrorf(lit, go1_13, "binary literals")
		return
	}
	if radix == 'o' || radix == 'O' {
		check.versionErrorf(lit, go1_13, "0o/0O-style octal literals")
		return
	}
	if lit.Kind != syntax.IntLit && (radix == 'x' || radix == 'X') {
		check.versionErrorf(lit, go1_13, "hexadecimal floating-point literals")
	}
}

// allowVersion reports whether the given package
// is allowed to use version major.minor.
func (check *Checker) allowVersion(pkg *Package, at poser, v version) bool {
	// We assume that imported packages have all been checked,
	// so we only have to check for the local package.
	if pkg != check.pkg {
		return true
	}

	// If the source file declares its Go version, use that to decide.
	if check.posVers != nil {
		if src, ok := check.posVers[base(at.Pos())]; ok && src.major >= 1 {
			return !src.before(v)
		}
	}

	// Otherwise fall back to the version in the checker.
	return check.version.equal(go0_0) || !check.version.before(v)
}

// verifyVersionf is like allowVersion but also accepts a format string and arguments
// which are used to report a version error if allowVersion returns false. It uses the
// current package.
func (check *Checker) verifyVersionf(at poser, v version, format string, args ...interface{}) bool {
	if !check.allowVersion(check.pkg, at, v) {
		check.versionErrorf(at, v, format, args...)
		return false
	}
	return true
}

// base finds the underlying PosBase of the source file containing pos,
// skipping over intermediate PosBase layers created by //line directives.
func base(pos syntax.Pos) *syntax.PosBase {
	b := pos.Base()
	for {
		bb := b.Pos().Base()
		if bb == nil || bb == b {
			break
		}
		b = bb
	}
	return b
}