Maybe overriding the spec might work (don’t have the time to try it out)?
defmodule MyAppError do
# ...
@spec exception(term) :: Exception.t
def exception(value) do
%MyAppError{message: "Oops!: #{inspect value}"}
end
# ...
end
I think your point about what causes problem is correct. Howver, overriding the spec causes the has overlapping domains; such contracts are currently unsupported and are simply ignored message.
Yeah, apparently, @spec doesn’t override the default spec injected by defexception. If I get this right, this means that even in your case (@spec exception(map) :: Exception.t), you add extra specification, so your exception can now take a string as well as a map.
I’m not sure if defexception should generate @spec. Let’s ping @josevalim to hear his thoughts on the matter.
In my opinion the pregenerated typespec is a bug, since the callback does allow a much broader type than String.t only.
Therefore it should be exclusively in the hands of the implementor to narrow the spec down from that. Injecting the spec premature even makes it impossible in terms of dialyzer to actually accept term… (without having to specify numbers, atoms, tuples, functions, etc separately)
What about enhancing the syntax with something that is currently invalid, something like:
defexception [:message] # Generates %__MODULE__{exception: true, message: nil} or so
# or a spec'd version:
defexception [:message :: String.t] # Generates %__MODULE__{exception: true, message: nil} or so with a spec that sets message to String.t
# Spec with a default
defexception [message: "default" :: String.t] # Etc...
# And more!
defexception [message: "default" :: String.t, blah: :undefined :: atom(), meta: nil] # meta defaults to `term()` since not specified
All syntax’s are valid and they are pretty trivially parseable to generate the same code as it does not but with an appropriately correct spec.