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 }