Skip to content
Closed
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
51 changes: 47 additions & 4 deletions lib/typed_struct.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ defmodule TypedStruct do
individual fields.
* `opaque` - if set to true, creates an opaque type for the struct.
* `module` - if set, creates the struct in a submodule named `module`.
* `parameters` - if set, adds these parameters to the generated `t()`.

## Examples

Expand Down Expand Up @@ -74,6 +75,42 @@ defmodule TypedStruct do
field :field_four, atom(), default: :hey
end
end

You can also add type parameters:

defmodule RepairOrder do
use TypedStruct

@type new :: %{status: :new}
@type in_progress :: %{status: :in_progress, progress: integer()}
@type completed :: %{status: :completed}

typedstruct parameters: [state] do
field :state, state()
customer: String.t()
end

@spec new(String.t()) :: t(new())
def new(customer) do
%{state: %{status: :new}, customer: customer}
end

@spec start_repair(t(new())) :: t(in_progress())
def start_repair(order) do
%{order | state: %{status: :in_progress, progress: 0}}
end

@spec advance_repair(t(in_progress()), integer()) :: t(in_progress())
def advance_repair(order, progress) do
put_in(order.state.progress, progress)
end

@spec finish_repair(t(in_progress())) :: t(completed())
def finish_repair(order) do
%{order | state: %{status: :completed}}
end
end

"""
defmacro typedstruct(opts \\ [], do: block) do
ast = TypedStruct.__typedstruct__(block, opts)
Expand Down Expand Up @@ -116,13 +153,19 @@ defmodule TypedStruct do

@doc false
defmacro __type__(types, opts) do
type_parameters = Keyword.get(opts, :parameters, [])

if Keyword.get(opts, :opaque, false) do
quote bind_quoted: [types: types] do
@opaque t() :: %__MODULE__{unquote_splicing(types)}
quote bind_quoted: [types: types, type_parameters: type_parameters] do
@opaque t(unquote_splicing(type_parameters)) :: %__MODULE__{
unquote_splicing(types)
}
end
else
quote bind_quoted: [types: types] do
@type t() :: %__MODULE__{unquote_splicing(types)}
quote bind_quoted: [types: types, type_parameters: type_parameters] do
@type t(unquote_splicing(type_parameters)) :: %__MODULE__{
unquote_splicing(types)
}
end
end
end
Expand Down
37 changes: 37 additions & 0 deletions test/typed_struct_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,21 @@ defmodule TypedStructTest do
end
end

{:module, _name, bytecode_parameterized, _exports} =
defmodule ParameterizedTestStruct do
use TypedStruct

typedstruct parameters: [a, b] do
field :a, a
field :b, b | nil
field :c, integer()
end
end

@bytecode bytecode
@bytecode_opaque bytecode_opaque
@bytecode_noalias bytecode_noalias
@bytecode_parameterized bytecode_parameterized

# Standard struct name used when comparing generated types.
@standard_struct_name TypedStructTest.TestStruct
Expand Down Expand Up @@ -156,6 +168,31 @@ defmodule TypedStructTest do
assert type1 == type2
end

test "generates parameterized types" do
# Define a second struct with the type expected for TestStruct.
{:module, _name, bytecode2, _exports} =
defmodule TestStruct4 do
defstruct [:a, :b, :c]

@type t(a, b) :: %__MODULE__{
a: a,
b: b | nil,
c: integer()
}
end

# Get both types and standardise them (remove line numbers and rename
# the second struct with the name of the first one).
type1 = @bytecode_parameterized |> extract_first_type() |> standardise()

type2 =
bytecode2
|> extract_first_type()
|> standardise(TypedStructTest.TestStruct4)

assert type1 == type2
end

test "generates the struct in a submodule if `module: ModuleName` is set" do
assert TestModule.Struct.__struct__() == %TestModule.Struct{field: nil}
end
Expand Down