Skip to content

Commit

Permalink
go/types, types2: instantiate generic alias types
Browse files Browse the repository at this point in the history
For #46477.

Change-Id: Ifa47d3ff87f67c60fa25654e54194ca8b31ea5a2
Reviewed-on: https://go-review.googlesource.com/c/go/+/567617
Auto-Submit: Robert Griesemer <[email protected]>
LUCI-TryBot-Result: Go LUCI <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
Reviewed-by: Robert Griesemer <[email protected]>
  • Loading branch information
griesemer authored and gopherbot committed May 23, 2024
1 parent ff2070d commit f294dde
Show file tree
Hide file tree
Showing 16 changed files with 222 additions and 66 deletions.
24 changes: 22 additions & 2 deletions src/cmd/compile/internal/types2/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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".
Expand Down
40 changes: 29 additions & 11 deletions src/cmd/compile/internal/types2/instantiate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down
3 changes: 3 additions & 0 deletions src/cmd/compile/internal/types2/predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
42 changes: 33 additions & 9 deletions src/cmd/compile/internal/types2/subst.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions src/cmd/compile/internal/types2/typestring.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
20 changes: 13 additions & 7 deletions src/cmd/compile/internal/types2/typexpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand All @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/types2/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 22 additions & 2 deletions src/go/types/alias.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/go/types/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 29 additions & 11 deletions src/go/types/instantiate.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/go/types/predicates.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f294dde

Please sign in to comment.