Skip to content
This repository was archived by the owner on Nov 10, 2025. It is now read-only.
This repository was archived by the owner on Nov 10, 2025. It is now read-only.

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

Description

@Brian-Williams

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions