Skip to content

feat: add ErrorAsType assertions for Go 1.27 - #1937

Open
mattjohnsonpint wants to merge 3 commits into
stretchr:masterfrom
mattjohnsonpint:mjp/errorastype-go1.27
Open

feat: add ErrorAsType assertions for Go 1.27#1937
mattjohnsonpint wants to merge 3 commits into
stretchr:masterfrom
mattjohnsonpint:mjp/errorastype-go1.27

Conversation

@mattjohnsonpint

Copy link
Copy Markdown

Summary

Add generic ErrorAsType and NotErrorAsType assertions for Go 1.27 consumers.

The underlying errors.AsType function arrived in Go 1.26, but Testify could not expose an equivalent API consistently because Go did not yet support generic methods. Go 1.27 removes that restriction, allowing these assertions to be generated as both package functions and methods on Assertions.

These assertions return the matched error with its concrete type while preserving Testify's failure diagnostics.

Closes #1860.

Motivation

Previously, checking and then using a specific error type required a separate target variable:

var syntaxErr *json.SyntaxError
require.ErrorAs(t, err, &syntaxErr)

With this change:

syntaxErr := require.ErrorAsType[*json.SyntaxError](t, err)

The assert variant also returns whether the assertion succeeded:

syntaxErr, ok := assert.ErrorAsType[*json.SyntaxError](t, err)

This avoids the target variable and subsequent cast without losing Testify's error-chain diagnostics.

Added API

For Go 1.27 and later, package assert adds:

assert.ErrorAsType[E error](t, err, msgAndArgs...) (E, bool)
assert.ErrorAsTypef[E error](t, err, msg, args...) (E, bool)
assert.NotErrorAsType[E error](t, err, msgAndArgs...) bool
assert.NotErrorAsTypef[E error](t, err, msg, args...) bool

The same assertions are available as generic methods:

assert.New(t).ErrorAsType[E](err, msgAndArgs...)
assert.New(t).ErrorAsTypef[E](err, msg, args...)
assert.New(t).NotErrorAsType[E](err, msgAndArgs...)
assert.New(t).NotErrorAsTypef[E](err, msg, args...)

Package require adds corresponding functions:

require.ErrorAsType[E error](t, err, msgAndArgs...) E
require.ErrorAsTypef[E error](t, err, msg, args...) E
require.NotErrorAsType[E error](t, err, msgAndArgs...)
require.NotErrorAsTypef[E error](t, err, msg, args...)

These are also available as methods on require.Assertions:

require.New(t).ErrorAsType[E](err, msgAndArgs...)
require.New(t).ErrorAsTypef[E](err, msg, args...)
require.New(t).NotErrorAsType[E](err, msgAndArgs...)
require.New(t).NotErrorAsTypef[E](err, msg, args...)

The require.ErrorAsType variants return the matched error directly after terminating the test on failure.

Code Generation

The code generator now supports:

  • Generic type parameters and constraints.
  • Generic method generation.
  • Assertions that return typed values followed by a success bool.
  • Corresponding require functions that return typed values.
  • Imports referenced by generic constraints, arguments, and results.
  • Version-specific generated output inferred from the source file's build constraint.

This keeps the new functions and methods generated consistently with Testify's existing API rather than introducing handwritten exceptions.

The _codegen module now requires Go 1.18 because generic signature inspection uses go/types APIs introduced in that release. This does not change Testify's Go 1.17 minimum. _codegen is a separate module used only during generation.

Compatibility

All new API and generated files use:

//go:build go1.27

The main module remains compatible with Go 1.17. Existing generated output remains stable when generation is run under Go 1.26.

The CI matrix also adds explicit Go 1.25 coverage. Go 1.26 and Go 1.27 are covered by oldstable and stable.

Testing

The tests cover:

  • Successful matches and returned typed errors.
  • Errors that do not contain the requested type.
  • Nil errors.
  • Wrapped error chains and failure diagnostics.
  • Formatted variants.
  • Package functions and generic Assertions methods.
  • assert and require behavior.
  • Generation under Go 1.26.
  • Package compatibility with Go 1.17.

Potential Future Work

The following APIs are suggestions for separate future changes. They are not included in this PR.

The generic code-generation support introduced here could enable a small number of additional assertions whose implementations genuinely rely on generic operations.

AsType

assert.AsType[T any](t, value, ...) (T, bool)
require.AsType[T any](t, value, ...) T

This could perform a type assertion against a type parameter:

target, ok := value.(T)

It could validate and return either a concrete type or an interface implementation.

PanicsAsType

assert.PanicsAsType[T any](t, f, ...) (T, bool)
require.PanicsAsType[T any](t, f, ...) T

This could apply a generic type assertion to the recovered panic value and return the typed payload for further inspection.

Typed channel assertions

Arbitrary typed channels cannot be converted to <-chan any, so assertions that accept and return their element type could also use generics directly:

assert.EventuallyReceives[T any](t, ch <-chan T, waitFor, ...) (T, bool)
require.EventuallyReceives[T any](t, ch <-chan T, waitFor, ...) T

assert.NeverReceives[T any](t, ch <-chan T, waitFor, ...) bool
require.NeverReceives[T any](t, ch <-chan T, waitFor, ...)

This PR intentionally remains focused on ErrorAsType; these APIs are listed only to show where the new generator support could be useful.

Go 1.27 makes it possible to expose generic assertions through the same function and method surfaces as existing assertions. Teach the generator to preserve that API shape so generic additions remain generated and maintainable rather than becoming handwritten exceptions.

Inspecting and rendering generic signatures relies on go/types APIs added in Go 1.18. The codegen module therefore declares Go 1.18 as its actual minimum; this does not change the Go 1.17 minimum of the main Testify module.
Provide a typed alternative to ErrorAs that returns the matched error without requiring callers to declare a target variable, while retaining Testify's useful failure diagnostics. Go 1.27 generic methods allow the API to remain consistent across package functions and Assertions objects.
As stable and oldstable advance to Go 1.27 and Go 1.26, retain explicit Go 1.25 coverage so every supported release remains tested. Run language-level formatting validation with the stable toolchain because Go 1.26 cannot parse generic methods, even in files excluded by build constraints.

Several CI helpers could previously report success when their underlying commands, generation checks, or action-pin validation failed. Ensure those failures are propagated and newly generated files are detected so a green build means the checks actually completed successfully.
@mattjohnsonpint

Copy link
Copy Markdown
Author

While reviewing the CI run, I noticed that the Go 1.26 gofmt step emitted errors for the Go 1.27 generic methods but still reported success. Build constraints do not prevent gofmt from parsing those files, and the script’s conditional command substitution masked its nonzero exit status.

I updated the CI changes to:

  • Run formatting validation only with the stable Go toolchain.
  • Ensure formatting and generation command failures propagate correctly.
  • Detect newly generated, untracked files.
  • Fix similar failure-masking behavior in the GitHub Actions hash checker, including errors hidden by a pipeline subshell or failed API request.

The updated checks now fail when their underlying validation fails, rather than producing error annotations within an otherwise successful job.

@mattjohnsonpint mattjohnsonpint changed the title assert: add ErrorAsType assertions for Go 1.27 feat: add ErrorAsType assertions for Go 1.27 Aug 20, 2026
@dolmen

dolmen commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Please move the CI changes to a separate PR.

@mattjohnsonpint

Copy link
Copy Markdown
Author

Please move the CI changes to a separate PR.

Done in #1941. I'll rebase this PR after it's merged. Thanks.

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

Successfully merging this pull request may close these issues.

assert.ErrorAsType: new assertion for Go 1.27+

2 participants