Solution
What’s happening here is that defguard receives code and returns code (to be more specific, it both receives a AST representation of code and then returns that AST modified). As as consequence, this means that the correct typespec would be this:
@spec is_pos_integer(Macro.t()) :: Macro.t()
Which many people in the community defend is not very useful.
I agree with this consensus, so the next best option is to add a @doc to the guard to clearly document it, and is what I suggest as well.
Sources: