From: Robert Griesemer Date: Wed, 28 Feb 2024 01:12:00 +0000 (-0800) Subject: go/types, types2: instantiate generic alias types X-Git-Tag: go1.23rc1~165 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=f294ddeb29c84572566b96584b1d17db26556b88;p=gostls13.git go/types, types2: instantiate generic alias types For #46477. Change-Id: Ifa47d3ff87f67c60fa25654e54194ca8b31ea5a2 Reviewed-on: https://go-review.googlesource.com/c/go/+/567617 Auto-Submit: Robert Griesemer LUCI-TryBot-Result: Go LUCI Reviewed-by: Robert Findley Reviewed-by: Robert Griesemer --- diff --git a/src/cmd/compile/internal/types2/alias.go b/src/cmd/compile/internal/types2/alias.go index 68475c54a4..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 @@ -30,7 +33,10 @@ 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 @@ -125,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/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index 5630d06bc9..72227ab122 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -21,11 +21,13 @@ type genericType interface { } // 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 @@ -35,10 +37,10 @@ type genericType interface { // 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 @@ -101,8 +103,9 @@ func (check *Checker) instance(pos syntax.Pos, orig genericType, targs []Type, e 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) @@ -122,6 +125,21 @@ func (check *Checker) instance(pos syntax.Pos, orig genericType, targs []Type, e 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/predicates.go b/src/cmd/compile/internal/types2/predicates.go index 986cd6aa61..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 } diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go index 215d1f2d4f..2690ef689c 100644 --- a/src/cmd/compile/internal/types2/subst.go +++ b/src/cmd/compile/internal/types2/subst.go @@ -96,17 +96,41 @@ 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? + var newTArgs []Type + if t.TypeArgs().Len() != n { + return Typ[Invalid] // error reported elsewhere + } + + // already instantiated + // For each (existing) type argument targ, 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() { + new_targ := subst.typ(targ) + if new_targ != targ { + if newTArgs == nil { + newTArgs = make([]Type, n) + copy(newTArgs, t.TypeArgs().list()) + } + newTArgs[i] = new_targ + } + } + + if newTArgs == nil { + return t // nothing to substitute + } + + return subst.check.newAliasInstance(subst.pos, t.orig, newTArgs, subst.ctxt) + case *Array: elem := subst.typOrNil(t.elem) if elem != t.elem { diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go index e067c3f4a7..7db86a70f1 100644 --- a/src/cmd/compile/internal/types2/typestring.go +++ b/src/cmd/compile/internal/types2/typestring.go @@ -335,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 1e00c7bd86..eca60ada7b 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -453,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 != "" { @@ -462,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/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/go/types/alias.go b/src/go/types/alias.go index 3fdd12ea02..af43471a32 100644 --- a/src/go/types/alias.go +++ b/src/go/types/alias.go @@ -7,7 +7,10 @@ package types -import "fmt" +import ( + "fmt" + "go/token" +) // An Alias represents an alias type. // Whether or not Alias types are created is controlled by the @@ -33,7 +36,10 @@ 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 @@ -128,6 +134,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 token.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/go/types/generate_test.go b/src/go/types/generate_test.go index f3047b2846..86b7716296 100644 --- a/src/go/types/generate_test.go +++ b/src/go/types/generate_test.go @@ -100,7 +100,7 @@ func generate(t *testing.T, filename string, write bool) { type action func(in *ast.File) var filemap = map[string]action{ - "alias.go": nil, + "alias.go": fixTokenPos, "assignments.go": func(f *ast.File) { renameImportPath(f, `"cmd/compile/internal/syntax"->"go/ast"`) renameSelectorExprs(f, "syntax.Name->ast.Ident", "ident.Value->ident.Name", "ast.Pos->token.Pos") // must happen before renaming identifiers diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index 38a7e3ffe9..7bec790b55 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go @@ -24,11 +24,13 @@ type genericType interface { } // 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 @@ -38,10 +40,10 @@ type genericType interface { // 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 @@ -104,8 +106,9 @@ func (check *Checker) instance(pos token.Pos, orig genericType, targs []Type, ex 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) @@ -125,6 +128,21 @@ func (check *Checker) instance(pos token.Pos, orig genericType, targs []Type, ex 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/go/types/predicates.go b/src/go/types/predicates.go index 83bc64772f..ba7901b3c3 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -140,6 +140,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 } diff --git a/src/go/types/subst.go b/src/go/types/subst.go index a3ea16d9b9..42e0c5ea2a 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -99,17 +99,41 @@ 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? + var newTArgs []Type + if t.TypeArgs().Len() != n { + return Typ[Invalid] // error reported elsewhere + } + + // already instantiated + // For each (existing) type argument targ, 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() { + new_targ := subst.typ(targ) + if new_targ != targ { + if newTArgs == nil { + newTArgs = make([]Type, n) + copy(newTArgs, t.TypeArgs().list()) + } + newTArgs[i] = new_targ + } + } + + if newTArgs == nil { + return t // nothing to substitute + } + + return subst.check.newAliasInstance(subst.pos, t.orig, newTArgs, subst.ctxt) + case *Array: elem := subst.typOrNil(t.elem) if elem != t.elem { diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index 9285bcbb81..54f06138ad 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -338,6 +338,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/go/types/typexpr.go b/src/go/types/typexpr.go index 302de4caab..b31f8b33f6 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -443,6 +443,10 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *TypeName) }() } + defer func() { + setDefType(def, res) + }() + var cause string gtyp := check.genericType(ix.X, &cause) if cause != "" { @@ -452,21 +456,23 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *TypeName) return gtyp // error already reported } - orig := asNamed(gtyp) - if orig == nil { - panic(fmt.Sprintf("%v: cannot instantiate %v", ix.Pos(), gtyp)) - } - // evaluate arguments targs := check.typeList(ix.Indices) 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(ix.Pos(), orig, targs, nil, check.context()) + } + + orig := asNamed(gtyp) + if orig == nil { + panic(fmt.Sprintf("%v: cannot instantiate %v", ix.Pos(), gtyp)) + } + // create the instance inst := asNamed(check.instance(ix.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/go/types/util.go b/src/go/types/util.go index bfaba809ad..5d4ccc6f1f 100644 --- a/src/go/types/util.go +++ b/src/go/types/util.go @@ -33,7 +33,7 @@ func hasDots(call *ast.CallExpr) bool { return call.Ellipsis.IsValid() } // dddErrPos returns the positioner for reporting an invalid ... use in a call. func dddErrPos(call *ast.CallExpr) positioner { return atPos(call.Ellipsis) } -// argErrPos returns positioner for reportign an invalid argument count. +// argErrPos returns positioner for reporting an invalid argument count. func argErrPos(call *ast.CallExpr) positioner { return inNode(call, call.Rparen) } // startPos returns the start position of node n. diff --git a/src/internal/types/testdata/spec/typeAliases1.23b.go b/src/internal/types/testdata/spec/typeAliases1.23b.go index d93e0214f8..c92c3d3a7e 100644 --- a/src/internal/types/testdata/spec/typeAliases1.23b.go +++ b/src/internal/types/testdata/spec/typeAliases1.23b.go @@ -28,14 +28,20 @@ type _[P any, Q int] = RHS[P, Q] type _[P int | float64] = RHS[P, int] type _[P, Q any] = RHS[P, Q /* ERROR "Q does not satisfy ~int" */] -// ---------------------------------------------------------------------------- -// NOTE: The code below does now work yet. -// TODO: Implement this. - // A generic type alias may be used like any other generic type. type A[P any] = RHS[P, int] -func _(a A /* ERROR "not a generic type" */ [string]) { +func _(a A[string]) { + a.p = "foo" + a.q = 42 +} + +// A generic alias may refer to another generic alias. +type B[P any] = A[P] + +func _(a B[string]) { a.p = "foo" a.q = 42 + // error messages print the instantiated alias type + a.r /* ERROR "a.r undefined (type B[string] has no field or method r)" */ = 0 }