]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: instantiate generic alias types
authorRobert Griesemer <gri@golang.org>
Wed, 28 Feb 2024 01:12:00 +0000 (17:12 -0800)
committerGopher Robot <gobot@golang.org>
Thu, 23 May 2024 03:01:18 +0000 (03:01 +0000)
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>
16 files changed:
src/cmd/compile/internal/types2/alias.go
src/cmd/compile/internal/types2/instantiate.go
src/cmd/compile/internal/types2/predicates.go
src/cmd/compile/internal/types2/subst.go
src/cmd/compile/internal/types2/typestring.go
src/cmd/compile/internal/types2/typexpr.go
src/cmd/compile/internal/types2/util.go
src/go/types/alias.go
src/go/types/generate_test.go
src/go/types/instantiate.go
src/go/types/predicates.go
src/go/types/subst.go
src/go/types/typestring.go
src/go/types/typexpr.go
src/go/types/util.go
src/internal/types/testdata/spec/typeAliases1.23b.go

index 68475c54a46f11c057dd7d242086f665c30b6b65..5148d5db03414231298b5680c0979f0758c3aa99 100644 (file)
@@ -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".
index 5630d06bc9d296c0c4d2b93b3630e2fac1e8ee44..72227ab12256dd867b58fe86ce60dd9f0400d727 100644 (file)
@@ -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
 
index 986cd6aa61be78dc114813ee683260d4b4f02507..6403be6bcb571939131fa6d5f8cb19c209eb150a 100644 (file)
@@ -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
 }
index 215d1f2d4f660841ddb5940fed68a6a30dbfafd2..2690ef689cbac6f1ff4f21b2ed6cf947d6473f61 100644 (file)
@@ -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 {
index e067c3f4a7d03574109d255e2debda840b1519e4..7db86a70f1c3ba0bbfb0901851102e5606a7592c 100644 (file)
@@ -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))
index 1e00c7bd86b813e0fe51c5015fac303c972deb32..eca60ada7b792419cc209c110de16beb4fa99fe0 100644 (file)
@@ -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() {
index 0422c033466e1f7ee887e935ded3da31d3731943..db0a3e70badb43490d653317e7782d6187240a75 100644 (file)
@@ -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.
index 3fdd12ea0295e119766a4c9de2794f5dcee77c55..af43471a324176a30053c91104bded71ba001719 100644 (file)
@@ -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".
index f3047b2846adb710ee4cd37b488dd4409c2b4e7d..86b77162961ec8e68bb5c0855c16841d00b9e3ae 100644 (file)
@@ -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
index 38a7e3ffe9b9fc7809e6d83ce7241a20a139cb39..7bec790b5586ad4081da6b47b136adf251bd4c9e 100644 (file)
@@ -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
 
index 83bc64772fb630510009b95a278871d78de27906..ba7901b3c31b17d77555ae89006c24fe0af4649e 100644 (file)
@@ -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
 }
index a3ea16d9b99999aeab973dd37b20ee9ab2c56e78..42e0c5ea2a33a591bfaa421d7b15a5da60b25da7 100644 (file)
@@ -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 {
index 9285bcbb818fc739bbc25d874476fde82e916484..54f06138adc624efd772272ded446f09115270ae 100644 (file)
@@ -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))
index 302de4caab68720736fa73d11c2f120632cf8518..b31f8b33f66beeeed6208b8fe7fac6450c129fa2 100644 (file)
@@ -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() {
index bfaba809adaae41ad53648fff510cffe616951b2..5d4ccc6f1ff15e14c9bc19be79eb62305a43488c 100644 (file)
@@ -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.
index d93e0214f851f1216031cc4df739c6c2f12cbb2a..c92c3d3a7e0ea93adc27c79b4d01a04c650e44d5 100644 (file)
@@ -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
 }