Background
I am playing around with defguard
in macro I am defining. The macro has an opaque type and I think dialyzer is having issues with this:
macro:
defmodule NewType do
defmacro deftype(name, type) do
quote do
defmodule unquote(name) do
@opaque t :: {unquote(name), unquote(type)}
@spec new(value :: unquote(type)) :: t
def new(value) when is_binary(value), do: {unquote(name), value}
@spec extract(new_type :: t) :: unquote(type)
def extract({unquote(name), value}), do: value
end
end
end
defguard is_type?(value, mod)
when is_tuple(value) and
elem(value, 0) == mod and is_binary(elem(value, 1))
end
In theory, an invocation of deftype Name, String.t()
would generate:
defmodule Name do
@opaque t :: {Name, String.t}
@spec new(value :: String.t()) :: t
def new(value) when is_binary(value), do: {Name, value}
@spec extract(new_type :: t) :: String.t()
def extract({Name, value}), do: value
end
And in fact, this code works as intended:
type.ex (module where I define several types)
defmodule Type do
import NewType
deftype Name, String.t()
end
test.ex
defmodule Test do
alias Type.Name
@spec print(Name.t()) :: binary
def print(name), do: Name.extract(name)
end
Problem
The problem arises when I try to use the guard:
test.ex
defmodule Test do
import NewType
alias Type.Name
@spec print(Name.t()) :: binary
def print(name) when is_type?(name, Name), do: Name.extract(name)
end
Everything runs as expected, but dialyzer does not see it the same way:
lib/test.ex:6:no_return
Function print/1 has no local return.
________________________________________________________________________________
lib/test.ex:6:call_without_opaque
Function call without opaqueness type mismatch.
Call does not have expected opaque term of type Type.Name.t() in the 1st position.
Type.Name.extract(_name :: tuple())
________________________________________________________________________________
done (warnings were emitted)
Halting VM with exit status 2
Even though Name.extract
does receive a Name.t
as a parameter, dialyzer states otherwise.
At this point, I think this might be because I am “exposing” the internal data structure in the guard with is_tuple
, but at the same time, I don’t see how else I can have defguard
and still use opaque types.
Question
Am I missing something?