One issue that has come up recently is that error output for compilations errors only contains the place the error occurred and the place that called the offending code. It might not sound like a problem, but pair that with macros and it will all become clear. It's easiest to show, so here's some code that produces a compilation error:
macro compilation_error(arg)
1 < {{ arg }}
end
puts compilation_error("asdf")
and the compilation error looks like:
Showing last frame. Use --error-trace for full trace.
There was a problem expanding macro 'compilation_error'
Code in temp.cr:10:6
10 | puts compilation_error("asdf")
^
Called macro defined in temp.cr:2:1
2 | macro compilation_error(arg)
Which expanded to:
> 1 | 1 < "asdf"
^
Error: no overload matches 'Int32#<' with type String
Overloads are:
...cut for brevity
The error output for this compilation error is kind of nice. It states there's an issue and then the very first code reference is to where the macro was called. The next code reference points towards the macro-generated code that caused the error. If the user is not familiar with what code is generated by the macro they are using (and maybe they don't even know it's a macro), the first code reference is often way more helpful than when it dives into the macro code.
Now, let's change how the macro works just a little bit and see what happens to the error output.
macro inner_compilation_error(arg)
1 < {{ arg }}
end
macro compilation_error(arg)
inner_compilation_error({{ arg }})
end
puts compilation_error("asdf")
And the new error output
Showing last frame. Use --error-trace for full trace.
There was a problem expanding macro 'inner_compilation_error'
Code in macro 'compilation_error'
1 | inner_compilation_error("asdf")
^
Called macro defined in eval:1:1
Which expanded to:
> 1 | 1 < "asdf"
^
Error: no overload matches 'Int32#<' with type String
Overloads are:
...cut for brevity
Can you notice the difference? Where in the error output does it point towards where the macro was called? The answer, it doesn't.
The change to the code was adding another macro method that the original macro method now delegates to. This is very common when making macros since the code generation can become complicated. The downside is that error output only shows the compilation error and the code that called it, and in this case it means that it never makes it out of the macro code. Not so bad when you manage and call the macro, annoying and confusing when you are calling macros provided to you from libraries. At least in this code example you can kind of reference that the error is because you passed in a string, but what if the argument passed into the top-level macro is augmented in some way? What if the string is split into an array? I believe the error output becomes confusing and of little value. (I've written this article without talking about --error-trace
because I believe that errors should be helpful without it)
Story from Lucky-land
In Lucky we generate URL helper methods to make it easier to create links in HTML, and we do this using macros called when users set up their actions (what we call controllers in a traditional MVC architecture). A common request for help in our Discord is to help figure out where users went wrong with their action setup. They copy and paste their action code and provide a picture of the stack-trace that looks something like this
You'll notice that the image of the stack-trace looks a little like the second example I laid out above. It talks about an error but the code references it points at are all in macro code. That's why user's end up staring at their action and are forced to ask for help. The awful thing about it is that the error isn't in their action at all. The user spends the most time staring at the action to see the error, but anyone who tries to help also has to spend time carefully reading the code. The annoying thing is that the real problem was with their usage of the generated URL helpers I mentioned, and not with the action implementation at all.
Because I had experienced this issue myself and had helped people out with this often, I really wanted to figure out how to get the usage site (where they originally called into macro code) into the error output. Without going into the code, I looked at the macro and saw something very similar to the code example above where the main macro delegated to another but nowhere else was this second macro called. Moving the second macro code into the first was the solution and now the usage site shows up in the error output!
Here's the pull request that made the change if you want to reference it
Inline render_html_page to change compilation error #1373
Purpose
No connected issue
Description
Before
After
Notice in the "Before" that the first code reference was a macro and not the usage site that caused the error. By removing the render_html_page
we actually see the usage site in the error which will help people track down issues.
That macro was extracted here but you'll notice that this PR achieves the same level of DRY-ness without the separate macro.
Checklist
- [ ] - An issue already exists detailing the issue/or feature request that this PR fixes
- [x] - All specs are formatted with
crystal tool format spec src
- [x] - Inline documentation has been added and/or updated
- [x] - Lucky builds on docker with
./script/setup
- [x] - All builds and specs pass on docker with
./script/test
Top comments (0)