Skip to content

Commit

Permalink
Support GODEBUG=gotypesalias=1
Browse files Browse the repository at this point in the history
Currently, the go/types.Type for a declared type alias
is the same type as the that which it is aliased too.

In Go 1.23, when GODEBUG=gotypesalias=1 is enabled by default,
it is a go/types.Alias instead, which is a unique type node
for aliases that points to the type it is aliased to via go/types.Unalias().
(see golang/go#63223 for more details).

errcheck's tests do not currently fail with GODEBUG=gotypesalias=1,
but that is because the tests themselves do not exercise the problematic situations,
such as the following:

```go
type t struct{}

func (x t) a() error { /* ... */ }

type embedt struct {
	t
}

type embedtalias = embedt

func foo() {
	embedtalias.a()
}
```

With GODEBUG=gotypesalias=1, errcheck will panic when inspecting `embedtalias.a()`
to see if it should be excluded from checking:

```
panic: cannot get Field of a type that is not a struct, got _/home/user/go/src/github.com/kisielk/errcheck/errcheck/testdata/src/a.embedtalias (*types.Alias)

goroutine 2962 [running]:
github.com/kisielk/errcheck/errcheck.getTypeAtFieldIndex({0x7779c0?, 0xc002eceab0?}, 0x4c?)
        /home/user/go/src/github.com/kisielk/errcheck/errcheck/embedded_walker.go:96 +0xf9
github.com/kisielk/errcheck/errcheck.walkThroughEmbeddedInterfaces(0x6b83a0?)
        /home/user/go/src/github.com/kisielk/errcheck/errcheck/embedded_walker.go:53 +0x8e
github.com/kisielk/errcheck/errcheck.(*visitor).namesForExcludeCheck(0xc0025eeff0, 0xc002596140?)
        /home/user/go/src/github.com/kisielk/errcheck/errcheck/errcheck.go:364 +0x165
github.com/kisielk/errcheck/errcheck.(*visitor).excludeCall(0xc0025eeff0, 0x4d?)
        /home/user/go/src/github.com/kisielk/errcheck/errcheck/errcheck.go:403 +0x72
github.com/kisielk/errcheck/errcheck.(*visitor).ignoreCall(0xc0025eeff0, 0xc002596140)
        /home/user/go/src/github.com/kisielk/errcheck/errcheck/errcheck.go:415 +0x2c
github.com/kisielk/errcheck/errcheck.(*visitor).Visit(0xc0025eeff0, {0x776d48?, 0xc0066e08a0})
        /home/user/go/src/github.com/kisielk/errcheck/errcheck/errcheck.go:564 +0x137
```

This change uses `types.Unalias` to follow through this extra level of indirection,
which fixes the panic and ensures the same behavior with both settings.

This change also adds these cases to the test suite, and runs the tests
using both settings of gotypesalias. Without the calls to `types.Unalias`,
the tests fail.
  • Loading branch information
JacobOaks committed Jun 14, 2024
1 parent b65821b commit b7d0b65
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 25 deletions.
43 changes: 27 additions & 16 deletions errcheck/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,33 @@ import (
)

func TestAnalyzer(t *testing.T) {
t.Run("default flags", func(t *testing.T) {
packageDir := filepath.Join(analysistest.TestData(), "src/a/")
_ = analysistest.Run(t, packageDir, Analyzer)
})
// Test with and without the gotypesalias flag,
// which will be set to 1 by default in Go 1.23.
for _, tt := range []string{
"gotypesalias=0",
"gotypesalias=1",
} {
t.Run(tt, func(t *testing.T) {
t.Setenv("GODEBUG", tt)

t.Run("check blanks", func(t *testing.T) {
packageDir := filepath.Join(analysistest.TestData(), "src/blank/")
_ = Analyzer.Flags.Set("blank", "true")
_ = analysistest.Run(t, packageDir, Analyzer)
_ = Analyzer.Flags.Set("blank", "false") // reset it
})
t.Run("default flags", func(t *testing.T) {
packageDir := filepath.Join(analysistest.TestData(), "src/a/")
_ = analysistest.Run(t, packageDir, Analyzer)
})

t.Run("check asserts", func(t *testing.T) {
packageDir := filepath.Join(analysistest.TestData(), "src/assert/")
_ = Analyzer.Flags.Set("assert", "true")
_ = analysistest.Run(t, packageDir, Analyzer)
_ = Analyzer.Flags.Set("assert", "false") // reset it
})
t.Run("check blanks", func(t *testing.T) {
packageDir := filepath.Join(analysistest.TestData(), "src/blank/")
_ = Analyzer.Flags.Set("blank", "true")
_ = analysistest.Run(t, packageDir, Analyzer)
_ = Analyzer.Flags.Set("blank", "false") // reset it
})

t.Run("check asserts", func(t *testing.T) {
packageDir := filepath.Join(analysistest.TestData(), "src/assert/")
_ = Analyzer.Flags.Set("assert", "true")
_ = analysistest.Run(t, packageDir, Analyzer)
_ = Analyzer.Flags.Set("assert", "false") // reset it
})
})
}
}
19 changes: 10 additions & 9 deletions errcheck/embedded_walker.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ import (
//
// For example, say we have:
//
// type Inner interface {Method()}
// type Middle interface {Inner}
// type Outer interface {Middle}
// type T struct {Outer}
// type U struct {T}
// type V struct {U}
// type Inner interface {Method()}
// type Middle interface {Inner}
// type Outer interface {Middle}
// type T struct {Outer}
// type U struct {T}
// type V struct {U}
//
// And then the selector:
//
// V.Method
// V.Method
//
// We'll return [Outer, Middle, Inner] by first walking through the embedded structs
// until we reach the Outer interface, then descending through the embedded interfaces
Expand Down Expand Up @@ -84,10 +84,11 @@ func walkThroughEmbeddedInterfaces(sel *types.Selection) ([]types.Type, bool) {
}

func getTypeAtFieldIndex(startingAt types.Type, fieldIndex int) types.Type {
t := maybeUnname(maybeDereference(startingAt))
t := maybeDereference(types.Unalias(startingAt))
t = maybeUnname(types.Unalias(t))
s, ok := t.(*types.Struct)
if !ok {
panic(fmt.Sprintf("cannot get Field of a type that is not a struct, got a %T", t))
panic(fmt.Sprintf("cannot get Field of a type that is not a struct, got %v", t))
}

return s.Field(fieldIndex).Type()
Expand Down
13 changes: 13 additions & 0 deletions errcheck/testdata/src/a/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ func main() {
_ = x.a() // ok, assigned to blank
x.a() // want "unchecked error"

// Methods on alias types and pointers to alias types
x2 := embedtalias{}
_ = x2.a() // ok, assigned to blank
x2.a() // want "unchecked error"

x3 := &embedtalias{}
_ = x3.a() // ok, assigned to blank
x3.a() // want "unchecked error"

var x4 embedtptralias
_ = x4.a() // ok, assigned to blank
x4.a() // want "unchecked error"

// Method call on a struct member
y := u{x}
_ = y.t.a() // ok, assigned to blank
Expand Down
8 changes: 8 additions & 0 deletions errcheck/testdata/src/a/t.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ func (x t) a() error {
type u struct {
t t
}

type embedt struct {
t
}

type embedtalias = embedt

type embedtptralias = *embedt

0 comments on commit b7d0b65

Please sign in to comment.