aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/types2
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/compile/internal/types2')
-rw-r--r--src/cmd/compile/internal/types2/alias.go58
-rw-r--r--src/cmd/compile/internal/types2/api.go12
-rw-r--r--src/cmd/compile/internal/types2/api_test.go27
-rw-r--r--src/cmd/compile/internal/types2/builtins.go16
-rw-r--r--src/cmd/compile/internal/types2/call.go2
-rw-r--r--src/cmd/compile/internal/types2/check.go47
-rw-r--r--src/cmd/compile/internal/types2/check_test.go8
-rw-r--r--src/cmd/compile/internal/types2/compiler_internal.go50
-rw-r--r--src/cmd/compile/internal/types2/conversions.go13
-rw-r--r--src/cmd/compile/internal/types2/decl.go7
-rw-r--r--src/cmd/compile/internal/types2/expr.go3
-rw-r--r--src/cmd/compile/internal/types2/format.go33
-rw-r--r--src/cmd/compile/internal/types2/infer.go11
-rw-r--r--src/cmd/compile/internal/types2/instantiate.go62
-rw-r--r--src/cmd/compile/internal/types2/issues_test.go12
-rw-r--r--src/cmd/compile/internal/types2/labels.go12
-rw-r--r--src/cmd/compile/internal/types2/named.go14
-rw-r--r--src/cmd/compile/internal/types2/object.go2
-rw-r--r--src/cmd/compile/internal/types2/operand.go8
-rw-r--r--src/cmd/compile/internal/types2/predicates.go7
-rw-r--r--src/cmd/compile/internal/types2/scope.go14
-rw-r--r--src/cmd/compile/internal/types2/signature.go3
-rw-r--r--src/cmd/compile/internal/types2/stdlib_test.go3
-rw-r--r--src/cmd/compile/internal/types2/stmt.go63
-rw-r--r--src/cmd/compile/internal/types2/subst.go75
-rw-r--r--src/cmd/compile/internal/types2/typeparam.go4
-rw-r--r--src/cmd/compile/internal/types2/typeset.go8
-rw-r--r--src/cmd/compile/internal/types2/typestring.go11
-rw-r--r--src/cmd/compile/internal/types2/typexpr.go34
-rw-r--r--src/cmd/compile/internal/types2/under.go2
-rw-r--r--src/cmd/compile/internal/types2/unify.go31
-rw-r--r--src/cmd/compile/internal/types2/universe.go42
-rw-r--r--src/cmd/compile/internal/types2/util.go2
-rw-r--r--src/cmd/compile/internal/types2/version.go19
34 files changed, 470 insertions, 245 deletions
diff --git a/src/cmd/compile/internal/types2/alias.go b/src/cmd/compile/internal/types2/alias.go
index 9b7a13f81e..5148d5db03 100644
--- a/src/cmd/compile/internal/types2/alias.go
+++ b/src/cmd/compile/internal/types2/alias.go
@@ -4,7 +4,10 @@
package types2
-import "fmt"
+import (
+ "cmd/compile/internal/syntax"
+ "fmt"
+)
// An Alias represents an alias type.
// Whether or not Alias types are created is controlled by the
@@ -14,7 +17,9 @@ import "fmt"
// which points directly to the actual (aliased) type.
type Alias struct {
obj *TypeName // corresponding declared alias object
+ orig *Alias // original, uninstantiated alias
tparams *TypeParamList // type parameters, or nil
+ targs *TypeList // type arguments, or nil
fromRHS Type // RHS of type alias declaration; may be an alias
actual Type // actual (aliased) type; never an alias
}
@@ -28,9 +33,37 @@ func NewAlias(obj *TypeName, rhs Type) *Alias {
return alias
}
-func (a *Alias) Obj() *TypeName { return a.obj }
+// Obj returns the type name for the declaration defining the alias type a.
+// For instantiated types, this is same as the type name of the origin type.
+func (a *Alias) Obj() *TypeName { return a.orig.obj }
+
+func (a *Alias) String() string { return TypeString(a, nil) }
+
+// Underlying returns the [underlying type] of the alias type a, which is the
+// underlying type of the aliased type. Underlying types are never Named,
+// TypeParam, or Alias types.
+//
+// [underlying type]: https://go.dev/ref/spec#Underlying_types.
func (a *Alias) Underlying() Type { return unalias(a).Underlying() }
-func (a *Alias) String() string { return TypeString(a, nil) }
+
+// Origin returns the generic Alias type of which a is an instance.
+// If a is not an instance of a generic alias, Origin returns a.
+func (a *Alias) Origin() *Alias { return a.orig }
+
+// TypeParams returns the type parameters of the alias type a, or nil.
+// A generic Alias and its instances have the same type parameters.
+func (a *Alias) TypeParams() *TypeParamList { return a.tparams }
+
+// SetTypeParams sets the type parameters of the alias type a.
+// The alias a must not have type arguments.
+func (a *Alias) SetTypeParams(tparams []*TypeParam) {
+ assert(a.targs == nil)
+ a.tparams = bindTParams(tparams)
+}
+
+// TypeArgs returns the type arguments used to instantiate the Alias type.
+// If a is not an instance of a generic alias, the result is nil.
+func (a *Alias) TypeArgs() *TypeList { return a.targs }
// Rhs returns the type R on the right-hand side of an alias
// declaration "type A = R", which may be another alias.
@@ -82,7 +115,10 @@ func asNamed(t Type) *Named {
// rhs must not be nil.
func (check *Checker) newAlias(obj *TypeName, rhs Type) *Alias {
assert(rhs != nil)
- a := &Alias{obj, nil, rhs, nil}
+ a := new(Alias)
+ a.obj = obj
+ a.orig = a
+ a.fromRHS = rhs
if obj.typ == nil {
obj.typ = a
}
@@ -95,6 +131,20 @@ func (check *Checker) newAlias(obj *TypeName, rhs Type) *Alias {
return a
}
+// newAliasInstance creates a new alias instance for the given origin and type
+// arguments, recording pos as the position of its synthetic object (for error
+// reporting).
+func (check *Checker) newAliasInstance(pos syntax.Pos, orig *Alias, targs []Type, ctxt *Context) *Alias {
+ assert(len(targs) > 0)
+ obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil)
+ rhs := check.subst(pos, orig.fromRHS, makeSubstMap(orig.TypeParams().list(), targs), nil, ctxt)
+ res := check.newAlias(obj, rhs)
+ res.orig = orig
+ res.tparams = orig.tparams
+ res.targs = newTypeList(targs)
+ return res
+}
+
func (a *Alias) cleanup() {
// Ensure a.actual is set before types are published,
// so Unalias is a pure "getter", not a "setter".
diff --git a/src/cmd/compile/internal/types2/api.go b/src/cmd/compile/internal/types2/api.go
index 029d105e2e..b9ec874d45 100644
--- a/src/cmd/compile/internal/types2/api.go
+++ b/src/cmd/compile/internal/types2/api.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-// Package types declares the data types and implements
+// Package types2 declares the data types and implements
// the algorithms for type-checking of Go packages. Use
// Config.Check to invoke the type checker for a package.
// Alternatively, create a new type checker with NewChecker
@@ -176,9 +176,13 @@ type Config struct {
// exactly one "%s" format, e.g. "[go.dev/e/%s]".
ErrorURL string
- // 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.
+ // 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.
+ //
+ // This setting must not differ among concurrent type-checking operations,
+ // since it affects the behavior of Universe.Lookup("any").
+ //
// This flag will eventually be removed (with Go 1.24 at the earliest).
EnableAlias bool
}
diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go
index cf3c105f6c..5126ac5111 100644
--- a/src/cmd/compile/internal/types2/api_test.go
+++ b/src/cmd/compile/internal/types2/api_test.go
@@ -1867,7 +1867,10 @@ func sameSlice(a, b []int) bool {
// the correct result at various positions within the source.
func TestScopeLookupParent(t *testing.T) {
imports := make(testImporter)
- conf := Config{Importer: imports}
+ conf := Config{
+ Importer: imports,
+ EnableAlias: true, // must match default Universe.Lookup behavior
+ }
var info Info
makePkg := func(path, src string) {
var err error
@@ -3022,3 +3025,25 @@ type C = int
t.Errorf("A.Rhs = %s, want %s", got, want)
}
}
+
+// Test the hijacking described of "any" described in golang/go#66921, for
+// (concurrent) type checking.
+func TestAnyHijacking_Check(t *testing.T) {
+ for _, enableAlias := range []bool{false, true} {
+ t.Run(fmt.Sprintf("EnableAlias=%t", enableAlias), func(t *testing.T) {
+ var wg sync.WaitGroup
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ pkg := mustTypecheck("package p; var x any", &Config{EnableAlias: enableAlias}, nil)
+ x := pkg.Scope().Lookup("x")
+ if _, gotAlias := x.Type().(*Alias); gotAlias != enableAlias {
+ t.Errorf(`Lookup("x").Type() is %T: got Alias: %t, want %t`, x.Type(), gotAlias, enableAlias)
+ }
+ }()
+ }
+ wg.Wait()
+ })
+ }
+}
diff --git a/src/cmd/compile/internal/types2/builtins.go b/src/cmd/compile/internal/types2/builtins.go
index b897a55212..808d39fd24 100644
--- a/src/cmd/compile/internal/types2/builtins.go
+++ b/src/cmd/compile/internal/types2/builtins.go
@@ -25,7 +25,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
if hasDots(call) && id != _Append {
check.errorf(dddErrPos(call),
InvalidDotDotDot,
- invalidOp+"invalid use of ... with built-in %s", quote(bin.name))
+ invalidOp+"invalid use of ... with built-in %s", bin.name)
check.use(argList...)
return
}
@@ -210,7 +210,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
if id == _Len {
code = InvalidLen
}
- check.errorf(x, code, invalidArg+"%s for %s", x, quote(bin.name))
+ check.errorf(x, code, invalidArg+"%s for built-in %s", x, bin.name)
}
return
}
@@ -533,7 +533,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
case _Max, _Min:
// max(x, ...)
// min(x, ...)
- check.verifyVersionf(call.Fun, go1_21, quote(bin.name))
+ check.verifyVersionf(call.Fun, go1_21, "built-in %s", bin.name)
op := token.LSS
if id == _Max {
@@ -576,7 +576,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
if x.mode != constant_ {
x.mode = value
// A value must not be untyped.
- check.assignment(x, &emptyInterface, "argument to "+quote(bin.name))
+ check.assignment(x, &emptyInterface, "argument to built-in "+bin.name)
if x.mode == invalid {
return
}
@@ -641,7 +641,7 @@ func (check *Checker) builtin(x *operand, call *syntax.CallExpr, id builtinId) (
if nargs > 0 {
params = make([]Type, nargs)
for i, a := range args {
- check.assignment(a, nil, "argument to "+quote(predeclaredFuncs[id].name))
+ check.assignment(a, nil, "argument to built-in"+predeclaredFuncs[id].name)
if a.mode == invalid {
return
}
@@ -960,7 +960,7 @@ func hasVarSize(t Type, seen map[*Named]bool) (varSized bool) {
// applyTypeFunc returns nil.
// If x is not a type parameter, the result is f(x).
func (check *Checker) applyTypeFunc(f func(Type) Type, x *operand, id builtinId) Type {
- if tp, _ := x.typ.(*TypeParam); tp != nil {
+ if tp, _ := Unalias(x.typ).(*TypeParam); tp != nil {
// Test if t satisfies the requirements for the argument
// type and collect possible result types at the same time.
var terms []*Term
@@ -992,7 +992,7 @@ func (check *Checker) applyTypeFunc(f func(Type) Type, x *operand, id builtinId)
default:
panic("unreachable")
}
- check.softErrorf(x, code, "%s not supported as argument to %s for go1.18 (see go.dev/issue/50937)", x, quote(predeclaredFuncs[id].name))
+ check.softErrorf(x, code, "%s not supported as argument to built-in %s for go1.18 (see go.dev/issue/50937)", x, predeclaredFuncs[id].name)
// Construct a suitable new type parameter for the result type.
// The type parameter is placed in the current package so export/import
@@ -1026,7 +1026,7 @@ func makeSig(res Type, args ...Type) *Signature {
// arrayPtrDeref returns A if typ is of the form *A and A is an array;
// otherwise it returns typ.
func arrayPtrDeref(typ Type) Type {
- if p, ok := typ.(*Pointer); ok {
+ if p, ok := Unalias(typ).(*Pointer); ok {
if a, _ := under(p.base).(*Array); a != nil {
return a
}
diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go
index ca9772ff41..7df4e8250e 100644
--- a/src/cmd/compile/internal/types2/call.go
+++ b/src/cmd/compile/internal/types2/call.go
@@ -719,7 +719,7 @@ func (check *Checker) selector(x *operand, e *syntax.SelectorExpr, def *TypeName
goto Error
}
if !exp.Exported() {
- check.errorf(e.Sel, UnexportedName, "%s not exported by package %s", quote(sel), quote(pkg.name))
+ check.errorf(e.Sel, UnexportedName, "name %s not exported by package %s", sel, pkg.name)
// ok to continue
}
}
diff --git a/src/cmd/compile/internal/types2/check.go b/src/cmd/compile/internal/types2/check.go
index ee7e2e8683..91ad474e9d 100644
--- a/src/cmd/compile/internal/types2/check.go
+++ b/src/cmd/compile/internal/types2/check.go
@@ -10,8 +10,8 @@ import (
"cmd/compile/internal/syntax"
"fmt"
"go/constant"
- "internal/godebug"
. "internal/types/errors"
+ "sync/atomic"
)
// nopos indicates an unknown position
@@ -20,11 +20,28 @@ var nopos syntax.Pos
// debugging/development support
const debug = false // leave on during development
-// gotypesalias controls the use of Alias types.
-// As of Apr 16 2024 they are used by default.
-// To disable their use, set GODEBUG to gotypesalias=0.
-// This GODEBUG flag will be removed in the near future (tentatively Go 1.24).
-var gotypesalias = godebug.New("gotypesalias")
+// _aliasAny changes the behavior of [Scope.Lookup] for "any" in the
+// [Universe] scope.
+//
+// This is necessary because while Alias creation is controlled by
+// [Config.EnableAlias], the representation of "any" is a global. In
+// [Scope.Lookup], we select this global representation based on the result of
+// [aliasAny], but as a result need to guard against this behavior changing
+// during the type checking pass. Therefore we implement the following rule:
+// any number of goroutines can type check concurrently with the same
+// EnableAlias value, but if any goroutine tries to type check concurrently
+// with a different EnableAlias value, we panic.
+//
+// To achieve this, _aliasAny is a state machine:
+//
+// 0: no type checking is occurring
+// negative: type checking is occurring without EnableAlias set
+// positive: type checking is occurring with EnableAlias set
+var _aliasAny int32
+
+func aliasAny() bool {
+ return atomic.LoadInt32(&_aliasAny) >= 0 // default true
+}
// exprInfo stores information about an untyped expression.
type exprInfo struct {
@@ -293,7 +310,7 @@ func (check *Checker) initFiles(files []*syntax.File) {
check.files = append(check.files, file)
default:
- check.errorf(file, MismatchedPkgName, "package %s; expected %s", quote(name), quote(pkg.name))
+ check.errorf(file, MismatchedPkgName, "package %s; expected package %s", name, pkg.name)
// ignore this file
}
}
@@ -356,7 +373,7 @@ func (check *Checker) initFiles(files []*syntax.File) {
check.errorf(file.PkgName, TooNew, "file requires newer Go version %v", fileVersion)
}
}
- versions[base(file.Pos())] = v // base(file.Pos()) may be nil for tests
+ versions[file.Pos().FileBase()] = v // file.Pos().FileBase() may be nil for tests
}
}
@@ -397,6 +414,20 @@ func (check *Checker) Files(files []*syntax.File) (err error) {
// syntax is properly type annotated even in a package containing
// errors.
func (check *Checker) checkFiles(files []*syntax.File) {
+ // Ensure that EnableAlias is consistent among concurrent type checking
+ // operations. See the documentation of [_aliasAny] for details.
+ if check.conf.EnableAlias {
+ if atomic.AddInt32(&_aliasAny, 1) <= 0 {
+ panic("EnableAlias set while !EnableAlias type checking is ongoing")
+ }
+ defer atomic.AddInt32(&_aliasAny, -1)
+ } else {
+ if atomic.AddInt32(&_aliasAny, -1) >= 0 {
+ panic("!EnableAlias set while EnableAlias type checking is ongoing")
+ }
+ defer atomic.AddInt32(&_aliasAny, 1)
+ }
+
print := func(msg string) {
if check.conf.Trace {
fmt.Println()
diff --git a/src/cmd/compile/internal/types2/check_test.go b/src/cmd/compile/internal/types2/check_test.go
index 63f831aa92..8b7b5316f0 100644
--- a/src/cmd/compile/internal/types2/check_test.go
+++ b/src/cmd/compile/internal/types2/check_test.go
@@ -247,17 +247,17 @@ func testFilesImpl(t *testing.T, filenames []string, srcs [][]byte, colDelta uin
panic("unreachable")
}
}
- pattern, err := strconv.Unquote(strings.TrimSpace(pattern))
+ unquoted, err := strconv.Unquote(strings.TrimSpace(pattern))
if err != nil {
- t.Errorf("%s:%d:%d: %v", filename, line, want.Pos.Col(), err)
+ t.Errorf("%s:%d:%d: invalid ERROR pattern (cannot unquote %s)", filename, line, want.Pos.Col(), pattern)
continue
}
if substr {
- if !strings.Contains(gotMsg, pattern) {
+ if !strings.Contains(gotMsg, unquoted) {
continue
}
} else {
- rx, err := regexp.Compile(pattern)
+ rx, err := regexp.Compile(unquoted)
if err != nil {
t.Errorf("%s:%d:%d: %v", filename, line, want.Pos.Col(), err)
continue
diff --git a/src/cmd/compile/internal/types2/compiler_internal.go b/src/cmd/compile/internal/types2/compiler_internal.go
new file mode 100644
index 0000000000..790a6779e4
--- /dev/null
+++ b/src/cmd/compile/internal/types2/compiler_internal.go
@@ -0,0 +1,50 @@
+// 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 types2
+
+import (
+ "cmd/compile/internal/syntax"
+ "fmt"
+)
+
+// This file should not be copied to go/types. See go.dev/issue/67477
+
+// RenameResult takes an array of (result) fields and an index, and if the indexed field
+// does not have a name and if the result in the signature also does not have a name,
+// then the signature and field are renamed to
+//
+// fmt.Sprintf("#rv%d", i+1)`
+//
+// the newly named object is inserted into the signature's scope,
+// and the object and new field name are returned.
+//
+// The intended use for RenameResult is to allow rangefunc to assign results within a closure.
+// This is a hack, as narrowly targeted as possible to discourage abuse.
+func (s *Signature) RenameResult(results []*syntax.Field, i int) (*Var, *syntax.Name) {
+ a := results[i]
+ obj := s.Results().At(i)
+
+ if !(obj.name == "" || obj.name == "_" && a.Name == nil || a.Name.Value == "_") {
+ panic("Cannot change an existing name")
+ }
+
+ pos := a.Pos()
+ typ := a.Type.GetTypeInfo().Type
+
+ name := fmt.Sprintf("#rv%d", i+1)
+ obj.name = name
+ s.scope.Insert(obj)
+ obj.setScopePos(pos)
+
+ tv := syntax.TypeAndValue{Type: typ}
+ tv.SetIsValue()
+
+ n := syntax.NewName(pos, obj.Name())
+ n.SetTypeInfo(tv)
+
+ a.Name = n
+
+ return obj, n
+}
diff --git a/src/cmd/compile/internal/types2/conversions.go b/src/cmd/compile/internal/types2/conversions.go
index b8d8f6e150..43208c3d9b 100644
--- a/src/cmd/compile/internal/types2/conversions.go
+++ b/src/cmd/compile/internal/types2/conversions.go
@@ -56,7 +56,7 @@ func (check *Checker) conversion(x *operand, T Type) {
// If T's type set is empty, or if it doesn't
// have specific types, constant x cannot be
// converted.
- ok = T.(*TypeParam).underIs(func(u Type) bool {
+ ok = Unalias(T).(*TypeParam).underIs(func(u Type) bool {
// u is nil if there are no specific type terms
if u == nil {
cause = check.sprintf("%s does not contain specific types", T)
@@ -139,13 +139,16 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool {
return true
}
- // "V and T have identical underlying types if tags are ignored
- // and V and T are not type parameters"
- V := x.typ
+ origT := T
+ V := Unalias(x.typ)
+ T = Unalias(T)
Vu := under(V)
Tu := under(T)
Vp, _ := V.(*TypeParam)
Tp, _ := T.(*TypeParam)
+
+ // "V and T have identical underlying types if tags are ignored
+ // and V and T are not type parameters"
if IdenticalIgnoreTags(Vu, Tu) && Vp == nil && Tp == nil {
return true
}
@@ -267,7 +270,7 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool {
}
x.typ = V.typ
if !x.convertibleTo(check, T, cause) {
- errorf("cannot convert %s (in %s) to type %s", V.typ, Vp, T)
+ errorf("cannot convert %s (in %s) to type %s", V.typ, Vp, origT)
return false
}
return true
diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go
index 246568e25e..6a266de7fd 100644
--- a/src/cmd/compile/internal/types2/decl.go
+++ b/src/cmd/compile/internal/types2/decl.go
@@ -8,6 +8,7 @@ import (
"cmd/compile/internal/syntax"
"fmt"
"go/constant"
+ "internal/buildcfg"
. "internal/types/errors"
)
@@ -522,6 +523,10 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *TypeN
// handle type parameters even if not allowed (Alias type is supported)
if tparam0 != nil {
+ if !versionErr && !buildcfg.Experiment.AliasTypeParams {
+ check.error(tdecl, UnsupportedFeature, "generic type alias requires GOEXPERIMENT=aliastypeparams")
+ versionErr = true
+ }
check.openScope(tdecl, "type parameters")
defer check.closeScope()
check.collectTypeParams(&alias.tparams, tdecl.TParamList)
@@ -738,7 +743,7 @@ func (check *Checker) checkFieldUniqueness(base *Named) {
// For historical consistency, we report the primary error on the
// method, and the alt decl on the field.
err := check.newError(DuplicateFieldAndMethod)
- err.addf(alt, "field and method with the same name %s", quote(fld.name))
+ err.addf(alt, "field and method with the same name %s", fld.name)
err.addAltDecl(fld)
err.report()
}
diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go
index b2ff262762..92949a924d 100644
--- a/src/cmd/compile/internal/types2/expr.go
+++ b/src/cmd/compile/internal/types2/expr.go
@@ -131,6 +131,7 @@ var op2str2 = [...]string{
// If typ is a type parameter, underIs returns the result of typ.underIs(f).
// Otherwise, underIs returns the result of f(under(typ)).
func underIs(typ Type, f func(Type) bool) bool {
+ typ = Unalias(typ)
if tpar, _ := typ.(*TypeParam); tpar != nil {
return tpar.underIs(f)
}
@@ -1012,7 +1013,7 @@ func (check *Checker) nonGeneric(T *target, x *operand) {
}
var what string
switch t := x.typ.(type) {
- case *Named:
+ case *Alias, *Named:
if isGeneric(t) {
what = "type"
}
diff --git a/src/cmd/compile/internal/types2/format.go b/src/cmd/compile/internal/types2/format.go
index 1b9cf606b7..442d219d1a 100644
--- a/src/cmd/compile/internal/types2/format.go
+++ b/src/cmd/compile/internal/types2/format.go
@@ -14,39 +14,6 @@ import (
"strings"
)
-// quote encloses s in `' quotes, as in `foo', except for _,
-// which is left alone.
-//
-// Use to prevent confusion when user supplied names alter the
-// meaning of an error message.
-//
-// For instance, report
-//
-// duplicate method `wanted'
-//
-// rather than
-//
-// duplicate method wanted
-//
-// Exceptions:
-//
-// - don't quote _:
-// `_' is ugly and not necessary
-// - don't quote after a ":" as there's no need for it:
-// undefined name: foo
-// - don't quote if the name is used correctly in a statement:
-// goto L jumps over variable declaration
-//
-// quote encloses s in `' quotes, as in `foo',
-// except for _ which is left alone.
-func quote(s string) string {
- if s == "_" {
- // `_' is ugly and not necessary
- return s
- }
- return "`" + s + "'"
-}
-
func sprintf(qf Qualifier, tpSubscripts bool, format string, args ...any) string {
for i, arg := range args {
switch a := arg.(type) {
diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go
index 1cdc4e79a2..122ac9e04f 100644
--- a/src/cmd/compile/internal/types2/infer.go
+++ b/src/cmd/compile/internal/types2/infer.go
@@ -184,6 +184,10 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
// Thus, for untyped arguments we only need to look at parameter types
// that are single type parameters.
// Also, untyped nils don't have a default type and can be ignored.
+ // Finally, it's not possible to have an alias type denoting a type
+ // parameter declared by the current function and use it in the same
+ // function signature; hence we don't need to Unalias before the
+ // .(*TypeParam) type assertion above.
untyped = append(untyped, i)
}
}
@@ -306,7 +310,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
// maximum untyped type for each of those parameters, if possible.
var maxUntyped map[*TypeParam]Type // lazily allocated (we may not need it)
for _, index := range untyped {
- tpar := params.At(index).typ.(*TypeParam) // is type parameter by construction of untyped
+ tpar := params.At(index).typ.(*TypeParam) // is type parameter (no alias) by construction of untyped
if u.at(tpar) == nil {
arg := args[index] // arg corresponding to tpar
if maxUntyped == nil {
@@ -689,6 +693,7 @@ type cycleFinder struct {
}
func (w *cycleFinder) typ(typ Type) {
+ typ = Unalias(typ)
if w.seen[typ] {
// We have seen typ before. If it is one of the type parameters
// in w.tparams, iterative substitution will lead to infinite expansion.
@@ -710,8 +715,8 @@ func (w *cycleFinder) typ(typ Type) {
case *Basic:
// nothing to do
- case *Alias:
- w.typ(Unalias(t))
+ // *Alias:
+ // This case should not occur because of Unalias(typ) at the top.
case *Array:
w.typ(t.elem)
diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go
index a25cb141ec..72227ab122 100644
--- a/src/cmd/compile/internal/types2/instantiate.go
+++ b/src/cmd/compile/internal/types2/instantiate.go
@@ -14,12 +14,20 @@ import (
. "internal/types/errors"
)
+// A genericType implements access to its type parameters.
+type genericType interface {
+ Type
+ TypeParams() *TypeParamList
+}
+
// Instantiate instantiates the type orig with the given type arguments targs.
-// orig must be a *Named or a *Signature type. If there is no error, the
-// resulting Type is an instantiated type of the same kind (either a *Named or
-// a *Signature). Methods attached to a *Named type are also instantiated, and
-// associated with a new *Func that has the same position as the original
-// method, but nil function scope.
+// orig must be an *Alias, *Named, or *Signature type. If there is no error,
+// the resulting Type is an instantiated type of the same kind (*Alias, *Named
+// or *Signature, respectively).
+//
+// Methods attached to a *Named type are also instantiated, and associated with
+// a new *Func that has the same position as the original method, but nil function
+// scope.
//
// If ctxt is non-nil, it may be used to de-duplicate the instance against
// previous instances with the same identity. As a special case, generic
@@ -29,10 +37,10 @@ import (
// not guarantee that identical instances are deduplicated in all cases.
//
// If validate is set, Instantiate verifies that the number of type arguments
-// and parameters match, and that the type arguments satisfy their
-// corresponding type constraints. If verification fails, the resulting error
-// may wrap an *ArgumentError indicating which type argument did not satisfy
-// its corresponding type parameter constraint, and why.
+// and parameters match, and that the type arguments satisfy their respective
+// type constraints. If verification fails, the resulting error may wrap an
+// *ArgumentError indicating which type argument did not satisfy its type parameter
+// constraint, and why.
//
// If validate is not set, Instantiate does not verify the type argument count
// or whether the type arguments satisfy their constraints. Instantiate is
@@ -41,17 +49,15 @@ import (
// count is incorrect; for *Named types, a panic may occur later inside the
// *Named API.
func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, error) {
+ assert(len(targs) > 0)
if ctxt == nil {
ctxt = NewContext()
}
+ orig_ := orig.(genericType) // signature of Instantiate must not change for backward-compatibility
+
if validate {
- var tparams []*TypeParam
- switch t := orig.(type) {
- case *Named:
- tparams = t.TypeParams().list()
- case *Signature:
- tparams = t.TypeParams().list()
- }
+ tparams := orig_.TypeParams().list()
+ assert(len(tparams) > 0)
if len(targs) != len(tparams) {
return nil, fmt.Errorf("got %d type arguments but %s has %d type parameters", len(targs), orig, len(tparams))
}
@@ -60,7 +66,7 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e
}
}
- inst := (*Checker)(nil).instance(nopos, orig, targs, nil, ctxt)
+ inst := (*Checker)(nil).instance(nopos, orig_, targs, nil, ctxt)
return inst, nil
}
@@ -75,7 +81,7 @@ func Instantiate(ctxt *Context, orig Type, targs []Type, validate bool) (Type, e
// must be non-nil.
//
// For Named types the resulting instance may be unexpanded.
-func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, expanding *Named, ctxt *Context) (res Type) {
+func (check *Checker) instance(pos syntax.Pos, orig genericType, targs []Type, expanding *Named, ctxt *Context) (res Type) {
// The order of the contexts below matters: we always prefer instances in the
// expanding instance context in order to preserve reference cycles.
//
@@ -97,8 +103,9 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, expandin
hashes[i] = ctxt.instanceHash(orig, targs)
}
- // If local is non-nil, updateContexts return the type recorded in
- // local.
+ // Record the result in all contexts.
+ // Prefer to re-use existing types from expanding context, if it exists, to reduce
+ // the memory pinned by the Named type.
updateContexts := func(res Type) Type {
for i := len(ctxts) - 1; i >= 0; i-- {
res = ctxts[i].update(hashes[i], orig, targs, res)
@@ -118,6 +125,21 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, expandin
case *Named:
res = check.newNamedInstance(pos, orig, targs, expanding) // substituted lazily
+ case *Alias:
+ // TODO(gri) is this correct?
+ assert(expanding == nil) // Alias instances cannot be reached from Named types
+
+ tparams := orig.TypeParams()
+ // TODO(gri) investigate if this is needed (type argument and parameter count seem to be correct here)
+ if !check.validateTArgLen(pos, orig.String(), tparams.Len(), len(targs)) {
+ return Typ[Invalid]
+ }
+ if tparams.Len() == 0 {
+ return orig // nothing to do (minor optimization)
+ }
+
+ return check.newAliasInstance(pos, orig, targs, ctxt)
+
case *Signature:
assert(expanding == nil) // function instances cannot be reached from Named types
diff --git a/src/cmd/compile/internal/types2/issues_test.go b/src/cmd/compile/internal/types2/issues_test.go
index b087550b80..b4da3c0b91 100644
--- a/src/cmd/compile/internal/types2/issues_test.go
+++ b/src/cmd/compile/internal/types2/issues_test.go
@@ -258,11 +258,11 @@ func TestIssue22525(t *testing.T) {
conf := Config{Error: func(err error) { got += err.Error() + "\n" }}
typecheck(src, &conf, nil) // do not crash
want := "\n" +
- "p:1:27: `a' declared and not used\n" +
- "p:1:30: `b' declared and not used\n" +
- "p:1:33: `c' declared and not used\n" +
- "p:1:36: `d' declared and not used\n" +
- "p:1:39: `e' declared and not used\n"
+ "p:1:27: declared and not used: a\n" +
+ "p:1:30: declared and not used: b\n" +
+ "p:1:33: declared and not used: c\n" +
+ "p:1:36: declared and not used: d\n" +
+ "p:1:39: declared and not used: e\n"
if got != want {
t.Errorf("got: %swant: %s", got, want)
}
@@ -600,7 +600,7 @@ var _ T = template /* ERRORx "cannot use.*text/template.* as T value" */.Templat
}
func TestIssue50646(t *testing.T) {
- anyType := Universe.Lookup("any").Type()
+ anyType := Universe.Lookup("any").Type().Underlying()
comparableType := Universe.Lookup("comparable").Type()
if !Comparable(anyType) {
diff --git a/src/cmd/compile/internal/types2/labels.go b/src/cmd/compile/internal/types2/labels.go
index 61b3ca7511..548df7925b 100644
--- a/src/cmd/compile/internal/types2/labels.go
+++ b/src/cmd/compile/internal/types2/labels.go
@@ -26,13 +26,11 @@ func (check *Checker) labels(body *syntax.BlockStmt) {
name := jmp.Label.Value
if alt := all.Lookup(name); alt != nil {
msg = "goto %s jumps into block"
- alt.(*Label).used = true // avoid another error
code = JumpIntoBlock
- // don't quote name here because "goto L" matches the code
+ alt.(*Label).used = true // avoid another error
} else {
msg = "label %s not declared"
code = UndeclaredLabel
- name = quote(name)
}
check.errorf(jmp.Label, code, msg, name)
}
@@ -41,7 +39,7 @@ func (check *Checker) labels(body *syntax.BlockStmt) {
for name, obj := range all.elems {
obj = resolve(name, obj)
if lbl := obj.(*Label); !lbl.used {
- check.softErrorf(lbl.pos, UnusedLabel, "label %s declared and not used", quote(lbl.name))
+ check.softErrorf(lbl.pos, UnusedLabel, "label %s declared and not used", lbl.name)
}
}
}
@@ -137,7 +135,7 @@ func (check *Checker) blockBranches(all *Scope, parent *block, lstmt *syntax.Lab
if alt := all.Insert(lbl); alt != nil {
err := check.newError(DuplicateLabel)
err.soft = true
- err.addf(lbl.pos, "label %s already declared", quote(name))
+ err.addf(lbl.pos, "label %s already declared", name)
err.addAltDecl(alt)
err.report()
// ok to continue
@@ -193,7 +191,7 @@ func (check *Checker) blockBranches(all *Scope, parent *block, lstmt *syntax.Lab
}
}
if !valid {
- check.errorf(s.Label, MisplacedLabel, "invalid break label %s", quote(name))
+ check.errorf(s.Label, MisplacedLabel, "invalid break label %s", name)
return
}
@@ -208,7 +206,7 @@ func (check *Checker) blockBranches(all *Scope, parent *block, lstmt *syntax.Lab
}
}
if !valid {
- check.errorf(s.Label, MisplacedLabel, "invalid continue label %s", quote(name))
+ check.errorf(s.Label, MisplacedLabel, "invalid continue label %s", name)
return
}
diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go
index aa7ab00c33..1859b27aa4 100644
--- a/src/cmd/compile/internal/types2/named.go
+++ b/src/cmd/compile/internal/types2/named.go
@@ -485,9 +485,17 @@ func (t *Named) methodIndex(name string, foldCase bool) int {
return -1
}
-// 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) }
+// Underlying returns the [underlying type] of the named type t, resolving all
+// forwarding declarations. Underlying types are never Named, TypeParam, or
+// Alias types.
+//
+// [underlying type]: https://go.dev/ref/spec#Underlying_types.
+func (t *Named) Underlying() Type {
+ // TODO(gri) Investigate if Unalias can be moved to where underlying is set.
+ return Unalias(t.resolve().underlying)
+}
+
+func (t *Named) String() string { return TypeString(t, nil) }
// ----------------------------------------------------------------------------
// Implementation
diff --git a/src/cmd/compile/internal/types2/object.go b/src/cmd/compile/internal/types2/object.go
index 3026777cad..f9a25473a1 100644
--- a/src/cmd/compile/internal/types2/object.go
+++ b/src/cmd/compile/internal/types2/object.go
@@ -577,7 +577,7 @@ func writeObject(buf *bytes.Buffer, obj Object, qf Qualifier) {
// Special handling for any: because WriteType will format 'any' as 'any',
// resulting in the object string `type any = any` rather than `type any =
// interface{}`. To avoid this, swap in a different empty interface.
- if obj == universeAny {
+ if obj.Name() == "any" && obj.Parent() == Universe {
assert(Identical(typ, &emptyInterface))
typ = &emptyInterface
}
diff --git a/src/cmd/compile/internal/types2/operand.go b/src/cmd/compile/internal/types2/operand.go
index 15ec86fb5e..a176b9faf3 100644
--- a/src/cmd/compile/internal/types2/operand.go
+++ b/src/cmd/compile/internal/types2/operand.go
@@ -186,7 +186,7 @@ func operandString(x *operand, qf Qualifier) string {
}
buf.WriteString(intro)
WriteType(&buf, x.typ, qf)
- if tpar, _ := x.typ.(*TypeParam); tpar != nil {
+ if tpar, _ := Unalias(x.typ).(*TypeParam); tpar != nil {
buf.WriteString(" constrained by ")
WriteType(&buf, tpar.bound, qf) // do not compute interface type sets here
// If we have the type set and it's empty, say so for better error messages.
@@ -260,7 +260,9 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
return true, 0 // avoid spurious errors
}
- V := x.typ
+ origT := T
+ V := Unalias(x.typ)
+ T = Unalias(T)
// x's type is identical to T
if Identical(V, T) {
@@ -386,7 +388,7 @@ func (x *operand) assignableTo(check *Checker, T Type, cause *string) (bool, Cod
x.typ = V.typ
ok, code = x.assignableTo(check, T, cause)
if !ok {
- errorf("cannot assign %s (in %s) to %s", V.typ, Vp, T)
+ errorf("cannot assign %s (in %s) to %s", V.typ, Vp, origT)
return false
}
return true
diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go
index 6d9e6ec760..6403be6bcb 100644
--- a/src/cmd/compile/internal/types2/predicates.go
+++ b/src/cmd/compile/internal/types2/predicates.go
@@ -137,6 +137,9 @@ func hasEmptyTypeset(t Type) bool {
// TODO(gri) should we include signatures or assert that they are not present?
func isGeneric(t Type) bool {
// A parameterized type is only generic if it doesn't have an instantiation already.
+ if alias, _ := t.(*Alias); alias != nil && alias.tparams != nil && alias.targs == nil {
+ return true
+ }
named := asNamed(t)
return named != nil && named.obj != nil && named.inst == nil && named.TypeParams().Len() > 0
}
@@ -518,7 +521,9 @@ func identicalInstance(xorig Type, xargs []Type, yorig Type, yargs []Type) bool
// it returns the incoming type for all other types. The default type
// for untyped nil is untyped nil.
func Default(t Type) Type {
- if t, ok := Unalias(t).(*Basic); ok {
+ // Alias and named types cannot denote untyped types
+ // so there's no need to call Unalias or under, below.
+ if t, _ := t.(*Basic); t != nil {
switch t.kind {
case UntypedBool:
return Typ[Bool]
diff --git a/src/cmd/compile/internal/types2/scope.go b/src/cmd/compile/internal/types2/scope.go
index b75e5cbaf7..f5ad25e81e 100644
--- a/src/cmd/compile/internal/types2/scope.go
+++ b/src/cmd/compile/internal/types2/scope.go
@@ -68,7 +68,19 @@ func (s *Scope) Child(i int) *Scope { return s.children[i] }
// Lookup returns the object in scope s with the given name if such an
// object exists; otherwise the result is nil.
func (s *Scope) Lookup(name string) Object {
- return resolve(name, s.elems[name])
+ obj := resolve(name, s.elems[name])
+ // Hijack Lookup for "any": with gotypesalias=1, we want the Universe to
+ // return an Alias for "any", and with gotypesalias=0 we want to return
+ // the legacy representation of aliases.
+ //
+ // This is rather tricky, but works out after auditing of the usage of
+ // s.elems. The only external API to access scope elements is Lookup.
+ //
+ // TODO: remove this once gotypesalias=0 is no longer supported.
+ if obj == universeAnyAlias && !aliasAny() {
+ return universeAnyNoAlias
+ }
+ return obj
}
// LookupParent follows the parent chain of scopes starting with s until
diff --git a/src/cmd/compile/internal/types2/signature.go b/src/cmd/compile/internal/types2/signature.go
index bb4d32b016..7a5a2c155f 100644
--- a/src/cmd/compile/internal/types2/signature.go
+++ b/src/cmd/compile/internal/types2/signature.go
@@ -73,9 +73,6 @@ func (s *Signature) Recv() *Var { return s.recv }
// TypeParams returns the type parameters of signature s, or nil.
func (s *Signature) TypeParams() *TypeParamList { return s.tparams }
-// SetTypeParams sets the type parameters of signature s.
-func (s *Signature) SetTypeParams(tparams []*TypeParam) { s.tparams = bindTParams(tparams) }
-
// RecvTypeParams returns the receiver type parameters of signature s, or nil.
func (s *Signature) RecvTypeParams() *TypeParamList { return s.rparams }
diff --git a/src/cmd/compile/internal/types2/stdlib_test.go b/src/cmd/compile/internal/types2/stdlib_test.go
index 405af78572..ed79b92c46 100644
--- a/src/cmd/compile/internal/types2/stdlib_test.go
+++ b/src/cmd/compile/internal/types2/stdlib_test.go
@@ -396,7 +396,8 @@ func typecheckFiles(path string, filenames []string, importer Importer) (*Packag
Error: func(err error) {
errs = append(errs, err)
},
- Importer: importer,
+ Importer: importer,
+ EnableAlias: true,
}
info := Info{Uses: make(map[*syntax.Name]Object)}
pkg, _ := conf.Check(path, files, &info)
diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go
index 7fd7009e13..656f0e2eb2 100644
--- a/src/cmd/compile/internal/types2/stmt.go
+++ b/src/cmd/compile/internal/types2/stmt.go
@@ -64,7 +64,7 @@ func (check *Checker) usage(scope *Scope) {
return cmpPos(unused[i].pos, unused[j].pos) < 0
})
for _, v := range unused {
- check.softErrorf(v.pos, UnusedVar, "%s declared and not used", quote(v.name))
+ check.softErrorf(v.pos, UnusedVar, "declared and not used: %s", v.name)
}
for _, scope := range scope.children {
@@ -496,7 +496,7 @@ func (check *Checker) stmt(ctxt stmtContext, s syntax.Stmt) {
for _, obj := range res.vars {
if alt := check.lookup(obj.name); alt != nil && alt != obj {
err := check.newError(OutOfScopeResult)
- err.addf(s, "result parameter %s not in scope at return", quote(obj.name))
+ err.addf(s, "result parameter %s not in scope at return", obj.name)
err.addf(alt, "inner declaration of %s", obj)
err.report()
// ok to continue
@@ -898,7 +898,7 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
lhs := [2]Expr{sKey, sValue} // sKey, sValue may be nil
rhs := [2]Type{key, val} // key, val may be nil
- constIntRange := x.mode == constant_ && isInteger(x.typ)
+ rangeOverInt := isInteger(x.typ)
if isDef {
// short variable declaration
@@ -923,19 +923,27 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
check.errorf(lhs, InvalidSyntaxTree, "cannot declare %s", lhs)
obj = NewVar(lhs.Pos(), check.pkg, "_", nil) // dummy variable
}
+ assert(obj.typ == nil)
- // initialize lhs variable
- if constIntRange {
- check.initVar(obj, &x, "range clause")
- } else if typ := rhs[i]; typ != nil {
- x.mode = value
- x.expr = lhs // we don't have a better rhs expression to use here
- x.typ = typ
- check.initVar(obj, &x, "assignment") // error is on variable, use "assignment" not "range clause"
- } else {
+ // initialize lhs iteration variable, if any
+ typ := rhs[i]
+ if typ == nil {
obj.typ = Typ[Invalid]
obj.used = true // don't complain about unused variable
+ continue
+ }
+
+ if rangeOverInt {
+ assert(i == 0) // at most one iteration variable (rhs[1] == nil for rangeOverInt)
+ check.initVar(obj, &x, "range clause")
+ } else {
+ var y operand
+ y.mode = value
+ y.expr = lhs // we don't have a better rhs expression to use here
+ y.typ = typ
+ check.initVar(obj, &y, "assignment") // error is on variable, use "assignment" not "range clause"
}
+ assert(obj.typ != nil)
}
// declare variables
@@ -954,21 +962,36 @@ func (check *Checker) rangeStmt(inner stmtContext, s *syntax.ForStmt, rclause *s
continue
}
- if constIntRange {
+ // assign to lhs iteration variable, if any
+ typ := rhs[i]
+ if typ == nil {
+ continue
+ }
+
+ if rangeOverInt {
+ assert(i == 0) // at most one iteration variable (rhs[1] == nil for rangeOverInt)
check.assignVar(lhs, nil, &x, "range clause")
- } else if typ := rhs[i]; typ != nil {
- x.mode = value
- x.expr = lhs // we don't have a better rhs expression to use here
- x.typ = typ
- check.assignVar(lhs, nil, &x, "assignment") // error is on variable, use "assignment" not "range clause"
+ // If the assignment succeeded, if x was untyped before, it now
+ // has a type inferred via the assignment. It must be an integer.
+ // (go.dev/issues/67027)
+ if x.mode != invalid && !isInteger(x.typ) {
+ check.softErrorf(lhs, InvalidRangeExpr, "cannot use iteration variable of type %s", x.typ)
+ }
+ } else {
+ var y operand
+ y.mode = value
+ y.expr = lhs // we don't have a better rhs expression to use here
+ y.typ = typ
+ check.assignVar(lhs, nil, &y, "assignment") // error is on variable, use "assignment" not "range clause"
}
}
- } else if constIntRange {
+ } else if rangeOverInt {
// If we don't have any iteration variables, we still need to
// check that a (possibly untyped) integer range expression x
// is valid.
// We do this by checking the assignment _ = x. This ensures
- // that an untyped x can be converted to a value of type int.
+ // that an untyped x can be converted to a value of its default
+ // type (rune or int).
check.assignment(&x, nil, "range clause")
}
diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go
index 215d1f2d4f..650ae846a6 100644
--- a/src/cmd/compile/internal/types2/subst.go
+++ b/src/cmd/compile/internal/types2/subst.go
@@ -96,15 +96,26 @@ func (subst *subster) typ(typ Type) Type {
// nothing to do
case *Alias:
- rhs := subst.typ(t.fromRHS)
- if rhs != t.fromRHS {
- // This branch cannot be reached because the RHS of an alias
- // may only contain type parameters of an enclosing function.
- // Such function bodies are never "instantiated" and thus
- // substitution is not called on locally declared alias types.
- // TODO(gri) adjust once parameterized aliases are supported
- panic("unreachable for unparameterized aliases")
- // return subst.check.newAlias(t.obj, rhs)
+ // This code follows the code for *Named types closely.
+ // TODO(gri) try to factor better
+ orig := t.Origin()
+ n := orig.TypeParams().Len()
+ if n == 0 {
+ return t // type is not parameterized
+ }
+
+ // TODO(gri) do we need this for Alias types?
+ if t.TypeArgs().Len() != n {
+ return Typ[Invalid] // error reported elsewhere
+ }
+
+ // already instantiated
+ // For each (existing) type argument determine if it needs
+ // to be substituted; i.e., if it is or contains a type parameter
+ // that has a type argument for it.
+ targs, updated := subst.typeList(t.TypeArgs().list())
+ if updated {
+ return subst.check.newAliasInstance(subst.pos, t.orig, targs, subst.ctxt)
}
case *Array:
@@ -221,18 +232,6 @@ func (subst *subster) typ(typ Type) Type {
}
case *Named:
- // dump is for debugging
- dump := func(string, ...interface{}) {}
- if subst.check != nil && subst.check.conf.Trace {
- subst.check.indent++
- defer func() {
- subst.check.indent--
- }()
- dump = func(format string, args ...interface{}) {
- subst.check.trace(subst.pos, format, args...)
- }
- }
-
// subst is called during expansion, so in this function we need to be
// careful not to call any methods that would cause t to be expanded: doing
// so would result in deadlock.
@@ -241,44 +240,26 @@ func (subst *subster) typ(typ Type) Type {
orig := t.Origin()
n := orig.TypeParams().Len()
if n == 0 {
- dump(">>> %s is not parameterized", t)
return t // type is not parameterized
}
- var newTArgs []Type
if t.TypeArgs().Len() != n {
return Typ[Invalid] // error reported elsewhere
}
// already instantiated
- dump(">>> %s already instantiated", t)
- // For each (existing) type argument targ, determine if it needs
+ // For each (existing) type argument determine if it needs
// to be substituted; i.e., if it is or contains a type parameter
// that has a type argument for it.
- for i, targ := range t.TypeArgs().list() {
- dump(">>> %d targ = %s", i, targ)
- new_targ := subst.typ(targ)
- if new_targ != targ {
- dump(">>> substituted %d targ %s => %s", i, targ, new_targ)
- if newTArgs == nil {
- newTArgs = make([]Type, n)
- copy(newTArgs, t.TypeArgs().list())
- }
- newTArgs[i] = new_targ
- }
+ targs, updated := subst.typeList(t.TypeArgs().list())
+ if updated {
+ // Create a new instance and populate the context to avoid endless
+ // recursion. The position used here is irrelevant because validation only
+ // occurs on t (we don't call validType on named), but we use subst.pos to
+ // help with debugging.
+ return subst.check.instance(subst.pos, orig, targs, subst.expanding, subst.ctxt)
}
- if newTArgs == nil {
- dump(">>> nothing to substitute in %s", t)
- return t // nothing to substitute
- }
-
- // Create a new instance and populate the context to avoid endless
- // recursion. The position used here is irrelevant because validation only
- // occurs on t (we don't call validType on named), but we use subst.pos to
- // help with debugging.
- return subst.check.instance(subst.pos, orig, newTArgs, subst.expanding, subst.ctxt)
-
case *TypeParam:
return subst.smap.lookup(t)
diff --git a/src/cmd/compile/internal/types2/typeparam.go b/src/cmd/compile/internal/types2/typeparam.go
index 5c6030b3fb..9ad064906f 100644
--- a/src/cmd/compile/internal/types2/typeparam.go
+++ b/src/cmd/compile/internal/types2/typeparam.go
@@ -86,6 +86,10 @@ func (t *TypeParam) SetConstraint(bound Type) {
t.iface()
}
+// Underlying returns the [underlying type] of the type parameter t, which is
+// the underlying type of its constraint. This type is always an interface.
+//
+// [underlying type]: https://go.dev/ref/spec#Underlying_types.
func (t *TypeParam) Underlying() Type {
return t.iface()
}
diff --git a/src/cmd/compile/internal/types2/typeset.go b/src/cmd/compile/internal/types2/typeset.go
index 2ce586e7a7..0457502e39 100644
--- a/src/cmd/compile/internal/types2/typeset.go
+++ b/src/cmd/compile/internal/types2/typeset.go
@@ -226,8 +226,8 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
case explicit:
if check != nil {
err := check.newError(DuplicateDecl)
- err.addf(atPos(pos), "duplicate method %s", quote(m.name))
- err.addf(atPos(mpos[other.(*Func)]), "other declaration of %s", quote(m.name))
+ err.addf(atPos(pos), "duplicate method %s", m.name)
+ err.addf(atPos(mpos[other.(*Func)]), "other declaration of method %s", m.name)
err.report()
}
default:
@@ -240,8 +240,8 @@ func computeInterfaceTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *_
check.later(func() {
if pos.IsKnown() && !check.allowVersion(atPos(pos), go1_14) || !Identical(m.typ, other.Type()) {
err := check.newError(DuplicateDecl)
- err.addf(atPos(pos), "duplicate method %s", quote(m.name))
- err.addf(atPos(mpos[other.(*Func)]), "other declaration of %s", quote(m.name))
+ err.addf(atPos(pos), "duplicate method %s", m.name)
+ err.addf(atPos(mpos[other.(*Func)]), "other declaration of method %s", m.name)
err.report()
}
}).describef(atPos(pos), "duplicate method check for %s", m.name)
diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go
index 723c074e60..7db86a70f1 100644
--- a/src/cmd/compile/internal/types2/typestring.go
+++ b/src/cmd/compile/internal/types2/typestring.go
@@ -211,10 +211,11 @@ func (w *typeWriter) typ(typ Type) {
case *Interface:
if w.ctxt == nil {
- if t == universeAny.Type() {
+ if t == universeAnyAlias.Type().Underlying() {
// When not hashing, we can try to improve type strings by writing "any"
- // for a type that is pointer-identical to universeAny. This logic should
- // be deprecated by more robust handling for aliases.
+ // for a type that is pointer-identical to universeAny.
+ // TODO(rfindley): this logic should not be necessary with
+ // gotypesalias=1. Remove once that is always the case.
w.string("any")
break
}
@@ -334,6 +335,10 @@ func (w *typeWriter) typ(typ Type) {
case *Alias:
w.typeName(t.obj)
+ if list := t.targs.list(); len(list) != 0 {
+ // instantiated type
+ w.typeList(list)
+ }
if w.ctxt != nil {
// TODO(gri) do we need to print the alias type name, too?
w.typ(Unalias(t.obj.typ))
diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go
index ec012c24eb..6c121ae054 100644
--- a/src/cmd/compile/internal/types2/typexpr.go
+++ b/src/cmd/compile/internal/types2/typexpr.go
@@ -41,11 +41,19 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
check.errorf(e, UndeclaredName, "undefined: %s", e.Value)
}
return
- case universeAny, universeComparable:
+ case universeComparable:
if !check.verifyVersionf(e, go1_18, "predeclared %s", e.Value) {
return // avoid follow-on errors
}
}
+ // Because the representation of any depends on gotypesalias, we don't check
+ // pointer identity here.
+ if obj.Name() == "any" && obj.Parent() == Universe {
+ if !check.verifyVersionf(e, go1_18, "predeclared %s", e.Value) {
+ return // avoid follow-on errors
+ }
+ }
+
check.recordUse(e, obj)
// If we want a type but don't have one, stop right here and avoid potential problems
@@ -87,7 +95,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
switch obj := obj.(type) {
case *PkgName:
- check.errorf(e, InvalidPkgUse, "use of package %s not in selector", quote(obj.name))
+ check.errorf(e, InvalidPkgUse, "use of package %s not in selector", obj.name)
return
case *Const:
@@ -109,7 +117,7 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *TypeName, wantType
case *TypeName:
if !check.conf.EnableAlias && check.isBrokenAlias(obj) {
- check.errorf(e, InvalidDeclCycle, "invalid use of type alias %s in recursive type (see go.dev/issue/50729)", quote(obj.name))
+ check.errorf(e, InvalidDeclCycle, "invalid use of type alias %s in recursive type (see go.dev/issue/50729)", obj.name)
return
}
x.mode = typexpr
@@ -445,6 +453,10 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def *
}()
}
+ defer func() {
+ setDefType(def, res)
+ }()
+
var cause string
gtyp := check.genericType(x, &cause)
if cause != "" {
@@ -454,21 +466,23 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def *
return gtyp // error already reported
}
- orig := asNamed(gtyp)
- if orig == nil {
- panic(fmt.Sprintf("%v: cannot instantiate %v", x.Pos(), gtyp))
- }
-
// evaluate arguments
targs := check.typeList(xlist)
if targs == nil {
- setDefType(def, Typ[Invalid]) // avoid errors later due to lazy instantiation
return Typ[Invalid]
}
+ if orig, _ := gtyp.(*Alias); orig != nil {
+ return check.instance(x.Pos(), orig, targs, nil, check.context())
+ }
+
+ orig := asNamed(gtyp)
+ if orig == nil {
+ panic(fmt.Sprintf("%v: cannot instantiate %v", x.Pos(), gtyp))
+ }
+
// create the instance
inst := asNamed(check.instance(x.Pos(), orig, targs, nil, check.context()))
- setDefType(def, inst)
// orig.tparams may not be set up, so we need to do expansion later.
check.later(func() {
diff --git a/src/cmd/compile/internal/types2/under.go b/src/cmd/compile/internal/types2/under.go
index 6b24399de4..2d90c35d3b 100644
--- a/src/cmd/compile/internal/types2/under.go
+++ b/src/cmd/compile/internal/types2/under.go
@@ -22,6 +22,7 @@ func under(t Type) Type {
// identical element types), the single underlying type is the restricted
// channel type if the restrictions are always the same, or nil otherwise.
func coreType(t Type) Type {
+ t = Unalias(t)
tpar, _ := t.(*TypeParam)
if tpar == nil {
return under(t)
@@ -51,6 +52,7 @@ func coreType(t Type) Type {
// and strings as identical. In this case, if successful and we saw
// a string, the result is of type (possibly untyped) string.
func coreString(t Type) Type {
+ t = Unalias(t)
tpar, _ := t.(*TypeParam)
if tpar == nil {
return under(t) // string or untyped string
diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go
index 6838f270c1..8c91294d2b 100644
--- a/src/cmd/compile/internal/types2/unify.go
+++ b/src/cmd/compile/internal/types2/unify.go
@@ -205,10 +205,10 @@ func (u *unifier) join(x, y *TypeParam) bool {
return true
}
-// asTypeParam returns x.(*TypeParam) if x is a type parameter recorded with u.
+// asBoundTypeParam returns x.(*TypeParam) if x is a type parameter recorded with u.
// Otherwise, the result is nil.
-func (u *unifier) asTypeParam(x Type) *TypeParam {
- if x, _ := x.(*TypeParam); x != nil {
+func (u *unifier) asBoundTypeParam(x Type) *TypeParam {
+ if x, _ := Unalias(x).(*TypeParam); x != nil {
if _, found := u.handles[x]; found {
return x
}
@@ -269,7 +269,7 @@ func (u *unifier) inferred(tparams []*TypeParam) []Type {
// asInterface returns the underlying type of x as an interface if
// it is a non-type parameter interface. Otherwise it returns nil.
func asInterface(x Type) (i *Interface) {
- if _, ok := x.(*TypeParam); !ok {
+ if _, ok := Unalias(x).(*TypeParam); !ok {
i, _ = under(x).(*Interface)
}
return i
@@ -291,11 +291,8 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
u.depth--
}()
- x = Unalias(x)
- y = Unalias(y)
-
// nothing to do if x == y
- if x == y {
+ if x == y || Unalias(x) == Unalias(y) {
return true
}
@@ -314,7 +311,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
// Ensure that if we have at least one
// - defined type, make sure one is in y
// - type parameter recorded with u, make sure one is in x
- if asNamed(x) != nil || u.asTypeParam(y) != nil {
+ if asNamed(x) != nil || u.asBoundTypeParam(y) != nil {
if traceInference {
u.tracef("%s ≡ %s\t// swap", y, x)
}
@@ -358,7 +355,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
// isTypeLit(x) is false and y was not changed above. In other
// words, if y was a defined type, it is still a defined type
// (relevant for the logic below).
- switch px, py := u.asTypeParam(x), u.asTypeParam(y); {
+ switch px, py := u.asBoundTypeParam(x), u.asBoundTypeParam(y); {
case px != nil && py != nil:
// both x and y are type parameters
if u.join(px, py) {
@@ -449,7 +446,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
}
// x != y if we get here
- assert(x != y)
+ assert(x != y && Unalias(x) != Unalias(y))
// If u.EnableInterfaceInference is set and we don't require exact unification,
// if both types are interfaces, one interface must have a subset of the
@@ -573,6 +570,10 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
emode |= exact
}
+ // Continue with unaliased types but don't lose original alias names, if any (go.dev/issue/67628).
+ xorig, x := x, Unalias(x)
+ yorig, y := y, Unalias(y)
+
switch x := x.(type) {
case *Basic:
// Basic types are singletons except for the rune and byte
@@ -751,7 +752,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
case *TypeParam:
// x must be an unbound type parameter (see comment above).
if debug {
- assert(u.asTypeParam(x) == nil)
+ assert(u.asBoundTypeParam(x) == nil)
}
// By definition, a valid type argument must be in the type set of
// the respective type constraint. Therefore, the type argument's
@@ -774,13 +775,13 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
// need to take care of that case separately.
if cx := coreType(x); cx != nil {
if traceInference {
- u.tracef("core %s ≡ %s", x, y)
+ u.tracef("core %s ≡ %s", xorig, yorig)
}
// If y is a defined type, it may not match against cx which
// is an underlying type (incl. int, string, etc.). Use assign
// mode here so that the unifier automatically takes under(y)
// if necessary.
- return u.nify(cx, y, assign, p)
+ return u.nify(cx, yorig, assign, p)
}
}
// x != y and there's nothing to do
@@ -789,7 +790,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
// avoid a crash in case of nil type
default:
- panic(sprintf(nil, true, "u.nify(%s, %s, %d)", x, y, mode))
+ panic(sprintf(nil, true, "u.nify(%s, %s, %d)", xorig, yorig, mode))
}
return false
diff --git a/src/cmd/compile/internal/types2/universe.go b/src/cmd/compile/internal/types2/universe.go
index 8e1e4a2bb7..9c76ac2373 100644
--- a/src/cmd/compile/internal/types2/universe.go
+++ b/src/cmd/compile/internal/types2/universe.go
@@ -23,7 +23,8 @@ var (
universeIota Object
universeByte Type // uint8 alias, but has name "byte"
universeRune Type // int32 alias, but has name "rune"
- universeAny Object
+ universeAnyNoAlias *TypeName
+ universeAnyAlias *TypeName
universeError Type
universeComparable Object
)
@@ -65,7 +66,7 @@ var Typ = [...]*Basic{
UntypedNil: {UntypedNil, IsUntyped, "untyped nil"},
}
-var aliases = [...]*Basic{
+var basicAliases = [...]*Basic{
{Byte, IsInteger | IsUnsigned, "byte"},
{Rune, IsInteger, "rune"},
}
@@ -74,15 +75,41 @@ func defPredeclaredTypes() {
for _, t := range Typ {
def(NewTypeName(nopos, nil, t.name, t))
}
- for _, t := range aliases {
+ for _, t := range basicAliases {
def(NewTypeName(nopos, nil, t.name, t))
}
// type any = interface{}
- // Note: don't use &emptyInterface for the type of any. Using a unique
- // pointer allows us to detect any and format it as "any" rather than
- // interface{}, which clarifies user-facing error messages significantly.
- def(NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet}))
+ //
+ // Implement two representations of any: one for the legacy gotypesalias=0,
+ // and one for gotypesalias=1. This is necessary for consistent
+ // representation of interface aliases during type checking, and is
+ // implemented via hijacking [Scope.Lookup] for the [Universe] scope.
+ //
+ // Both representations use the same distinguished pointer for their RHS
+ // interface type, allowing us to detect any (even with the legacy
+ // representation), and format it as "any" rather than interface{}, which
+ // clarifies user-facing error messages significantly.
+ //
+ // TODO(rfindley): once the gotypesalias GODEBUG variable is obsolete (and we
+ // consistently use the Alias node), we should be able to clarify user facing
+ // error messages without using a distinguished pointer for the any
+ // interface.
+ {
+ universeAnyNoAlias = NewTypeName(nopos, nil, "any", &Interface{complete: true, tset: &topTypeSet})
+ universeAnyNoAlias.setColor(black)
+ // ensure that the any TypeName reports a consistent Parent, after
+ // hijacking Universe.Lookup with gotypesalias=0.
+ universeAnyNoAlias.setParent(Universe)
+
+ // It shouldn't matter which representation of any is actually inserted
+ // into the Universe, but we lean toward the future and insert the Alias
+ // representation.
+ universeAnyAlias = NewTypeName(nopos, nil, "any", nil)
+ universeAnyAlias.setColor(black)
+ _ = NewAlias(universeAnyAlias, universeAnyNoAlias.Type().Underlying()) // Link TypeName and Alias
+ def(universeAnyAlias)
+ }
// type error interface{ Error() string }
{
@@ -250,7 +277,6 @@ func init() {
universeIota = Universe.Lookup("iota")
universeByte = Universe.Lookup("byte").Type()
universeRune = Universe.Lookup("rune").Type()
- universeAny = Universe.Lookup("any")
universeError = Universe.Lookup("error").Type()
universeComparable = Universe.Lookup("comparable")
}
diff --git a/src/cmd/compile/internal/types2/util.go b/src/cmd/compile/internal/types2/util.go
index 0422c03346..db0a3e70ba 100644
--- a/src/cmd/compile/internal/types2/util.go
+++ b/src/cmd/compile/internal/types2/util.go
@@ -36,7 +36,7 @@ func dddErrPos(call *syntax.CallExpr) *syntax.CallExpr {
return call
}
-// argErrPos returns the node (poser) for reportign an invalid argument count.
+// argErrPos returns the node (poser) for reporting an invalid argument count.
func argErrPos(call *syntax.CallExpr) *syntax.CallExpr { return call }
// ExprString returns a string representation of x.
diff --git a/src/cmd/compile/internal/types2/version.go b/src/cmd/compile/internal/types2/version.go
index 241b10d3e6..39ecb9c3af 100644
--- a/src/cmd/compile/internal/types2/version.go
+++ b/src/cmd/compile/internal/types2/version.go
@@ -5,7 +5,6 @@
package types2
import (
- "cmd/compile/internal/syntax"
"fmt"
"go/version"
"internal/goversion"
@@ -56,7 +55,7 @@ var (
func (check *Checker) allowVersion(at poser, v goVersion) bool {
fileVersion := check.conf.GoVersion
if pos := at.Pos(); pos.IsKnown() {
- fileVersion = check.versions[base(pos)]
+ fileVersion = check.versions[pos.FileBase()]
}
// We need asGoVersion (which calls version.Lang) below
@@ -76,19 +75,3 @@ func (check *Checker) verifyVersionf(at poser, v goVersion, format string, args
}
return true
}
-
-// base finds the underlying PosBase of the source file containing pos,
-// skipping over intermediate PosBase layers created by //line directives.
-// The positions must be known.
-func base(pos syntax.Pos) *syntax.PosBase {
- assert(pos.IsKnown())
- b := pos.Base()
- for {
- bb := b.Pos().Base()
- if bb == nil || bb == b {
- break
- }
- b = bb
- }
- return b
-}