Skip to content

Commit 3399d7b

Browse files
committed
feat: add parameters option
Allows to add paramameters to the generated struct type. For example: typedstruct parameters: [a, b] do field :a, a field :b, b | nil field :c, integer() end Generates the type: @type t(a, b) :: %__MODULE__{ a: a, b: b | nil, c: integer() }
1 parent 12aa1d5 commit 3399d7b

File tree

2 files changed

+84
-4
lines changed

2 files changed

+84
-4
lines changed

lib/typed_struct.ex

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ defmodule TypedStruct do
3535
individual fields.
3636
* `opaque` - if set to true, creates an opaque type for the struct.
3737
* `module` - if set, creates the struct in a submodule named `module`.
38+
* `parameters` - if set, adds these parameters to the generated `t()`.
3839
3940
## Examples
4041
@@ -74,6 +75,42 @@ defmodule TypedStruct do
7475
field :field_four, atom(), default: :hey
7576
end
7677
end
78+
79+
You can also add type parameters:
80+
81+
defmodule RepairOrder do
82+
use TypedStruct
83+
84+
@type new :: %{status: :new}
85+
@type in_progress :: %{status: :in_progress, progress: integer()}
86+
@type completed :: %{status: :completed}
87+
88+
typedstruct parameters: [state] do
89+
field :state, state()
90+
customer: String.t()
91+
end
92+
93+
@spec new(String.t()) :: t(new())
94+
def new(customer) do
95+
%{state: %{status: :new}, customer: customer}
96+
end
97+
98+
@spec start_repair(t(new())) :: t(in_progress())
99+
def start_repair(order) do
100+
put_in(order.state, %{status: :in_progress, progress: 0})
101+
end
102+
103+
@spec advance_repair(t(in_progress()), integer()) :: t(in_progress())
104+
def advance_repair(order, progress) do
105+
put_in(order.state.progress, progress)
106+
end
107+
108+
@spec finish_repair(t(in_progress())) :: t(completed())
109+
def finish_repair(order) do
110+
put_in(order.state, %{status: :completed})
111+
end
112+
end
113+
77114
"""
78115
defmacro typedstruct(opts \\ [], do: block) do
79116
ast = TypedStruct.__typedstruct__(block, opts)
@@ -116,13 +153,19 @@ defmodule TypedStruct do
116153

117154
@doc false
118155
defmacro __type__(types, opts) do
156+
type_parameters = Keyword.get(opts, :parameters, [])
157+
119158
if Keyword.get(opts, :opaque, false) do
120-
quote bind_quoted: [types: types] do
121-
@opaque t() :: %__MODULE__{unquote_splicing(types)}
159+
quote bind_quoted: [types: types, type_parameters: type_parameters] do
160+
@opaque t(unquote_splicing(type_parameters)) :: %__MODULE__{
161+
unquote_splicing(types)
162+
}
122163
end
123164
else
124-
quote bind_quoted: [types: types] do
125-
@type t() :: %__MODULE__{unquote_splicing(types)}
165+
quote bind_quoted: [types: types, type_parameters: type_parameters] do
166+
@type t(unquote_splicing(type_parameters)) :: %__MODULE__{
167+
unquote_splicing(types)
168+
}
126169
end
127170
end
128171
end

test/typed_struct_test.exs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,21 @@ defmodule TypedStructTest do
6060
end
6161
end
6262

63+
{:module, _name, bytecode_parameterized, _exports} =
64+
defmodule ParameterizedTestStruct do
65+
use TypedStruct
66+
67+
typedstruct parameters: [a, b] do
68+
field :a, a
69+
field :b, b | nil
70+
field :c, integer()
71+
end
72+
end
73+
6374
@bytecode bytecode
6475
@bytecode_opaque bytecode_opaque
6576
@bytecode_noalias bytecode_noalias
77+
@bytecode_parameterized bytecode_parameterized
6678

6779
# Standard struct name used when comparing generated types.
6880
@standard_struct_name TypedStructTest.TestStruct
@@ -156,6 +168,31 @@ defmodule TypedStructTest do
156168
assert type1 == type2
157169
end
158170

171+
test "generates parameterized types" do
172+
# Define a second struct with the type expected for TestStruct.
173+
{:module, _name, bytecode2, _exports} =
174+
defmodule TestStruct4 do
175+
defstruct [:a, :b, :c]
176+
177+
@type t(a, b) :: %__MODULE__{
178+
a: a,
179+
b: b | nil,
180+
c: integer()
181+
}
182+
end
183+
184+
# Get both types and standardise them (remove line numbers and rename
185+
# the second struct with the name of the first one).
186+
type1 = @bytecode_parameterized |> extract_first_type() |> standardise()
187+
188+
type2 =
189+
bytecode2
190+
|> extract_first_type()
191+
|> standardise(TypedStructTest.TestStruct4)
192+
193+
assert type1 == type2
194+
end
195+
159196
test "generates the struct in a submodule if `module: ModuleName` is set" do
160197
assert TestModule.Struct.__struct__() == %TestModule.Struct{field: nil}
161198
end

0 commit comments

Comments
 (0)