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 }