One possibilitiy might be to:
- take the original Elixir AST
- traverse it top-down, passing the meta-field from each parent to the child.
- every time a ‘leaf’ (i.e. literal) is encountered that does not have its own meta field, replace it by a “placeholder” node that does contain the desired metadata (as well as internally still the original literal), which has a syntax that is distinguishable from proper AST nodes.
- Use this “embellished AST” as original truth to create your mutated versions off of.
- For every mutated embellished AST:
- Before compiling to execute, traverse it once more to revert the placeholder nodes (to make sure the AST becomes proper compileable Elixir again)
- Now when there is a problem, you still have the information about line numbers and other metadata related to the thing you muated in your “mutated embellished AST”, so you can refer to them.
To accept all Elixir code you’ll need a placeholder that itself is not proper AST, like an non-quoted struct.
This does mean that for your top-down traversal you probably won’t be able to use Macro.prewalk
because it will break when encountering the placeholder struct. However, you can easily create your own variant of that function which has a special case for the placeholder struct.