I have a macro that in some circumstances must throw an error. Is there a recommended way of doing that?
For example, I can throw the error directly from the macro, or return code that throws the error.
return :(1 + 1)
return :(1 + 1)
If I understand it correctly, macro
@A would cause an error during compile time, while macro
@B would cause an error during run time. I don’t think this would make any difference to the user who calls my macro in their code. However, it makes a difference for me in my unit tests. In particular, I can do
julia> @test_throws ErrorException @B
Expression: #= REPL:1 =# @B
But I cannot test
@A the same way, because it doesn’t even compile
julia> @test_throws ErrorException @A
ERROR: Error message
@ Base ./error.jl:33
 var"@A"(__source__::LineNumberNode, __module__::Module)
. . .
I’m hoping to have discussion about this. Would you recommend one versus the other and why? Am I thinking about this correctly? Are there other considerations that I should take into account?
I think that you have actually answered yourself:
That does make a difference for users too. Let’s imagine that the macro is not used interactively or in global scope, but inside a function. Then
@A would throw the error in the moment of defining the function (it does not even need to be compiled), but
@B would let the function exist, which would fail in the moment of calling it.
You have to decide if you need one thing or the other.
@test_throws LoadError @eval @A
but I think that only allows to capture
julia> macro errormacro(ex)
@errormacro (macro with 1 method)
julia> Test.@test_throws ArgumentError @macroexpand @errormacro 2
Tiny caveat: Works only from Julia 1.7 on
From the docstring of
LoadErrors are no longer emitted by @macroexpand, @macroexpand1, and macroexpand as of Julia 1.7.
If the error can be thrown at compile-time, I’d say it’s best to fail during macro expansion. Examples of early macro expansion failure include the
Test macros themselves:
julia> using Test
julia> @macroexpand @test true unsupported_kwarg=nothing
ERROR: invalid test macro call: @test true unsupported_kwarg = nothing
This example also illustrates how
@macroexpand allows to clearly separate the macro expansion stage from the runtime, which allows testing for errors that happen during macro expansion:
julia> macro A()
@A (macro with 1 method)
julia> @test_throws ErrorException @macroexpand @A
Thanks everyone for your replies and ideas. I learned about using
@macroexpand for this test and this is very helpful, thanks again!
Btw, I tested it and it doesn’t work in Julia 1.6, but it works great in 1.7 and 1.8.
Another way of thinking about it is that macros are basically syntax transformations. So it makes sense to throw errors as soon as possible (like syntax errors)