For #46477.
Change-Id: Ifa47d3ff87f67c60fa25654e54194ca8b31ea5a2
Reviewed-on: https://go-review.googlesource.com/c/go/+/567617
Auto-Submit: Robert Griesemer <gri@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@google.com>
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
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
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".
}
// 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
// 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
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)
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
// 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
}
// 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 {
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))
}()
}
+ defer func() {
+ setDefType(def, res)
+ }()
+
var cause string
gtyp := check.genericType(x, &cause)
if cause != "" {
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() {
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.
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
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
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".
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
}
// 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
// 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
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)
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
// 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
}
// 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 {
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))
}()
}
+ defer func() {
+ setDefType(def, res)
+ }()
+
var cause string
gtyp := check.genericType(ix.X, &cause)
if cause != "" {
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() {
// 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.
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
}