github.com/goplus/gox@v1.14.13-0.20240308130321-6ff7f61cfae8/package.go (about) 1 /* 2 Copyright 2021 The GoPlus Authors (goplus.org) 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7 Unless required by applicable law or agreed to in writing, software 8 distributed under the License is distributed on an "AS IS" BASIS, 9 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 See the License for the specific language governing permissions and 11 limitations under the License. 12 */ 13 14 package gox 15 16 import ( 17 "go/ast" 18 "go/token" 19 "go/types" 20 "log" 21 "sort" 22 "strings" 23 "syscall" 24 25 "github.com/goplus/gox/packages" 26 ) 27 28 type LoadNamedFunc = func(at *Package, typ *types.Named) 29 30 const ( 31 DbgFlagInstruction = 1 << iota 32 DbgFlagImport 33 DbgFlagMatch 34 DbgFlagComments 35 DbgFlagWriteFile 36 DbgFlagSetDebug 37 DbgFlagPersistCache 38 DbgFlagAll = DbgFlagInstruction | DbgFlagImport | DbgFlagMatch | 39 DbgFlagComments | DbgFlagWriteFile | DbgFlagSetDebug | DbgFlagPersistCache 40 ) 41 42 var ( 43 debugInstr bool 44 debugMatch bool 45 debugImport bool 46 debugComments bool 47 debugWriteFile bool 48 debugImportIox bool 49 ) 50 51 func SetDebug(dbgFlags int) { 52 debugInstr = (dbgFlags & DbgFlagInstruction) != 0 53 debugImport = (dbgFlags & DbgFlagImport) != 0 54 debugMatch = (dbgFlags & DbgFlagMatch) != 0 55 debugComments = (dbgFlags & DbgFlagComments) != 0 56 debugWriteFile = (dbgFlags & DbgFlagWriteFile) != 0 57 if (dbgFlags & DbgFlagSetDebug) != 0 { 58 log.Printf("SetDebug: import=%v, match=%v, instr=%v\n", debugImport, debugMatch, debugInstr) 59 } 60 } 61 62 type fatalMsg string 63 64 func fatal(msg string) { 65 panic(fatalMsg(msg)) 66 } 67 68 // ---------------------------------------------------------------------------- 69 70 // Recorder represents a gox event recorder. 71 type Recorder interface { 72 // Member maps identifiers to the objects they denote. 73 Member(id ast.Node, obj types.Object) 74 // Call maps func to the the objects they denote. 75 Call(fn ast.Node, obj types.Object) 76 } 77 78 // ---------------------------------------------------------------------------- 79 80 type dbgPositioner interface { 81 // Position gets position of a Pos. 82 Position(p token.Pos) token.Position 83 } 84 85 type NodeInterpreter interface { 86 // LoadExpr is called to load an expr code. 87 LoadExpr(expr ast.Node) string 88 } 89 90 // Config type 91 type Config struct { 92 // Types provides type information for the package (optional). 93 Types *types.Package 94 95 // Fset provides source position information for syntax trees and types (optional). 96 // If Fset is nil, Load will use a new fileset, but preserve Fset's value. 97 Fset *token.FileSet 98 99 // HandleErr is called to handle errors (optional). 100 HandleErr func(err error) 101 102 // NodeInterpreter is to interpret an ast.Node (optional). 103 NodeInterpreter NodeInterpreter 104 105 // LoadNamed is called to load a delay-loaded named type (optional). 106 LoadNamed LoadNamedFunc 107 108 // An Importer resolves import paths to Packages (optional). 109 Importer types.Importer 110 111 // DefaultGoFile specifies default file name. It can be empty. 112 DefaultGoFile string 113 114 // PkgPathIox specifies package path of github.com/goplus/gop/builtin/iox 115 PkgPathIox string 116 117 // NewBuiltin is to create the builin package (optional). 118 NewBuiltin func(pkg *Package, conf *Config) *types.Package 119 120 // CanImplicitCast checkes can cast V to T implicitly (optional). 121 CanImplicitCast func(pkg *Package, V, T types.Type, pv *Element) bool 122 123 // untyped bigint, untyped bigrat, untyped bigfloat (optional). 124 UntypedBigInt, UntypedBigRat, UntypedBigFloat *types.Named 125 126 // A Recorder records selected objects such as methods, etc (optional). 127 Recorder Recorder 128 129 // (internal) only for testing 130 DbgPositioner dbgPositioner 131 132 // NoSkipConstant is to disable optimization of skipping constant (optional). 133 NoSkipConstant bool 134 } 135 136 // ---------------------------------------------------------------------------- 137 138 type importUsed bool 139 140 type File struct { 141 decls []ast.Decl 142 fname string 143 imps map[string]*ast.Ident // importPath => impRef (nil means force-import) 144 dirty bool 145 } 146 147 func newFile(fname string) *File { 148 return &File{fname: fname, imps: make(map[string]*ast.Ident)} 149 } 150 151 func (p *File) newImport(name, pkgPath string) *ast.Ident { 152 id := p.imps[pkgPath] 153 if id == nil { 154 id = &ast.Ident{Name: name, Obj: &ast.Object{Data: importUsed(false)}} 155 p.imps[pkgPath] = id 156 p.dirty = true 157 } 158 return id 159 } 160 161 func (p *File) forceImport(pkgPath string) { 162 if _, ok := p.imps[pkgPath]; !ok { 163 p.imps[pkgPath] = nil 164 p.dirty = true 165 } 166 } 167 168 func (p *File) markUsed(this *Package) { 169 if p.dirty { 170 astVisitor{this}.markUsed(p.decls) 171 p.dirty = false 172 } 173 } 174 175 // Name returns the name of this file. 176 func (p *File) Name() string { 177 return p.fname 178 } 179 180 type astVisitor struct { 181 this *Package 182 } 183 184 func (p astVisitor) Visit(node ast.Node) (w ast.Visitor) { 185 if node == nil { 186 return nil 187 } 188 switch v := node.(type) { 189 case *ast.CommentGroup, *ast.Ident, *ast.BasicLit: 190 case *ast.SelectorExpr: 191 x := v.X 192 if id, ok := x.(*ast.Ident); ok && id.Obj != nil { 193 if used, ok := id.Obj.Data.(importUsed); ok && bool(!used) { 194 id.Obj.Data = importUsed(true) 195 if name, renamed := p.this.requireName(id.Name); renamed { 196 id.Name = name 197 id.Obj.Name = name 198 } 199 } 200 } else { 201 ast.Walk(p, x) 202 } 203 case *ast.FuncDecl: 204 ast.Walk(p, v.Type) 205 if v.Body != nil { 206 ast.Walk(p, v.Body) 207 } 208 case *ast.ValueSpec: 209 if v.Type != nil { 210 ast.Walk(p, v.Type) 211 } 212 for _, val := range v.Values { 213 ast.Walk(p, val) 214 } 215 case *ast.TypeSpec: 216 ast.Walk(p, v.Type) 217 case *ast.BranchStmt: 218 case *ast.LabeledStmt: 219 ast.Walk(p, v.Stmt) 220 default: 221 return p 222 } 223 return nil 224 } 225 226 func (p astVisitor) markUsed(decls []ast.Decl) { 227 for _, decl := range decls { 228 ast.Walk(p, decl) 229 } 230 } 231 232 func isPkgInMod(pkgPath, modPath string) bool { 233 if strings.HasPrefix(pkgPath, modPath) { 234 suffix := pkgPath[len(modPath):] 235 return suffix == "" || suffix[0] == '/' 236 } 237 return false 238 } 239 240 const ( 241 FlagDepModGop = 1 << iota // depends module github.com/goplus/gop 242 FlagDepModX // depends module github.com/qiniu/x 243 ) 244 245 // CheckGopDeps checks dependencies of Go+ modules. 246 // The return flags can be FlagDepModGop and FlagDepModX. 247 func (p *File) CheckGopDeps(this *Package) (flags int) { 248 p.markUsed(this) 249 for pkgPath, id := range p.imps { 250 if id == nil || id.Obj.Data.(importUsed) { 251 if isPkgInMod(pkgPath, "github.com/goplus/gop") { 252 flags |= FlagDepModGop 253 } else if isPkgInMod(pkgPath, "github.com/qiniu/x") { 254 flags |= FlagDepModX 255 } 256 } 257 } 258 return 259 } 260 261 func (p *File) getDecls(this *Package) (decls []ast.Decl) { 262 p.markUsed(this) 263 specs := make([]ast.Spec, 0, len(p.imps)) 264 for pkgPath, id := range p.imps { 265 if id == nil { // force-used 266 specs = append(specs, &ast.ImportSpec{ 267 Name: underscore, // _ 268 Path: stringLit(pkgPath), 269 }) 270 } else if id.Obj.Data.(importUsed) { 271 var name *ast.Ident 272 if id.Obj.Name != "" { 273 name = ident(id.Obj.Name) 274 } 275 specs = append(specs, &ast.ImportSpec{ 276 Name: name, 277 Path: stringLit(pkgPath), 278 }) 279 } 280 } 281 sort.Slice(specs, func(i, j int) bool { 282 return specs[i].(*ast.ImportSpec).Path.Value < specs[j].(*ast.ImportSpec).Path.Value 283 }) 284 285 var valGopPkg ast.Expr 286 var addGopPkg bool 287 if p.fname == this.conf.DefaultGoFile { 288 valGopPkg, addGopPkg = checkGopPkg(this) 289 } 290 if len(specs) == 0 && !addGopPkg { 291 return p.decls 292 } 293 294 decls = make([]ast.Decl, 0, len(p.decls)+2) 295 decls = append(decls, &ast.GenDecl{Tok: token.IMPORT, Specs: specs}) 296 if addGopPkg { 297 decls = append(decls, &ast.GenDecl{Tok: token.CONST, Specs: []ast.Spec{ 298 &ast.ValueSpec{ 299 Names: []*ast.Ident{{Name: gopPackage}}, 300 Values: []ast.Expr{ 301 valGopPkg, 302 }, 303 }, 304 }}) 305 } 306 return append(decls, p.decls...) 307 } 308 309 // ---------------------------------------------------------------------------- 310 311 // ObjectDocs maps an object to its document. 312 type ObjectDocs = map[types.Object]*ast.CommentGroup 313 314 // Package type 315 type Package struct { 316 PkgRef 317 Docs ObjectDocs 318 Fset *token.FileSet 319 320 autoNames 321 cb CodeBuilder 322 imp types.Importer 323 files map[string]*File 324 file *File 325 conf *Config 326 unsafe_ PkgRef 327 builtin PkgRef 328 pkgBig PkgRef 329 utBigInt *types.Named 330 utBigRat *types.Named 331 utBigFlt *types.Named 332 commentedStmts map[ast.Stmt]*ast.CommentGroup 333 implicitCast func(pkg *Package, V, T types.Type, pv *Element) bool 334 335 expObjTypes []types.Type // types of export objects 336 isGopPkg bool 337 allowRedecl bool // for c2go 338 } 339 340 const ( 341 goxPrefix = "Gop_" 342 ) 343 344 // NewPackage creates a new package. 345 func NewPackage(pkgPath, name string, conf *Config) *Package { 346 if conf == nil { 347 conf = new(Config) 348 } 349 fset := conf.Fset 350 if fset == nil { 351 fset = token.NewFileSet() 352 } 353 imp := conf.Importer 354 if imp == nil { 355 imp = packages.NewImporter(fset) 356 } 357 newBuiltin := conf.NewBuiltin 358 if newBuiltin == nil { 359 newBuiltin = newBuiltinDefault 360 } 361 fname := conf.DefaultGoFile 362 file := newFile(fname) 363 files := map[string]*File{fname: file} 364 pkg := &Package{ 365 Fset: fset, 366 file: file, 367 files: files, 368 conf: conf, 369 } 370 pkg.initAutoNames() 371 pkg.imp = imp 372 pkg.Types = conf.Types 373 if pkg.Types == nil { 374 pkg.Types = types.NewPackage(pkgPath, name) 375 } 376 pkg.builtin.Types = newBuiltin(pkg, conf) 377 pkg.implicitCast = conf.CanImplicitCast 378 pkg.utBigInt = conf.UntypedBigInt 379 pkg.utBigRat = conf.UntypedBigRat 380 pkg.utBigFlt = conf.UntypedBigFloat 381 pkg.cb.init(pkg) 382 return pkg 383 } 384 385 func (p *Package) setDoc(o types.Object, doc *ast.CommentGroup) { 386 if p.Docs == nil { 387 p.Docs = make(ObjectDocs) 388 } 389 p.Docs[o] = doc 390 } 391 392 func (p *Package) setStmtComments(stmt ast.Stmt, comments *ast.CommentGroup) { 393 if p.commentedStmts == nil { 394 p.commentedStmts = make(map[ast.Stmt]*ast.CommentGroup) 395 } 396 p.commentedStmts[stmt] = comments 397 } 398 399 // SetRedeclarable sets to allow redeclaration of variables/functions or not. 400 func (p *Package) SetRedeclarable(allowRedecl bool) { 401 p.allowRedecl = allowRedecl 402 } 403 404 // Sizeof returns sizeof typ in bytes. 405 func (p *Package) Sizeof(typ types.Type) int64 { 406 return align(std.Sizeof(typ), std.Alignof(typ)) 407 } 408 409 // align returns the smallest y >= x such that y % a == 0. 410 func align(x, a int64) int64 { 411 y := x + a - 1 412 return y - y%a 413 } 414 415 func (p *Package) Offsetsof(fields []*types.Var) []int64 { 416 return std.Offsetsof(fields) 417 } 418 419 // Builtin returns the buitlin package. 420 func (p *Package) Builtin() PkgRef { 421 return p.builtin 422 } 423 424 // Builtin returns the unsafe package. 425 func (p *Package) Unsafe() PkgRef { 426 return p.unsafe_ 427 } 428 429 // CB returns the code builder. 430 func (p *Package) CB() *CodeBuilder { 431 return &p.cb 432 } 433 434 // SetCurFile sets new current file to write. 435 // If createIfNotExists is true, then create a new file named `fname` if it not exists. 436 // It returns an `old` file to restore in the future (by calling `RestoreCurFile`). 437 func (p *Package) SetCurFile(fname string, createIfNotExists bool) (old *File, err error) { 438 old = p.file 439 f, ok := p.files[fname] 440 if !ok { 441 if createIfNotExists { 442 f = newFile(fname) 443 p.files[fname] = f 444 } else { 445 return nil, syscall.ENOENT 446 } 447 } 448 p.file = f 449 return 450 } 451 452 // CurFile returns the current file. 453 func (p *Package) CurFile() *File { 454 return p.file 455 } 456 457 // RestoreCurFile sets current file to an `old` file that was returned by `SetCurFile`. 458 func (p *Package) RestoreCurFile(file *File) (old *File) { 459 old = p.file 460 p.file = file 461 return 462 } 463 464 // File returns a file by its name. 465 // If `fname` is not provided, it returns the default (NOT current) file. 466 func (p *Package) File(fname ...string) (file *File, ok bool) { 467 var name string 468 if len(fname) == 1 { 469 name = fname[0] 470 } else { 471 name = p.conf.DefaultGoFile 472 } 473 file, ok = p.files[name] 474 return 475 } 476 477 // ForEachFile walks all files to `doSth`. 478 func (p *Package) ForEachFile(doSth func(fname string, file *File)) { 479 for fname, file := range p.files { 480 doSth(fname, file) 481 } 482 } 483 484 // ----------------------------------------------------------------------------