Skip to content

Commit 440f3c3

Browse files
committed
internal/aliases: expose Enabled
The predicate that determines whether the type checker creates types.Alias nodes is complex: it depends on an environment variable (GODEBUG) that is somewhat tricky to parse correctly, since it is a rightmost-wins list. Critically, however, its default value is a function of the go directive in the application's go.mod file, which is inaccessible to logic in x/tools (per-file build tags can detect the toolchain version, but not the go.mod version). Equally critically, the current effective value of the gotypesalias variable changes when os.Setenv(GODEBUG) is called, which happens in tests, and there is no way to detect those events; therefore any attempt to cache the value is wrong. This change exposes a simplified version of the Enabled function from aliases, which always computes the ground truth by invoking the type checker, regardless of cost. It also adds an 'enabled' parameter to NewAlias so that callers can amortize this cost. Also, various minor cleanups related to aliases. Updates golang/go#64581 Change-Id: If926edefb8e1c1f63c17e4fad0a808e27bac6d5b Reviewed-on: https://go-review.googlesource.com/c/tools/+/580455 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent a363d11 commit 440f3c3

File tree

14 files changed

+80
-93
lines changed

14 files changed

+80
-93
lines changed

go.mod

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
module golang.org/x/tools
22

3-
go 1.19
3+
go 1.19 // => default GODEBUG has gotypesalias=0
44

55
require (
6+
github.com/google/go-cmp v0.6.0
67
github.com/yuin/goldmark v1.4.13
78
golang.org/x/mod v0.17.0
89
golang.org/x/net v0.24.0
9-
)
10-
11-
require golang.org/x/sync v0.7.0
12-
13-
require github.com/google/go-cmp v0.6.0 // indirect
14-
15-
require (
16-
golang.org/x/sys v0.19.0 // indirect
10+
golang.org/x/sync v0.7.0
1711
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2
1812
)
13+
14+
require golang.org/x/sys v0.19.0 // indirect

gopls/doc/generate.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ func main() {
4747
func doMain(write bool) (bool, error) {
4848
// TODO(adonovan): when we can rely on go1.23,
4949
// switch to gotypesalias=1 behavior.
50+
//
51+
// (Since this program is run by 'go run',
52+
// the gopls/go.mod file's go 1.19 directive doesn't
53+
// have its usual effect of setting gotypesalias=0.)
5054
os.Setenv("GODEBUG", "gotypesalias=0")
5155

5256
api, err := loadAPI()

gopls/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module golang.org/x/tools/gopls
22

3-
go 1.19
3+
go 1.19 // => default GODEBUG has gotypesalias=0
44

55
require (
66
github.com/google/go-cmp v0.6.0

gopls/internal/analysis/fillreturns/fillreturns_test.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ import (
99

1010
"golang.org/x/tools/go/analysis/analysistest"
1111
"golang.org/x/tools/gopls/internal/analysis/fillreturns"
12-
"golang.org/x/tools/internal/testenv"
12+
"golang.org/x/tools/internal/aliases"
1313
)
1414

1515
func Test(t *testing.T) {
16-
// TODO(golang/go#65294): delete (and update expectations)
17-
// once gotypesalias=1 is the default.
18-
testenv.SkipMaterializedAliases(t, "expectations need updating for materialized aliases")
16+
// TODO(golang/go#65294): update expectations and delete this
17+
// check once we update go.mod to go1.23 so that
18+
// gotypesalias=1 becomes the only supported mode.
19+
if aliases.Enabled() {
20+
t.Skip("expectations need updating for materialized aliases")
21+
}
1922

2023
testdata := analysistest.TestData()
2124
analysistest.RunWithSuggestedFixes(t, testdata, fillreturns.Analyzer, "a", "typeparams")

gopls/internal/test/integration/misc/staticcheck_test.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package misc
77
import (
88
"testing"
99

10+
"golang.org/x/tools/internal/aliases"
1011
"golang.org/x/tools/internal/testenv"
1112

1213
. "golang.org/x/tools/gopls/internal/test/integration"
@@ -15,8 +16,11 @@ import (
1516
func TestStaticcheckGenerics(t *testing.T) {
1617
testenv.NeedsGo1Point(t, 20) // staticcheck requires go1.20+
1718

18-
// TODO(golang/go#65249): re-enable and fix this test with gotypesalias=1.
19-
testenv.SkipMaterializedAliases(t, "staticcheck needs updates for materialized aliases")
19+
// TODO(golang/go#65249): re-enable and fix this test once we
20+
// update go.mod to go1.23 so that gotypesalias=1 becomes the default.
21+
if aliases.Enabled() {
22+
t.Skip("staticheck doesn't yet support aliases (dominikh/go-tools#1523)")
23+
}
2024

2125
const files = `
2226
-- go.mod --
@@ -83,8 +87,11 @@ var FooErr error = errors.New("foo")
8387
func TestStaticcheckRelatedInfo(t *testing.T) {
8488
testenv.NeedsGo1Point(t, 20) // staticcheck is only supported at Go 1.20+
8589

86-
// TODO(golang/go#65249): re-enable and fix this test with gotypesalias=1.
87-
testenv.SkipMaterializedAliases(t, "staticcheck needs updates for materialized aliases")
90+
// TODO(golang/go#65249): re-enable and fix this test once we
91+
// update go.mod to go1.23 so that gotypesalias=1 becomes the default.
92+
if aliases.Enabled() {
93+
t.Skip("staticheck doesn't yet support aliases (dominikh/go-tools#1523)")
94+
}
8895

8996
const files = `
9097
-- go.mod --

internal/aliases/aliases.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ import (
1616
// NewAlias creates a new TypeName in Package pkg that
1717
// is an alias for the type rhs.
1818
//
19-
// When GoVersion>=1.22 and GODEBUG=gotypesalias=1 (or unset),
20-
// the Type() of the return value is a *types.Alias.
21-
func NewAlias(pos token.Pos, pkg *types.Package, name string, rhs types.Type) *types.TypeName {
22-
if enabled() {
19+
// The enabled parameter determines whether the resulting [TypeName]'s
20+
// type is an [types.Alias]. Its value must be the result of a call to
21+
// [Enabled], which computes the effective value of
22+
// GODEBUG=gotypesalias=... by invoking the type checker. The Enabled
23+
// function is expensive and should be called once per task (e.g.
24+
// package import), not once per call to NewAlias.
25+
func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type) *types.TypeName {
26+
if enabled {
2327
tname := types.NewTypeName(pos, pkg, name, nil)
2428
newAlias(tname, rhs)
2529
return tname

internal/aliases/aliases_go121.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ func (*Alias) Obj() *types.TypeName { panic("unreachable") }
2424
// Unalias returns the type t for go <=1.21.
2525
func Unalias(t types.Type) types.Type { return t }
2626

27-
// Always false for go <=1.21. Ignores GODEBUG.
28-
func enabled() bool { return false }
29-
3027
func newAlias(name *types.TypeName, rhs types.Type) *Alias { panic("unreachable") }
28+
29+
// Enabled reports whether [NewAlias] should create [types.Alias] types.
30+
//
31+
// Before go1.22, this function always returns false.
32+
func Enabled() bool { return false }

internal/aliases/aliases_go122.go

Lines changed: 19 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ import (
1212
"go/parser"
1313
"go/token"
1414
"go/types"
15-
"os"
16-
"strings"
17-
"sync"
1815
)
1916

2017
// Alias is an alias of types.Alias.
@@ -33,40 +30,23 @@ func newAlias(tname *types.TypeName, rhs types.Type) *Alias {
3330
return a
3431
}
3532

36-
// enabled returns true when types.Aliases are enabled.
37-
func enabled() bool {
38-
// Use the gotypesalias value in GODEBUG if set.
39-
godebug := os.Getenv("GODEBUG")
40-
value := -1 // last set value.
41-
for _, f := range strings.Split(godebug, ",") {
42-
switch f {
43-
case "gotypesalias=1":
44-
value = 1
45-
case "gotypesalias=0":
46-
value = 0
47-
}
48-
}
49-
switch value {
50-
case 0:
51-
return false
52-
case 1:
53-
return true
54-
default:
55-
return aliasesDefault()
56-
}
33+
// Enabled reports whether [NewAlias] should create [types.Alias] types.
34+
//
35+
// This function is expensive! Call it sparingly.
36+
func Enabled() bool {
37+
// The only reliable way to compute the answer is to invoke go/types.
38+
// We don't parse the GODEBUG environment variable, because
39+
// (a) it's tricky to do so in a manner that is consistent
40+
// with the godebug package; in particular, a simple
41+
// substring check is not good enough. The value is a
42+
// rightmost-wins list of options. But more importantly:
43+
// (b) it is impossible to detect changes to the effective
44+
// setting caused by os.Setenv("GODEBUG"), as happens in
45+
// many tests. Therefore any attempt to cache the result
46+
// is just incorrect.
47+
fset := token.NewFileSet()
48+
f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0)
49+
pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil)
50+
_, enabled := pkg.Scope().Lookup("A").Type().(*types.Alias)
51+
return enabled
5752
}
58-
59-
// aliasesDefault reports if aliases are enabled by default.
60-
func aliasesDefault() bool {
61-
// Dynamically check if Aliases will be produced from go/types.
62-
aliasesDefaultOnce.Do(func() {
63-
fset := token.NewFileSet()
64-
f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0)
65-
pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil)
66-
_, gotypesaliasDefault = pkg.Scope().Lookup("A").Type().(*types.Alias)
67-
})
68-
return gotypesaliasDefault
69-
}
70-
71-
var gotypesaliasDefault bool
72-
var aliasesDefaultOnce sync.Once

internal/aliases/aliases_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ func TestNewAlias(t *testing.T) {
5353
t.Run(godebug, func(t *testing.T) {
5454
t.Setenv("GODEBUG", godebug)
5555

56-
A := aliases.NewAlias(token.NoPos, pkg, "A", tv.Type)
56+
enabled := aliases.Enabled()
57+
58+
A := aliases.NewAlias(enabled, token.NoPos, pkg, "A", tv.Type)
5759
if got, want := A.Name(), "A"; got != want {
5860
t.Errorf("Expected A.Name()==%q. got %q", want, got)
5961
}

internal/gcimporter/iimport.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte
210210
p := iimporter{
211211
version: int(version),
212212
ipath: path,
213+
aliases: aliases.Enabled(),
213214
shallow: shallow,
214215
reportf: reportf,
215216

@@ -369,6 +370,7 @@ type iimporter struct {
369370
version int
370371
ipath string
371372

373+
aliases bool
372374
shallow bool
373375
reportf ReportFunc // if non-nil, used to report bugs
374376

@@ -567,7 +569,7 @@ func (r *importReader) obj(name string) {
567569
// tparams := r.tparamList()
568570
// alias.SetTypeParams(tparams)
569571
// }
570-
r.declare(aliases.NewAlias(pos, r.currPkg, name, typ))
572+
r.declare(aliases.NewAlias(r.p.aliases, pos, r.currPkg, name, typ))
571573

572574
case constTag:
573575
typ, val := r.value()

internal/gcimporter/ureader_yes.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type pkgReader struct {
2626

2727
ctxt *types.Context
2828
imports map[string]*types.Package // previously imported packages, indexed by path
29+
aliases bool // create types.Alias nodes
2930

3031
// lazily initialized arrays corresponding to the unified IR
3132
// PosBase, Pkg, and Type sections, respectively.
@@ -99,6 +100,7 @@ func readUnifiedPackage(fset *token.FileSet, ctxt *types.Context, imports map[st
99100

100101
ctxt: ctxt,
101102
imports: imports,
103+
aliases: aliases.Enabled(),
102104

103105
posBases: make([]string, input.NumElems(pkgbits.RelocPosBase)),
104106
pkgs: make([]*types.Package, input.NumElems(pkgbits.RelocPkg)),
@@ -524,7 +526,7 @@ func (pr *pkgReader) objIdx(idx pkgbits.Index) (*types.Package, string) {
524526
case pkgbits.ObjAlias:
525527
pos := r.pos()
526528
typ := r.typ()
527-
declare(aliases.NewAlias(pos, objPkg, objName, typ))
529+
declare(aliases.NewAlias(r.p.aliases, pos, objPkg, objName, typ))
528530

529531
case pkgbits.ObjConst:
530532
pos := r.pos()

internal/pkgbits/decoder.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ type PkgDecoder struct {
2323
// version is the file format version.
2424
version uint32
2525

26+
// aliases determines whether types.Aliases should be created
27+
aliases bool
28+
2629
// sync indicates whether the file uses sync markers.
2730
sync bool
2831

@@ -73,6 +76,7 @@ func (pr *PkgDecoder) SyncMarkers() bool { return pr.sync }
7376
func NewPkgDecoder(pkgPath, input string) PkgDecoder {
7477
pr := PkgDecoder{
7578
pkgPath: pkgPath,
79+
//aliases: aliases.Enabled(),
7680
}
7781

7882
// TODO(mdempsky): Implement direct indexing of input string to

internal/testenv/exec.go

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"reflect"
1313
"runtime"
1414
"strconv"
15-
"strings"
1615
"sync"
1716
"testing"
1817
"time"
@@ -191,22 +190,3 @@ func Command(t testing.TB, name string, args ...string) *exec.Cmd {
191190
t.Helper()
192191
return CommandContext(t, context.Background(), name, args...)
193192
}
194-
195-
// SkipMaterializedAliases skips the test if go/types would create
196-
// instances of types.Alias, which some tests do not yet handle
197-
// correctly.
198-
func SkipMaterializedAliases(t *testing.T, message string) {
199-
if hasMaterializedAliases(Go1Point()) {
200-
t.Skipf("%s", message)
201-
}
202-
}
203-
204-
func hasMaterializedAliases(minor int) bool {
205-
if minor >= 23 && !strings.Contains(os.Getenv("GODEBUG"), "gotypesalias=0") {
206-
return true // gotypesalias=1 became the default in go1.23
207-
}
208-
if minor == 22 && strings.Contains(os.Getenv("GODEBUG"), "gotypesalias=1") {
209-
return true // gotypesalias=0 was the default in go1.22
210-
}
211-
return false // types.Alias didn't exist in go1.21
212-
}

refactor/rename/rename_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"testing"
1919

2020
"golang.org/x/tools/go/buildutil"
21+
"golang.org/x/tools/internal/aliases"
2122
"golang.org/x/tools/internal/testenv"
2223
)
2324

@@ -1320,12 +1321,12 @@ func main() {
13201321
return nil
13211322
}
13221323

1323-
if !test.alias {
1324-
t.Setenv("GODEBUG", "gotypesalias=0")
1325-
} else if !strings.Contains(fmt.Sprint(build.Default.ReleaseTags), " go1.22") {
1326-
t.Skip("skipping test that requires materialized type aliases")
1327-
} else {
1328-
t.Setenv("GODEBUG", "gotypesalias=1")
1324+
// Skip tests that require aliases when not enables.
1325+
// (No test requires _no_ aliases,
1326+
// so there is no contrapositive case.)
1327+
if test.alias && !aliases.Enabled() {
1328+
t.Log("test requires aliases")
1329+
continue
13291330
}
13301331

13311332
err := Main(ctxt, test.offset, test.from, test.to)

0 commit comments

Comments
 (0)