Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions internal/packages/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ func loadPackageEx(dedup Deduper, ld *loader, lpkg *loaderPackage) {
return // not a source package, don't get syntax trees
}

var packageSelectors map[token.Pos]string
appendError := func(err error) {
// Convert various error types into the one true Error.
var errs []packages.Error
Expand Down Expand Up @@ -285,6 +286,12 @@ func loadPackageEx(dedup Deduper, ld *loader, lpkg *loaderPackage) {

case types.Error:
// from type checker
if selector, ok := packageSelectors[err.Pos]; ok {
name := selector[strings.LastIndexByte(selector, '.')+1:]
Comment thread
MeteorsLiu marked this conversation as resolved.
if strings.HasPrefix(err.Msg, "name "+name+" not exported by package ") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rewrite is coupled to go/types' exact diagnostic wording (name X not exported by package ). If a future Go toolchain rephrases that message, the rewrite silently stops firing and the original message resurfaces. The fallback is graceful and the goroot errorcheck test would catch the regression, so this is fine — a short comment noting the dependency on the go/types string would help the next reader understand why the match is so literal.

err.Msg = "undefined: " + selector
}
}
lpkg.TypeErrors = append(lpkg.TypeErrors, err)
errs = append(errs, packages.Error{
Pos: err.Fset.Position(err.Pos).String(),
Expand Down Expand Up @@ -366,6 +373,20 @@ func loadPackageEx(dedup Deduper, ld *loader, lpkg *loaderPackage) {
if ld.Context.Err() != nil {
return
}
packageSelectors = make(map[token.Pos]string)
for _, file := range files {
ast.Inspect(file, func(node ast.Node) bool {
sel, ok := node.(*ast.SelectorExpr)
if !ok {
return true
}
qualifier, ok := sel.X.(*ast.Ident)
if ok {
packageSelectors[sel.Sel.Pos()] = qualifier.Name + "." + sel.Sel.Name

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This records every ident-qualified selector (x.field, v.Method, pkg.Name), not only package qualifiers. Harmless today because the rewrite only activates on the name X not exported by package message, which go/types emits exclusively for package-qualified unexported access — so a non-package qualifier can never reach it. Just flagging it in case that message guard is ever loosened.

}
return true
})
}

lpkg.TypesInfo = &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Expand Down
16 changes: 16 additions & 0 deletions internal/packages/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,22 @@ var x string`)
assertPackageErrorAbsent(t, pkg, "no metadata for /foo")
})

t.Run("unexported package member", func(t *testing.T) {
dir := t.TempDir()
writeLoadTestFile(t, filepath.Join(dir, "go.mod"), "module example.com/private\ngo 1.24\n")
if err := os.Mkdir(filepath.Join(dir, "dep"), 0o755); err != nil {
t.Fatal(err)
}
writeLoadTestFile(t, filepath.Join(dir, "dep", "dep.go"), "package dep\nvar hidden int\n")
writeLoadTestFile(t, filepath.Join(dir, "load.go"), `package private
import alias "example.com/private/dep"
var _ = alias.hidden
`)
pkg := loadOnePackage(t, dir, "go1.24")
assertPackageError(t, pkg, "undefined: alias.hidden")
assertPackageErrorAbsent(t, pkg, "not exported by package")
})

}

func loadOnePackage(t *testing.T, dir, goVersion string) *Package {
Expand Down
4 changes: 0 additions & 4 deletions test/goroot/xfail.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2046,10 +2046,6 @@ xfails:
directive: errorcheck
case: escape_map.go
reason: gc-specific -m escape-analysis diagnostics are not implemented by llgo
- version: go1.26
directive: errorcheck
case: runtime.go
reason: gc runtime private-symbol compiler diagnostics are not implemented by llgo
- version: go1.26
directive: errorcheck
case: fixedbugs/notinheap.go
Expand Down
Loading