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  }