Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 23 additions & 23 deletions examples/typed_gen_server/lib/typed_gen_server/stage1.ex
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
defmodule TypedGenServer.Stage1.Server do
use GenServer
use GradualizerEx.TypeAnnotation
# use GenServer
use Gradient.TypeAnnotation

## 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(); 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()
#@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()

Expand All @@ -20,19 +23,18 @@ 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
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
Expand All @@ -41,27 +43,25 @@ defmodule TypedGenServer.Stage1.Server do
end
end

@impl true
# @impl true
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why comment out this here and in the other places?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only makes sense if use GenServer is also enabled at the top, but this in turn leads to a type error reported within GenServer implementation. I don't want this to clutter the output when demoing, but ultimately we would want these uncommented and the error sorted out.

Good point, though, I'll create a ticket to track this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked as #64

def init(state) 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
43 changes: 28 additions & 15 deletions examples/typed_gen_server/lib/typed_gen_server/stage2.ex
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
defmodule TypedGenServer.Stage2.Server do
use GenServer
use GradualizerEx.TypeAnnotation
# use GenServer
use Gradient.TypeAnnotation
alias Stage2.TypedServer

## 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(); GradualizerEx.type_check_file(:code.which( TypedGenServer.Stage2.Server ), [:infer])
## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage2.Server ), [:infer])

@opaque t :: pid()

## 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()

Expand All @@ -24,10 +27,9 @@ 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
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
Expand All @@ -46,12 +48,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
Expand All @@ -62,8 +64,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

Expand All @@ -78,15 +80,26 @@ end
defmodule Test.TypedGenServer.Stage2.Server do
alias TypedGenServer.Stage2.Server

## Run with:
## recompile(); Test.TypedGenServer.Stage2.Server.test()
##
## 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])
## recompile(); Gradient.type_check_file(:code.which( Test.TypedGenServer.Stage2.Server ), [:infer, ex_check: false])

@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")
# "payload" = Server.echo(pid, "payload")
end
end
23 changes: 11 additions & 12 deletions examples/typed_gen_server/lib/typed_gen_server/stage3.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
defmodule Stage3.TypedServer do

## This doesn't play well with:
## {:ok, srv} = MultiServer.start_link()
## Due to:
Expand All @@ -18,21 +17,21 @@ 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()}

## 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()

Expand All @@ -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
Expand Down Expand Up @@ -92,14 +91,14 @@ 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
{:ok, srv} = Server.start_link()
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
36 changes: 24 additions & 12 deletions examples/typed_gen_server/lib/typed_gen_server/stage4.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
defmodule TypedGenServer.Stage4.Server do
use GenServer
# use GenServer
use Gradient.TypeAnnotation
use Gradient.TypedServer
alias Gradient.TypedServer

## 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])
## recompile(); Gradient.type_check_file(:code.which( TypedGenServer.Stage4.Server ), [:infer, ex_check: false])

@opaque t :: pid()

Expand All @@ -27,7 +31,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
Expand All @@ -38,22 +42,22 @@ 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
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
Expand All @@ -64,11 +68,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

Expand All @@ -85,13 +89,21 @@ 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
{: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")
# "payload" = Server.echo(pid, "payload")
end
end
6 changes: 2 additions & 4 deletions examples/typed_gen_server/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@ 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

defp dialyzer do
[
plt_add_deps: :app_tree,
#ignore_warnings: "dialyzer.ignore-warnings",
# ignore_warnings: "dialyzer.ignore-warnings",
flags: ~w(
error_handling
race_conditions
Expand All @@ -43,5 +42,4 @@ defmodule TypedGenServer.MixProject do
)a
]
end

end
3 changes: 1 addition & 2 deletions examples/typed_gen_server/mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +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_ex": {:git, "https://github.com/erszcz/gradualizer-ex.git", "dc64f484c83a5ce286696fb79756577a1b9853f5", [branch: "rs/wip"]},
"gradualizer": {:git, "https://github.com/erszcz/Gradualizer.git", "764ddc6b4cc007008ea68eb9ec7a3f3adc2f1951", [ref: "typed-gen-server"]},
}
Loading