diff options
Diffstat (limited to 'src/cmd/compile/internal/types2')
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 -} |