Repository: goplus/gop
Pull request #1518 (2023-11-03 03:35:36)

gop go -ignore-notated-error
- https://github.com/goplus/gox/pull/302


Patch:
diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go
index 365bf0e2..82aaf6b9 100644
--- a/cmd/internal/build/build.go
+++ b/cmd/internal/build/build.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-// Package build implements the ``gop build'' command.
+// Package build implements the “gop build” command.
 package build
 
 import (
@@ -23,7 +23,6 @@ import (
 	"os"
 	"path/filepath"
 	"reflect"
-	"syscall"
 
 	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
@@ -105,7 +104,7 @@ func build(proj gopprojs.Proj, conf *gop.Config, build *gocmd.BuildConfig) {
 	default:
 		log.Panicln("`gop build` doesn't support", reflect.TypeOf(v))
 	}
-	if err == syscall.ENOENT {
+	if gop.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop build %v: not found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/cmd/internal/doc/doc.go b/cmd/internal/doc/doc.go
index 82ca12cf..c369a2b5 100644
--- a/cmd/internal/doc/doc.go
+++ b/cmd/internal/doc/doc.go
@@ -24,7 +24,6 @@ import (
 	"reflect"
 	"strconv"
 	"strings"
-	"syscall"
 
 	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
@@ -95,7 +94,7 @@ func outlinePkg(proj gopprojs.Proj, conf *gop.Config) {
 	default:
 		log.Panicln("`gop doc` doesn't support", reflect.TypeOf(v))
 	}
-	if err == syscall.ENOENT {
+	if gop.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop doc %v: not Go/Go+ files found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/cmd/internal/gengo/go.go b/cmd/internal/gengo/go.go
index 59e61791..ee55eb1f 100644
--- a/cmd/internal/gengo/go.go
+++ b/cmd/internal/gengo/go.go
@@ -38,10 +38,12 @@ var Cmd = &base.Command{
 }
 
 var (
-	flag           = &Cmd.Flag
-	flagVerbose    = flag.Bool("v", false, "print verbose information.")
-	flagCheckMode  = flag.Bool("t", false, "do check syntax only, no generate gop_autogen.go")
-	flagSingleMode = flag.Bool("s", false, "run in single file mode")
+	flag                 = &Cmd.Flag
+	flagVerbose          = flag.Bool("v", false, "print verbose information")
+	flagCheckMode        = flag.Bool("t", false, "do check syntax only, no generate gop_autogen.go")
+	flagSingleMode       = flag.Bool("s", false, "run in single file mode")
+	flagIgnoreNotatedErr = flag.Bool(
+		"ignore-notated-error", false, "ignore notated errors, only available together with -t (check mode)")
 )
 
 func init() {
@@ -69,9 +71,13 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
+	var conf *gop.Config
 	flags := gop.GenFlagPrintError | gop.GenFlagPrompt
 	if *flagCheckMode {
 		flags |= gop.GenFlagCheckOnly
+		if *flagIgnoreNotatedErr {
+			conf = &gop.Config{IgnoreNotatedError: true}
+		}
 	}
 	if *flagSingleMode {
 		flags |= gop.GenFlagSingleFile
@@ -79,9 +85,9 @@ func runCmd(cmd *base.Command, args []string) {
 	for _, proj := range projs {
 		switch v := proj.(type) {
 		case *gopprojs.DirProj:
-			_, _, err = gop.GenGoEx(v.Dir, nil, true, flags)
+			_, _, err = gop.GenGoEx(v.Dir, conf, true, flags)
 		case *gopprojs.PkgPathProj:
-			_, _, err = gop.GenGoPkgPathEx("", v.Path, nil, true, flags)
+			_, _, err = gop.GenGoPkgPathEx("", v.Path, conf, true, flags)
 		default:
 			log.Panicln("`gop go` doesn't support", reflect.TypeOf(v))
 		}
diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go
index cb321756..583f5709 100644
--- a/cmd/internal/install/install.go
+++ b/cmd/internal/install/install.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-// Package install implements the ``gop install'' command.
+// Package install implements the “gop install” command.
 package install
 
 import (
@@ -22,7 +22,6 @@ import (
 	"log"
 	"os"
 	"reflect"
-	"syscall"
 
 	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
@@ -95,7 +94,7 @@ func install(proj gopprojs.Proj, conf *gop.Config, install *gocmd.InstallConfig)
 	default:
 		log.Panicln("`gop install` doesn't support", reflect.TypeOf(v))
 	}
-	if err == syscall.ENOENT {
+	if gop.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop install %v: not found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go
index fcf1fa03..525ae141 100644
--- a/cmd/internal/run/run.go
+++ b/cmd/internal/run/run.go
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-// Package run implements the ``gop run'' command.
+// Package run implements the “gop run” command.
 package run
 
 import (
 	"fmt"
 	"os"
 	"reflect"
-	"syscall"
 
 	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
@@ -104,7 +103,7 @@ func run(proj gopprojs.Proj, args []string, chDir bool, conf *gop.Config, run *g
 	default:
 		log.Panicln("`gop run` doesn't support", reflect.TypeOf(v))
 	}
-	if err == syscall.ENOENT {
+	if gop.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop run %v: not found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/gengo.go b/gengo.go
index 19da5f6f..66eb0e23 100644
--- a/gengo.go
+++ b/gengo.go
@@ -79,7 +79,7 @@ func genGoDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenF
 					if strings.HasPrefix(d.Name(), "_") { // skip _
 						return filepath.SkipDir
 					}
-					if e := genGoIn(path, conf, genTestPkg, flags); e != nil {
+					if e := genGoIn(path, conf, genTestPkg, flags); e != nil && notIgnNotated(e, conf) {
 						if flags&GenFlagPrintError != 0 {
 							fmt.Fprintln(os.Stderr, e)
 						}
@@ -106,13 +106,19 @@ func genGoDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenF
 		}
 		return list.ToError()
 	}
-	err = genGoIn(dir, conf, genTestPkg, flags)
-	if err != nil && (flags&GenFlagPrintError) != 0 {
-		fmt.Fprintln(os.Stderr, err)
+	if e := genGoIn(dir, conf, genTestPkg, flags); e != nil && notIgnNotated(e, conf) {
+		if (flags & GenFlagPrintError) != 0 {
+			fmt.Fprintln(os.Stderr, e)
+		}
+		err = e
 	}
 	return
 }
 
+func notIgnNotated(e error, conf *Config) bool {
+	return !(conf != nil && conf.IgnoreNotatedError && IgnoreNotated(e))
+}
+
 func genGoEntry(list *errors.List, path string, d fs.DirEntry, conf *Config, flags GenFlags) error {
 	fname := d.Name()
 	if strings.HasPrefix(fname, "_") { // skip _
@@ -120,7 +126,7 @@ func genGoEntry(list *errors.List, path string, d fs.DirEntry, conf *Config, fla
 			return filepath.SkipDir
 		}
 	} else if !d.IsDir() && strings.HasSuffix(fname, ".gop") {
-		if e := genGoSingleFile(path, conf, flags); e != nil {
+		if e := genGoSingleFile(path, conf, flags); e != nil && notIgnNotated(e, conf) {
 			if flags&GenFlagPrintError != 0 {
 				fmt.Fprintln(os.Stderr, e)
 			}
@@ -134,9 +140,9 @@ func genGoSingleFile(file string, conf *Config, flags GenFlags) (err error) {
 	dir, fname := filepath.Split(file)
 	autogen := dir + strings.TrimSuffix(fname, ".gop") + "_augogen.go"
 	if (flags & GenFlagPrompt) != 0 {
-		fmt.Println("GenGo", file, "...")
+		fmt.Fprintln(os.Stderr, "GenGo", file, "...")
 	}
-	out, err := LoadFiles([]string{file}, nil)
+	out, err := LoadFiles([]string{file}, conf)
 	if err != nil {
 		return errors.NewWith(err, `LoadFiles(files, conf)`, -2, "gop.LoadFiles", file)
 	}
@@ -152,7 +158,7 @@ func genGoSingleFile(file string, conf *Config, flags GenFlags) (err error) {
 func genGoIn(dir string, conf *Config, genTestPkg bool, flags GenFlags, gen ...*bool) (err error) {
 	out, test, err := LoadDir(dir, conf, genTestPkg, (flags&GenFlagPrompt) != 0)
 	if err != nil {
-		if err == syscall.ENOENT { // no Go+ source files
+		if NotFound(err) { // no Go+ source files
 			return nil
 		}
 		return errors.NewWith(err, `LoadDir(dir, conf, genTestPkg)`, -5, "gop.LoadDir", dir, conf, genTestPkg)
diff --git a/go.mod b/go.mod
index ec5c16e6..4daf1bdd 100644
--- a/go.mod
+++ b/go.mod
@@ -4,8 +4,8 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.7.0
-	github.com/goplus/c2go v0.7.17-0.20231102134817-4d12585f3af3
-	github.com/goplus/gox v1.12.2-0.20231102143433-424983d7a5ba
+	github.com/goplus/c2go v0.7.17-0.20231103102716-f59c28136a0b
+	github.com/goplus/gox v1.12.2-0.20231103090303-19b970256b0e
 	github.com/goplus/mod v0.11.8
 	github.com/qiniu/x v1.13.1
 	golang.org/x/tools v0.14.0
diff --git a/go.sum b/go.sum
index b5329298..dd3012f7 100644
--- a/go.sum
+++ b/go.sum
@@ -4,11 +4,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.17-0.20231102134817-4d12585f3af3 h1:+/enB6wmQenM2Ihwv/v41lSgdTf3ARPi6A8FB1A/YH4=
-github.com/goplus/c2go v0.7.17-0.20231102134817-4d12585f3af3/go.mod h1:DX0qhR2JSukHgIGMS9f6Vcu0N76LYBQy2tB+UnmIx3U=
-github.com/goplus/gox v1.12.2-0.20231102132054-7ebd369dd022/go.mod h1:MOJnwqA8/rSUkn0vsayHFK7TD/3XQrdexMTqdzR70X0=
-github.com/goplus/gox v1.12.2-0.20231102143433-424983d7a5ba h1:XPnDAw65OtpBFbR2XQkFz53HXN06ibEHX5xQIDffGXs=
-github.com/goplus/gox v1.12.2-0.20231102143433-424983d7a5ba/go.mod h1:MOJnwqA8/rSUkn0vsayHFK7TD/3XQrdexMTqdzR70X0=
+github.com/goplus/c2go v0.7.17-0.20231103102716-f59c28136a0b h1:qtQfKpXI2wx1N1wc6qA2EQIsPS1vEKNPswftLaGrUHU=
+github.com/goplus/c2go v0.7.17-0.20231103102716-f59c28136a0b/go.mod h1:fVpYuVxuvIQCrEhVrMkvivFwnWzr3EWWBQrnkfMMN+M=
+github.com/goplus/gox v1.12.2-0.20231103090303-19b970256b0e h1:l1a+oX9m3xWuHWXVGRpnNo+/vag8qR8DQ933euQlS6w=
+github.com/goplus/gox v1.12.2-0.20231103090303-19b970256b0e/go.mod h1:MOJnwqA8/rSUkn0vsayHFK7TD/3XQrdexMTqdzR70X0=
 github.com/goplus/mod v0.11.8 h1:90Kbg+MJhrdsonNGXYx3HUcW4IbBlRHYpIMssJaMMxM=
 github.com/goplus/mod v0.11.8/go.mod h1:yl2QncBKTdXk+8UaNsdo4u2zSpGEJYA5JKjgD3K2h00=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
diff --git a/load.go b/load.go
index 9d2065af..a26bad87 100644
--- a/load.go
+++ b/load.go
@@ -18,15 +18,16 @@ package gop
 
 import (
 	"fmt"
-	"go/token"
 	"go/types"
 	"io/fs"
+	"os"
 	"strings"
 	"syscall"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/token"
 	"github.com/goplus/gop/x/c2go"
 	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/gox"
@@ -35,6 +36,85 @@ import (
 	"github.com/qiniu/x/errors"
 )
 
+var (
+	ErrNotFound      = syscall.ENOENT
+	ErrIgnoreNotated = errors.New("notated error ignored")
+)
+
+// NotFound returns if cause err is ErrNotFound or not
+func NotFound(err error) bool {
+	return errors.Err(err) == ErrNotFound
+}
+
+// IgnoreNotated returns if cause err is ErrIgnoreNotated or not.
+func IgnoreNotated(err error) bool {
+	return errors.Err(err) == ErrIgnoreNotated
+}
+
+// ErrorPos returns where the error occurs.
+func ErrorPos(err error) token.Pos {
+	switch v := err.(type) {
+	case *gox.CodeError:
+		return v.Pos
+	case *gox.MatchError:
+		return v.Pos()
+	case *gox.ImportError:
+		return v.Pos
+	}
+	return token.NoPos
+}
+
+func ignNotatedErrs(err error, pkg *ast.Package, fset *token.FileSet) error {
+	switch v := err.(type) {
+	case errors.List:
+		var ret errors.List
+		for _, e := range v {
+			if isNotatedErr(e, pkg, fset) {
+				continue
+			}
+			ret = append(ret, e)
+		}
+		if len(ret) == 0 {
+			return ErrIgnoreNotated
+		}
+		return ret
+	default:
+		if isNotatedErr(err, pkg, fset) {
+			return ErrIgnoreNotated
+		}
+		return err
+	}
+}
+
+func isNotatedErr(err error, pkg *ast.Package, fset *token.FileSet) (notatedErr bool) {
+	pos := ErrorPos(err)
+	f := fset.File(pos)
+	if f == nil {
+		return
+	}
+	gopf, ok := pkg.Files[f.Name()]
+	if !ok {
+		return
+	}
+	lines := token.Lines(f)
+	i := f.Line(pos) - 1 // base 0
+	start := lines[i]
+	var end int
+	if i+1 < len(lines) {
+		end = lines[i+1]
+	} else {
+		end = f.Size()
+	}
+	text := string(gopf.Code[start:end])
+	commentOff := strings.Index(text, "//")
+	if commentOff < 0 {
+		return
+	}
+	return strings.Contains(text[commentOff+2:], "compile error:")
+}
+
+// -----------------------------------------------------------------------------
+
 type Config struct {
 	Gop      *env.Gop
 	Fset     *token.FileSet
@@ -43,12 +123,7 @@ type Config struct {
 
 	DontUpdateGoMod     bool
 	DontCheckModChanged bool
-}
-
-// -----------------------------------------------------------------------------
-
-func NotFound(err error) bool {
-	return errors.Err(err) == syscall.ENOENT
+	IgnoreNotatedError  bool
 }
 
 func LoadMod(dir string, gop *env.Gop, conf *Config) (mod *gopmod.Module, err error) {
@@ -102,7 +177,7 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 		return
 	}
 	if len(pkgs) == 0 {
-		return nil, nil, syscall.ENOENT
+		return nil, nil, ErrNotFound
 	}
 
 	imp := conf.Importer
@@ -133,15 +208,18 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 			continue
 		}
 		if promptGenGo != nil && promptGenGo[0] {
-			fmt.Printf("GenGo %v ...\n", dir)
+			fmt.Fprintln(os.Stderr, "GenGo", dir, "...")
 		}
 		out, err = cl.NewPackage("", pkg, clConf)
 		if err != nil {
+			if conf.IgnoreNotatedError {
+				err = ignNotatedErrs(err, pkg, fset)
+			}
 			return
 		}
 	}
 	if out == nil {
-		return nil, nil, syscall.ENOENT
+		return nil, nil, ErrNotFound
 	}
 	if pkgTest != nil && genTestPkg {
 		test, err = cl.NewPackage("", pkgTest, clConf)
@@ -191,7 +269,9 @@ func LoadFiles(files []string, conf *Config) (out *gox.Package, err error) {
 		}
 		out, err = cl.NewPackage("", pkg, clConf)
 		if err != nil {
-			err = errors.NewWith(err, `cl.NewPackage("", pkg, clConf)`, -2, "cl.NewPackage", "", pkg, clConf)
+			if conf.IgnoreNotatedError {
+				err = ignNotatedErrs(err, pkg, fset)
+			}
 		}
 		break
 	}
diff --git a/outline.go b/outline.go
index 9ddcb354..a24fd1b4 100644
--- a/outline.go
+++ b/outline.go
@@ -24,7 +24,6 @@ import (
 	"path"
 	"path/filepath"
 	"strings"
-	"syscall"
 
 	"github.com/goplus/gop/cl/outline"
 	"github.com/goplus/gop/parser"
@@ -76,7 +75,7 @@ func Outline(dir string, conf *Config) (out outline.Package, err error) {
 		return
 	}
 	if len(pkgs) == 0 {
-		err = syscall.ENOENT
+		err = ErrNotFound
 		return
 	}
 
@@ -107,7 +106,7 @@ func Outline(dir string, conf *Config) (out outline.Package, err error) {
 		}
 	}
 	if !out.Valid() {
-		err = syscall.ENOENT
+		err = ErrNotFound
 	}
 	return
 }
diff --git a/token/internal/tokenutil/lines_go118.go b/token/internal/tokenutil/lines_go118.go
new file mode 100644
index 00000000..1a53fa7e
--- /dev/null
+++ b/token/internal/tokenutil/lines_go118.go
@@ -0,0 +1,71 @@
+//go:build !go1.19
+// +build !go1.19
+
+/*
+ * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tokenutil
+
+import (
+	"go/token"
+	"sync"
+	"unsafe"
+)
+
+// -----------------------------------------------------------------------------
+// File
+
+// A File is a handle for a file belonging to a FileSet.
+// A File has a name, size, and line offset table.
+type File struct {
+	set  *token.FileSet
+	name string // file name as provided to AddFile
+	base int    // Pos value range for this file is [base...base+size]
+	size int    // file size as provided to AddFile
+
+	// lines and infos are protected by mutex
+	mutex sync.Mutex
+	lines []int // lines contains the offset of the first character for each line (the first entry is always 0)
+	infos []lineInfo
+}
+
+// A lineInfo object describes alternative file, line, and column
+// number information (such as provided via a //line directive)
+// for a given file offset.
+type lineInfo struct {
+	// fields are exported to make them accessible to gob
+	Offset       int
+	Filename     string
+	Line, Column int
+}
+
+// Lines returns the effective line offset table of the form described by SetLines.
+// Callers must not mutate the result.
+func (f *File) Lines() []int {
+	f.mutex.Lock()
+	lines := f.lines
+	f.mutex.Unlock()
+	return lines
+}
+
+// Lines returns the effective line offset table of the form described by SetLines.
+// Callers must not mutate the result.
+func Lines(f *token.File) []int {
+	file := (*File)(unsafe.Pointer(f))
+	return file.Lines()
+}
+
+// -----------------------------------------------------------------------------
diff --git a/token/internal/tokenutil/lines_go120.go b/token/internal/tokenutil/lines_go120.go
new file mode 100644
index 00000000..2835f8a8
--- /dev/null
+++ b/token/internal/tokenutil/lines_go120.go
@@ -0,0 +1,70 @@
+//go:build go1.19 && !go1.21
+// +build go1.19,!go1.21
+
+/*
+ * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tokenutil
+
+import (
+	"go/token"
+	"sync"
+	"unsafe"
+)
+
+// -----------------------------------------------------------------------------
+// File
+
+// A File is a handle for a file belonging to a FileSet.
+// A File has a name, size, and line offset table.
+type File struct {
+	name string // file name as provided to AddFile
+	base int    // Pos value range for this file is [base...base+size]
+	size int    // file size as provided to AddFile
+
+	// lines and infos are protected by mutex
+	mutex sync.Mutex
+	lines []int // lines contains the offset of the first character for each line (the first entry is always 0)
+	infos []lineInfo
+}
+
+// A lineInfo object describes alternative file, line, and column
+// number information (such as provided via a //line directive)
+// for a given file offset.
+type lineInfo struct {
+	// fields are exported to make them accessible to gob
+	Offset       int
+	Filename     string
+	Line, Column int
+}
+
+// Lines returns the effective line offset table of the form described by SetLines.
+// Callers must not mutate the result.
+func (f *File) Lines() []int {
+	f.mutex.Lock()
+	lines := f.lines
+	f.mutex.Unlock()
+	return lines
+}
+
+// Lines returns the effective line offset table of the form described by SetLines.
+// Callers must not mutate the result.
+func Lines(f *token.File) []int {
+	file := (*File)(unsafe.Pointer(f))
+	return file.Lines()
+}
+
+// -----------------------------------------------------------------------------
diff --git a/token/internal/tokenutil/lines_go121.go b/token/internal/tokenutil/lines_go121.go
new file mode 100644
index 00000000..ee801928
--- /dev/null
+++ b/token/internal/tokenutil/lines_go121.go
@@ -0,0 +1,30 @@
+//go:build go1.21
+// +build go1.21
+
+/*
+ * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tokenutil
+
+import (
+	"go/token"
+)
+
+// Lines returns the effective line offset table of the form described by SetLines.
+// Callers must not mutate the result.
+func Lines(f *token.File) []int {
+	return f.Lines()
+}
diff --git a/token/types.go b/token/types.go
index a10efe50..a0cfa1d6 100644
--- a/token/types.go
+++ b/token/types.go
@@ -18,6 +18,8 @@ package token
 
 import (
 	"go/token"
+
+	"github.com/goplus/gop/token/internal/tokenutil"
 )
 
 // Pos is a compact encoding of a source position within a file set.
@@ -39,7 +41,6 @@ import (
 // equivalent to comparing the respective source file offsets. If p and q
 // are in different files, p < q is true if the file implied by p was added
 // to the respective file set before the file implied by q.
-//
 type Pos = token.Pos
 
 const (
@@ -59,6 +60,12 @@ type Position = token.Position
 // A File has a name, size, and line offset table.
 type File = token.File
 
+// Lines returns the effective line offset table of the form described by SetLines.
+// Callers must not mutate the result.
+func Lines(f *File) []int {
+	return tokenutil.Lines(f)
+}
+
 // A FileSet represents a set of source files. Methods of file sets are
 // synchronized; multiple goroutines may invoke them concurrently.
 type FileSet = token.FileSet
diff --git a/x/typesutil/check.go b/x/typesutil/check.go
index 92ee7ddd..9690da11 100644
--- a/x/typesutil/check.go
+++ b/x/typesutil/check.go
@@ -207,25 +207,25 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error)
 	return
 }
 
-func convErr(fset *token.FileSet, e error) (ret types.Error, ok bool) {
+func convErr(fset *token.FileSet, e error) (ret *types.Error, ok bool) {
 	switch v := e.(type) {
 	case *gox.CodeError:
-		ret.Pos, ret.Msg = v.Pos, v.Msg
-		typesutil.SetErrorGo116(&ret, 0, v.Pos, v.Pos)
+		ret = &types.Error{Fset: fset, Pos: v.Pos, Msg: v.Msg}
+		typesutil.SetErrorGo116(ret, 0, v.Pos, v.Pos)
 	case *gox.MatchError:
-		end := token.NoPos
+		pos, end := token.NoPos, token.NoPos
 		if v.Src != nil {
-			ret.Pos, end = v.Src.Pos(), v.Src.End()
+			pos, end = v.Src.Pos(), v.Src.End()
 		}
-		ret.Msg = v.Message("")
-		typesutil.SetErrorGo116(&ret, 0, ret.Pos, end)
+		ret = &types.Error{Fset: fset, Pos: pos, Msg: v.Message("")}
+		typesutil.SetErrorGo116(ret, 0, pos, end)
 	case *gox.ImportError:
-		ret.Pos, ret.Msg = v.Pos, v.Err.Error()
-		typesutil.SetErrorGo116(&ret, 0, v.Pos, v.Pos)
+		ret = &types.Error{Fset: fset, Pos: v.Pos, Msg: v.Err.Error()}
+		typesutil.SetErrorGo116(ret, 0, v.Pos, v.Pos)
 	default:
 		return
 	}
-	ret.Fset, ok = fset, true
+	ok = true
 	return
 }
 


Test patch:
diff --git a/cmd/internal/test/test.go b/cmd/internal/test/test.go
index aa8cc042..65655165 100644
--- a/cmd/internal/test/test.go
+++ b/cmd/internal/test/test.go
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-// Package test implements the ``gop test'' command.
+// Package test implements the “gop test” command.
 package test
 
 import (
@@ -22,7 +22,6 @@ import (
 	"log"
 	"os"
 	"reflect"
-	"syscall"
 
 	"github.com/goplus/gop"
 	"github.com/goplus/gop/cl"
@@ -95,7 +94,7 @@ func test(proj gopprojs.Proj, conf *gop.Config, test *gocmd.TestConfig) {
 	default:
 		log.Panicln("`gop test` doesn't support", reflect.TypeOf(v))
 	}
-	if err == syscall.ENOENT {
+	if gop.NotFound(err) {
 		fmt.Fprintf(os.Stderr, "gop test %v: not found\n", obj)
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
diff --git a/token/internal/tokenutil/lines_test.go b/token/internal/tokenutil/lines_test.go
new file mode 100644
index 00000000..4c41f8ce
--- /dev/null
+++ b/token/internal/tokenutil/lines_test.go
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tokenutil
+
+import (
+	"go/token"
+	"reflect"
+	"testing"
+)
+
+func TestLines(t *testing.T) {
+	fset := token.NewFileSet()
+	f := fset.AddFile("foo.go", 100, 100)
+	lines := []int{0, 10, 50}
+	f.SetLines(lines)
+	ret := Lines(f)
+	if !reflect.DeepEqual(ret, lines) {
+		t.Fatal("TestLines failed:", ret)
+	}
+}