github.com/goplus/igop@v0.25.0/cmd/internal/export/loader.go (about) 1 /* 2 * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package export 18 19 import ( 20 "bytes" 21 "fmt" 22 "go/ast" 23 "go/build" 24 "go/constant" 25 "go/printer" 26 "go/token" 27 "go/types" 28 "log" 29 "strconv" 30 "strings" 31 32 "golang.org/x/tools/go/loader" 33 ) 34 35 type Program struct { 36 prog *loader.Program 37 ctx *build.Context 38 fset *token.FileSet 39 } 40 41 func NewProgram(ctx *build.Context) *Program { 42 if ctx == nil { 43 ctx = &build.Default 44 ctx.BuildTags = strings.Split(flagBuildTags, ",") 45 } 46 return &Program{ctx: ctx, fset: token.NewFileSet()} 47 } 48 49 func (p *Program) Load(pkgs []string) error { 50 var cfg loader.Config 51 cfg.Build = p.ctx 52 cfg.Fset = p.fset 53 if flagExportSource { 54 cfg.AfterTypeCheck = p.typeCheck 55 } 56 for _, pkg := range pkgs { 57 cfg.Import(pkg) 58 } 59 iprog, err := cfg.Load() 60 if err != nil { 61 return fmt.Errorf("conf.Load failed: %s", err) 62 } 63 p.prog = iprog 64 return nil 65 } 66 67 func (p *Program) typeCheck(info *loader.PackageInfo, files []*ast.File) { 68 for _, file := range files { 69 for _, decl := range file.Decls { 70 if fn, ok := decl.(*ast.FuncDecl); ok { 71 if funcHasTypeParams(fn) { 72 continue 73 } 74 if recv := recvType(fn); recv != nil && !ast.IsExported(recv.Name) { 75 continue 76 } 77 if !ast.IsExported(fn.Name.Name) { 78 continue 79 } 80 fn.Body = nil 81 } 82 } 83 } 84 } 85 86 func recvType(fn *ast.FuncDecl) *ast.Ident { 87 if fn.Recv == nil { 88 return nil 89 } 90 if len(fn.Recv.List) != 1 { 91 return nil 92 } 93 expr := fn.Recv.List[0].Type 94 retry: 95 switch v := expr.(type) { 96 case *ast.ParenExpr: 97 expr = v.X 98 goto retry 99 case *ast.StarExpr: 100 expr = v.X 101 goto retry 102 case *ast.Ident: 103 return v 104 } 105 return nil 106 } 107 108 func loadProgram(path string, ctx *build.Context) (*Program, error) { 109 var cfg loader.Config 110 cfg.Build = ctx 111 cfg.Import(path) 112 113 iprog, err := cfg.Load() 114 if err != nil { 115 return nil, fmt.Errorf("conf.Load failed: %s", err) 116 } 117 return &Program{prog: iprog, ctx: ctx}, nil 118 } 119 120 func (p *Program) DumpDeps(path string) { 121 pkg := p.prog.Package(path) 122 for _, im := range pkg.Pkg.Imports() { 123 fmt.Println(im.Path()) 124 } 125 } 126 127 func (p *Program) dumpDeps(path string, sep string) { 128 pkg := p.prog.Package(path) 129 for _, im := range pkg.Pkg.Imports() { 130 fmt.Println(sep, im.Path()) 131 p.dumpDeps(im.Path(), sep+" ") 132 } 133 } 134 135 func (p *Program) DumpExport(path string) { 136 pkg := p.prog.Package(path) 137 for _, v := range pkg.Pkg.Scope().Names() { 138 if token.IsExported(v) { 139 fmt.Println(v) 140 } 141 } 142 } 143 144 /* 145 type ConstValue struct { 146 Typ string 147 Value constant.Value 148 } 149 150 type Package struct { 151 Name string 152 Path string 153 Types []reflect.Type 154 Vars map[string]reflect.Value 155 Funcs map[string]reflect.Value 156 Consts map[string]ConstValue 157 Deps map[string]string 158 } 159 160 */ 161 162 type Package struct { 163 Name string 164 Path string 165 Deps []string 166 NamedTypes []string 167 Interfaces []string 168 AliasTypes []string 169 Vars []string 170 Funcs []string 171 Consts []string 172 TypedConsts []string 173 UntypedConsts []string 174 Links []string 175 Source string 176 usedPkg bool 177 } 178 179 func (p *Package) IsEmpty() bool { 180 return len(p.NamedTypes) == 0 && len(p.Interfaces) == 0 && 181 len(p.AliasTypes) == 0 && len(p.Vars) == 0 && 182 len(p.Funcs) == 0 && len(p.Consts) == 0 && 183 len(p.TypedConsts) == 0 && len(p.UntypedConsts) == 0 184 } 185 186 /* 187 func unmarshalFloat(str string) constant.Value { 188 if sep := strings.IndexByte(str, '/'); sep >= 0 { 189 x := constant.MakeFromLiteral(str[:sep], token.FLOAT, 0) 190 y := constant.MakeFromLiteral(str[sep+1:], token.FLOAT, 0) 191 return constant.BinaryOp(x, token.QUO, y) 192 } 193 return constant.MakeFromLiteral(str, token.FLOAT, 0) 194 } 195 */ 196 197 func (p *Program) constToLit(named string, c constant.Value) string { 198 switch c.Kind() { 199 case constant.Bool: 200 if named != "" { 201 return fmt.Sprintf("constant.MakeBool(bool(%v))", named) 202 } 203 return fmt.Sprintf("constant.MakeBool(%v)", constant.BoolVal(c)) 204 case constant.String: 205 if named != "" { 206 return fmt.Sprintf("constant.MakeString(string(%v))", named) 207 } 208 return fmt.Sprintf("constant.MakeString(%q)", constant.StringVal(c)) 209 case constant.Int: 210 if v, ok := constant.Int64Val(c); ok { 211 if named != "" { 212 return fmt.Sprintf("constant.MakeInt64(int64(%v))", named) 213 } 214 return fmt.Sprintf("constant.MakeInt64(%v)", v) 215 } else if v, ok := constant.Uint64Val(c); ok { 216 if named != "" { 217 return fmt.Sprintf("constant.MakeUint64(uint64(%v))", named) 218 } 219 return fmt.Sprintf("constant.MakeUint64(%v)", v) 220 } 221 return fmt.Sprintf("constant.MakeFromLiteral(%q, token.INT, 0)", c.ExactString()) 222 case constant.Float: 223 s := c.ExactString() 224 if pos := strings.IndexByte(s, '/'); pos >= 0 { 225 sx := s[:pos] 226 sy := s[pos+1:] 227 // simplify 314/100 => 3.14 228 // 80901699437494742410229341718281905886015458990288143106772431 229 // 50000000000000000000000000000000000000000000000000000000000000 230 if strings.HasPrefix(sy, "1") && strings.Count(sy, "0") == len(sy)-1 { 231 if len(sx) == len(sy) { 232 return fmt.Sprintf("constant.MakeFromLiteral(\"%v.%v\", token.FLOAT, 0)", sx[:1], sx[1:]) 233 } else if len(sx) == len(sy)-1 { 234 return fmt.Sprintf("constant.MakeFromLiteral(\"0.%v\", token.FLOAT, 0)", sx) 235 } else if len(sx) < len(sy) { 236 return fmt.Sprintf("constant.MakeFromLiteral(\"%v.%ve-%v\", token.FLOAT, 0)", sx[:1], sx[1:], len(sy)-len(sx)) 237 } 238 } else if strings.HasPrefix(sy, "5") && strings.Count(sy, "0") == len(sy)-1 { 239 if len(sx) == len(sy) { 240 c := constant.BinaryOp(constant.MakeFromLiteral(sx, token.INT, 0), token.MUL, constant.MakeInt64(2)) 241 sx = c.ExactString() 242 return fmt.Sprintf("constant.MakeFromLiteral(\"%v.%v\", token.FLOAT, 0)", sx[:1], sx[1:]) 243 } 244 } else if strings.HasPrefix(sx, "1") && strings.Count(sx, "0") == len(sx)-1 { 245 // skip 246 } 247 x := fmt.Sprintf("constant.MakeFromLiteral(%q, token.INT, 0)", sx) 248 y := fmt.Sprintf("constant.MakeFromLiteral(%q, token.INT, 0)", sy) 249 return fmt.Sprintf("constant.BinaryOp(%v, token.QUO, %v)", x, y) 250 } 251 if pos := strings.LastIndexAny(s, "123456789"); pos != -1 { 252 sx := s[:pos+1] 253 return fmt.Sprintf("constant.MakeFromLiteral(\"%v.%ve+%v\", token.FLOAT, 0)", sx[:1], sx[1:], len(s)-1) 254 } 255 return fmt.Sprintf("constant.MakeFromLiteral(%q, token.FLOAT, 0)", s) 256 case constant.Complex: 257 re := p.constToLit("", constant.Real(c)) 258 im := p.constToLit("", constant.Imag(c)) 259 return fmt.Sprintf("constant.BinaryOp(%v, token.ADD, constan.MakeImag(%v))", re, im) 260 default: 261 panic("unreachable") 262 } 263 } 264 265 func (p *Program) ExportSource(e *Package, info *loader.PackageInfo) error { 266 pkg := info.Pkg 267 pkgPath := pkg.Path() 268 pkgName := pkg.Name() 269 270 outf := new(ast.File) 271 outf.Name = ast.NewIdent(pkgName) 272 273 var specs []ast.Spec 274 for _, im := range pkg.Imports() { 275 specs = append(specs, &ast.ImportSpec{ 276 Path: &ast.BasicLit{ 277 Kind: token.STRING, 278 Value: strconv.Quote(im.Path()), 279 }, 280 }) 281 } 282 if len(specs) > 0 { 283 outf.Decls = append(outf.Decls, &ast.GenDecl{ 284 Tok: token.IMPORT, 285 Specs: specs, 286 }) 287 } 288 289 var links []string 290 for _, file := range info.Files { 291 outf.Imports = append(outf.Imports, file.Imports...) 292 for _, decl := range file.Decls { 293 switch d := decl.(type) { 294 case *ast.GenDecl: 295 if d.Tok == token.IMPORT { 296 continue 297 } 298 if d.Tok == token.VAR { 299 var skip bool 300 for _, spec := range d.Specs { 301 for _, name := range spec.(*ast.ValueSpec).Names { 302 if name.Name == "_" { 303 skip = true 304 continue 305 } 306 } 307 } 308 if skip { 309 continue 310 } 311 } 312 outf.Decls = append(outf.Decls, d) 313 case *ast.FuncDecl: 314 outf.Decls = append(outf.Decls, d) 315 if funcHasTypeParams(d) { 316 continue 317 } 318 fnName := d.Name.Name 319 if d.Recv == nil && d.Body == nil && !ast.IsExported(fnName) { 320 decl := &ast.FuncDecl{} 321 decl.Type = d.Type 322 lcName := "_" + fnName 323 decl.Name = ast.NewIdent(lcName) 324 decl.Doc = &ast.CommentGroup{[]*ast.Comment{ 325 &ast.Comment{Text: fmt.Sprintf("//go:linkname %v %v.%v", lcName, pkgPath, d.Name)}, 326 }} 327 var buf bytes.Buffer 328 printer.Fprint(&buf, p.fset, decl) 329 links = append(links, buf.String()) 330 e.Funcs = append(e.Funcs, fmt.Sprintf("%q : reflect.ValueOf(%v)", fnName, lcName)) 331 } 332 } 333 } 334 } 335 var buf bytes.Buffer 336 err := printer.Fprint(&buf, p.fset, outf) 337 if err != nil { 338 return err 339 } 340 e.Links = links 341 e.Source = strconv.Quote(buf.String()) 342 return nil 343 } 344 345 func (p *Program) ExportPkg(path string, sname string) (*Package, error) { 346 info := p.prog.Package(path) 347 if info == nil { 348 return nil, fmt.Errorf("not found path %v", path) 349 } 350 pkg := info.Pkg 351 pkgPath := pkg.Path() 352 pkgName := pkg.Name() 353 e := &Package{Name: pkgName, Path: pkgPath} 354 pkgName = sname 355 for _, v := range pkg.Imports() { 356 e.Deps = append(e.Deps, fmt.Sprintf("%q: %q", v.Path(), v.Name())) 357 } 358 var foundGeneric bool 359 for _, name := range pkg.Scope().Names() { 360 if !token.IsExported(name) { 361 continue 362 } 363 obj := pkg.Scope().Lookup(name) 364 switch t := obj.(type) { 365 case *types.Const: 366 named := pkgName + "." + t.Name() 367 if typ := t.Type().String(); strings.HasPrefix(typ, "untyped ") { 368 e.UntypedConsts = append(e.UntypedConsts, fmt.Sprintf("%q: {%q, %v}", t.Name(), t.Type().String(), p.constToLit(named, t.Val()))) 369 } else { 370 e.TypedConsts = append(e.TypedConsts, fmt.Sprintf("%q : {reflect.TypeOf(%v), %v}", t.Name(), pkgName+"."+t.Name(), p.constToLit(named, t.Val()))) 371 } 372 e.usedPkg = true 373 case *types.Var: 374 e.Vars = append(e.Vars, fmt.Sprintf("%q : reflect.ValueOf(&%v)", t.Name(), pkgName+"."+t.Name())) 375 e.usedPkg = true 376 case *types.Func: 377 if hasTypeParam(t.Type()) { 378 if !flagExportSource { 379 log.Println("skip typeparam", t) 380 } 381 foundGeneric = true 382 continue 383 } 384 e.Funcs = append(e.Funcs, fmt.Sprintf("%q : reflect.ValueOf(%v)", t.Name(), pkgName+"."+t.Name())) 385 e.usedPkg = true 386 case *types.TypeName: 387 if hasTypeParam(t.Type()) { 388 if !flagExportSource { 389 log.Println("skip typeparam", t) 390 } 391 foundGeneric = true 392 continue 393 } 394 if t.IsAlias() { 395 name := obj.Name() 396 switch typ := obj.Type().(type) { 397 case *types.Basic: 398 e.AliasTypes = append(e.AliasTypes, fmt.Sprintf("%q: reflect.TypeOf((*%v)(nil)).Elem()", name, typ.Name())) 399 // case *types.Named: 400 // e.AliasTypes = append(e.AliasTypes, fmt.Sprintf("%q: reflect.TypeOf((*%v.%v)(nil)).Elem()", name, sname, name)) 401 default: 402 e.AliasTypes = append(e.AliasTypes, fmt.Sprintf("%q: reflect.TypeOf((*%v.%v)(nil)).Elem()", name, sname, name)) 403 } 404 e.usedPkg = true 405 continue 406 } 407 typeName := t.Name() 408 if types.IsInterface(t.Type()) { 409 e.Interfaces = append(e.Interfaces, fmt.Sprintf("%q : reflect.TypeOf((*%v.%v)(nil)).Elem()", typeName, pkgName, typeName)) 410 } else { 411 e.NamedTypes = append(e.NamedTypes, fmt.Sprintf("%q : reflect.TypeOf((*%v.%v)(nil)).Elem()", typeName, pkgName, typeName)) 412 } 413 e.usedPkg = true 414 default: 415 log.Panicf("unreachable %v %T\n", name, t) 416 } 417 } 418 if flagExportSource && foundGeneric { 419 if err := p.ExportSource(e, info); err != nil { 420 log.Println("export source failed", err) 421 } 422 } 423 return e, nil 424 }