Hey, welcome to the ElixirForum, and Elixir meta-programming!
I think the conceptual nudge that will help you the most here is a little clarification in the role of various concepts during the compilation process:
- a
Module
is a unit of compilation + a namespace
- a
module attribute
is a constant language-level value determined at compile time
- a
Macro
is a special function executed at compile time that receives AST as input and places its return value into the AST where invoked
- macros can access module attributes, ie in compilation hooks via attribute functions.
Emphasis on value. What you place in a module attribute should be a language-level primitive value, that may be operated on later by compile-time hooks. You’ll notice the input to all documented module attributes, with the exception of type-related stuff, are just tuples, lists, strings, atoms, etc.
Typespec-related attributes get special builtin compiler-powered semantics and expressivity, that I do not think you can tap into in user-land code.
You cannot place arbitrary typespec syntax into an arbitrary module attribute today.
The easiest way to get this is to use macros, which specialize in AST transformation. You should try offering, for instance, defarg
and defret
macros. They could emit type-spec related module attributes AST compatible with the type system. Macros are more than capable of consuming the typespec syntax and operating on it, doing library magic with it, and re-emitting the syntax into typespec module attributes.
As an alternative, you could invent a language-level data primitive syntax that expresses what you want, like @arg a: [:is_integer, :is_integer]
, and compile it into a typespec via a compilation hook (and perform your library’s magic in the same pass).
The takeaway here, though, is that you’ll need to leverage macros get what you want, with module attributes as possible input to them and output from them.
One note:
You definitely don’t want to do this–even if it is possible (which would surprise me) overriding module attribute definition would have a high chance of breaking tons of stuff in caller code. @
is a macro in the Kernel
module for documentation and compiler bootstrapping purposes only.