github.com/goplus/gop@v1.2.6/x/typesutil/check.go (about)

     1  /*
     2   * Copyright (c) 2023 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 typesutil
    18  
    19  import (
    20  	goast "go/ast"
    21  	"go/types"
    22  	"path/filepath"
    23  	"strings"
    24  
    25  	"github.com/goplus/gogen"
    26  	"github.com/goplus/gop/ast"
    27  	"github.com/goplus/gop/cl"
    28  	"github.com/goplus/gop/token"
    29  	"github.com/goplus/gop/x/c2go"
    30  	"github.com/goplus/gop/x/typesutil/internal/typesutil"
    31  	"github.com/goplus/mod/gopmod"
    32  	"github.com/qiniu/x/errors"
    33  	"github.com/qiniu/x/log"
    34  )
    35  
    36  // -----------------------------------------------------------------------------
    37  
    38  type dbgFlags int
    39  
    40  const (
    41  	DbgFlagVerbose dbgFlags = 1 << iota
    42  	DbgFlagPrintError
    43  	DbgFlagDisableRecover
    44  	DbgFlagDefault = DbgFlagVerbose | DbgFlagPrintError
    45  	DbgFlagAll     = DbgFlagDefault | DbgFlagDisableRecover
    46  )
    47  
    48  var (
    49  	debugVerbose  bool
    50  	debugPrintErr bool
    51  )
    52  
    53  func SetDebug(flags dbgFlags) {
    54  	debugVerbose = (flags & DbgFlagVerbose) != 0
    55  	debugPrintErr = (flags & DbgFlagPrintError) != 0
    56  	if (flags & DbgFlagDisableRecover) != 0 {
    57  		cl.SetDisableRecover(true)
    58  	}
    59  }
    60  
    61  // -----------------------------------------------------------------------------
    62  
    63  type Project = cl.Project
    64  
    65  type Config struct {
    66  	// Types provides type information for the package (required).
    67  	Types *types.Package
    68  
    69  	// Fset provides source position information for syntax trees and types (required).
    70  	Fset *token.FileSet
    71  
    72  	// WorkingDir is the directory in which to run gop compiler (optional).
    73  	// If WorkingDir is not set, os.Getwd() is used.
    74  	WorkingDir string
    75  
    76  	// C2goBase specifies base of standard c2go packages (optional).
    77  	// Default is github.com/goplus/.
    78  	C2goBase string
    79  
    80  	// Mod represents a Go+ module (optional).
    81  	Mod *gopmod.Module
    82  
    83  	// If IgnoreFuncBodies is set, skip compiling function bodies (optional).
    84  	IgnoreFuncBodies bool
    85  
    86  	// If UpdateGoTypesOverload is set, update go types overload data (optional).
    87  	UpdateGoTypesOverload bool
    88  }
    89  
    90  // A Checker maintains the state of the type checker.
    91  // It must be created with NewChecker.
    92  type Checker struct {
    93  	conf    *types.Config
    94  	opts    *Config
    95  	goInfo  *types.Info
    96  	gopInfo *Info
    97  }
    98  
    99  // NewChecker returns a new Checker instance for a given package.
   100  // Package files may be added incrementally via checker.Files.
   101  func NewChecker(conf *types.Config, opts *Config, goInfo *types.Info, gopInfo *Info) *Checker {
   102  	return &Checker{conf, opts, goInfo, gopInfo}
   103  }
   104  
   105  // Files checks the provided files as part of the checker's package.
   106  func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error) {
   107  	opts := p.opts
   108  	pkgTypes := opts.Types
   109  	fset := opts.Fset
   110  	conf := p.conf
   111  	if len(gopFiles) == 0 {
   112  		checker := types.NewChecker(conf, fset, pkgTypes, p.goInfo)
   113  		return checker.Files(goFiles)
   114  	}
   115  	files := make([]*goast.File, 0, len(goFiles))
   116  	gofs := make(map[string]*goast.File)
   117  	gopfs := make(map[string]*ast.File)
   118  	for _, goFile := range goFiles {
   119  		f := fset.File(goFile.Pos())
   120  		if f == nil {
   121  			continue
   122  		}
   123  		file := f.Name()
   124  		fname := filepath.Base(file)
   125  		if strings.HasPrefix(fname, "gop_autogen") {
   126  			continue
   127  		}
   128  		gofs[file] = goFile
   129  		files = append(files, goFile)
   130  	}
   131  	for _, gopFile := range gopFiles {
   132  		f := fset.File(gopFile.Pos())
   133  		if f == nil {
   134  			continue
   135  		}
   136  		gopfs[f.Name()] = gopFile
   137  	}
   138  	if debugVerbose {
   139  		log.Println("typesutil.Check:", pkgTypes.Path(), "gopFiles =", len(gopfs), "goFiles =", len(gofs))
   140  	}
   141  	pkg := &ast.Package{
   142  		Name:    pkgTypes.Name(),
   143  		Files:   gopfs,
   144  		GoFiles: gofs,
   145  	}
   146  	mod := opts.Mod
   147  	if mod == nil {
   148  		mod = gopmod.Default
   149  	}
   150  	_, err = cl.NewPackage(pkgTypes.Path(), pkg, &cl.Config{
   151  		Types:          pkgTypes,
   152  		Fset:           fset,
   153  		C2goBase:       opts.C2goBase,
   154  		LookupPub:      c2go.LookupPub(mod),
   155  		LookupClass:    mod.LookupClass,
   156  		Importer:       conf.Importer,
   157  		Recorder:       NewRecorder(p.gopInfo),
   158  		NoFileLine:     true,
   159  		NoAutoGenMain:  true,
   160  		NoSkipConstant: true,
   161  		Outline:        opts.IgnoreFuncBodies,
   162  	})
   163  	if err != nil {
   164  		if onErr := conf.Error; onErr != nil {
   165  			if list, ok := err.(errors.List); ok {
   166  				for _, e := range list {
   167  					if ce, ok := convErr(fset, e); ok {
   168  						onErr(ce)
   169  					}
   170  				}
   171  			} else if ce, ok := convErr(fset, err); ok {
   172  				onErr(ce)
   173  			}
   174  		}
   175  		if debugPrintErr {
   176  			log.Println("typesutil.Check err:", err)
   177  			log.SingleStack()
   178  		}
   179  		// don't return even if err != nil
   180  	}
   181  	if len(files) > 0 {
   182  		scope := pkgTypes.Scope()
   183  		objMap := DeleteObjects(scope, files)
   184  		checker := types.NewChecker(conf, fset, pkgTypes, p.goInfo)
   185  		err = checker.Files(files)
   186  		// TODO: how to process error?
   187  		CorrectTypesInfo(scope, objMap, p.gopInfo.Uses)
   188  		if opts.UpdateGoTypesOverload {
   189  			gogen.InitThisGopPkg(pkgTypes)
   190  		}
   191  	}
   192  	return
   193  }
   194  
   195  type astIdent interface {
   196  	comparable
   197  	ast.Node
   198  }
   199  
   200  type objMapT = map[types.Object]types.Object
   201  
   202  // CorrectTypesInfo corrects types info to avoid there are two instances for the same Go object.
   203  func CorrectTypesInfo[Ident astIdent](scope *types.Scope, objMap objMapT, uses map[Ident]types.Object) {
   204  	for o := range objMap {
   205  		objMap[o] = scope.Lookup(o.Name())
   206  	}
   207  	for id, old := range uses {
   208  		if new := objMap[old]; new != nil {
   209  			uses[id] = new
   210  		}
   211  	}
   212  }
   213  
   214  // DeleteObjects deletes all objects defined in Go files and returns deleted objects.
   215  func DeleteObjects(scope *types.Scope, files []*goast.File) objMapT {
   216  	objMap := make(objMapT)
   217  	for _, f := range files {
   218  		for _, decl := range f.Decls {
   219  			switch v := decl.(type) {
   220  			case *goast.GenDecl:
   221  				for _, spec := range v.Specs {
   222  					switch v := spec.(type) {
   223  					case *goast.ValueSpec:
   224  						for _, name := range v.Names {
   225  							scopeDelete(objMap, scope, name.Name)
   226  						}
   227  					case *goast.TypeSpec:
   228  						scopeDelete(objMap, scope, v.Name.Name)
   229  					}
   230  				}
   231  			case *goast.FuncDecl:
   232  				if v.Recv == nil {
   233  					scopeDelete(objMap, scope, v.Name.Name)
   234  				}
   235  			}
   236  		}
   237  	}
   238  	return objMap
   239  }
   240  
   241  func convErr(fset *token.FileSet, e error) (ret types.Error, ok bool) {
   242  	switch v := e.(type) {
   243  	case *gogen.CodeError:
   244  		ret.Pos, ret.Msg = v.Pos, v.Msg
   245  		typesutil.SetErrorGo116(&ret, 0, v.Pos, v.Pos)
   246  	case *gogen.MatchError:
   247  		end := token.NoPos
   248  		if v.Src != nil {
   249  			ret.Pos, end = v.Src.Pos(), v.Src.End()
   250  		}
   251  		ret.Msg = v.Message("")
   252  		typesutil.SetErrorGo116(&ret, 0, ret.Pos, end)
   253  	case *gogen.ImportError:
   254  		ret.Pos, ret.Msg = v.Pos, v.Err.Error()
   255  		typesutil.SetErrorGo116(&ret, 0, v.Pos, v.Pos)
   256  	default:
   257  		return
   258  	}
   259  	ret.Fset, ok = fset, true
   260  	return
   261  }
   262  
   263  func scopeDelete(objMap map[types.Object]types.Object, scope *types.Scope, name string) {
   264  	if o := typesutil.ScopeDelete(scope, name); o != nil {
   265  		objMap[o] = nil
   266  	}
   267  }