github.com/gopherjs/gopherjs@v1.19.0-beta1.0.20240506212314-27071a8796e4/compiler/internal/typeparams/collect.go (about)

     1  package typeparams
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/types"
     7  
     8  	"github.com/gopherjs/gopherjs/compiler/typesutil"
     9  	"github.com/gopherjs/gopherjs/internal/govendor/subst"
    10  	"golang.org/x/exp/typeparams"
    11  )
    12  
    13  // Resolver translates types defined in terms of type parameters into concrete
    14  // types, given a mapping from type params to type arguments.
    15  type Resolver struct {
    16  	subster *subst.Subster
    17  	selMemo map[typesutil.Selection]typesutil.Selection
    18  }
    19  
    20  // NewResolver creates a new Resolver with tParams entries mapping to tArgs
    21  // entries with the same index.
    22  func NewResolver(tc *types.Context, tParams []*types.TypeParam, tArgs []types.Type) *Resolver {
    23  	r := &Resolver{
    24  		subster: subst.New(tc, tParams, tArgs),
    25  		selMemo: map[typesutil.Selection]typesutil.Selection{},
    26  	}
    27  	return r
    28  }
    29  
    30  // Substitute replaces references to type params in the provided type definition
    31  // with the corresponding concrete types.
    32  func (r *Resolver) Substitute(typ types.Type) types.Type {
    33  	if r == nil || r.subster == nil || typ == nil {
    34  		return typ // No substitutions to be made.
    35  	}
    36  	return r.subster.Type(typ)
    37  }
    38  
    39  // SubstituteAll same as Substitute, but accepts a TypeList are returns
    40  // substitution results as a slice in the same order.
    41  func (r *Resolver) SubstituteAll(list *types.TypeList) []types.Type {
    42  	result := make([]types.Type, list.Len())
    43  	for i := range result {
    44  		result[i] = r.Substitute(list.At(i))
    45  	}
    46  	return result
    47  }
    48  
    49  // SubstituteSelection replaces a method of field selection on a generic type
    50  // defined in terms of type parameters with a method selection on a concrete
    51  // instantiation of the type.
    52  func (r *Resolver) SubstituteSelection(sel typesutil.Selection) typesutil.Selection {
    53  	if r == nil || r.subster == nil || sel == nil {
    54  		return sel // No substitutions to be made.
    55  	}
    56  	if concrete, ok := r.selMemo[sel]; ok {
    57  		return concrete
    58  	}
    59  
    60  	switch sel.Kind() {
    61  	case types.MethodExpr, types.MethodVal, types.FieldVal:
    62  		recv := r.Substitute(sel.Recv())
    63  		if types.Identical(recv, sel.Recv()) {
    64  			return sel // Non-generic receiver, no substitution necessary.
    65  		}
    66  
    67  		// Look up the method on the instantiated receiver.
    68  		pkg := sel.Obj().Pkg()
    69  		obj, index, _ := types.LookupFieldOrMethod(recv, true, pkg, sel.Obj().Name())
    70  		if obj == nil {
    71  			panic(fmt.Errorf("failed to lookup field %q in type %v", sel.Obj().Name(), recv))
    72  		}
    73  		typ := obj.Type()
    74  
    75  		if sel.Kind() == types.MethodExpr {
    76  			typ = typesutil.RecvAsFirstArg(typ.(*types.Signature))
    77  		}
    78  		concrete := typesutil.NewSelection(sel.Kind(), recv, index, obj, typ)
    79  		r.selMemo[sel] = concrete
    80  		return concrete
    81  	default:
    82  		panic(fmt.Errorf("unexpected selection kind %v: %v", sel.Kind(), sel))
    83  	}
    84  }
    85  
    86  // ToSlice converts TypeParamList into a slice with the same order of entries.
    87  func ToSlice(tpl *types.TypeParamList) []*types.TypeParam {
    88  	result := make([]*types.TypeParam, tpl.Len())
    89  	for i := range result {
    90  		result[i] = tpl.At(i)
    91  	}
    92  	return result
    93  }
    94  
    95  // visitor implements ast.Visitor and collects instances of generic types and
    96  // functions into an InstanceSet.
    97  //
    98  // When traversing an AST subtree corresponding to a generic type, method or
    99  // function, Resolver must be provided mapping the type parameters into concrete
   100  // types.
   101  type visitor struct {
   102  	instances *PackageInstanceSets
   103  	resolver  *Resolver
   104  	info      *types.Info
   105  }
   106  
   107  var _ ast.Visitor = &visitor{}
   108  
   109  func (c *visitor) Visit(n ast.Node) (w ast.Visitor) {
   110  	w = c // Always traverse the full depth of the AST tree.
   111  
   112  	ident, ok := n.(*ast.Ident)
   113  	if !ok {
   114  		return
   115  	}
   116  
   117  	instance, ok := c.info.Instances[ident]
   118  	if !ok {
   119  		return
   120  	}
   121  
   122  	obj := c.info.ObjectOf(ident)
   123  
   124  	// For types embedded in structs, the object the identifier resolves to is a
   125  	// *types.Var representing the implicitly declared struct field. However, the
   126  	// instance relates to the *types.TypeName behind the field type, which we
   127  	// obtain here.
   128  	typ := obj.Type()
   129  	if ptr, ok := typ.(*types.Pointer); ok {
   130  		typ = ptr.Elem()
   131  	}
   132  	if t, ok := typ.(*types.Named); ok {
   133  		obj = t.Obj()
   134  	}
   135  	c.instances.Add(Instance{
   136  		Object: obj,
   137  		TArgs:  c.resolver.SubstituteAll(instance.TypeArgs),
   138  	})
   139  
   140  	if t, ok := obj.Type().(*types.Named); ok {
   141  		for i := 0; i < t.NumMethods(); i++ {
   142  			method := t.Method(i)
   143  			c.instances.Add(Instance{
   144  				Object: typeparams.OriginMethod(method), // TODO(nevkontakte): Can be replaced with method.Origin() in Go 1.19.
   145  				TArgs:  c.resolver.SubstituteAll(instance.TypeArgs),
   146  			})
   147  		}
   148  	}
   149  	return
   150  }
   151  
   152  // seedVisitor implements ast.Visitor that collects information necessary to
   153  // kickstart generic instantiation discovery.
   154  //
   155  // It serves double duty:
   156  //   - Builds a map from types.Object instances representing generic types,
   157  //     methods and functions to AST nodes that define them.
   158  //   - Collects an initial set of generic instantiations in the non-generic code.
   159  type seedVisitor struct {
   160  	visitor
   161  	objMap  map[types.Object]ast.Node
   162  	mapOnly bool // Only build up objMap, ignore any instances.
   163  }
   164  
   165  var _ ast.Visitor = &seedVisitor{}
   166  
   167  func (c *seedVisitor) Visit(n ast.Node) ast.Visitor {
   168  	// Generic functions, methods and types require type arguments to scan for
   169  	// generic instantiations, remember their node for later and do not descend
   170  	// further.
   171  	switch n := n.(type) {
   172  	case *ast.FuncDecl:
   173  		obj := c.info.Defs[n.Name]
   174  		sig := obj.Type().(*types.Signature)
   175  		if sig.TypeParams().Len() != 0 || sig.RecvTypeParams().Len() != 0 {
   176  			c.objMap[obj] = n
   177  			return &seedVisitor{
   178  				visitor: c.visitor,
   179  				objMap:  c.objMap,
   180  				mapOnly: true,
   181  			}
   182  		}
   183  	case *ast.TypeSpec:
   184  		obj := c.info.Defs[n.Name]
   185  		named, ok := obj.Type().(*types.Named)
   186  		if !ok {
   187  			break
   188  		}
   189  		if named.TypeParams().Len() != 0 && named.TypeArgs().Len() == 0 {
   190  			c.objMap[obj] = n
   191  			return nil
   192  		}
   193  	}
   194  
   195  	if !c.mapOnly {
   196  		// Otherwise check for fully defined instantiations and descend further into
   197  		// the AST tree.
   198  		c.visitor.Visit(n)
   199  	}
   200  	return c
   201  }
   202  
   203  // Collector scans type-checked AST tree and adds discovered generic type and
   204  // function instances to the InstanceSet.
   205  //
   206  // Collector will scan non-generic code for any instantiations of generic types
   207  // or functions and add them to the InstanceSet. Then it will scan generic types
   208  // and function with discovered sets of type arguments for more instantiations,
   209  // until no new ones are discovered.
   210  //
   211  // InstanceSet may contain unprocessed instances of generic types and functions,
   212  // which will be also scanned, for example found in depending packages.
   213  //
   214  // Note that instances of generic type methods are automatically added to the
   215  // set whenever their receiver type instance is encountered.
   216  type Collector struct {
   217  	TContext  *types.Context
   218  	Info      *types.Info
   219  	Instances *PackageInstanceSets
   220  }
   221  
   222  // Scan package files for generic instances.
   223  func (c *Collector) Scan(pkg *types.Package, files ...*ast.File) {
   224  	if c.Info.Instances == nil || c.Info.Defs == nil {
   225  		panic(fmt.Errorf("types.Info must have Instances and Defs populated"))
   226  	}
   227  	objMap := map[types.Object]ast.Node{}
   228  
   229  	// Collect instances of generic objects in non-generic code in the package and
   230  	// add then to the existing InstanceSet.
   231  	sc := seedVisitor{
   232  		visitor: visitor{
   233  			instances: c.Instances,
   234  			resolver:  nil,
   235  			info:      c.Info,
   236  		},
   237  		objMap: objMap,
   238  	}
   239  	for _, file := range files {
   240  		ast.Walk(&sc, file)
   241  	}
   242  
   243  	for iset := c.Instances.Pkg(pkg); !iset.exhausted(); {
   244  		inst, _ := iset.next()
   245  		switch typ := inst.Object.Type().(type) {
   246  		case *types.Signature:
   247  			v := visitor{
   248  				instances: c.Instances,
   249  				resolver:  NewResolver(c.TContext, ToSlice(SignatureTypeParams(typ)), inst.TArgs),
   250  				info:      c.Info,
   251  			}
   252  			ast.Walk(&v, objMap[inst.Object])
   253  		case *types.Named:
   254  			obj := typ.Obj()
   255  			v := visitor{
   256  				instances: c.Instances,
   257  				resolver:  NewResolver(c.TContext, ToSlice(typ.TypeParams()), inst.TArgs),
   258  				info:      c.Info,
   259  			}
   260  			ast.Walk(&v, objMap[obj])
   261  		}
   262  	}
   263  }