Skip to content
Merged
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
16 changes: 15 additions & 1 deletion pkg/linters/sortslice/sortslice.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package sortslice
import (
"fmt"
"go/ast"
"go/types"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
Expand Down Expand Up @@ -52,12 +53,25 @@ func run(pass *analysis.Pass) (any, error) {
return
}
pkgIdent, ok := sel.X.(*ast.Ident)
if !ok || pkgIdent.Name != "sort" {
if !ok {
return
}
if pass.TypesInfo == nil {
return
}
obj := pass.TypesInfo.ObjectOf(pkgIdent)

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.

Missing nil guard on pass.TypesInfo itself: the code calls pass.TypesInfo.ObjectOf(...) without first checking pass.TypesInfo != nil, so a non-standard or embedded driver that omits type info would panic here rather than skip gracefully.

💡 Suggested fix

Add an early guard before the ObjectOf call:

if pass.TypesInfo == nil {
    return
}
obj := pass.TypesInfo.ObjectOf(pkgIdent)

The existing if obj == nil check (line 61) only protects against ObjectOf returning nil on an initialised TypesInfo — it does not prevent a nil-pointer dereference on pass.TypesInfo itself. Standard drivers (go vet, golangci-lint, analysistest) always populate TypesInfo, so this will not trigger in normal CI; but it is a latent panic for any embedded or custom driver that skips type-checking. Adding the guard is a one-liner and is consistent with the defensive style already present in this function.

// ObjectOf can be nil when type information is incomplete.
if obj == nil {
return
}
pkgName, ok := obj.(*types.PkgName)
if !ok || pkgName.Imported().Path() != "sort" {

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.

[/tdd] The ReportRangef calls a few lines below use hardcoded "sort.Slice" / "sort.SliceStable" strings, so aliased-import users see sort.Slice is not type-safe for code that says s.Slice(...). The alias_import.go testdata validates this as intentional. If so, a short comment in the switch block explaining the design choice would protect future contributors from accidentally "fixing" it to the local alias name.

💡 Alternative: dynamic message using the local alias

If you prefer the message to match the user's actual code:

case "Slice":
    pass.ReportRangef(call, "%s.Slice is not type-safe; use slices.SortFunc instead", pkgIdent.Name)
case "SliceStable":
    pass.ReportRangef(call, "%s.SliceStable is not type-safe; use slices.SortStableFunc instead", pkgIdent.Name)

With import s "sort" the diagnostic would then read s.Slice is not type-safe, which maps directly to the code the user wrote. The alias_import.go // want annotations would need updating accordingly.

return
}

switch sel.Sel.Name {
case "Slice":
// Keep diagnostics on canonical stdlib names even for aliased imports.
pass.ReportRangef(call, "sort.Slice is not type-safe; use slices.SortFunc instead")
case "SliceStable":
pass.ReportRangef(call, "sort.SliceStable is not type-safe; use slices.SortStableFunc instead")
Expand Down
11 changes: 11 additions & 0 deletions pkg/linters/sortslice/testdata/src/sortslice/alias_import.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package sortslice

import s "sort"

func BadSliceAliasedImport(items []string) {
s.Slice(items, func(i, j int) bool { return items[i] < items[j] }) // want `sort\.Slice is not type-safe`

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.

[/diagnose] The // want annotation documents that the expected diagnostic text is sort\.Slice even though the call is s.Slice. This is a useful precision signal: when type-info resolution was added, the message string was intentionally left as the canonical package name rather than the local alias. A short comment here (or at the switch in sortslice.go) would make this boundary explicit and prevent a future refactor from "fixing" the message to s.Slice and breaking this expectation.

}

func BadSliceStableAliasedImport(items []string) {
s.SliceStable(items, func(i, j int) bool { return items[i] < items[j] }) // want `sort\.SliceStable is not type-safe`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package sortslice

type customSort struct{}

func (customSort) Slice(_ []string, _ func(i, j int) bool) {}
func (customSort) SliceStable(_ []string, _ func(i, j int) bool) {}

func GoodShadowedSortIdentifier(items []string) {
sort := customSort{}
sort.Slice(items, func(i, j int) bool { return items[i] < items[j] })
sort.SliceStable(items, func(i, j int) bool { return items[i] < items[j] })
}
Loading