Describe the bug
The unexported-return rule produces non-deterministic and incorrect results when a directory contains both source files and external test files (package foo + package foo_test):
- Wrong package qualifier — the return type is reported as
*foo_test.T instead of *foo.T.
- Flaky detection — the issue is sometimes not reported at all (~15% of runs in the reproduction below).
Root cause: dots.ResolvePackages returns GoFiles + TestGoFiles + XTestGoFiles collapsed into a single []string. lint.Linter.lintPackage then puts all of them into one lint.Package whose TypeCheck picks anyFile by iterating a Go map (non-deterministic order) and passes that file's package name to go/types.Config.Check. When anyFile is an XTestGoFile (package foo_test) the whole package is type-checked under the foo_test name, and mixing files from two different Go packages in a single types.Config.Check call also causes some types to fail to resolve (yielding nil from TypeOf), which silently drops the issue.
To Reproduce
Steps to reproduce the behavior:
- I updated revive
go install github.com/mgechev/revive@v1.15.0
- Minimal reproduction: https://gist.github.com/far4599/937fb8f53d644c54cddbb07d769ba98d
git clone https://gist.github.com/far4599/937fb8f53d644c54cddbb07d769ba98d revive-repro
cd revive-repro
go mod tidy
bash reproduce.sh
revive-repro/
go.mod # module revive-repro
pub.go # generic function returning unexported type
pub_internal_test.go # package pub (internal test)
pub_external_test.go # package pub_test (external test)
reproduce.sh # runs revive 20 times and classifies results
pub.go:
package pub
type impl[T any] struct{ val T }
func New[T any](v T) *impl[T] { return &impl[T]{val: v} }
pub_internal_test.go (package pub):
package pub
import "testing"
func TestInternal(t *testing.T) { _ = New[int](1) }
pub_external_test.go (package pub_test):
package pub_test
import (
"testing"
"revive-repro"
)
func TestExternal(t *testing.T) { _ = pub.New[string]("x") }
No custom config file is required — the default rules are enough to trigger the bug.
Expected behavior
Every run produces exactly one issue with the correct package qualifier:
pub.go:6:22: exported func New returns unexported type *pub.impl[T], which can be annoying to use
Logs
Typical bash reproduce.sh output over 20 runs:
Run 1: WRONG — pub.go:6:22: ... *pub_test.impl[T] ...
Run 8: MISSING — issue not reported at all
Run 18: OK — pub.go:6:22: ... *pub.impl[T] ...
Results:
OK (correct package name): 1 / 20
WRONG (pub_test.impl[T]): 16 / 20
MISSING (race, lost): 3 / 20
Desktop (please complete the following information):
- OS: macOS 26.4
- Go: 1.26.0
Additional context
Non-generic unexported return types are not affected — only generic functions trigger the bug, because the go/types package qualifies generic type names with the enclosing package path while non-generic ones often match regardless of which test variant was used.
Downstream impact: this race surfaces in golangci-lint as nolintlint flagging //nolint:revive directives as "unused" on the runs where revive drops the issue, producing intermittent CI failures.
A fix with RED→GREEN test is available at https://github.com/far4599/revive — the repro test fails without the fix and passes with it, and all existing tests continue to pass.
Describe the bug
The
unexported-returnrule produces non-deterministic and incorrect results when a directory contains both source files and external test files (package foo+package foo_test):*foo_test.Tinstead of*foo.T.Root cause:
dots.ResolvePackagesreturnsGoFiles + TestGoFiles + XTestGoFilescollapsed into a single[]string.lint.Linter.lintPackagethen puts all of them into onelint.PackagewhoseTypeCheckpicksanyFileby iterating a Go map (non-deterministic order) and passes that file's package name togo/types.Config.Check. WhenanyFileis anXTestGoFile(package foo_test) the whole package is type-checked under thefoo_testname, and mixing files from two different Go packages in a singletypes.Config.Checkcall also causes some types to fail to resolve (yieldingnilfromTypeOf), which silently drops the issue.To Reproduce
Steps to reproduce the behavior:
go install github.com/mgechev/revive@v1.15.0git clone https://gist.github.com/far4599/937fb8f53d644c54cddbb07d769ba98d revive-repro cd revive-repro go mod tidy bash reproduce.shpub.go:pub_internal_test.go(package pub):pub_external_test.go(package pub_test):No custom config file is required — the default rules are enough to trigger the bug.
Expected behavior
Every run produces exactly one issue with the correct package qualifier:
Logs
Typical
bash reproduce.shoutput over 20 runs:Desktop (please complete the following information):
Additional context
Non-generic unexported return types are not affected — only generic functions trigger the bug, because the
go/typespackage qualifies generic type names with the enclosing package path while non-generic ones often match regardless of which test variant was used.Downstream impact: this race surfaces in golangci-lint as
nolintlintflagging//nolint:revivedirectives as "unused" on the runs where revive drops the issue, producing intermittent CI failures.A fix with RED→GREEN test is available at https://github.com/far4599/revive — the repro test fails without the fix and passes with it, and all existing tests continue to pass.