whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
// Copyright 2024 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 aliases
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// Package aliases defines backward compatible shims
|
||||
// for the types.Alias type representation added in 1.22.
|
||||
// This defines placeholders for x/tools until 1.26.
|
||||
|
||||
// NewAlias creates a new TypeName in Package pkg that
|
||||
// is an alias for the type rhs.
|
||||
//
|
||||
// The enabled parameter determines whether the resulting [TypeName]'s
|
||||
// type is an [types.Alias]. Its value must be the result of a call to
|
||||
// [Enabled], which computes the effective value of
|
||||
// GODEBUG=gotypesalias=... by invoking the type checker. The Enabled
|
||||
// function is expensive and should be called once per task (e.g.
|
||||
// package import), not once per call to NewAlias.
|
||||
//
|
||||
// Precondition: enabled || len(tparams)==0.
|
||||
// If materialized aliases are disabled, there must not be any type parameters.
|
||||
func NewAlias(enabled bool, pos token.Pos, pkg *types.Package, name string, rhs types.Type, tparams []*types.TypeParam) *types.TypeName {
|
||||
if enabled {
|
||||
tname := types.NewTypeName(pos, pkg, name, nil)
|
||||
newAlias(tname, rhs, tparams)
|
||||
return tname
|
||||
}
|
||||
if len(tparams) > 0 {
|
||||
panic("cannot create an alias with type parameters when gotypesalias is not enabled")
|
||||
}
|
||||
return types.NewTypeName(pos, pkg, name, rhs)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
//go:build !go1.22
|
||||
// +build !go1.22
|
||||
|
||||
package aliases
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// Alias is a placeholder for a go/types.Alias for <=1.21.
|
||||
// It will never be created by go/types.
|
||||
type Alias struct{}
|
||||
|
||||
func (*Alias) String() string { panic("unreachable") }
|
||||
func (*Alias) Underlying() types.Type { panic("unreachable") }
|
||||
func (*Alias) Obj() *types.TypeName { panic("unreachable") }
|
||||
func Rhs(alias *Alias) types.Type { panic("unreachable") }
|
||||
func TypeParams(alias *Alias) *types.TypeParamList { panic("unreachable") }
|
||||
func SetTypeParams(alias *Alias, tparams []*types.TypeParam) { panic("unreachable") }
|
||||
func TypeArgs(alias *Alias) *types.TypeList { panic("unreachable") }
|
||||
func Origin(alias *Alias) *Alias { panic("unreachable") }
|
||||
|
||||
// Unalias returns the type t for go <=1.21.
|
||||
func Unalias(t types.Type) types.Type { return t }
|
||||
|
||||
func newAlias(name *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias {
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Enabled reports whether [NewAlias] should create [types.Alias] types.
|
||||
//
|
||||
// Before go1.22, this function always returns false.
|
||||
func Enabled() bool { return false }
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright 2024 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.
|
||||
|
||||
//go:build go1.22
|
||||
// +build go1.22
|
||||
|
||||
package aliases
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// Alias is an alias of types.Alias.
|
||||
type Alias = types.Alias
|
||||
|
||||
// Rhs returns the type on the right-hand side of the alias declaration.
|
||||
func Rhs(alias *Alias) types.Type {
|
||||
if alias, ok := any(alias).(interface{ Rhs() types.Type }); ok {
|
||||
return alias.Rhs() // go1.23+
|
||||
}
|
||||
|
||||
// go1.22's Alias didn't have the Rhs method,
|
||||
// so Unalias is the best we can do.
|
||||
return Unalias(alias)
|
||||
}
|
||||
|
||||
// TypeParams returns the type parameter list of the alias.
|
||||
func TypeParams(alias *Alias) *types.TypeParamList {
|
||||
if alias, ok := any(alias).(interface{ TypeParams() *types.TypeParamList }); ok {
|
||||
return alias.TypeParams() // go1.23+
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTypeParams sets the type parameters of the alias type.
|
||||
func SetTypeParams(alias *Alias, tparams []*types.TypeParam) {
|
||||
if alias, ok := any(alias).(interface {
|
||||
SetTypeParams(tparams []*types.TypeParam)
|
||||
}); ok {
|
||||
alias.SetTypeParams(tparams) // go1.23+
|
||||
} else if len(tparams) > 0 {
|
||||
panic("cannot set type parameters of an Alias type in go1.22")
|
||||
}
|
||||
}
|
||||
|
||||
// TypeArgs returns the type arguments used to instantiate the Alias type.
|
||||
func TypeArgs(alias *Alias) *types.TypeList {
|
||||
if alias, ok := any(alias).(interface{ TypeArgs() *types.TypeList }); ok {
|
||||
return alias.TypeArgs() // go1.23+
|
||||
}
|
||||
return nil // empty (go1.22)
|
||||
}
|
||||
|
||||
// Origin returns the generic Alias type of which alias is an instance.
|
||||
// If alias is not an instance of a generic alias, Origin returns alias.
|
||||
func Origin(alias *Alias) *Alias {
|
||||
if alias, ok := any(alias).(interface{ Origin() *types.Alias }); ok {
|
||||
return alias.Origin() // go1.23+
|
||||
}
|
||||
return alias // not an instance of a generic alias (go1.22)
|
||||
}
|
||||
|
||||
// Unalias is a wrapper of types.Unalias.
|
||||
func Unalias(t types.Type) types.Type { return types.Unalias(t) }
|
||||
|
||||
// newAlias is an internal alias around types.NewAlias.
|
||||
// Direct usage is discouraged as the moment.
|
||||
// Try to use NewAlias instead.
|
||||
func newAlias(tname *types.TypeName, rhs types.Type, tparams []*types.TypeParam) *Alias {
|
||||
a := types.NewAlias(tname, rhs)
|
||||
SetTypeParams(a, tparams)
|
||||
return a
|
||||
}
|
||||
|
||||
// Enabled reports whether [NewAlias] should create [types.Alias] types.
|
||||
//
|
||||
// This function is expensive! Call it sparingly.
|
||||
func Enabled() bool {
|
||||
// The only reliable way to compute the answer is to invoke go/types.
|
||||
// We don't parse the GODEBUG environment variable, because
|
||||
// (a) it's tricky to do so in a manner that is consistent
|
||||
// with the godebug package; in particular, a simple
|
||||
// substring check is not good enough. The value is a
|
||||
// rightmost-wins list of options. But more importantly:
|
||||
// (b) it is impossible to detect changes to the effective
|
||||
// setting caused by os.Setenv("GODEBUG"), as happens in
|
||||
// many tests. Therefore any attempt to cache the result
|
||||
// is just incorrect.
|
||||
fset := token.NewFileSet()
|
||||
f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0)
|
||||
pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil)
|
||||
_, enabled := pkg.Scope().Lookup("A").Type().(*types.Alias)
|
||||
return enabled
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright 2024 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 aliases_test
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/internal/aliases"
|
||||
"golang.org/x/tools/internal/testenv"
|
||||
)
|
||||
|
||||
// Assert that Obj exists on Alias.
|
||||
var _ func(*aliases.Alias) *types.TypeName = (*aliases.Alias).Obj
|
||||
|
||||
// TestNewAlias tests that alias.NewAlias creates an alias of a type
|
||||
// whose underlying and Unaliased type is *Named.
|
||||
// When gotypesalias=1 (or unset) and GoVersion >= 1.22, the type will
|
||||
// be an *aliases.Alias.
|
||||
func TestNewAlias(t *testing.T) {
|
||||
const source = `
|
||||
package p
|
||||
|
||||
type Named int
|
||||
`
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "hello.go", source, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var conf types.Config
|
||||
pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expr := `*Named`
|
||||
tv, err := types.Eval(fset, pkg, 0, expr)
|
||||
if err != nil {
|
||||
t.Fatalf("Eval(%s) failed: %v", expr, err)
|
||||
}
|
||||
|
||||
for _, godebug := range []string{
|
||||
// The default gotypesalias value follows the x/tools/go.mod version
|
||||
// The go.mod is at 1.19 so the default is gotypesalias=0.
|
||||
// "", // Use the default GODEBUG value.
|
||||
"gotypesalias=0",
|
||||
"gotypesalias=1",
|
||||
} {
|
||||
t.Run(godebug, func(t *testing.T) {
|
||||
t.Setenv("GODEBUG", godebug)
|
||||
|
||||
enabled := aliases.Enabled()
|
||||
|
||||
A := aliases.NewAlias(enabled, token.NoPos, pkg, "A", tv.Type, nil)
|
||||
if got, want := A.Name(), "A"; got != want {
|
||||
t.Errorf("Expected A.Name()==%q. got %q", want, got)
|
||||
}
|
||||
|
||||
if got, want := A.Type().Underlying(), tv.Type; got != want {
|
||||
t.Errorf("Expected A.Type().Underlying()==%q. got %q", want, got)
|
||||
}
|
||||
if got, want := aliases.Unalias(A.Type()), tv.Type; got != want {
|
||||
t.Errorf("Expected Unalias(A)==%q. got %q", want, got)
|
||||
}
|
||||
|
||||
if testenv.Go1Point() >= 22 && godebug != "gotypesalias=0" {
|
||||
if _, ok := A.Type().(*aliases.Alias); !ok {
|
||||
t.Errorf("Expected A.Type() to be a types.Alias(). got %q", A.Type())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewAlias tests that alias.NewAlias can create a parameterized alias
|
||||
// A[T] of a type whose underlying and Unaliased type is *T. The test then
|
||||
// instantiates A[Named] and checks that the underlying and Unaliased type
|
||||
// of A[Named] is *Named.
|
||||
//
|
||||
// Requires gotypesalias GODEBUG and aliastypeparams GOEXPERIMENT.
|
||||
func TestNewParameterizedAlias(t *testing.T) {
|
||||
testenv.NeedsGoExperiment(t, "aliastypeparams")
|
||||
|
||||
t.Setenv("GODEBUG", "gotypesalias=1") // needed until gotypesalias is removed (1.27).
|
||||
enabled := aliases.Enabled()
|
||||
if !enabled {
|
||||
t.Fatal("Need materialized aliases enabled")
|
||||
}
|
||||
|
||||
const source = `
|
||||
package p
|
||||
|
||||
type Named int
|
||||
`
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, "hello.go", source, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var conf types.Config
|
||||
pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// type A[T ~int] = *T
|
||||
tparam := types.NewTypeParam(
|
||||
types.NewTypeName(token.NoPos, pkg, "T", nil),
|
||||
types.NewUnion([]*types.Term{types.NewTerm(true, types.Typ[types.Int])}),
|
||||
)
|
||||
ptrT := types.NewPointer(tparam)
|
||||
A := aliases.NewAlias(enabled, token.NoPos, pkg, "A", ptrT, []*types.TypeParam{tparam})
|
||||
if got, want := A.Name(), "A"; got != want {
|
||||
t.Errorf("NewAlias: got %q, want %q", got, want)
|
||||
}
|
||||
|
||||
if got, want := A.Type().Underlying(), ptrT; !types.Identical(got, want) {
|
||||
t.Errorf("A.Type().Underlying (%q) is not identical to %q", got, want)
|
||||
}
|
||||
if got, want := aliases.Unalias(A.Type()), ptrT; !types.Identical(got, want) {
|
||||
t.Errorf("Unalias(A)==%q is not identical to %q", got, want)
|
||||
}
|
||||
|
||||
if _, ok := A.Type().(*aliases.Alias); !ok {
|
||||
t.Errorf("Expected A.Type() to be a types.Alias(). got %q", A.Type())
|
||||
}
|
||||
|
||||
pkg.Scope().Insert(A) // Add A to pkg so it is available to types.Eval.
|
||||
|
||||
named, ok := pkg.Scope().Lookup("Named").(*types.TypeName)
|
||||
if !ok {
|
||||
t.Fatalf("Failed to Lookup(%q) in package %s", "Named", pkg)
|
||||
}
|
||||
ptrNamed := types.NewPointer(named.Type())
|
||||
|
||||
const expr = `A[Named]`
|
||||
tv, err := types.Eval(fset, pkg, 0, expr)
|
||||
if err != nil {
|
||||
t.Fatalf("Eval(%s) failed: %v", expr, err)
|
||||
}
|
||||
|
||||
if got, want := tv.Type.Underlying(), ptrNamed; !types.Identical(got, want) {
|
||||
t.Errorf("A[Named].Type().Underlying (%q) is not identical to %q", got, want)
|
||||
}
|
||||
if got, want := aliases.Unalias(tv.Type), ptrNamed; !types.Identical(got, want) {
|
||||
t.Errorf("Unalias(A[Named])==%q is not identical to %q", got, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user