Created
March 12, 2016 00:21
-
-
Save koudelka/1006570983a87bf7eba1 to your computer and use it in GitHub Desktop.
TypeBully is an (incomplete) toy module to force Elixir functions to respect their typespecs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# TypeBully forces functions to respect their typespecs by rewriting the function clauses to include proper guards. | |
# | |
# For example, the Wimp module below: cry/3 has typespecs, but no enforcing guards, normally, calling cry/3 would | |
# always match the first function clause, but TypeBully forces it to select the correct one based on its typespec. | |
# | |
# | |
# Without TypeBully: | |
# | |
# iex(1)> Wimp.cry("whaa") | |
# "first" | |
# iex(2)> Wimp.cry(1) | |
# "first" | |
# | |
# With TypeBully: | |
# | |
# iex(1)> Wimp.cry("whaa") | |
# "first" | |
# iex(2)> Wimp.cry(1) | |
# "second" | |
# | |
defmodule TypeBully do | |
defmacro __using__(_env) do | |
quote do | |
Module.register_attribute(__MODULE__, :bodies, accumulate: true) | |
@on_definition unquote(__MODULE__) | |
@before_compile unquote(__MODULE__) | |
end | |
end | |
defmacro __before_compile__(env) do | |
functions = env.module | |
|> Module.get_attribute(:bodies) | |
|> Enum.map(fn {kind, name, args, _guards, body, spec} -> | |
guards = | |
Enum.zip(args, spec) | |
|> Enum.filter(fn {_, type} -> type != :any end) | |
|> Enum.map(fn {arg, type} -> | |
case type do | |
"string" -> "is_binary(#{arg})" | |
t -> "is_#{t}(#{arg})" | |
end | |
end) | |
|> Enum.join(" and ") | |
~s""" | |
#{kind} #{name}(#{Enum.join(args, ",")}) when #{guards} do | |
#{Macro.to_string body} | |
end | |
""" | |
|> Code.string_to_quoted! | |
end) | |
quote do | |
@bodies | |
|> Enum.map(fn {_kind, name, args, _guards, _body, _spec} -> | |
{name, length(args)} | |
end) | |
|> Enum.uniq | |
|> defoverridable | |
unquote_splicing(functions) | |
end | |
end | |
def __on_definition__(env, kind, name, args, guards, body) do | |
[{:spec, {:::, _, [{_, _, spec}, _]}, _} | _rest] = Module.get_attribute(env.module, :spec) | |
spec = Enum.map(spec, fn s -> | |
case s do | |
{_, _, [type]} -> type |> Atom.to_string |> String.downcase | |
{type, _, nil} -> type | |
end | |
end) | |
arg_names = Enum.map(args, fn {a, _, _} -> a end) | |
Module.put_attribute(env.module, :bodies, {kind, name, arg_names, guards, body, spec}) | |
end | |
end | |
defmodule Wimp do | |
use TypeBully | |
@spec cry(String) :: String | |
def cry(a) do | |
"first" | |
end | |
@spec cry(Integer) :: String | |
def cry(a) do | |
"second" | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment