Skip to content

Commit

Permalink
go/types, types2: implement Alias proposal (export API)
Browse files Browse the repository at this point in the history
This CL exports the previously unexported Alias type and
corresponding functions and methods per issue #63223.

Whether Alias types are used or not is controlled by
the gotypesalias setting with the GODEBUG environment
variable. Setting gotypesalias to "1" enables the Alias
types:

	GODEBUG=gotypesalias=1

By default, gotypesalias is not set.

Adjust test cases that enable/disable the use of Alias
types to use -gotypesalias=1 or -gotypesalias=0 rather
than -alias and -alias=false for consistency and to
avoid confusion.

For #63223.

Change-Id: I51308cad3320981afac97dd8c6f6a416fdb0be55
Reviewed-on: https://go-review.googlesource.com/c/go/+/541737
Run-TryBot: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@google.com>
  • Loading branch information
griesemer authored and gopherbot committed Nov 13, 2023
1 parent 42bd21b commit 30de0b5
Show file tree
Hide file tree
Showing 46 changed files with 209 additions and 159 deletions.
6 changes: 6 additions & 0 deletions api/next/63223.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pkg go/types, func NewAlias(*TypeName, Type) *Alias #63223
pkg go/types, func Unalias(Type) Type #63223
pkg go/types, method (*Alias) Obj() *TypeName #63223
pkg go/types, method (*Alias) String() string #63223
pkg go/types, method (*Alias) Underlying() Type #63223
pkg go/types, type Alias struct #63223
7 changes: 7 additions & 0 deletions doc/godebug.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ patterns and unescape both patterns and request paths by segment.
This behavior can be controlled by the
[`httpmuxgo121` setting](/pkg/net/http/#ServeMux).

Go 1.22 added the [Alias type](/pkg/go/types#Alias) to [go/types](/pkg/go/types)
for the explicit representation of [type aliases](/ref/spec#Type_declarations).
Whether the type checker produces `Alias` types or not is controlled by the
[`gotypesalias` setting](/pkg/go/types#Alias).
For Go 1.22 it defaults to `gotypesalias=0`.
For Go 1.23, `gotypealias=1` will become the default.
This setting will be removed in a future release, Go 1.24 at the earliest.

### Go 1.21

Expand Down
39 changes: 21 additions & 18 deletions src/cmd/compile/internal/types2/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,39 +6,42 @@ package types2

import "fmt"

// Names starting with a _ are intended to be exported eventually
// (go.dev/issue/63223).

// An _Alias represents an alias type.
type _Alias struct {
// An Alias represents an alias type.
// Whether or not Alias types are created is controlled by the
// gotypesalias setting with the GODEBUG environment variable.
// For gotypesalias=1, alias declarations produce an Alias type.
// Otherwise, the alias information is only in the type name,
// which points directly to the actual (aliased) type.
type Alias struct {
obj *TypeName // corresponding declared alias object
fromRHS Type // RHS of type alias declaration; may be an alias
actual Type // actual (aliased) type; never an alias
}

// _NewAlias creates a new Alias type with the given type name and rhs.
// NewAlias creates a new Alias type with the given type name and rhs.
// rhs must not be nil.
func _NewAlias(obj *TypeName, rhs Type) *_Alias {
func NewAlias(obj *TypeName, rhs Type) *Alias {
return (*Checker)(nil).newAlias(obj, rhs)
}

func (a *_Alias) Underlying() Type { return a.actual.Underlying() }
func (a *_Alias) String() string { return TypeString(a, nil) }
func (a *Alias) Obj() *TypeName { return a.obj }
func (a *Alias) Underlying() Type { return a.actual.Underlying() }
func (a *Alias) String() string { return TypeString(a, nil) }

// Type accessors

// _Unalias returns t if it is not an alias type;
// Unalias returns t if it is not an alias type;
// otherwise it follows t's alias chain until it
// reaches a non-alias type which is then returned.
// Consequently, the result is never an alias type.
func _Unalias(t Type) Type {
if a0, _ := t.(*_Alias); a0 != nil {
func Unalias(t Type) Type {
if a0, _ := t.(*Alias); a0 != nil {
if a0.actual != nil {
return a0.actual
}
for a := a0; ; {
t = a.fromRHS
a, _ = t.(*_Alias)
a, _ = t.(*Alias)
if a == nil {
break
}
Expand All @@ -54,15 +57,15 @@ func _Unalias(t Type) Type {
// asNamed returns t as *Named if that is t's
// actual type. It returns nil otherwise.
func asNamed(t Type) *Named {
n, _ := _Unalias(t).(*Named)
n, _ := Unalias(t).(*Named)
return n
}

// newAlias creates a new Alias type with the given type name and rhs.
// rhs must not be nil.
func (check *Checker) newAlias(obj *TypeName, rhs Type) *_Alias {
func (check *Checker) newAlias(obj *TypeName, rhs Type) *Alias {
assert(rhs != nil)
a := &_Alias{obj, rhs, nil}
a := &Alias{obj, rhs, nil}
if obj.typ == nil {
obj.typ = a
}
Expand All @@ -75,6 +78,6 @@ func (check *Checker) newAlias(obj *TypeName, rhs Type) *_Alias {
return a
}

func (a *_Alias) cleanup() {
_Unalias(a)
func (a *Alias) cleanup() {
Unalias(a)
}
5 changes: 0 additions & 5 deletions src/cmd/compile/internal/types2/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,6 @@ type Config struct {
// for unused imports.
DisableUnusedImportCheck bool

// If EnableAlias is set, alias declarations produce an _Alias type.
// Otherwise the alias information is only in the type name, which
// points directly to the actual (aliased) type.
_EnableAlias bool

// If a non-empty ErrorURL format string is provided, it is used
// to format an error URL link that is appended to the first line
// of an error message. ErrorURL must be a format string containing
Expand Down
33 changes: 22 additions & 11 deletions src/cmd/compile/internal/types2/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"go/constant"
"internal/godebug"
"internal/goversion"
. "internal/types/errors"
)
Expand All @@ -21,6 +22,9 @@ var nopos syntax.Pos
// debugging/development support
const debug = false // leave on during development

// gotypesalias controls the use of Alias types.
var gotypesalias = godebug.New("gotypesalias")

// exprInfo stores information about an untyped expression.
type exprInfo struct {
isLhs bool // expression is lhs operand of a shift with delayed type-check
Expand Down Expand Up @@ -93,6 +97,12 @@ type actionDesc struct {
type Checker struct {
// package information
// (initialized by NewChecker, valid for the life-time of checker)

// If enableAlias is set, alias declarations produce an Alias type.
// Otherwise the alias information is only in the type name, which
// points directly to the actual (aliased) type.
enableAlias bool

conf *Config
ctxt *Context // context for de-duplicating instances
pkg *Package
Expand Down Expand Up @@ -153,13 +163,13 @@ func (check *Checker) addDeclDep(to Object) {
}

// Note: The following three alias-related functions are only used
// when _Alias types are not enabled.
// when Alias types are not enabled.

// brokenAlias records that alias doesn't have a determined type yet.
// It also sets alias.typ to Typ[Invalid].
// Not used if check.conf._EnableAlias is set.
// Not used if check.enableAlias is set.
func (check *Checker) brokenAlias(alias *TypeName) {
assert(!check.conf._EnableAlias)
assert(!check.enableAlias)
if check.brokenAliases == nil {
check.brokenAliases = make(map[*TypeName]bool)
}
Expand All @@ -169,14 +179,14 @@ func (check *Checker) brokenAlias(alias *TypeName) {

// validAlias records that alias has the valid type typ (possibly Typ[Invalid]).
func (check *Checker) validAlias(alias *TypeName, typ Type) {
assert(!check.conf._EnableAlias)
assert(!check.enableAlias)
delete(check.brokenAliases, alias)
alias.typ = typ
}

// isBrokenAlias reports whether alias doesn't have a determined type yet.
func (check *Checker) isBrokenAlias(alias *TypeName) bool {
assert(!check.conf._EnableAlias)
assert(!check.enableAlias)
return check.brokenAliases[alias]
}

Expand Down Expand Up @@ -246,12 +256,13 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
// (previously, pkg.goVersion was mutated here: go.dev/issue/61212)

return &Checker{
conf: conf,
ctxt: conf.Context,
pkg: pkg,
Info: info,
objMap: make(map[Object]*declInfo),
impMap: make(map[importKey]*Package),
enableAlias: gotypesalias.Value() == "1",
conf: conf,
ctxt: conf.Context,
pkg: pkg,
Info: info,
objMap: make(map[Object]*declInfo),
impMap: make(map[importKey]*Package),
}
}

Expand Down
16 changes: 12 additions & 4 deletions src/cmd/compile/internal/types2/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func parseFlags(src []byte, flags *flag.FlagSet) error {
// testFiles type-checks the package consisting of the given files, and
// compares the resulting errors with the ERROR annotations in the source.
// Except for manual tests, each package is type-checked twice, once without
// use of _Alias types, and once with _Alias types.
// use of Alias types, and once with Alias types.
//
// The srcs slice contains the file content for the files named in the
// filenames slice. The colDelta parameter specifies the tolerance for position
Expand All @@ -122,9 +122,11 @@ func parseFlags(src []byte, flags *flag.FlagSet) error {
//
// If provided, opts may be used to mutate the Config before type-checking.
func testFiles(t *testing.T, filenames []string, srcs [][]byte, colDelta uint, manual bool, opts ...func(*Config)) {
// Alias types are disabled by default
testFilesImpl(t, filenames, srcs, colDelta, manual, opts...)
if !manual {
testFilesImpl(t, filenames, srcs, colDelta, manual, append(opts, func(conf *Config) { *boolFieldAddr(conf, "_EnableAlias") = true })...)
t.Setenv("GODEBUG", "gotypesalias=1")
testFilesImpl(t, filenames, srcs, colDelta, manual, opts...)
}
}

Expand Down Expand Up @@ -168,15 +170,16 @@ func testFilesImpl(t *testing.T, filenames []string, srcs [][]byte, colDelta uin
}

// apply flag setting (overrides custom configuration)
var goexperiment string
var goexperiment, gotypesalias string
flags := flag.NewFlagSet("", flag.PanicOnError)
flags.StringVar(&conf.GoVersion, "lang", "", "")
flags.StringVar(&goexperiment, "goexperiment", "", "")
flags.BoolVar(&conf.FakeImportC, "fakeImportC", false, "")
flags.BoolVar(boolFieldAddr(&conf, "_EnableAlias"), "alias", false, "")
flags.StringVar(&gotypesalias, "gotypesalias", "", "")
if err := parseFlags(srcs[0], flags); err != nil {
t.Fatal(err)
}

exp, err := buildcfg.ParseGOEXPERIMENT(runtime.GOOS, runtime.GOARCH, goexperiment)
if err != nil {
t.Fatal(err)
Expand All @@ -187,6 +190,11 @@ func testFilesImpl(t *testing.T, filenames []string, srcs [][]byte, colDelta uin
}()
buildcfg.Experiment = *exp

// By default, gotypesalias is not set.
if gotypesalias != "" {
t.Setenv("GODEBUG", "gotypesalias="+gotypesalias)
}

// Provide Config.Info with all maps so that info recording is tested.
info := Info{
Types: make(map[syntax.Expr]TypeAndValue),
Expand Down
8 changes: 4 additions & 4 deletions src/cmd/compile/internal/types2/decl.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ loop:
// the syntactic information. We should consider storing
// this information explicitly in the object.
var alias bool
if check.conf._EnableAlias {
if check.enableAlias {
alias = obj.IsAlias()
} else {
if d := check.objMap[obj]; d != nil {
Expand Down Expand Up @@ -328,7 +328,7 @@ func (check *Checker) cycleError(cycle []Object) {
if tname != nil && tname.IsAlias() {
// If we use Alias nodes, it is initialized with Typ[Invalid].
// TODO(gri) Adjust this code if we initialize with nil.
if !check.conf._EnableAlias {
if !check.enableAlias {
check.validAlias(tname, Typ[Invalid])
}
}
Expand Down Expand Up @@ -514,7 +514,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN
// alias declaration
if aliasDecl {
check.verifyVersionf(tdecl, go1_9, "type aliases")
if check.conf._EnableAlias {
if check.enableAlias {
// TODO(gri) Should be able to use nil instead of Typ[Invalid] to mark
// the alias as incomplete. Currently this causes problems
// with certain cycles. Investigate.
Expand All @@ -523,7 +523,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN
rhs = check.definedType(tdecl.Type, obj)
assert(rhs != nil)
alias.fromRHS = rhs
_Unalias(alias) // resolve alias.actual
Unalias(alias) // resolve alias.actual
} else {
check.brokenAlias(obj)
rhs = check.typ(tdecl.Type)
Expand Down
8 changes: 4 additions & 4 deletions src/cmd/compile/internal/types2/infer.go
Original file line number Diff line number Diff line change
Expand Up @@ -542,8 +542,8 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
case *Basic:
// nothing to do

case *_Alias:
return w.isParameterized(_Unalias(t))
case *Alias:
return w.isParameterized(Unalias(t))

case *Array:
return w.isParameterized(t.elem)
Expand Down Expand Up @@ -696,8 +696,8 @@ func (w *cycleFinder) typ(typ Type) {
case *Basic:
// nothing to do

case *_Alias:
w.typ(_Unalias(t))
case *Alias:
w.typ(Unalias(t))

case *Array:
w.typ(t.elem)
Expand Down
5 changes: 2 additions & 3 deletions src/cmd/compile/internal/types2/issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -989,9 +989,8 @@ type A = []int
type S struct{ A }
`

var conf Config
*boolFieldAddr(&conf, "_EnableAlias") = true
pkg := mustTypecheck(src, &conf, nil)
t.Setenv("GODEBUG", "gotypesalias=1")
pkg := mustTypecheck(src, nil, nil)

S := pkg.Scope().Lookup("S")
if S == nil {
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/types2/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,7 @@ func (check *Checker) newAssertableTo(pos syntax.Pos, V, T Type, cause *string)
// with an underlying pointer type!) and returns its base and true.
// Otherwise it returns (typ, false).
func deref(typ Type) (Type, bool) {
if p, _ := _Unalias(typ).(*Pointer); p != nil {
if p, _ := Unalias(typ).(*Pointer); p != nil {
// p.base should never be nil, but be conservative
if p.base == nil {
if debug {
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/types2/mono.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func (w *monoGraph) assign(pkg *Package, pos syntax.Pos, tpar *TypeParam, targ T
// type parameters.
var do func(typ Type)
do = func(typ Type) {
switch typ := _Unalias(typ).(type) {
switch typ := Unalias(typ).(type) {
default:
panic("unexpected type")

Expand Down
4 changes: 2 additions & 2 deletions src/cmd/compile/internal/types2/named.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,8 +453,8 @@ func (t *Named) AddMethod(m *Func) {
}
}

// TODO(gri) Investigate if _Unalias can be moved to where underlying is set.
func (t *Named) Underlying() Type { return _Unalias(t.resolve().underlying) }
// TODO(gri) Investigate if Unalias can be moved to where underlying is set.
func (t *Named) Underlying() Type { return Unalias(t.resolve().underlying) }
func (t *Named) String() string { return TypeString(t, nil) }

// ----------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/types2/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func (obj *TypeName) IsAlias() bool {
switch t := obj.typ.(type) {
case nil:
return false
// case *_Alias:
// case *Alias:
// handled by default case
case *Basic:
// unsafe.Pointer is not an alias.
Expand Down
Loading

0 comments on commit 30de0b5

Please sign in to comment.