Skip to content

Commit 9b1f8f3

Browse files
adonovangopherbot
authored andcommitted
go/types: compute effective Go version independent of token.Pos
Previously, the Checker.allowVersion method would use a token.Pos to try to infer which file of the current package the checker was "in". This proved fragile when type-checking syntax that had been modified or synthesized and whose positions were invalid. This change records the effective version in the checker state (checker.environment.version). Just like other aspects of the environment, the version changes from one file to the next and must be saved and restored with each check.later closure. Similarly, declInfo captures and temporarily reinstates the effective version when checking each object. + Test of position independence in go/types and types2 + Test of panic avoidance in go/types Fixes #69477 Fixes #69338 Change-Id: Ic06f9d88151c64a4f7848f8942d08e3c312cdd6f Reviewed-on: https://go-review.googlesource.com/c/go/+/613735 Reviewed-by: Robert Griesemer <[email protected]> Auto-Submit: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 906338f commit 9b1f8f3

30 files changed

+222
-168
lines changed

src/cmd/compile/internal/types2/api_predicates.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func AssertableTo(V *Interface, T Type) bool {
1919
if !isValid(T.Underlying()) {
2020
return false
2121
}
22-
return (*Checker)(nil).newAssertableTo(nopos, V, T, nil)
22+
return (*Checker)(nil).newAssertableTo(V, T, nil)
2323
}
2424

2525
// AssignableTo reports whether a value of type V is assignable to a variable
@@ -57,15 +57,15 @@ func Implements(V Type, T *Interface) bool {
5757
if !isValid(V.Underlying()) {
5858
return false
5959
}
60-
return (*Checker)(nil).implements(nopos, V, T, false, nil)
60+
return (*Checker)(nil).implements(V, T, false, nil)
6161
}
6262

6363
// Satisfies reports whether type V satisfies the constraint T.
6464
//
6565
// The behavior of Satisfies is unspecified if V is Typ[Invalid] or an uninstantiated
6666
// generic type.
6767
func Satisfies(V Type, T *Interface) bool {
68-
return (*Checker)(nil).implements(nopos, V, T, true, nil)
68+
return (*Checker)(nil).implements(V, T, true, nil)
6969
}
7070

7171
// Identical reports whether x and y are identical types.

src/cmd/compile/internal/types2/api_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3206,3 +3206,35 @@ func TestAnyHijacking_Check(t *testing.T) {
32063206
})
32073207
}
32083208
}
3209+
3210+
// This test function only exists for go/types.
3211+
// func TestVersionIssue69477(t *testing.T)
3212+
3213+
// TestVersionWithoutPos is a regression test for issue #69477,
3214+
// in which the type checker would use position information
3215+
// to compute which file it is "in" based on syntax position.
3216+
//
3217+
// As a rule the type checker should not depend on position
3218+
// information for correctness, only for error messages and
3219+
// Object.Pos. (Scope.LookupParent was a mistake.)
3220+
//
3221+
// The Checker now holds the effective version in a state variable.
3222+
func TestVersionWithoutPos(t *testing.T) {
3223+
f := mustParse("//go:build go1.22\n\npackage p; var _ int")
3224+
3225+
// Splice in a decl from another file. Its pos will be wrong.
3226+
f.DeclList[0] = mustParse("package q; func _(s func(func() bool)) { for range s {} }").DeclList[0]
3227+
3228+
// Type check. The checker will consult the effective
3229+
// version (1.22) for the for-range stmt to know whether
3230+
// range-over-func are permitted: they are not.
3231+
// (Previously, no error was reported.)
3232+
pkg := NewPackage("p", "p")
3233+
check := NewChecker(&Config{}, pkg, nil)
3234+
err := check.Files([]*syntax.File{f})
3235+
got := fmt.Sprint(err)
3236+
want := "range over s (variable of type func(func() bool)): requires go1.23"
3237+
if !strings.Contains(got, want) {
3238+
t.Errorf("check error was %q, want substring %q", got, want)
3239+
}
3240+
}

src/cmd/compile/internal/types2/call.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (check *Checker) funcInst(T *target, pos syntax.Pos, x *operand, inst *synt
8787
var params []*Var
8888
var reverse bool
8989
if T != nil && sig.tparams != nil {
90-
if !versionErr && !check.allowVersion(instErrPos, go1_21) {
90+
if !versionErr && !check.allowVersion(go1_21) {
9191
if inst != nil {
9292
check.versionErrorf(instErrPos, go1_21, "partially instantiated function in assignment")
9393
} else {
@@ -372,7 +372,7 @@ func (check *Checker) genericExprList(elist []syntax.Expr) (resList []*operand,
372372
// nor permitted. Checker.funcInst must infer missing type arguments in that case.
373373
infer := true // for -lang < go1.21
374374
n := len(elist)
375-
if n > 0 && check.allowVersion(elist[0], go1_21) {
375+
if n > 0 && check.allowVersion(go1_21) {
376376
infer = false
377377
}
378378

@@ -542,7 +542,7 @@ func (check *Checker) arguments(call *syntax.CallExpr, sig *Signature, targs []T
542542
// collect type parameters of callee
543543
n := sig.TypeParams().Len()
544544
if n > 0 {
545-
if !check.allowVersion(call.Pos(), go1_18) {
545+
if !check.allowVersion(go1_18) {
546546
if iexpr, _ := call.Fun.(*syntax.IndexExpr); iexpr != nil {
547547
check.versionErrorf(iexpr, go1_18, "function instantiation")
548548
} else {

src/cmd/compile/internal/types2/check.go

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ type exprInfo struct {
5656
type environment struct {
5757
decl *declInfo // package-level declaration whose init expression/function body is checked
5858
scope *Scope // top-most scope for lookups
59+
version goVersion // current accepted language version; changes across files
5960
pos syntax.Pos // if valid, identifiers are looked up as if at position pos (used by Eval)
6061
iota constant.Value // value of iota in a constant declaration; nil otherwise
6162
errpos syntax.Pos // if valid, identifier position of a constant with inherited initializer
@@ -90,8 +91,9 @@ type dotImportKey struct {
9091

9192
// An action describes a (delayed) action.
9293
type action struct {
93-
f func() // action to be executed
94-
desc *actionDesc // action description; may be nil, requires debug to be set
94+
version goVersion // applicable language version
95+
f func() // action to be executed
96+
desc *actionDesc // action description; may be nil, requires debug to be set
9597
}
9698

9799
// If debug is set, describef sets a printf-formatted description for action a.
@@ -119,10 +121,9 @@ type Checker struct {
119121
ctxt *Context // context for de-duplicating instances
120122
pkg *Package
121123
*Info
122-
version goVersion // accepted language version
123-
nextID uint64 // unique Id for type parameters (first valid Id is 1)
124-
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
125-
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
124+
nextID uint64 // unique Id for type parameters (first valid Id is 1)
125+
objMap map[Object]*declInfo // maps package-level objects and (non-interface) methods to declaration info
126+
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
126127
// see TODO in validtype.go
127128
// valids instanceLookup // valid *Named (incl. instantiated) types per the validType check
128129

@@ -140,7 +141,7 @@ type Checker struct {
140141
// (initialized by Files, valid only for the duration of check.Files;
141142
// maps and lists are allocated on demand)
142143
files []*syntax.File // list of package files
143-
versions map[*syntax.PosBase]string // maps files to version strings (each file has an entry); shared with Info.FileVersions if present
144+
versions map[*syntax.PosBase]string // maps files to version strings (each file has an entry); shared with Info.FileVersions if present; may be unaltered Config.GoVersion
144145
imports []*PkgName // list of imported packages
145146
dotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported through
146147
brokenAliases map[*TypeName]bool // set of aliases with broken (not yet determined) types
@@ -219,7 +220,7 @@ func (check *Checker) rememberUntyped(e syntax.Expr, lhs bool, mode operandMode,
219220
// via action.describef for debugging, if desired.
220221
func (check *Checker) later(f func()) *action {
221222
i := len(check.delayed)
222-
check.delayed = append(check.delayed, action{f: f})
223+
check.delayed = append(check.delayed, action{version: check.version, f: f})
223224
return &check.delayed[i]
224225
}
225226

@@ -268,13 +269,12 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
268269
// (previously, pkg.goVersion was mutated here: go.dev/issue/61212)
269270

270271
return &Checker{
271-
conf: conf,
272-
ctxt: conf.Context,
273-
pkg: pkg,
274-
Info: info,
275-
version: asGoVersion(conf.GoVersion),
276-
objMap: make(map[Object]*declInfo),
277-
impMap: make(map[importKey]*Package),
272+
conf: conf,
273+
ctxt: conf.Context,
274+
pkg: pkg,
275+
Info: info,
276+
objMap: make(map[Object]*declInfo),
277+
impMap: make(map[importKey]*Package),
278278
}
279279
}
280280

@@ -321,10 +321,10 @@ func (check *Checker) initFiles(files []*syntax.File) {
321321
}
322322
check.versions = versions
323323

324-
pkgVersionOk := check.version.isValid()
325-
if pkgVersionOk && len(files) > 0 && check.version.cmp(go_current) > 0 {
324+
pkgVersion := asGoVersion(check.conf.GoVersion)
325+
if pkgVersion.isValid() && len(files) > 0 && pkgVersion.cmp(go_current) > 0 {
326326
check.errorf(files[0], TooNew, "package requires newer Go version %v (application built with %v)",
327-
check.version, go_current)
327+
pkgVersion, go_current)
328328
}
329329

330330
// determine Go version for each file
@@ -479,6 +479,7 @@ func (check *Checker) processDelayed(top int) {
479479
// are processed in a delayed fashion) that may
480480
// add more actions (such as nested functions), so
481481
// this is a sufficiently bounded process.
482+
savedVersion := check.version
482483
for i := top; i < len(check.delayed); i++ {
483484
a := &check.delayed[i]
484485
if check.conf.Trace {
@@ -488,13 +489,15 @@ func (check *Checker) processDelayed(top int) {
488489
check.trace(nopos, "-- delayed %p", a.f)
489490
}
490491
}
491-
a.f() // may append to check.delayed
492+
check.version = a.version // reestablish the effective Go version captured earlier
493+
a.f() // may append to check.delayed
492494
if check.conf.Trace {
493495
fmt.Println()
494496
}
495497
}
496498
assert(top <= len(check.delayed)) // stack must not have shrunk
497499
check.delayed = check.delayed[:top]
500+
check.version = savedVersion
498501
}
499502

500503
// cleanup runs cleanup for all collected cleaners.

src/cmd/compile/internal/types2/conversions.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool {
200200
switch a := Tu.(type) {
201201
case *Array:
202202
if Identical(s.Elem(), a.Elem()) {
203-
if check == nil || check.allowVersion(x, go1_20) {
203+
if check == nil || check.allowVersion(go1_20) {
204204
return true
205205
}
206206
// check != nil
@@ -213,7 +213,7 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool {
213213
case *Pointer:
214214
if a, _ := under(a.Elem()).(*Array); a != nil {
215215
if Identical(s.Elem(), a.Elem()) {
216-
if check == nil || check.allowVersion(x, go1_17) {
216+
if check == nil || check.allowVersion(go1_17) {
217217
return true
218218
}
219219
// check != nil

src/cmd/compile/internal/types2/decl.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,7 @@ func (check *Checker) objDecl(obj Object, def *TypeName) {
169169
defer func(env environment) {
170170
check.environment = env
171171
}(check.environment)
172-
check.environment = environment{
173-
scope: d.file,
174-
}
172+
check.environment = environment{scope: d.file, version: d.version}
175173

176174
// Const and var declarations must not have initialization
177175
// cycles. We track them by remembering the current declaration

src/cmd/compile/internal/types2/infer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
110110
// Unify parameter and argument types for generic parameters with typed arguments
111111
// and collect the indices of generic parameters with untyped arguments.
112112
// Terminology: generic parameter = function parameter with a type-parameterized type
113-
u := newUnifier(tparams, targs, check.allowVersion(pos, go1_21))
113+
u := newUnifier(tparams, targs, check.allowVersion(go1_21))
114114

115115
errorf := func(tpar, targ Type, arg *operand) {
116116
// provide a better error message if we can

src/cmd/compile/internal/types2/instantiate.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e
8282
// must be non-nil.
8383
//
8484
// For Named types the resulting instance may be unexpanded.
85+
//
86+
// check may be nil (when not type-checking syntax); pos is used only only if check is non-nil.
8587
func (check *Checker) instance(pos syntax.Pos, orig genericType, targs []Type, expanding *Named, ctxt *Context) (res Type) {
8688
// The order of the contexts below matters: we always prefer instances in the
8789
// expanding instance context in order to preserve reference cycles.
@@ -198,6 +200,7 @@ func (check *Checker) validateTArgLen(pos syntax.Pos, name string, want, got int
198200
panic(fmt.Sprintf("%v: %s", pos, msg))
199201
}
200202

203+
// check may be nil; pos is used only if check is non-nil.
201204
func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type, ctxt *Context) (int, error) {
202205
smap := makeSubstMap(tparams, targs)
203206
for i, tpar := range tparams {
@@ -209,7 +212,7 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type,
209212
// the parameterized type.
210213
bound := check.subst(pos, tpar.bound, smap, nil, ctxt)
211214
var cause string
212-
if !check.implements(pos, targs[i], bound, true, &cause) {
215+
if !check.implements(targs[i], bound, true, &cause) {
213216
return i, errors.New(cause)
214217
}
215218
}
@@ -222,7 +225,7 @@ func (check *Checker) verify(pos syntax.Pos, tparams []*TypeParam, targs []Type,
222225
//
223226
// If the provided cause is non-nil, it may be set to an error string
224227
// explaining why V does not implement (or satisfy, for constraints) T.
225-
func (check *Checker) implements(pos syntax.Pos, V, T Type, constraint bool, cause *string) bool {
228+
func (check *Checker) implements(V, T Type, constraint bool, cause *string) bool {
226229
Vu := under(V)
227230
Tu := under(T)
228231
if !isValid(Vu) || !isValid(Tu) {
@@ -295,7 +298,7 @@ func (check *Checker) implements(pos syntax.Pos, V, T Type, constraint bool, cau
295298
// so that ordinary, non-type parameter interfaces implement comparable.
296299
if constraint && comparableType(V, true /* spec comparability */, nil, nil) {
297300
// V is comparable if we are at Go 1.20 or higher.
298-
if check == nil || check.allowVersion(atPos(pos), go1_20) { // atPos needed so that go/types generate passes
301+
if check == nil || check.allowVersion(go1_20) {
299302
return true
300303
}
301304
if cause != nil {

src/cmd/compile/internal/types2/literals.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
// literal is not compatible with the current language version.
1717
func (check *Checker) langCompat(lit *syntax.BasicLit) {
1818
s := lit.Value
19-
if len(s) <= 2 || check.allowVersion(lit, go1_13) {
19+
if len(s) <= 2 || check.allowVersion(go1_13) {
2020
return
2121
}
2222
// len(s) > 2

src/cmd/compile/internal/types2/lookup.go

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66

77
package types2
88

9-
import (
10-
"bytes"
11-
"cmd/compile/internal/syntax"
12-
)
9+
import "bytes"
1310

1411
// Internal use of LookupFieldOrMethod: If the obj result is a method
1512
// associated with a concrete (non-interface) type, the method's signature
@@ -531,14 +528,14 @@ func (check *Checker) assertableTo(V, T Type, cause *string) bool {
531528
// in constraint position (we have not yet defined that behavior in the spec).
532529
// The underlying type of V must be an interface.
533530
// If the result is false and cause is not nil, *cause is set to the error cause.
534-
func (check *Checker) newAssertableTo(pos syntax.Pos, V, T Type, cause *string) bool {
531+
func (check *Checker) newAssertableTo(V, T Type, cause *string) bool {
535532
// no static check is required if T is an interface
536533
// spec: "If T is an interface type, x.(T) asserts that the
537534
// dynamic type of x implements the interface T."
538535
if IsInterface(T) {
539536
return true
540537
}
541-
return check.implements(pos, T, V, false, cause)
538+
return check.implements(T, V, false, cause)
542539
}
543540

544541
// deref dereferences typ if it is a *Pointer (but not a *Named type

src/cmd/compile/internal/types2/operand.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
307307
// Also handle the case where T is a pointer to an interface so that we get
308308
// the Checker.implements error cause.
309309
if _, ok := Tu.(*Interface); ok && Tp == nil || isInterfacePtr(Tu) {
310-
if check.implements(x.Pos(), V, T, false, cause) {
310+
if check.implements(V, T, false, cause) {
311311
return true, 0
312312
}
313313
// V doesn't implement T but V may still be assignable to T if V
@@ -322,7 +322,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
322322

323323
// If V is an interface, check if a missing type assertion is the problem.
324324
if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil {
325-
if check.implements(x.Pos(), T, V, false, nil) {
325+
if check.implements(T, V, false, nil) {
326326
// T implements V, so give hint about type assertion.
327327
if cause != nil {
328328
*cause = "need type assertion"

0 commit comments

Comments
 (0)