Played with this a little during ElixirConf US 2019. Posting some findings here.
https://github.com/ericmj/ex2ms works well today, but I wanted to see if I could take some of my defguard experience and build an implementation that deferred all expansion work to the Elixir compiler, to avoid hard-coded translations like
these elixir-to-erl mappings. I stole the actual AST to MS rewrite logic from ex2ms, but tried to leverage :elixir_expand.expand beforehand. I ran into a few challenges that have convinced me that first-class support for MS generation would require changes to the Elixir compiler and/or erlang to get the inclusion-into-core standards I want, mostly evidenced in my test project
https://github.com/christhekeele/matcha via code comments or commented out tests (also stolen from ex2ms).
The test project's macros make three passes on the AST:
- expansion
- conversion
- validation
Expansion
The hard part of expanding Elixir code into matchspecs with the compiler is the "tracing problem", which can be best summarized as: trace matchspecs support function-like calls to drive the tracing harness, that don't actually have real-world compiled function analogs anywhere. This makes it difficult for a compiler to verify their correctness.
I started playing around with getting around this by defining :trace and :table modules with stub functions. The idea was to alias them into the AST before expansion, then strip the module from the call out afterwards. Not fully implemented in the test project yet. More on this in a bit.
The other stumbling block I ran into was expanding multiple clauses verbatim. I had to wrap the clauses provided to the macro in a fn block for the compiler to expand them, which prevents interpolating values anywhere into the matchspec via the ^ operator (the way ex2ms allows for).
Conversion
I kept the conversion logic from ex2ms, but assumed all (valid) remaining remote calls would have been expanded into :erlang or :trace/:table module references, after which I'd strip out the module call. This works more or less as expected.
Validation
I ran the results through :erlang.match_spec_test to detect issues at compile time rather than runtime, which seems to work fine.
Conclusion
Since full support for the ^ operator would require a new context or construct in the compiler, and since erlang does not provide authoritative information on what's valid in the matchspec grammar, I think the bespoke approach of ex2ms is the most satisfactory for now. I may find some time to contribute to it with better arity checking for trace functions, more helpful error messages, and an extra validation pass sometime--perhaps even before next ElixirConf.
Ideally I'd like to have concrete no-ops for the tracing functions within erlang, and some sort of predicate function in
http://erlang.org/doc/man/erl_internal.html to handle validation of local/remote calls in matchspecs, before pursuing the handling of the pin operator. But that's more work than I'm willing to put in to this for now.