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  // ----------------------------------------------------------------------------