errortype

command module
v0.0.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 16, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

README

Errortype

Go Reference Test CodeQL Coverage Go Report Card Codeberg CI License

errortype is a static analysis tool that performs three checks:

  1. Inconsistent Error Type Usage: Ensures error types are used consistently as either pointers or values in returns, type assertions, and errors.As calls.

  2. Pointless Comparisons: Detects comparisons against newly allocated addresses (like errors.Is(err, &url.Error{}) or ptr == &MyStruct{}), which are almost always incorrect.

  3. Error Naming Conventions (opt-in): Checks that sentinel error variables use the Err prefix (e.g., ErrNotFound) and structured error types use the Error suffix (e.g., ParseError), following the Go naming conventions.

Getting Started

Installation
Go
go install fillmore-labs.com/errortype@latest
Homebrew
brew install fillmore-labs/tap/errortype
Eget

Install eget, then:

eget fillmore-labs/errortype

Usage

Analyze your entire project:

errortype -naming -style-check ./...
Command-Line Flags
Flag Description Default
-naming Check error naming conventions (recommended) false
-style-check Check for confusing uses of errors.As (recommended) false
-check-unused Report unused results of errors.As-like functions true
-check-is Suppress diagnostics on errors.Is if the type has an Is(error) bool method true
-deep-is-check In Is methods, diagnose any unwrapping call, not just those using target false
-unchecked-assert Diagnose unchecked type asserts on errors false
-prefix-filter Restrict variable analysis to explicitly named variables (err or Err) true
-c <N> Lines of context around each issue (-1 = none, 0 = offending line only) -1
-test Analyze test files true
-overrides <file> Read type overrides from a YAML file (see Override File) —
-suggest <file> Append suggestions to an override file (- for stdout) —
-heuristics <list> Heuristics to use (“off” to disable) (Experimental) var,usage,receivers
-tracetypes <regex> Trace type detection in matching packages (Experimental) —

Inconsistent Error Type Usage

A common and subtle bug occurs when error types are used inconsistently — sometimes as values, sometimes as pointers. This can cause errors.As checks to silently fail.

Consider this code (Go Playground):

package main

import (
	"crypto/aes"
	"errors"
	"fmt"
)

func main() {
	key := []byte("My kung fu is better than yours")
	_, err := aes.NewCipher(key)

	var kse *aes.KeySizeError
	if errors.As(err, &kse) {
		fmt.Printf("AES keys must be 16, 24, or 32 bytes long, got %d bytes.\n", kse)
	} else if err != nil {
		fmt.Println(err)
	}
}

This prints the generic error because aes.KeySizeError is a value type, not a pointer. Changing line 13 to var kse aes.KeySizeError fixes it.

Running errortype . reports:

.../main.go:14:20: Target for value error "crypto/aes.KeySizeError" ⏎
    is a pointer-to-pointer, use a pointer to a value instead: ⏎
    "var kse aes.KeySizeError; ... errors.As(err, &kse)". (et:err)
How Intended Usage Is Detected

The linter determines an error type's intended use (pointer vs. value) by analyzing its defining package, in order of precedence:

  1. Overrides: User-defined overrides (see Override File) take highest priority.

  2. Unwrap related methods: Methods like Is, As, and Unwrap with pointer receivers are only visible when the error is used as a pointer.

    func (e *PointerError) Unwrap() error { /* ... */ } // Only visible from error(&PointerError{}).
    
  3. Package-level variable assignments: var _ error = ... declarations explicitly state intent.

    var _ error = ValueError{}         // Declares ValueError as a value type.
    var _ error = (*PointerError)(nil) // Declares PointerError as a pointer type.
    
  4. Usage in functions: Consistent usage in return statements or type assertions.

    return ValueError{} // Suggests value type
    
    if _, ok := err.(*PointerError); ok { /* ... */ } // Suggests pointer type
    

    [!NOTE]

    This heuristic is a fallback and should not be relied upon for defining a type's contract.

  5. Consistent method receivers: If all methods have the same receiver type, that style is used.

Designing Linter-Friendly Packages

To make intent explicit, add a variable assignment in the declaring package:

type ValueError struct{ /* ... */ }

func (v ValueError) Error() string { /* ... */ }

type PointerError struct{ /* ... */ }

func (p PointerError) Error() string { /* ... */ }

// Explicitly declare intended usage.
var (
	_ error = ValueError{}
	_ error = (*PointerError)(nil)
)
Overriding Detected Types

When the linter reports ambiguous usage from an imported package you cannot modify, use an override file (see Override File).

Wrapper Functions

errortype automatically detects wrapper functions around errors.Is, errors.As, and errors.AsType within the analyzed package. This allows the linter to validate arguments passed to your custom error helpers just as it does for the standard library functions.

For example, a test helper like this is recognized as an errors.As wrapper:

func RequireErrorsAs(t *testing.T, err error, target any, format string, args ...any) {
	t.Helper()

	if !errors.As(err, target) {
		t.Fatalf(format, args...)
	}
}

The linter detects that err and target are forwarded to errors.As and applies the same checks to call sites of RequireErrorsAs.

Pointless Comparisons

errortype detects comparisons against newly allocated addresses. Per the Go spec, &MyStruct{} and new(T) each create a unique address, so ptr == &MyStruct{} is almost always false. For zero-sized types, the result is undefined.

Examples
Error Handling with errors.Is
import (
	"errors"
	"log"
	"net/url"
)

func handleNetworkError(err error) {
	// Always false — &url.Error{} creates a unique address.
	if errors.Is(err, &url.Error{}) {
		log.Fatal("Cannot connect to service")
	}

	// Correct approach:
	var urlErr *url.Error
	if errors.As(err, &urlErr) {
		log.Fatal("Error connecting to service:", urlErr)
	}
	// ...
}
Direct Pointer Comparisons
import (
	"github.com/operator-framework/api/pkg/operators/v1alpha1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"time"
)

func validateUpdateStrategy(spec *v1alpha1.CatalogSourceSpec) {
	expectedDuration := 30 * time.Second

	// Always false — &metav1.Duration{} creates a unique address.
	if spec.UpdateStrategy.Interval != &metav1.Duration{Duration: expectedDuration} {
		// ...
	}

	// Correct: compare values after nil check.
	if spec.UpdateStrategy.Interval == nil || spec.UpdateStrategy.Interval.Duration != expectedDuration {
		// ...
	}
}
Special Cases for errors.Is

The linter suppresses diagnostics when the error type has an Unwrap() error method (since errors.Is traverses the chain) or an Is(error) bool method (custom comparison logic). Disable with -check-is=false.

Error Naming Conventions

When enabled with -naming, errortype checks that error names follow Go conventions:

  • Sentinel errors (variables) should start with Err (or err for unexported), e.g., ErrNotFound.
  • Structured error types should end with Error, e.g., ParseError.

This flag is off by default since it can be noisy on existing codebases, but its usage is recommended for new projects and codebases that already follow these conventions.

See also Go: Naming Errors and the Parlance of Error Types for a detailed discussion.

Override File

For complex projects or third-party libraries with ambiguous error types, provide an override file.

Generate a sample with:

errortype -suggest=errortypes.yaml ./...

This creates a file with the following structure:

# Override types for your.path/package
---
pointer: # Types that should always be used as pointers
  - imported.path/one.PointerOverride

value: # Types that should always be used as values
  - imported.path/two.ValueOverride

suppress: # Types to ignore during analysis
  - imported.path/one.ErrorToIgnore

inconsistent: # Types with inconsistent usage (generated by -suggest, ignored by linter)
  - imported.path/two.InconsistentUsage

Review entries in inconsistent and move them to pointer, value, or suppress as appropriate. Then run:

errortype -overrides=errortypes.yaml ./...

[!NOTE]

A suggestion makes your code consistent with how the type is used in your package, but this may conflict with its intended design. Refactoring is often preferable to overriding.

Overrides vs. Autodetection
  • Autodetection runs on the package where an error type is defined (see How Intended Usage Is Detected).
  • Overrides force a style based on usage in your code, overriding autodetection.

When possible, improve detection in the defining package by making usage explicit (see Designing Linter-Friendly Packages).

Diagnostic Code Reference

errortype uses short codes to categorize issues:

Error Type Consistency
Code Name Description
et:ret Return Mismatch Error type returned incorrectly (value as pointer or vice versa)
et:ast Assertion Mismatch Incorrect type in assertion or switch
et:asn Assignment Mismatch Incorrect type in assignment
et:err Argument Mismatch Incorrect target passed to errors.As-like function
et:emb Ambiguous Usage Could not determine if error is pointer or value type — use an override
et:var Variable Mismatch Incorrect assignment in variable declaration starting with Err/err
et:rcv Receiver Mismatch Unwrap-related method on value error should use value receiver
Pointer Comparisons
Code Name Description
et:cmp Pointless Error Comparison Comparison against &T{} in errors.Is — always false. Use errors.As instead.
et:equ Pointless Comparison Pointer compared against &T{} — always false. Dereference and compare values.
Naming Conventions
Code Name Description
et:nam Variable Naming Sentinel error name should start with Err (exported) or err.
et:nam+ Type Naming Structured error type name should end with Error.
Other Issues
Code Name Description
et:unw Calling Unwrap Unwrapping function called inside Is(error) bool — use shallow comparison instead.
et:sty Style Mismatch Target to errors.As is not an address operation — declare a variable for clarity.
et:arg Invalid Argument Invalid target to an errors.As-like function, possible panic.
et:sig Wrong Signature Unwrap-related method has wrong signature (also flagged by stdmethods).
et:unu Unused Result Result of errors.Is-like function is unused.
et:uca Unchecked Type Assert Unchecked type assert might panic on wrapped error — prefer errors.As.

Integration

golangci-lint Module Plugin

Add .custom-gcl.yaml to your project:

---
version: v2.11.3

name: golangci-lint
destination: .

plugins:
  - module: fillmore-labs.com/errortype
    import: fillmore-labs.com/errortype/gclplugin
    version: v0.0.11

Run golangci-lint custom to build a custom executable. Configure in .golangci.yaml:

---
version: "2"
linters:
  enable:
    - errortype
  settings:
    custom:
      errortype:
        type: module
        description: errortype helps prevent subtle bugs in error handling.
        original-url: https://fillmore-labs.com/errortype
        settings:
          naming: true
          style-check: true
          deep-is-check: false
          check-is: true
          unchecked-assert: false
          check-unused: false
          prefix-filter: true
          overrides:
            pointer:
              - your.pkg/a.PointerOverrideError
            value:
              - your.pkg/a.ValueOverrideError
            suppress:
              - your.pkg/a.SuppressOverrideError

Then run:

./golangci-lint run .

See the module plugin documentation.

License

Licensed under the Apache License 2.0. See LICENSE for details.

Documentation

Overview

errortype is a Go static analysis tool that helps prevent subtle bugs in error handling.

Usage:

errortype [flags] [package ...]

It performs two main checks:

  1. Inconsistent Error Type Usage: It analyzes function returns, type assertions, and calls to functions like errors.As to ensure that custom error types are used consistently as either pointers or values.

  2. Pointless Pointer Comparisons: It detects comparisons of pointers against the address of a newly created value (e.g., 'ptr == &MyStruct{}'), which are almost always incorrect.

For inconsistent error type usage, it automatically determines the correct usage for most error types but may require a configuration file for ambiguous cases.

The flags are:

-c int
	display offending line with this many lines of context (default -1)
-check-is
	suppress compare diagnostic on errors.Is if the compared type has an "Is(error) bool" method (default true)
-check-unused
	report unchecked calls on errors.As-like functions
-deep-is-check
	diagnose all "Unwrap" functions in "Is" methods, not only those on target
-heuristics list
	list of heuristics used (default: "var,usage,receivers", "off" to disable)
-overrides file
	read error type overrides from this file
-style-check
	check for confusing uses of errors.As (default true)
-suggest file
	append suggestions to this file, "-" for standard output
-test
	indicates whether test files should be analyzed, too (default true)
-tracetypes regex
	information of error type detection in packages matching this regex
-unchecked-assert
	report unchecked type asserts on errors

Examples

To check the current package:

errortype .

To check errors across packages, using and suggesting overrides:

errortype -overrides=overrides.yaml -suggest=- ./...

Directories

Path Synopsis
result
Package result defines the types used to represent error type usage patterns detected during static analysis.
Package result defines the types used to represent error type usage patterns detected during static analysis.
Package gclplugin provides golangci-lint plugin integration for the errortype analyzer.
Package gclplugin provides golangci-lint plugin integration for the errortype analyzer.
internal
analyze
Package analyze provides the main errortype analyzer for detecting and enforcing consistent error type usage in Go programs.
Package analyze provides the main errortype analyzer for detecting and enforcing consistent error type usage in Go programs.
bitflag
Package bitflag provides utilities for working with integer-based bit flags.
Package bitflag provides utilities for working with integer-based bit flags.
detect
Package detect implements a multi-stage usage analysis for Go error types.
Package detect implements a multi-stage usage analysis for Go error types.
overrides
Package overrides provides functionality for reading and writing YAML configuration files that specify error type handling overrides.
Package overrides provides functionality for reading and writing YAML configuration files that specify error type handling overrides.
run
typeutil
Package typeutil provides utility functions for Go type analysis and manipulation.
Package typeutil provides utility functions for Go type analysis and manipulation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL