Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

False positive if err.(type) switch checks Unwrap interfaces #88

Open
Brian-Williams opened this issue Nov 30, 2024 · 0 comments
Open

Comments

@Brian-Williams
Copy link

Brian-Williams commented Nov 30, 2024

Bug

The lint "type switch on error will fail on wrapped errors" triggers even when you check the Unwrap interfaces.

Error message

Error: example.go:27:14: type switch on error will fail on wrapped errors. Use errors.As to check for specific errors (errorlint)
switch x := err.(type) {

Short example

func ParseAllErr(err error) {
	switch x := err.(type) {
	case interface{ Unwrap() error }:
		fmt.Printf("single unwrap err: %s\n", err)
	case interface{ Unwrap() []error }:
		for _, e := range x.Unwrap() {
			if e != nil {
				fmt.Printf("multi-error found: %s\n", e)
				ParseAllErr(e)
			}
		}
	default:
		// Specific handling for non wrapping errors
		fmt.Printf("error without Unwrap: %s\n", err)
	}
}

func main() {
	err := errors.Join(
		SingleError{Err: errors.New("one")},
		MultiError{Err: errors.New("two"), Err2: errors.New("three")},
	)
	ParseAllErr(err)
}

Runnable example

More fleshed out example in playground: https://go.dev/play/p/X7xE6VILmY9

package main

import (
	"errors"
	"fmt"
)

type SingleError struct {
	Err error
}
type MultiError struct {
	Err  error
	Err2 error
}

func (s SingleError) Error() string {
	return s.Err.Error()
}
func (s SingleError) Unwrap() error {
	return s.Err
}
func (m MultiError) Error() string {
	return fmt.Sprintf("%v, %v", m.Err, m.Err2)
}
func (m MultiError) Unwrap() []error {
	return []error{m.Err, m.Err2}
}

func ParseAllErr(err error) {
	switch x := err.(type) {
	case interface{ Unwrap() error }:
		fmt.Printf("single unwrap err: %s\n", err)
	case interface{ Unwrap() []error }:
		for _, e := range x.Unwrap() {
			if e != nil {
				fmt.Printf("multi-error found: %s\n", e)
				ParseAllErr(e)
			}
		}
	default:
		// Specific handling for non wrapping errors
		fmt.Printf("error without Unwrap: %s\n", err)
	}
}

func main() {
	err := errors.Join(
		SingleError{Err: errors.New("one")},
		MultiError{Err: errors.New("two"), Err2: errors.New("three")},
		MultiError{
			Err: errors.New("four"),
			Err2: MultiError{
				Err:  errors.New("five"),
				Err2: errors.New("six"),
			},
		},
	)
	ParseAllErr(err)

}

Workaround

You can use errors.As with the interfaces to get around the linter in some cases.

	var unwrappable interface{ Unwrap() error }
	if errors.As(err, &unwrappable) { // handling }

    	var multiUnwrappable interface{ Unwrap() []error }
	if errors.As(err, &multiUnwrappable) { //handling }

If we were to create a breadth first version of errors.As then we would need to write code like the examples that trigger the lint.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant