From 185ac5db53d8a9db5b09b390ef0c6ccafb561c6d Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 11:01:08 +0100 Subject: [PATCH 01/13] Drop GradualizerEx from typed GenServer example and use Gradient --- examples/typed_gen_server/lib/typed_gen_server/stage1.ex | 4 ++-- examples/typed_gen_server/lib/typed_gen_server/stage2.ex | 6 +++--- examples/typed_gen_server/lib/typed_gen_server/stage3.ex | 6 +++--- examples/typed_gen_server/mix.exs | 3 +-- examples/typed_gen_server/mix.lock | 1 - 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex index 0846ee92..e2eb7e42 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex @@ -1,12 +1,12 @@ defmodule TypedGenServer.Stage1.Server do use GenServer - use GradualizerEx.TypeAnnotation + use Gradient.TypeAnnotation ## Start IEx with: ## iex -S mix run --no-start ## ## Then use the following to recheck the file on any change: - ## recompile(); GradualizerEx.type_check_file(:code.which( TypedGenServer.Stage1.Server ), [:infer]) + ## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage1.Server ), [:infer]) ## Try switching between the definitions and see what happens @type message :: Contract.Echo.req() | Contract.Hello.req() diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex index fe122a1e..58495f3b 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex @@ -1,13 +1,13 @@ defmodule TypedGenServer.Stage2.Server do use GenServer - use GradualizerEx.TypeAnnotation + use Gradient.TypeAnnotation alias Stage2.TypedServer ## Start IEx with: ## iex -S mix run --no-start ## ## Then use the following to recheck the file on any change: - ## recompile(); GradualizerEx.type_check_file(:code.which( TypedGenServer.Stage2.Server ), [:infer]) + ## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage2.Server ), [:infer]) @opaque t :: pid() @@ -79,7 +79,7 @@ defmodule Test.TypedGenServer.Stage2.Server do alias TypedGenServer.Stage2.Server ## Typecheck with: - ## recompile(); GradualizerEx.type_check_file(:code.which( Test.TypedGenServer.Stage2.Server ), [:infer]) + ## recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage2.Server ), [:infer]) @spec test :: any() def test do diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage3.ex b/examples/typed_gen_server/lib/typed_gen_server/stage3.ex index 02023c74..47622651 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage3.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage3.ex @@ -18,14 +18,14 @@ end defmodule TypedGenServer.Stage3.Server do use GenServer - use GradualizerEx.TypeAnnotation + use Gradient.TypeAnnotation alias Stage3.TypedServer ## Start IEx with: ## iex -S mix run --no-start ## ## Then use the following to recheck the file on any change: - ## recompile(); GradualizerEx.type_check_file(:code.which( TypedGenServer.Stage3.Server ), [:infer]) + ## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage3.Server ), [:infer]) @opaque t :: {__MODULE__, pid()} @@ -92,7 +92,7 @@ defmodule Test.TypedGenServer.Stage3.Server do alias TypedGenServer.Stage3.Server ## Typecheck with: - ## recompile(); GradualizerEx.type_check_file(:code.which( Test.TypedGenServer.Stage3.Server ), [:infer]) + ## recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage3.Server ), [:infer]) @spec test :: any() def test do diff --git a/examples/typed_gen_server/mix.exs b/examples/typed_gen_server/mix.exs index fd9946b4..0d312f93 100644 --- a/examples/typed_gen_server/mix.exs +++ b/examples/typed_gen_server/mix.exs @@ -26,8 +26,7 @@ defmodule TypedGenServer.MixProject do {:gradient, path: "../../"}, {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, {:gradualizer, - github: "erszcz/Gradualizer", ref: "typed-gen-server", manager: :rebar3, override: true}, - {:gradualizer_ex, github: "erszcz/gradualizer-ex", branch: "rs/wip"} + github: "erszcz/Gradualizer", ref: "typed-gen-server", manager: :rebar3, override: true} ] end diff --git a/examples/typed_gen_server/mix.lock b/examples/typed_gen_server/mix.lock index 846fa08c..7966b087 100644 --- a/examples/typed_gen_server/mix.lock +++ b/examples/typed_gen_server/mix.lock @@ -2,5 +2,4 @@ "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "gradualizer": {:git, "https://github.com/erszcz/Gradualizer.git", "bd54184dae19d5117534c6e6885fa5abd1fe5da4", [ref: "typed-gen-server"]}, - "gradualizer_ex": {:git, "https://github.com/erszcz/gradualizer-ex.git", "dc64f484c83a5ce286696fb79756577a1b9853f5", [branch: "rs/wip"]}, } From 1a214ae509031b42cf2f0b27aea1cfa227755b94 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 11:04:39 +0100 Subject: [PATCH 02/13] Use Gradualizer version from Gradient instead of a custom override --- examples/typed_gen_server/mix.exs | 4 +--- examples/typed_gen_server/mix.lock | 2 +- lib/gradient/type_annotation.ex | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/examples/typed_gen_server/mix.exs b/examples/typed_gen_server/mix.exs index 0d312f93..f8c92ca5 100644 --- a/examples/typed_gen_server/mix.exs +++ b/examples/typed_gen_server/mix.exs @@ -24,9 +24,7 @@ defmodule TypedGenServer.MixProject do defp deps do [ {:gradient, path: "../../"}, - {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, - {:gradualizer, - github: "erszcz/Gradualizer", ref: "typed-gen-server", manager: :rebar3, override: true} + {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false} ] end diff --git a/examples/typed_gen_server/mix.lock b/examples/typed_gen_server/mix.lock index 7966b087..acbae84a 100644 --- a/examples/typed_gen_server/mix.lock +++ b/examples/typed_gen_server/mix.lock @@ -1,5 +1,5 @@ %{ "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "gradualizer": {:git, "https://github.com/erszcz/Gradualizer.git", "bd54184dae19d5117534c6e6885fa5abd1fe5da4", [ref: "typed-gen-server"]}, + "gradualizer": {:git, "https://github.com/josefs/Gradualizer.git", "6e89b4e1cd489637a848cc5ca55058c8a241bf7d", [ref: "6e89b4e"]}, } diff --git a/lib/gradient/type_annotation.ex b/lib/gradient/type_annotation.ex index 9028adc8..57d97a69 100644 --- a/lib/gradient/type_annotation.ex +++ b/lib/gradient/type_annotation.ex @@ -8,7 +8,7 @@ defmodule Gradient.TypeAnnotation do defp annotate(type_op, expr, type) do erlang_type = elixir_type_to_erlang(type) # IO.inspect(erlang_type, label: "erlang type") - {type_op, [], [expr, Macro.to_string(erlang_type)]} + {type_op, [], [expr, erlang_type]} # |> IO.inspect(label: "annotation node") end From a75a2eedcd6eb6723bd74f724bd700306af572d3 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 13:02:17 +0100 Subject: [PATCH 03/13] Format 'nonexhaustive patterns' warning --- lib/gradient/elixir_fmt.ex | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/lib/gradient/elixir_fmt.ex b/lib/gradient/elixir_fmt.ex index 0be7abe7..00ebdac0 100644 --- a/lib/gradient/elixir_fmt.ex +++ b/lib/gradient/elixir_fmt.ex @@ -69,6 +69,39 @@ defmodule Gradient.ElixirFmt do format_expr_type_error(expression, actual_type, expected_type, opts) end + def format_type_error({:nonexhaustive, anno, example}, opts) do + formatted_example = + case example do + [x | xs] -> + :lists.foldl( + fn a, acc -> + [pp_expr(a, opts), "\n\t" | acc] + end, + [pp_expr(x, opts)], + xs + ) + |> Enum.reverse() + + x -> + pp_expr(x, opts) + end + + :io_lib.format( + "~sNonexhaustive patterns~s~s", + [ + format_location(anno, :brief, opts), + format_location(anno, :verbose, opts), + case :proplists.get_value(:fmt_location, opts, :verbose) do + :brief -> + :io_lib.format(": ~s\n", formatted_example) + + :verbose -> + :io_lib.format("\nExample values which are not covered:~n\t~s~n", [formatted_example]) + end + ] + ) + end + def format_type_error( {:spec_error, :wrong_spec_name, anno, name, arity}, opts From 86f2655025283ee97f5cc9c14fb6200ca3ac94b6 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 13:04:12 +0100 Subject: [PATCH 04/13] Format code: mix format --- .../lib/typed_gen_server/stage1.ex | 14 +++++++------- .../lib/typed_gen_server/stage2.ex | 14 +++++++------- .../lib/typed_gen_server/stage3.ex | 17 ++++++++--------- .../lib/typed_gen_server/stage4.ex | 16 ++++++++-------- examples/typed_gen_server/mix.exs | 3 +-- 5 files changed, 31 insertions(+), 33 deletions(-) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex index e2eb7e42..c91adee1 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex @@ -10,8 +10,8 @@ defmodule TypedGenServer.Stage1.Server do ## Try switching between the definitions and see what happens @type message :: Contract.Echo.req() | Contract.Hello.req() - #@type message :: Contract.Echo.req() - #@type message :: {:echo_req, String.t()} | {:hello, String.t()} + # @type message :: Contract.Echo.req() + # @type message :: {:echo_req, String.t()} | {:hello, String.t()} @type state :: map() @@ -22,17 +22,17 @@ defmodule TypedGenServer.Stage1.Server do @spec echo(pid(), String.t()) :: String.t() # @spec echo(pid(), String.t()) :: {:echo_req, String.t()} def echo(pid, message) do - case annotate_type( GenServer.call(pid, {:echo_req, message}), Contract.Echo.res() ) do - #case call_echo(pid, message) do + case annotate_type(GenServer.call(pid, {:echo_req, message}), Contract.Echo.res()) do + # case call_echo(pid, message) do ## Try changing the pattern or the returned response {:echo_res, response} -> response end end - #@spec call_echo(pid(), String.t()) :: Contract.Echo.res() - #defp call_echo(pid, message) do + # @spec call_echo(pid(), String.t()) :: Contract.Echo.res() + # defp call_echo(pid, message) do # GenServer.call(pid, {:echo_req, message}) - #end + # end @spec hello(pid(), String.t()) :: :ok def hello(pid, name) do diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex index 58495f3b..8daab482 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex @@ -13,8 +13,8 @@ defmodule TypedGenServer.Stage2.Server do ## Try switching between the definitions and see what happens @type message :: Contract.Echo.req() | Contract.Hello.req() - #@type message :: Contract.Echo.req() - #@type message :: {:echo_req, String.t()} | {:hello, String.t()} + # @type message :: Contract.Echo.req() + # @type message :: {:echo_req, String.t()} | {:hello, String.t()} @type state :: map() @@ -26,8 +26,8 @@ defmodule TypedGenServer.Stage2.Server do @spec echo(t(), String.t()) :: String.t() # @spec echo(t(), String.t()) :: {:echo_req, String.t()} def echo(pid, message) do - case annotate_type( GenServer.call(pid, {:echo_req, message}), Contract.Echo.res() ) do - #case call_echo(pid, message) do + case annotate_type(GenServer.call(pid, {:echo_req, message}), Contract.Echo.res()) do + # case call_echo(pid, message) do ## Try changing the pattern or the returned response {:echo_res, response} -> response end @@ -62,8 +62,8 @@ defmodule TypedGenServer.Stage2.Server do ## This could register {:echo_req, payload} <-> {:echo_res, payload} mapping ## and response type at compile time to generate call_echo() automatically. ## Thanks Robert! - #TypedServer.reply( from, {:echo_res, payload}, Contract.Echo.res() ) - GenServer.reply( from, {:echo_res, payload} ) + # TypedServer.reply( from, {:echo_res, payload}, Contract.Echo.res() ) + GenServer.reply(from, {:echo_res, payload}) state end @@ -87,6 +87,6 @@ defmodule Test.TypedGenServer.Stage2.Server do pid = self() "payload" = Server.echo(srv, "payload") ## This won't typecheck, since Server.echo only accepts Server.t(), that is our Server pids - #"payload" = Server.echo(pid, "payload") + # "payload" = Server.echo(pid, "payload") end end diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage3.ex b/examples/typed_gen_server/lib/typed_gen_server/stage3.ex index 47622651..5420af98 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage3.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage3.ex @@ -1,5 +1,4 @@ defmodule Stage3.TypedServer do - ## This doesn't play well with: ## {:ok, srv} = MultiServer.start_link() ## Due to: @@ -31,8 +30,8 @@ defmodule TypedGenServer.Stage3.Server do ## Try switching between the definitions and see what happens @type message :: Contract.Echo.req() | Contract.Hello.req() - #@type message :: Contract.Echo.req() - #@type message :: {:echo_req, String.t()} | {:hello, String.t()} + # @type message :: Contract.Echo.req() + # @type message :: {:echo_req, String.t()} | {:hello, String.t()} @type state :: map() @@ -44,17 +43,17 @@ defmodule TypedGenServer.Stage3.Server do @spec echo(t(), String.t()) :: String.t() # @spec echo(t(), String.t()) :: {:echo_req, String.t()} def echo(_server = {__MODULE__, _pid}, message) do - case annotate_type( GenServer.call(_pid, {:echo_req, message}), Contract.Echo.res() ) do - #case call_echo(_server, message) do + case annotate_type(GenServer.call(_pid, {:echo_req, message}), Contract.Echo.res()) do + # case call_echo(_server, message) do ## Try changing the pattern or the returned response {:echo_res, response} -> response end end - #@spec call_echo(t(), String.t()) :: Contract.Echo.res() - #defp call_echo({__MODULE__, pid}, message) do + # @spec call_echo(t(), String.t()) :: Contract.Echo.res() + # defp call_echo({__MODULE__, pid}, message) do # GenServer.call(pid, {:echo_req, message}) - #end + # end @spec hello(t(), String.t()) :: :ok def hello({__MODULE__, pid}, name) do @@ -100,6 +99,6 @@ defmodule Test.TypedGenServer.Stage3.Server do pid = self() "payload" = Server.echo(srv, "payload") ## This won't typecheck, since Server.echo only accepts Server.t(), that is Server pids - #"payload" = Server.echo(pid, "payload") + # "payload" = Server.echo(pid, "payload") end end diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex index 2ed12d42..9faad294 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex @@ -27,7 +27,7 @@ defmodule TypedGenServer.Stage4.Server do @spec echo(t(), String.t()) :: String.t() # @spec echo(t(), String.t()) :: {:echo_req, String.t()} def echo(pid, message) do - #case annotate_type(GenServer.call(pid, {:echo_req, message}), Contract.Echo.res()) do + # case annotate_type(GenServer.call(pid, {:echo_req, message}), Contract.Echo.res()) do case call_echo_req(pid, message) do ## Try changing the pattern or the returned response {:echo_res, response} -> response @@ -38,10 +38,10 @@ defmodule TypedGenServer.Stage4.Server do ## thanks to using TypedServer.reply/3 instead of GenServer.reply/2. ## We don't have to define it! ## TODO: use the correct type instead of any as the second param! - #@spec call_echo_req(t(), any) :: Contract.Echo.res() - #defp call_echo_req(pid, message) do + # @spec call_echo_req(t(), any) :: Contract.Echo.res() + # defp call_echo_req(pid, message) do # GenServer.call(pid, {:echo_req, message}) - #end + # end @spec hello(t(), String.t()) :: :ok def hello(pid, name) do @@ -64,11 +64,11 @@ defmodule TypedGenServer.Stage4.Server do ## TypedServer.reply/3 registers a {:echo_req, payload} <-> Contract.Echo.res() mapping ## and generates call_echo_req() at compile time. ## Thanks for the idea, @rvirding! - TypedServer.reply( from, {:echo_res, payload}, Contract.Echo.res() ) + TypedServer.reply(from, {:echo_res, payload}, Contract.Echo.res()) ## This will not typecheck - awesome! - #TypedServer.reply( from, {:invalid_tag, payload}, Contract.Echo.res() ) + # TypedServer.reply( from, {:invalid_tag, payload}, Contract.Echo.res() ) ## And this is the well known untyped equivalent. - #GenServer.reply(from, {:echo_res, payload}) + # GenServer.reply(from, {:echo_res, payload}) state end @@ -92,6 +92,6 @@ defmodule Test.TypedGenServer.Stage4.Server do pid = self() "payload" = Server.echo(srv, "payload") ## This won't typecheck, since Server.echo only accepts Server.t(), that is our Server pids - #"payload" = Server.echo(pid, "payload") + # "payload" = Server.echo(pid, "payload") end end diff --git a/examples/typed_gen_server/mix.exs b/examples/typed_gen_server/mix.exs index f8c92ca5..7abc9fe8 100644 --- a/examples/typed_gen_server/mix.exs +++ b/examples/typed_gen_server/mix.exs @@ -31,7 +31,7 @@ defmodule TypedGenServer.MixProject do defp dialyzer do [ plt_add_deps: :app_tree, - #ignore_warnings: "dialyzer.ignore-warnings", + # ignore_warnings: "dialyzer.ignore-warnings", flags: ~w( error_handling race_conditions @@ -40,5 +40,4 @@ defmodule TypedGenServer.MixProject do )a ] end - end From 1feefd3f290486de1e106ca46b87819e2c3dc8c7 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 14:15:44 +0100 Subject: [PATCH 05/13] Use a typed server test example which fails visibly at runtime --- examples/typed_gen_server/lib/typed_gen_server/stage2.ex | 9 ++++++++- examples/typed_gen_server/lib/typed_gen_server/stage4.ex | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex index 8daab482..8db291ae 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex @@ -84,7 +84,14 @@ defmodule Test.TypedGenServer.Stage2.Server do @spec test :: any() def test do {:ok, srv} = Server.start_link() - pid = self() + + pid = + spawn(fn -> + receive do + :unlikely -> :ok + end + end) + "payload" = Server.echo(srv, "payload") ## This won't typecheck, since Server.echo only accepts Server.t(), that is our Server pids # "payload" = Server.echo(pid, "payload") diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex index 9faad294..e11990e0 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex @@ -89,7 +89,14 @@ defmodule Test.TypedGenServer.Stage4.Server do @spec test :: any() def test do {:ok, srv} = Server.start_link() - pid = self() + + pid = + spawn(fn -> + receive do + :unlikely -> :ok + end + end) + "payload" = Server.echo(srv, "payload") ## This won't typecheck, since Server.echo only accepts Server.t(), that is our Server pids # "payload" = Server.echo(pid, "payload") From 0c9db884fcd78dee3faadc04e9c11d23c7f62712 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 14:16:10 +0100 Subject: [PATCH 06/13] Work around the spec check __info__ spec bug --- examples/typed_gen_server/lib/typed_gen_server/stage2.ex | 1 + examples/typed_gen_server/lib/typed_gen_server/stage4.ex | 1 + 2 files changed, 2 insertions(+) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex index 8db291ae..c04d709d 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex @@ -80,6 +80,7 @@ defmodule Test.TypedGenServer.Stage2.Server do ## Typecheck with: ## recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage2.Server ), [:infer]) + ## recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage2.Server ), [:infer, ex_check: false]) @spec test :: any() def test do diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex index e11990e0..36d0d676 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex @@ -85,6 +85,7 @@ defmodule Test.TypedGenServer.Stage4.Server do ## Typecheck with: ## recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage4.Server ), [:infer]) + ## recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage4.Server ), [:infer, ex_check: false]) @spec test :: any() def test do From 391074889bf25dfe24aa154773d01ecf4096b456 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 14:21:09 +0100 Subject: [PATCH 07/13] Mute internal GenServer warnings --- examples/typed_gen_server/lib/typed_gen_server/stage1.ex | 6 +++--- examples/typed_gen_server/lib/typed_gen_server/stage2.ex | 6 +++--- examples/typed_gen_server/lib/typed_gen_server/stage4.ex | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex index c91adee1..fff2a609 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex @@ -1,5 +1,5 @@ defmodule TypedGenServer.Stage1.Server do - use GenServer + # use GenServer use Gradient.TypeAnnotation ## Start IEx with: @@ -41,12 +41,12 @@ defmodule TypedGenServer.Stage1.Server do end end - @impl true + # @impl true def init(state) do {:ok, state} end - @impl true + # @impl true def handle_call(m, from, state) do {:noreply, handle(m, from, state)} end diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex index c04d709d..26be3a23 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex @@ -1,5 +1,5 @@ defmodule TypedGenServer.Stage2.Server do - use GenServer + # use GenServer use Gradient.TypeAnnotation alias Stage2.TypedServer @@ -46,12 +46,12 @@ defmodule TypedGenServer.Stage2.Server do end end - @impl true + # @impl true def init(state) do {:ok, state} end - @impl true + # @impl true def handle_call(m, from, state) do {:noreply, handle(m, from, state)} end diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex index 36d0d676..a12db2d5 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex @@ -1,5 +1,5 @@ defmodule TypedGenServer.Stage4.Server do - use GenServer + # use GenServer use Gradient.TypeAnnotation use Gradient.TypedServer alias Gradient.TypedServer @@ -48,12 +48,12 @@ defmodule TypedGenServer.Stage4.Server do GenServer.call(pid, {:hello, name}) end - @impl true + # @impl true def init(state) do {:ok, state} end - @impl true + # @impl true def handle_call(m, from, state) do {:noreply, handle(m, from, state)} end From 02f43e4b4d1a298dbe3695ef6ec2a6aaa1dfeb5f Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 14:22:55 +0100 Subject: [PATCH 08/13] Mute Gradient.TypedServer.CompileHooks debug info --- lib/gradient/typed_server/compile_hooks.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gradient/typed_server/compile_hooks.ex b/lib/gradient/typed_server/compile_hooks.ex index 0cddbe1b..69d56f41 100644 --- a/lib/gradient/typed_server/compile_hooks.ex +++ b/lib/gradient/typed_server/compile_hooks.ex @@ -17,10 +17,10 @@ defmodule Gradient.TypedServer.CompileHooks do end def __on_definition__(env, _kind, name, args, _guards, body) do - if name == :handle do - # IO.inspect({name, env}, limit: :infinity) - IO.inspect({env.module, Module.get_attribute(env.module, :spec)}) - end + # if name == :handle do + # # IO.inspect({name, env}, limit: :infinity) + # #{env.module, Module.get_attribute(env.module, :spec)} |> IO.inspect() + # end request_handler = Module.get_attribute(env.module, :request_handler, :handle) From 043c4f8f9885402364b0277cd59f52567b2f4ccc Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 14:39:06 +0100 Subject: [PATCH 09/13] Add snippet to start Gradient to the examples --- examples/typed_gen_server/lib/typed_gen_server/stage1.ex | 3 +++ examples/typed_gen_server/lib/typed_gen_server/stage2.ex | 3 +++ examples/typed_gen_server/lib/typed_gen_server/stage4.ex | 3 +++ 3 files changed, 9 insertions(+) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex index fff2a609..9064d5b4 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex @@ -5,6 +5,9 @@ defmodule TypedGenServer.Stage1.Server do ## Start IEx with: ## iex -S mix run --no-start ## + ## Start Gradient: + ## Application.ensure_all_started(:gradient) + ## ## Then use the following to recheck the file on any change: ## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage1.Server ), [:infer]) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex index 26be3a23..9e9c4fe6 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex @@ -6,6 +6,9 @@ defmodule TypedGenServer.Stage2.Server do ## Start IEx with: ## iex -S mix run --no-start ## + ## Start Gradient: + ## Application.ensure_all_started(:gradient) + ## ## Then use the following to recheck the file on any change: ## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage2.Server ), [:infer]) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex index a12db2d5..f266a262 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex @@ -7,6 +7,9 @@ defmodule TypedGenServer.Stage4.Server do ## Start IEx with: ## iex -S mix run --no-start ## + ## Start Gradient: + ## Application.ensure_all_started(:gradient) + ## ## Then use the following to recheck the file on any change: ## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage4.Server ), [:infer]) From a956195df02a815f505875428f1e7dd199362ef7 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 15:44:19 +0100 Subject: [PATCH 10/13] Use Gradualizer which treats Elixir String.t() as refinable --- examples/typed_gen_server/mix.exs | 4 +++- examples/typed_gen_server/mix.lock | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/typed_gen_server/mix.exs b/examples/typed_gen_server/mix.exs index 7abc9fe8..5c00fe3f 100644 --- a/examples/typed_gen_server/mix.exs +++ b/examples/typed_gen_server/mix.exs @@ -24,7 +24,9 @@ defmodule TypedGenServer.MixProject do defp deps do [ {:gradient, path: "../../"}, - {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false} + {:dialyxir, "~> 1.1", only: [:dev, :test], runtime: false}, + {:gradualizer, + github: "erszcz/Gradualizer", ref: "typed-gen-server", manager: :rebar3, override: true} ] end diff --git a/examples/typed_gen_server/mix.lock b/examples/typed_gen_server/mix.lock index acbae84a..793b743f 100644 --- a/examples/typed_gen_server/mix.lock +++ b/examples/typed_gen_server/mix.lock @@ -1,5 +1,5 @@ %{ "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "gradualizer": {:git, "https://github.com/josefs/Gradualizer.git", "6e89b4e1cd489637a848cc5ca55058c8a241bf7d", [ref: "6e89b4e"]}, + "gradualizer": {:git, "https://github.com/erszcz/Gradualizer.git", "764ddc6b4cc007008ea68eb9ec7a3f3adc2f1951", [ref: "typed-gen-server"]}, } From 14ea6a70bef46f5b7e5ecb26cbedd9c1d989fb9c Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Wed, 9 Mar 2022 16:28:04 +0100 Subject: [PATCH 11/13] Simplify TypedGenServer.Stage1.Server not to use GenServer.reply --- .../lib/typed_gen_server/stage1.ex | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex index 9064d5b4..fea1e8db 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex @@ -49,22 +49,20 @@ defmodule TypedGenServer.Stage1.Server do {:ok, state} end - # @impl true - def handle_call(m, from, state) do - {:noreply, handle(m, from, state)} - end + @type called(a) :: {:noreply, state()} + | {:reply, a, state()} - @spec handle(message(), any, any) :: state() + # @impl true + @spec handle_call(message(), GenServer.from(), state()) + :: called(Contract.Echo.res() | Contract.Hello.res()) ## Try breaking the pattern match, e.g. by changing 'echo_req' - def handle({:echo_req, payload}, from, state) do - GenServer.reply(from, {:echo_res, payload}) - state + def handle_call({:echo_req, payload}, _from, state) do + {:reply, {:echo_res, payload}, state} end ## Try commenting out the following clause - def handle({:hello, name}, from, state) do + def handle_call({:hello, name}, _from, state) do IO.puts("Hello, #{name}!") - GenServer.reply(from, :ok) - state + {:reply, :ok, state} end end From 285bfa95d275988240de3a038e13e1deb8a8689a Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Fri, 11 Mar 2022 13:07:28 +0100 Subject: [PATCH 12/13] Add more pointers, remove some cruft --- examples/typed_gen_server/lib/typed_gen_server/stage1.ex | 1 - examples/typed_gen_server/lib/typed_gen_server/stage2.ex | 4 +++- examples/typed_gen_server/lib/typed_gen_server/stage4.ex | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex index fea1e8db..4e6a41af 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage1.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage1.ex @@ -23,7 +23,6 @@ defmodule TypedGenServer.Stage1.Server do end @spec echo(pid(), String.t()) :: String.t() - # @spec echo(pid(), String.t()) :: {:echo_req, String.t()} def echo(pid, message) do case annotate_type(GenServer.call(pid, {:echo_req, message}), Contract.Echo.res()) do # case call_echo(pid, message) do diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex index 9e9c4fe6..f90b7b81 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage2.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage2.ex @@ -27,7 +27,6 @@ defmodule TypedGenServer.Stage2.Server do end @spec echo(t(), String.t()) :: String.t() - # @spec echo(t(), String.t()) :: {:echo_req, String.t()} def echo(pid, message) do case annotate_type(GenServer.call(pid, {:echo_req, message}), Contract.Echo.res()) do # case call_echo(pid, message) do @@ -81,6 +80,9 @@ end defmodule Test.TypedGenServer.Stage2.Server do alias TypedGenServer.Stage2.Server + ## Run with: + ## recompile(); Test.TypedGenServer.Stage2.Server.test() + ## ## Typecheck with: ## recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage2.Server ), [:infer]) ## recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage2.Server ), [:infer, ex_check: false]) diff --git a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex index f266a262..baa56812 100644 --- a/examples/typed_gen_server/lib/typed_gen_server/stage4.ex +++ b/examples/typed_gen_server/lib/typed_gen_server/stage4.ex @@ -12,6 +12,7 @@ defmodule TypedGenServer.Stage4.Server do ## ## Then use the following to recheck the file on any change: ## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage4.Server ), [:infer]) + ## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage4.Server ), [:infer, ex_check: false]) @opaque t :: pid() From bedf71951ce763534e8dbf25e43543b9c1d48667 Mon Sep 17 00:00:00 2001 From: Radek Szymczyszyn Date: Mon, 21 Mar 2022 12:05:44 +0100 Subject: [PATCH 13/13] Remove unused debug code --- lib/gradient/typed_server/compile_hooks.ex | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/gradient/typed_server/compile_hooks.ex b/lib/gradient/typed_server/compile_hooks.ex index 69d56f41..d2415f27 100644 --- a/lib/gradient/typed_server/compile_hooks.ex +++ b/lib/gradient/typed_server/compile_hooks.ex @@ -17,11 +17,6 @@ defmodule Gradient.TypedServer.CompileHooks do end def __on_definition__(env, _kind, name, args, _guards, body) do - # if name == :handle do - # # IO.inspect({name, env}, limit: :infinity) - # #{env.module, Module.get_attribute(env.module, :spec)} |> IO.inspect() - # end - request_handler = Module.get_attribute(env.module, :request_handler, :handle) case request_handler do