Skip to content

Enable error_context in assertions#15309

Closed
dtwiers wants to merge 1 commit intoelixir-lang:mainfrom
dtwiers:hybrid-assertions
Closed

Enable error_context in assertions#15309
dtwiers wants to merge 1 commit intoelixir-lang:mainfrom
dtwiers:hybrid-assertions

Conversation

@dtwiers
Copy link
Copy Markdown

@dtwiers dtwiers commented Apr 24, 2026

This enables 2 features within assertions:

  • assert foo == bar, diff: true, message: "this must be a string, and is intended to be the main error message" will give the error message as-is, but will also enable the same beautiful diffing we've enjoyed in assert/1.
  • assert foo == bar, error_context: %{this: "could be any shape"}, which implies diff: true, also enables the diffing output, but adds an additional context: <whatever you put here>, which I have found to be extremely handy if you want to assert something in a loop (you put the iterator value in the error_context and you know which item failed). You can still use message: "my main error message" with it too, if you want more details

Thoughts anyone? I asked about this as a proposal all the way back in September and finally got around to implementing it. It's not only my first PR into Elixir, it's my first PR into any major FOSS project.


"""
defmacro assert({:=, meta, [left, right]} = assertion) do
code = escape_quoted(:assert, meta, mark_as_generated(assertion))
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since assert/1 was a macro but assert/2 was a function, I needed to move things around a bit to enable diffing within assert/2, hence the decent size of this PR

Assisted-by: opencode:OpenAI/GPT-5.4
@dtwiers dtwiers force-pushed the hybrid-assertions branch from e8684d2 to 57c9f6c Compare April 24, 2026 20:31
@josevalim
Copy link
Copy Markdown
Member

Hi @dtwiers! Thank you for the PR!

Unfortunately, this pull request may lead to breakages, in case someone was expecting assert/2 to always be a function. Furthermore, this pull request has one slight issue, which is that the options are compile-time only. For example, this works:

assert foo == bar, diff: true, message: "this must be a string, and is intended to be the main error message"

But not this:

opts = [diff: true, message: "this must be a string, and is intended to be the main error message"]
assert foo == bar

Which is something we generally try to avoid in Elixir (although in this case it is something we can easily solve, exactly because of the function/macro semantics). So I understand the use case you are asking for here but unfortunately I don't think this is the way to go.

One option I thought of is to have some sort of assertion_context id: 13 that you could call before each assertion to give the context as side band data (it would work across multiple assertions too), but we would also need to see how it overlaps with tags. I suggest a mailing discussion or forum discussion to potentially iron out these details. Thanks!

@josevalim josevalim closed this Apr 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants