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

@Brian-Williams

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.

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