From b6db807ad576f56c23854ca08a92566a3411e4f3 Mon Sep 17 00:00:00 2001 From: visualfc Date: Thu, 29 Oct 2026 20:46:43 +0800 Subject: [PATCH 1/2] types: fix error --- types/types.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/types/types.go b/types/types.go index bed11ff..ab7b6d5 100644 --- a/types/types.go +++ b/types/types.go @@ -257,12 +257,16 @@ func runTypes(cmd *command.Command, args []string) error { } conf := NewPkgConfig(false, !typesSkipTests) pkg, outconf, err := w.Check(pkgName, conf, cursor) - if err != nil { - return fmt.Errorf("error loading package %q: %w", pkgName, err) - } + // Check reports type errors even when it still returns a usable package. if pkg == nil { + if err != nil { + return fmt.Errorf("error loading package %q: %w", pkgName, err) + } return fmt.Errorf("error loading package %q: no package returned", pkgName) } + if err != nil && typesVerbose { + fmt.Fprintln(cmd.Stderr, err) + } if cursor != nil && w.findMode.IsValid() { return w.LookupCursor(pkg, outconf, cursor) } From dcd73648f9cfff71b56d862822428af3f3324f3e Mon Sep 17 00:00:00 2001 From: visualfc Date: Thu, 29 Oct 2026 20:51:52 +0800 Subject: [PATCH 2/2] types: add typeerror check --- types/_testdata/typeerror/broken.go | 5 ++ types/_testdata/typeerror/cconst.go | 14 ++++ types/_testdata/typeerror/ok.go | 12 +++ types/runtypes_test.go | 125 ++++++++++++++++++++++++++++ 4 files changed, 156 insertions(+) create mode 100644 types/_testdata/typeerror/broken.go create mode 100644 types/_testdata/typeerror/cconst.go create mode 100644 types/_testdata/typeerror/ok.go create mode 100644 types/runtypes_test.go diff --git a/types/_testdata/typeerror/broken.go b/types/_testdata/typeerror/broken.go new file mode 100644 index 0000000..32be965 --- /dev/null +++ b/types/_testdata/typeerror/broken.go @@ -0,0 +1,5 @@ +package fixture + +func broken() { + var _ int = "type error" +} diff --git a/types/_testdata/typeerror/cconst.go b/types/_testdata/typeerror/cconst.go new file mode 100644 index 0000000..945d3b1 --- /dev/null +++ b/types/_testdata/typeerror/cconst.go @@ -0,0 +1,14 @@ +package fixture + +import "C" + +// IntEQ mimics CGo LLVM enum constants such as llvm.IntEQ = C.LLVMIntEQ. +type IntPredicate int + +const IntEQ IntPredicate = C.LLVMIntEQ + +func CreateICmp(pred IntPredicate) {} + +func useInvalidCConst() { + CreateICmp(IntEQ) +} diff --git a/types/_testdata/typeerror/ok.go b/types/_testdata/typeerror/ok.go new file mode 100644 index 0000000..6f0b933 --- /dev/null +++ b/types/_testdata/typeerror/ok.go @@ -0,0 +1,12 @@ +package fixture + +type Config struct { + Wasm bool +} + +func Foo() {} + +func Bar() { + Foo() + _ = Config{Wasm: true} +} diff --git a/types/runtypes_test.go b/types/runtypes_test.go new file mode 100644 index 0000000..9d0f95c --- /dev/null +++ b/types/runtypes_test.go @@ -0,0 +1,125 @@ +package types + +import ( + "bytes" + "go/build" + "path/filepath" + "sort" + "strconv" + "strings" + "testing" + + "github.com/visualfc/gotools/pkg/command" +) + +func TestRunTypesFindUsageDespiteTypeErrors(t *testing.T) { + dir, err := filepath.Abs(filepath.Join("_testdata", "typeerror")) + if err != nil { + t.Fatal(err) + } + assertCheckTypeErrorKeepsPackage(t, dir) + + for _, ident := range []string{"Foo", "Wasm"} { + t.Run(ident, func(t *testing.T) { + refs := identRefs(t, dir, ident) + if len(refs) < 2 { + t.Fatalf("testdata needs a definition and at least one use for %s, got %v", ident, refs) + } + want := make([]string, len(refs)) + for i, ref := range refs { + want[i] = ref.key + } + sort.Strings(want) + for _, ref := range refs { + got, stdout, stderr := runTypesUsagePositions(t, dir, ref.file, ref.offset) + if stderr != "" { + t.Fatalf("cursor %s:%d stderr = %q", ref.file, ref.offset, stderr) + } + if strings.Contains(stdout, "error loading package") { + t.Fatalf("cursor %s:%d stdout = %q", ref.file, ref.offset, stdout) + } + if !sameStringSet(got, want) { + t.Errorf("cursor %s:%d\n got %v\nwant %v\nstdout=%s", ref.file, ref.offset, got, want, stdout) + } + } + }) + } +} + +func TestRunTypesFindDefDespiteTypeErrors(t *testing.T) { + dir, err := filepath.Abs(filepath.Join("_testdata", "typeerror")) + if err != nil { + t.Fatal(err) + } + assertCheckTypeErrorKeepsPackage(t, dir) + + refs := identRefs(t, dir, "Foo") + if len(refs) < 2 { + t.Fatalf("testdata needs a definition and at least one use for Foo, got %v", refs) + } + def := refs[0] + for _, ref := range refs { + stdout, stderr := runTypesOutput(t, dir, ref.file, ref.offset, false, true, true) + if stderr != "" { + t.Fatalf("cursor %s:%d stderr = %q", ref.file, ref.offset, stderr) + } + if !strings.Contains(stdout, def.key) && !strings.Contains(stdout, def.file+":") { + t.Fatalf("cursor %s:%d stdout = %q, want definition %s", ref.file, ref.offset, stdout, def.key) + } + } +} + +func assertCheckTypeErrorKeepsPackage(t *testing.T, dir string) { + t.Helper() + w := NewPkgWalker(&build.Default) + w.SetOutput(&bytes.Buffer{}, &bytes.Buffer{}) + pkg, _, err := w.Check(dir, DefaultPkgConfig(), nil) + if err == nil { + t.Fatal("Check error = nil; typeerror testdata must keep a type error") + } + if pkg == nil { + t.Fatalf("Check package = nil, err = %v", err) + } + if !strings.Contains(err.Error(), "cannot use") { + t.Fatalf("Check error = %v, want a type error", err) + } +} + +func runTypesUsagePositions(t *testing.T, dir, file string, offset int) (got []string, stdout, stderr string) { + t.Helper() + stdout, stderr = runTypesOutput(t, dir, file, offset, true, true, false) + for _, line := range strings.Split(stdout, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + fileName, lineNo, col, ok := parsePositionLine(line) + if !ok { + continue + } + got = append(got, posKey(fileName, lineNo, col)) + } + sort.Strings(got) + return uniqueStrings(got), stdout, stderr +} + +func runTypesOutput(t *testing.T, dir, file string, offset int, use, info, def bool) (stdout, stderr string) { + t.Helper() + oldUse, oldInfo, oldDef, oldPos, oldStdin, oldVerbose := typesFindUse, typesFindInfo, typesFindDef, typesFilePos, typesFileStdin, typesVerbose + defer func() { + typesFindUse, typesFindInfo, typesFindDef, typesFilePos, typesFileStdin, typesVerbose = oldUse, oldInfo, oldDef, oldPos, oldStdin, oldVerbose + }() + typesFindUse = use + typesFindInfo = info + typesFindDef = def + typesFileStdin = false + typesVerbose = false + typesFilePos = file + ":" + strconv.Itoa(offset) + + var outBuf, errBuf bytes.Buffer + cmd := &command.Command{Stdout: &outBuf, Stderr: &errBuf} + if err := runTypes(cmd, []string{dir}); err != nil { + t.Fatalf("runTypes %s:%d: %v\nstderr=%s\nstdout=%s", file, offset, err, errBuf.String(), outBuf.String()) + } + return outBuf.String(), errBuf.String() +}