Skip to content

Commit

Permalink
Merge pull request #128 from Qqwy/compilation_performance2
Browse files Browse the repository at this point in the history
Improve compilation performance and reduce compiled module size
  • Loading branch information
Qqwy authored Jun 14, 2022
2 parents 9db92a6 + fbb0f7e commit 2e0cc95
Show file tree
Hide file tree
Showing 16 changed files with 208 additions and 52 deletions.
3 changes: 2 additions & 1 deletion lib/type_check/builtin.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1072,14 +1072,15 @@ defmodule TypeCheck.Builtin do
if_recompiling? do
# @spec! named_type(name :: atom() | String.t(), type :: TypeCheck.Type.t()) :: TypeCheck.Builtin.NamedType.t()
end
def named_type(name, type, type_kind \\ :type) do
def named_type(name, type, type_kind \\ :type, called_as \\ nil) do
TypeCheck.Type.ensure_type!(type)

build_struct(TypeCheck.Builtin.NamedType)
|> Map.put(:name, name)
|> Map.put(:type, type)
|> Map.put(:local, true)
|> Map.put(:type_kind, type_kind)
|> Map.put(:called_as, called_as)
end

@doc typekind: :extension
Expand Down
10 changes: 8 additions & 2 deletions lib/type_check/builtin/compound_fixed_map.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defmodule TypeCheck.Builtin.CompoundFixedMap do
{:ok, bindings1 ++ bindings2, Map.merge(fixed_part, flexible_part)}
else
{:error, {_, reason, info, _val}} ->
{:error, {unquote(Macro.escape(s)), reason, info, unquote(param)}}
{:error, {unquote(TypeCheck.Internals.Escaper.escape(s)), reason, info, unquote(param)}}
end
end

Expand All @@ -50,7 +50,7 @@ defmodule TypeCheck.Builtin.CompoundFixedMap do
val when is_map(val) ->
{:ok, [], val}
other ->
{:error, {unquote(Macro.escape(s)), :not_a_map, %{}, other}}
{:error, {unquote(TypeCheck.Internals.Escaper.escape(s)), :not_a_map, %{}, other}}
end
end
end
Expand Down Expand Up @@ -88,6 +88,12 @@ defmodule TypeCheck.Builtin.CompoundFixedMap do
end
end

defimpl TypeCheck.Protocols.Escape do
def escape(s) do
%{s | fixed: TypeCheck.Protocols.Escape.escape(s.fixed), flexible: TypeCheck.Protocols.Escape.escape(s.flexible)}
end
end

if Code.ensure_loaded?(StreamData) do
defimpl TypeCheck.Protocols.ToStreamData do
def to_gen(s) do
Expand Down
6 changes: 6 additions & 0 deletions lib/type_check/builtin/fixed_list.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ defmodule TypeCheck.Builtin.FixedList do
end
end

defimpl TypeCheck.Protocols.Escape do
def escape(s) do
update_in(s.element_types, &Enum.map(&1, fn val -> TypeCheck.Protocols.Escape.escape(val) end))
end
end

defimpl TypeCheck.Protocols.Inspect do
def inspect(s, opts) do
s.element_types
Expand Down
15 changes: 11 additions & 4 deletions lib/type_check/builtin/fixed_map.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ defmodule TypeCheck.Builtin.FixedMap do
| {t(), :value_error,
%{problem: lazy(TypeCheck.TypeError.Formatter.problem_tuple()), key: any()}, map()}


defimpl TypeCheck.Protocols.Escape do
def escape(s) do
update_in(s.keypairs, &Enum.map(&1, fn {key, val} -> {key, TypeCheck.Protocols.Escape.escape(val)} end))
end
end

defimpl TypeCheck.Protocols.ToCheck do
# Optimization: If we have no expectations on keys -> value types, remove those useless checks.
def to_check(s = %TypeCheck.Builtin.FixedMap{keypairs: keypairs}, param)
Expand Down Expand Up @@ -48,7 +55,7 @@ defmodule TypeCheck.Builtin.FixedMap do
val when is_map(val) ->
{:ok, [], val}
other ->
{:error, {unquote(Macro.escape(s)), :not_a_map, %{}, other}}
{:error, {unquote(TypeCheck.Internals.Escaper.escape(s)), :not_a_map, %{}, other}}
end
end
end
Expand All @@ -69,7 +76,7 @@ defmodule TypeCheck.Builtin.FixedMap do

missing_keys ->
{:error,
{unquote(Macro.escape(s)), :missing_keys, %{keys: missing_keys}, unquote(param)}}
{unquote(TypeCheck.Internals.Escaper.escape(s)), :missing_keys, %{keys: missing_keys}, unquote(param)}}
end
end
end
Expand All @@ -89,7 +96,7 @@ defmodule TypeCheck.Builtin.FixedMap do

superfluous_keys ->
{:error,
{unquote(Macro.escape(s)), :superfluous_keys, %{keys: superfluous_keys},
{unquote(TypeCheck.Internals.Escaper.escape(s)), :superfluous_keys, %{keys: superfluous_keys},
unquote(param)}}
end
end
Expand Down Expand Up @@ -126,7 +133,7 @@ defmodule TypeCheck.Builtin.FixedMap do
else
{{:error, error}, key} ->
{:error,
{unquote(Macro.escape(s)), :value_error, %{problem: error, key: key}, unquote(param)}}
{unquote(TypeCheck.Internals.Escaper.escape(s)), :value_error, %{problem: error, key: key}, unquote(param)}}
end
end
end
Expand Down
8 changes: 7 additions & 1 deletion lib/type_check/builtin/fixed_tuple.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ defmodule TypeCheck.Builtin.FixedTuple do
%{problem: lazy(TypeCheck.TypeError.Formatter.problem_tuple()), index: integer()},
tuple()}

defimpl TypeCheck.Protocols.Escape do
def escape(s) do
update_in(s.element_types, &Enum.map(&1, fn val -> TypeCheck.Protocols.Escape.escape(val) end))
end
end

defimpl TypeCheck.Protocols.ToCheck do
def to_check(s = %{element_types: types_list}, param) do
element_checks_ast = build_element_checks_ast(types_list, param, s)
Expand All @@ -23,7 +29,7 @@ defmodule TypeCheck.Builtin.FixedTuple do

x when tuple_size(x) != unquote(expected_size) ->
{:error,
{unquote(Macro.escape(s)), :different_size, %{expected_size: unquote(expected_size)},
{unquote(TypeCheck.Internals.Escaper.escape(s)), :different_size, %{expected_size: unquote(expected_size)},
x}}

_ ->
Expand Down
100 changes: 73 additions & 27 deletions lib/type_check/builtin/function.ex
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
defmodule TypeCheck.Builtin.Function do
defstruct [param_types: nil, return_type: %TypeCheck.Builtin.Any{}]
defstruct param_types: nil, return_type: %TypeCheck.Builtin.Any{}

use TypeCheck

@opaque! t :: %TypeCheck.Builtin.Function{
param_types: list(TypeCheck.Type.t()) | nil,
return_type: TypeCheck.Type.t()
}
param_types: list(TypeCheck.Type.t()) | nil,
return_type: TypeCheck.Type.t()
}
@type! problem_tuple :: {t(), :no_match, %{}, any()}

defimpl TypeCheck.Protocols.Escape do
def escape(s) do
s
|> Map.update!(:param_types, fn
nil -> nil
list when is_list(list) -> Enum.map(list, &TypeCheck.Protocols.Escape.escape(&1))
end)
|> Map.update!(:return_type, &TypeCheck.Protocols.Escape.escape(&1))
end
end

defimpl TypeCheck.Protocols.ToCheck do
def to_check(s, param) do
quote generated: true, location: :keep do
p = unquote(param)

case p do
unquote(is_function_check(s)) ->
wrapped_fun = unquote(@for.contravariant_wrapper(s, param))
{:ok, [], wrapped_fun}

_ ->
{:error, {unquote(Macro.escape(s)), :no_match, %{}, unquote(param)}}
end
Expand All @@ -28,6 +42,7 @@ defmodule TypeCheck.Builtin.Function do
quote generated: true, location: :keep do
x when is_function(x)
end

list ->
quote generated: true, location: :keep do
x when is_function(x, unquote(length(list)))
Expand All @@ -40,50 +55,70 @@ defmodule TypeCheck.Builtin.Function do
case s do
%{param_types: nil, return_type: %TypeCheck.Builtin.Any{}} ->
original

%{param_types: [], return_type: %TypeCheck.Builtin.Any{}} ->
original

%{param_types: nil, return_type: return_type} ->
quote generated: true, location: :keep, bind_quoted: [fun: original, s: Macro.escape(s), return_type: Macro.escape(return_type)] do
quote generated: true,
location: :keep,
bind_quoted: [
fun: original,
s: Macro.escape(s),
return_type: Macro.escape(return_type)
] do
{:arity, arity} = Function.info(fun, :arity)
clean_params = Macro.generate_arguments(arity, __MODULE__)
return_code_check = TypeCheck.Protocols.ToCheck.to_check(return_type, Macro.var(:result, nil))

return_code_check =
TypeCheck.Protocols.ToCheck.to_check(return_type, Macro.var(:result, nil))

wrapper_ast =
quote do
fn unquote_splicing(clean_params) ->
var!(result, nil) = var!(fun).(unquote_splicing(clean_params))

case unquote(return_code_check) do
{:ok, _bindings, altered_return_value} ->
altered_return_value

{:error, problem} ->
raise TypeCheck.TypeError,
{unquote(Macro.escape(s)), :return_error,
%{problem: problem, arguments: unquote(clean_params)}, var!(result, nil)}
{unquote(Macro.escape(s)), :return_error,
%{problem: problem, arguments: unquote(clean_params)},
var!(result, nil)}
end
end
end

{fun, _} = Code.eval_quoted(wrapper_ast, [fun: fun], __ENV__)

fun
end

%{param_types: [], return_type: return_type} ->
return_code_check = TypeCheck.Protocols.ToCheck.to_check(return_type, Macro.var(:result, nil))
return_code_check =
TypeCheck.Protocols.ToCheck.to_check(return_type, Macro.var(:result, nil))

quote generated: true, location: :keep do
fn ->
var!(result, nil) = unquote(original).()

case unquote(return_code_check) do
{:ok, _bindings, altered_return_value} ->
altered_return_value

{:error, problem} ->
raise TypeCheck.TypeError,
{unquote(Macro.escape(s)), :return_error,
%{problem: problem, arguments: []}, var!(result, nil)}
{unquote(Macro.escape(s)), :return_error,
%{problem: problem, arguments: []}, var!(result, nil)}
end
end
end

%{param_types: param_types, return_type: return_type} ->
clean_params = Macro.generate_arguments(length(param_types), __MODULE__)

param_checks =
param_types
|> Enum.zip(clean_params)
Expand All @@ -92,7 +127,8 @@ defmodule TypeCheck.Builtin.Function do
param_check_code(param_type, clean_param, index)
end)

return_code_check = TypeCheck.Protocols.ToCheck.to_check(return_type, Macro.var(:result, nil))
return_code_check =
TypeCheck.Protocols.ToCheck.to_check(return_type, Macro.var(:result, nil))

quote do
fn unquote_splicing(clean_params) ->
Expand All @@ -102,17 +138,20 @@ defmodule TypeCheck.Builtin.Function do
case unquote(return_code_check) do
{:ok, _bindings, altered_return_value} ->
altered_return_value

{:error, problem} ->
raise TypeCheck.TypeError,
{unquote(Macro.escape(s)), :return_error,
%{problem: problem, arguments: unquote(clean_params)}, var!(result, nil)}
{unquote(Macro.escape(s)), :return_error,
%{problem: problem, arguments: unquote(clean_params)}, var!(result, nil)}
end
else
{{:error, problem}, index, param_type} ->
raise TypeCheck.TypeError,
{
{unquote(Macro.escape(s)), :param_error,
%{index: index, problem: problem}, unquote(clean_params)}, []}
{
{unquote(Macro.escape(s)), :param_error,
%{index: index, problem: problem}, unquote(clean_params)},
[]
}
end
end
end
Expand All @@ -121,9 +160,11 @@ defmodule TypeCheck.Builtin.Function do

def param_check_code(param_type, clean_param, index) do
impl = TypeCheck.Protocols.ToCheck.to_check(param_type, clean_param)

quote generated: true, location: :keep do
[
{{:ok, _bindings, altered_param}, _index, _param_type} <- {unquote(impl), unquote(index), unquote(Macro.escape(param_type))},
{{:ok, _bindings, altered_param}, _index, _param_type} <-
{unquote(impl), unquote(index), unquote(Macro.escape(param_type))},
clean_param = altered_param
]
end
Expand All @@ -135,6 +176,7 @@ defmodule TypeCheck.Builtin.Function do
%{param_types: nil, return_type: %TypeCheck.Builtin.Any{}} ->
"function()"
|> Inspect.Algebra.color(:builtin_type, opts)

%{param_types: nil, return_type: return_type} ->
inspected_return_type = TypeCheck.Protocols.Inspect.inspect(return_type, opts)

Expand All @@ -143,6 +185,7 @@ defmodule TypeCheck.Builtin.Function do
|> Inspect.Algebra.glue(Inspect.Algebra.color("->", :builtin_type, opts))
|> Inspect.Algebra.glue(inspected_return_type)
|> Inspect.Algebra.concat(Inspect.Algebra.color(")", :builtin_type, opts))

%{param_types: types, return_type: return_type} ->
inspected_param_types =
types
Expand Down Expand Up @@ -172,6 +215,7 @@ defmodule TypeCheck.Builtin.Function do
|> StreamData.bind(fn {arity, seed} ->
create_wrapper(result_type, arity, seed)
end)

%{param_types: param_types, return_type: result_type} when is_list(param_types) ->
arity = length(param_types)

Expand All @@ -184,18 +228,20 @@ defmodule TypeCheck.Builtin.Function do

defp create_wrapper(result_type, arity, hash_seed) do
clean_params = Macro.generate_arguments(arity, __MODULE__)

wrapper_ast =
quote do
fn unquote_splicing(clean_params) ->
persistent_seed = :erlang.phash2(unquote(clean_params), unquote(hash_seed))

unquote(Macro.escape(result_type))
|> TypeCheck.Protocols.ToStreamData.to_gen()
|> StreamData.seeded(persistent_seed)
|> Enum.take(1)
|> List.first
fn unquote_splicing(clean_params) ->
persistent_seed = :erlang.phash2(unquote(clean_params), unquote(hash_seed))

unquote(Macro.escape(result_type))
|> TypeCheck.Protocols.ToStreamData.to_gen()
|> StreamData.seeded(persistent_seed)
|> Enum.take(1)
|> List.first()
end
end
end

{fun, _} = Code.eval_quoted(wrapper_ast)
StreamData.constant(fun)
end
Expand Down
7 changes: 7 additions & 0 deletions lib/type_check/builtin/guarded.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ defmodule TypeCheck.Builtin.Guarded do

@type! t() :: %TypeCheck.Builtin.Guarded{type: TypeCheck.Type.t(), guard: ast()}


defimpl TypeCheck.Protocols.Escape do
def escape(s) do
update_in(s.type, &TypeCheck.Protocols.Escape.escape(&1))
end
end

@doc false
def extract_names(type) do
case type do
Expand Down
Loading

0 comments on commit 2e0cc95

Please sign in to comment.