1
0
mirror of https://github.com/golang/go synced 2024-11-16 23:04:44 -07:00

go/types, types2: simplify implementation of validType (fix TODO)

Now that validType is using the correct type nest (CL 409694),
the top entry of the type nest corresponds to the instantiated
type. Thus we can use that type instance to look up the value
of type parameters, there's no need anymore to create an environment
to look up type arguments.

Remove the need to pass around the environment and remove all
associated types and functions.

Updates #52698.

Change-Id: Ie37eace88896386e667ef93c77a4fc3cd0be6eb9
Reviewed-on: https://go-review.googlesource.com/c/go/+/410294
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer 2022-06-02 20:40:17 -07:00
parent 07eca49055
commit fc97075949
2 changed files with 70 additions and 136 deletions

View File

@ -9,20 +9,20 @@ package types2
// (Cycles involving alias types, as in "type A = [10]A" are detected // (Cycles involving alias types, as in "type A = [10]A" are detected
// earlier, via the objDecl cycle detection mechanism.) // earlier, via the objDecl cycle detection mechanism.)
func (check *Checker) validType(typ *Named) { func (check *Checker) validType(typ *Named) {
check.validType0(typ, nil, nil, nil) check.validType0(typ, nil, nil)
} }
// validType0 checks if the given type is valid. If typ is a type parameter // validType0 checks if the given type is valid. If typ is a type parameter
// its value is looked up in the provided environment. The environment is // its value is looked up in the type argument list of the instantiated
// nil if typ is not part of (the RHS of) an instantiated type, in that case // (enclosing) type, if it exists. Otherwise the type parameter must be from
// any type parameter encountered must be from an enclosing function and can // an enclosing function and can be ignored.
// be ignored. The nest list describes the stack (the "nest in memory") of // The nest list describes the stack (the "nest in memory") of types which
// types which contain (or embed in the case of interfaces) other types. For // contain (or embed in the case of interfaces) other types. For instance, a
// instance, a struct named S which contains a field of named type F contains // struct named S which contains a field of named type F contains (the memory
// (the memory of) F in S, leading to the nest S->F. If a type appears in its // of) F in S, leading to the nest S->F. If a type appears in its own nest
// own nest (say S->F->S) we have an invalid recursive type. The path list is // (say S->F->S) we have an invalid recursive type. The path list is the full
// the full path of named types in a cycle, it is only needed for error reporting. // path of named types in a cycle, it is only needed for error reporting.
func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) bool { func (check *Checker) validType0(typ Type, nest, path []*Named) bool {
switch t := typ.(type) { switch t := typ.(type) {
case nil: case nil:
// We should never see a nil type but be conservative and panic // We should never see a nil type but be conservative and panic
@ -32,25 +32,25 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named)
} }
case *Array: case *Array:
return check.validType0(t.elem, env, nest, path) return check.validType0(t.elem, nest, path)
case *Struct: case *Struct:
for _, f := range t.fields { for _, f := range t.fields {
if !check.validType0(f.typ, env, nest, path) { if !check.validType0(f.typ, nest, path) {
return false return false
} }
} }
case *Union: case *Union:
for _, t := range t.terms { for _, t := range t.terms {
if !check.validType0(t.typ, env, nest, path) { if !check.validType0(t.typ, nest, path) {
return false return false
} }
} }
case *Interface: case *Interface:
for _, etyp := range t.embeddeds { for _, etyp := range t.embeddeds {
if !check.validType0(etyp, env, nest, path) { if !check.validType0(etyp, nest, path) {
return false return false
} }
} }
@ -100,7 +100,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named)
// Every type added to nest is also added to path; thus every type that is in nest // Every type added to nest is also added to path; thus every type that is in nest
// must also be in path (invariant). But not every type in path is in nest, since // must also be in path (invariant). But not every type in path is in nest, since
// nest may be pruned (see below, *TypeParam case). // nest may be pruned (see below, *TypeParam case).
if !check.validType0(t.Origin().fromRHS, env.push(t), append(nest, t), append(path, t)) { if !check.validType0(t.Origin().fromRHS, append(nest, t), append(path, t)) {
return false return false
} }
@ -108,18 +108,25 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named)
case *TypeParam: case *TypeParam:
// A type parameter stands for the type (argument) it was instantiated with. // A type parameter stands for the type (argument) it was instantiated with.
// Check the corresponding type argument for validity if we have one. // Check the corresponding type argument for validity if we are in an
if env != nil { // instantiated type.
if targ := env.tmap[t]; targ != nil { if len(nest) > 0 {
// Type arguments found in targ must be looked inst := nest[len(nest)-1] // the type instance
// up in the enclosing environment env.link. The // Find the corresponding type argument for the type parameter
// type argument must be valid in the enclosing // and proceed with checking that type argument.
// type (where the current type was instantiated), for i, tparam := range inst.TypeParams().list() {
// hence we must check targ's validity in the type // The type parameter and type argument lists should
// nest excluding the current (instantiated) type // match in length but be careful in case of errors.
// (see the example at the end of this file). if t == tparam && i < inst.TypeArgs().Len() {
targ := inst.TypeArgs().At(i)
// The type argument must be valid in the enclosing
// type (where inst was instantiated), hence we must
// check targ's validity in the type nest excluding
// the current (instantiated) type (see the example
// at the end of this file).
// For error reporting we keep the full path. // For error reporting we keep the full path.
return check.validType0(targ, env.link, nest[:len(nest)-1], path) return check.validType0(targ, nest[:len(nest)-1], path)
}
} }
} }
} }
@ -137,46 +144,6 @@ func makeObjList(tlist []*Named) []Object {
return olist return olist
} }
// A tparamEnv provides the environment for looking up the type arguments
// with which type parameters for a given instance were instantiated.
// If we don't have an instance, the corresponding tparamEnv is nil.
type tparamEnv struct {
tmap substMap
link *tparamEnv
}
func (env *tparamEnv) push(typ *Named) *tparamEnv {
// If typ is not an instantiated type there are no typ-specific
// type parameters to look up and we don't need an environment.
targs := typ.TypeArgs()
if targs == nil {
return nil // no instance => nil environment
}
// Populate tmap: remember the type argument for each type parameter.
// We cannot use makeSubstMap because the number of type parameters
// and arguments may not match due to errors in the source (too many
// or too few type arguments). Populate tmap "manually".
tparams := typ.TypeParams()
n, m := targs.Len(), tparams.Len()
if n > m {
n = m // too many targs
}
tmap := make(substMap, n)
for i := 0; i < n; i++ {
tmap[tparams.At(i)] = targs.At(i)
}
return &tparamEnv{tmap: tmap, link: env}
}
// TODO(gri) Alternative implementation:
// We may not need to build a stack of environments to
// look up the type arguments for type parameters. The
// same information should be available via the path:
// We should be able to just walk the path backwards
// and find the type arguments in the instance objects.
// Here is an example illustrating why we need to exclude the // Here is an example illustrating why we need to exclude the
// instantiated type from nest when evaluating the validity of // instantiated type from nest when evaluating the validity of
// a type parameter. Given the declarations // a type parameter. Given the declarations

View File

@ -9,20 +9,20 @@ package types
// (Cycles involving alias types, as in "type A = [10]A" are detected // (Cycles involving alias types, as in "type A = [10]A" are detected
// earlier, via the objDecl cycle detection mechanism.) // earlier, via the objDecl cycle detection mechanism.)
func (check *Checker) validType(typ *Named) { func (check *Checker) validType(typ *Named) {
check.validType0(typ, nil, nil, nil) check.validType0(typ, nil, nil)
} }
// validType0 checks if the given type is valid. If typ is a type parameter // validType0 checks if the given type is valid. If typ is a type parameter
// its value is looked up in the provided environment. The environment is // its value is looked up in the type argument list of the instantiated
// nil if typ is not part of (the RHS of) an instantiated type, in that case // (enclosing) type, if it exists. Otherwise the type parameter must be from
// any type parameter encountered must be from an enclosing function and can // an enclosing function and can be ignored.
// be ignored. The nest list describes the stack (the "nest in memory") of // The nest list describes the stack (the "nest in memory") of types which
// types which contain (or embed in the case of interfaces) other types. For // contain (or embed in the case of interfaces) other types. For instance, a
// instance, a struct named S which contains a field of named type F contains // struct named S which contains a field of named type F contains (the memory
// (the memory of) F in S, leading to the nest S->F. If a type appears in its // of) F in S, leading to the nest S->F. If a type appears in its own nest
// own nest (say S->F->S) we have an invalid recursive type. The path list is // (say S->F->S) we have an invalid recursive type. The path list is the full
// the full path of named types in a cycle, it is only needed for error reporting. // path of named types in a cycle, it is only needed for error reporting.
func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named) bool { func (check *Checker) validType0(typ Type, nest, path []*Named) bool {
switch t := typ.(type) { switch t := typ.(type) {
case nil: case nil:
// We should never see a nil type but be conservative and panic // We should never see a nil type but be conservative and panic
@ -32,25 +32,25 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named)
} }
case *Array: case *Array:
return check.validType0(t.elem, env, nest, path) return check.validType0(t.elem, nest, path)
case *Struct: case *Struct:
for _, f := range t.fields { for _, f := range t.fields {
if !check.validType0(f.typ, env, nest, path) { if !check.validType0(f.typ, nest, path) {
return false return false
} }
} }
case *Union: case *Union:
for _, t := range t.terms { for _, t := range t.terms {
if !check.validType0(t.typ, env, nest, path) { if !check.validType0(t.typ, nest, path) {
return false return false
} }
} }
case *Interface: case *Interface:
for _, etyp := range t.embeddeds { for _, etyp := range t.embeddeds {
if !check.validType0(etyp, env, nest, path) { if !check.validType0(etyp, nest, path) {
return false return false
} }
} }
@ -100,7 +100,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named)
// Every type added to nest is also added to path; thus every type that is in nest // Every type added to nest is also added to path; thus every type that is in nest
// must also be in path (invariant). But not every type in path is in nest, since // must also be in path (invariant). But not every type in path is in nest, since
// nest may be pruned (see below, *TypeParam case). // nest may be pruned (see below, *TypeParam case).
if !check.validType0(t.Origin().fromRHS, env.push(t), append(nest, t), append(path, t)) { if !check.validType0(t.Origin().fromRHS, append(nest, t), append(path, t)) {
return false return false
} }
@ -108,18 +108,25 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, nest, path []*Named)
case *TypeParam: case *TypeParam:
// A type parameter stands for the type (argument) it was instantiated with. // A type parameter stands for the type (argument) it was instantiated with.
// Check the corresponding type argument for validity if we have one. // Check the corresponding type argument for validity if we are in an
if env != nil { // instantiated type.
if targ := env.tmap[t]; targ != nil { if len(nest) > 0 {
// Type arguments found in targ must be looked inst := nest[len(nest)-1] // the type instance
// up in the enclosing environment env.link. The // Find the corresponding type argument for the type parameter
// type argument must be valid in the enclosing // and proceed with checking that type argument.
// type (where the current type was instantiated), for i, tparam := range inst.TypeParams().list() {
// hence we must check targ's validity in the type // The type parameter and type argument lists should
// nest excluding the current (instantiated) type // match in length but be careful in case of errors.
// (see the example at the end of this file). if t == tparam && i < inst.TypeArgs().Len() {
targ := inst.TypeArgs().At(i)
// The type argument must be valid in the enclosing
// type (where inst was instantiated), hence we must
// check targ's validity in the type nest excluding
// the current (instantiated) type (see the example
// at the end of this file).
// For error reporting we keep the full path. // For error reporting we keep the full path.
return check.validType0(targ, env.link, nest[:len(nest)-1], path) return check.validType0(targ, nest[:len(nest)-1], path)
}
} }
} }
} }
@ -137,46 +144,6 @@ func makeObjList(tlist []*Named) []Object {
return olist return olist
} }
// A tparamEnv provides the environment for looking up the type arguments
// with which type parameters for a given instance were instantiated.
// If we don't have an instance, the corresponding tparamEnv is nil.
type tparamEnv struct {
tmap substMap
link *tparamEnv
}
func (env *tparamEnv) push(typ *Named) *tparamEnv {
// If typ is not an instantiated type there are no typ-specific
// type parameters to look up and we don't need an environment.
targs := typ.TypeArgs()
if targs == nil {
return nil // no instance => nil environment
}
// Populate tmap: remember the type argument for each type parameter.
// We cannot use makeSubstMap because the number of type parameters
// and arguments may not match due to errors in the source (too many
// or too few type arguments). Populate tmap "manually".
tparams := typ.TypeParams()
n, m := targs.Len(), tparams.Len()
if n > m {
n = m // too many targs
}
tmap := make(substMap, n)
for i := 0; i < n; i++ {
tmap[tparams.At(i)] = targs.At(i)
}
return &tparamEnv{tmap: tmap, link: env}
}
// TODO(gri) Alternative implementation:
// We may not need to build a stack of environments to
// look up the type arguments for type parameters. The
// same information should be available via the path:
// We should be able to just walk the path backwards
// and find the type arguments in the instance objects.
// Here is an example illustrating why we need to exclude the // Here is an example illustrating why we need to exclude the
// instantiated type from nest when evaluating the validity of // instantiated type from nest when evaluating the validity of
// a type parameter. Given the declarations // a type parameter. Given the declarations