Skip to content

Commit 3b63b1a

Browse files
committed
Merge branch 'release/0.2.1'
2 parents cd37656 + f8b2106 commit 3b63b1a

File tree

6 files changed

+199
-35
lines changed

6 files changed

+199
-35
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic
77
Versioning](https://semver.org/spec/v2.0.0.html).
88

9+
## [0.2.1] - 2020-07-19
10+
11+
### Added
12+
13+
* Add the `module: ModuleName` top-level option to create the typed struct in a
14+
submodule.
15+
16+
### Changed
17+
18+
* Update the `@typedoc` example in the documentation to put it inside the
19+
`typedstruct` block and not above. While putting it above works in the
20+
general case, it is mandatory to put it inside the block when defining a
21+
submodule.
22+
923
## [0.2.0] - 2020-05-31
1024

1125
### Added
@@ -64,6 +78,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).
6478
* Default values
6579
* Enforced keys
6680

81+
[0.2.1]: https://github.com/ejpcmac/typed_struct/compare/v0.2.0...v0.2.1
6782
[0.2.0]: https://github.com/ejpcmac/typed_struct/compare/v0.1.4...v0.2.0
6883
[0.1.4]: https://github.com/ejpcmac/typed_struct/compare/v0.1.3...v0.1.4
6984
[0.1.3]: https://github.com/ejpcmac/typed_struct/compare/v0.1.2...v0.1.3

CONTRIBUTING.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,7 @@ To make a change, please use this workflow:
144144
6. Run the tests and static analyzers to ensure there is no regression and all
145145
works as expected:
146146

147-
$ mix test
148-
$ mix dialyzer
149-
$ mix credo
147+
$ mix check
150148

151149
7. If it’s all good, open a pull request to merge your branch into the `develop`
152150
branch on the main repository.

README.md

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ defmodule Person do
6363

6464
use TypedStruct
6565

66-
@typedoc "A person"
6766
typedstruct do
67+
@typedoc "A person"
68+
6869
field :name, String.t(), enforce: true
6970
field :age, non_neg_integer()
7071
field :happy?, boolean(), default: true
@@ -82,7 +83,7 @@ Thanks to TypedStruct, this is now possible :)
8283
To use TypedStruct in your project, add this to your Mix dependencies:
8384

8485
```elixir
85-
{:typed_struct, "~> 0.2.0"}
86+
{:typed_struct, "~> 0.2.1"}
8687
```
8788

8889
If you do not plan to compile modules using TypedStruct at runtime, you can add
@@ -162,14 +163,41 @@ defmodule MyOpaqueStruct do
162163
end
163164
```
164165

166+
If you often define submodules containing only a struct, you can avoid
167+
boilerplate code:
168+
169+
```elixir
170+
defmodule MyModule do
171+
use TypedStruct
172+
173+
# You now have %MyModule.Struct{}.
174+
typedstruct module: Struct do
175+
field :field, term()
176+
end
177+
end
178+
```
179+
165180
### Documentation
166181

167-
To add a `@typedoc` to the struct type, just add the attribute above the
182+
To add a `@typedoc` to the struct type, just add the attribute in the
168183
`typedstruct` block:
169184

170185
```elixir
171-
@typedoc "A typed struct"
172186
typedstruct do
187+
@typedoc "A typed struct"
188+
189+
field :a_string, String.t()
190+
field :an_int, integer()
191+
end
192+
```
193+
194+
You can also document submodules this way:
195+
196+
```elixir
197+
typedstruct module: MyStruct do
198+
@moduledoc "A submodule with a typed struct."
199+
@typedoc "A typed struct in a submodule"
200+
173201
field :a_string, String.t()
174202
field :an_int, integer()
175203
end
@@ -305,14 +333,44 @@ specification:
305333
typedstruct opaque: true do
306334
field :name, String.t()
307335
end
336+
```
308337

309-
# Becomes
338+
generates the following type:
310339

340+
```elixir
311341
@opaque t() :: %__MODULE__{
312342
name: String.t()
313343
}
314344
```
315345

346+
When passing `module: ModuleName`, the whole `typedstruct` block is wrapped in a
347+
module definition. This way, the following definition:
348+
349+
```elixir
350+
defmodule MyModule do
351+
use TypedStruct
352+
353+
typedstruct module: Struct do
354+
field :field, term()
355+
end
356+
end
357+
```
358+
359+
becomes:
360+
361+
```elixir
362+
defmodule MyModule do
363+
defmodule Struct do
364+
@enforce_keys []
365+
defstruct field: nil
366+
367+
@type t() :: %__MODULE__{
368+
field: term() | nil
369+
}
370+
end
371+
end
372+
```
373+
316374
## [Contributing](CONTRIBUTING.md)
317375

318376
Before contributing to this project, please read the

lib/typed_struct.ex

Lines changed: 107 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ defmodule TypedStruct do
5858
5959
use TypedStruct
6060
61-
@typedoc "A person"
6261
typedstruct do
62+
@typedoc "A person"
63+
6364
field :name, String.t(), enforce: true
6465
field :age, non_neg_integer()
6566
field :happy?, boolean(), default: true
@@ -144,13 +145,36 @@ defmodule TypedStruct do
144145
end
145146
end
146147
148+
If you often define submodules containing only a struct, you can avoid
149+
boilerplate code:
150+
151+
defmodule MyModule do
152+
use TypedStruct
153+
154+
# You now have %MyModule.Struct{}.
155+
typedstruct module: Struct do
156+
field :field, term()
157+
end
158+
end
159+
147160
### Documentation
148161
149-
To add a `@typedoc` to the struct type, just add the attribute above the
162+
To add a `@typedoc` to the struct type, just add the attribute in the
150163
`typedstruct` block:
151164
152-
@typedoc "A typed struct"
153165
typedstruct do
166+
@typedoc "A typed struct"
167+
168+
field :a_string, String.t()
169+
field :an_int, integer()
170+
end
171+
172+
You can also document submodules this way:
173+
174+
typedstruct module: MyStruct do
175+
@moduledoc "A submodule with a typed struct."
176+
@typedoc "A typed struct in a submodule"
177+
154178
field :a_string, String.t()
155179
field :an_int, integer()
156180
end
@@ -257,11 +281,35 @@ defmodule TypedStruct do
257281
field :name, String.t()
258282
end
259283
260-
# Becomes
284+
generates the following type:
261285
262286
@opaque t() :: %__MODULE__{
263287
name: String.t()
264288
}
289+
290+
When passing `module: ModuleName`, the whole `typedstruct` block is wrapped in
291+
a module definition. This way, the following definition:
292+
293+
defmodule MyModule do
294+
use TypedStruct
295+
296+
typedstruct module: Struct do
297+
field :field, term()
298+
end
299+
end
300+
301+
becomes:
302+
303+
defmodule MyModule do
304+
defmodule Struct do
305+
@enforce_keys []
306+
defstruct field: nil
307+
308+
@type t() :: %__MODULE__{
309+
field: term() | nil
310+
}
311+
end
312+
end
265313
"""
266314

267315
@doc false
@@ -282,7 +330,8 @@ defmodule TypedStruct do
282330
* `enforce` - if set to true, sets `enforce: true` to all fields by default.
283331
This can be overridden by setting `enforce: false` or a default value on
284332
individual fields.
285-
* `opaque` - if set to true, creates an opaque type for the struct
333+
* `opaque` - if set to true, creates an opaque type for the struct.
334+
* `module` - if set, creates the struct in a submodule named `module`.
286335
287336
## Examples
288337
@@ -309,8 +358,48 @@ defmodule TypedStruct do
309358
field :field_four, atom(), default: :hey
310359
end
311360
end
361+
362+
You can create the struct in a submodule instead:
363+
364+
defmodule MyModule do
365+
use TypedStruct
366+
367+
typedstruct, module: Struct do
368+
field :field_one, String.t()
369+
field :field_two, integer(), enforce: true
370+
field :field_three, boolean(), enforce: true
371+
field :field_four, atom(), default: :hey
372+
end
373+
end
312374
"""
313375
defmacro typedstruct(opts \\ [], do: block) do
376+
if is_nil(opts[:module]) do
377+
quote do
378+
Module.eval_quoted(
379+
__ENV__,
380+
TypedStruct.__typedstruct__(
381+
unquote(Macro.escape(block)),
382+
unquote(opts)
383+
)
384+
)
385+
end
386+
else
387+
quote do
388+
defmodule unquote(opts[:module]) do
389+
Module.eval_quoted(
390+
__ENV__,
391+
TypedStruct.__typedstruct__(
392+
unquote(Macro.escape(block)),
393+
unquote(opts)
394+
)
395+
)
396+
end
397+
end
398+
end
399+
end
400+
401+
@doc false
402+
def __typedstruct__(block, opts) do
314403
quote do
315404
Module.register_attribute(__MODULE__, :ts_plugins, accumulate: true)
316405
Module.register_attribute(__MODULE__, :ts_fields, accumulate: true)
@@ -342,6 +431,19 @@ defmodule TypedStruct do
342431
end
343432
end
344433

434+
@doc false
435+
defmacro __type__(types, opts) do
436+
if Keyword.get(opts, :opaque, false) do
437+
quote bind_quoted: [types: types] do
438+
@opaque t() :: %__MODULE__{unquote_splicing(types)}
439+
end
440+
else
441+
quote bind_quoted: [types: types] do
442+
@type t() :: %__MODULE__{unquote_splicing(types)}
443+
end
444+
end
445+
end
446+
345447
@doc """
346448
Registers a plugin for the currently defined struct.
347449
@@ -408,10 +510,6 @@ defmodule TypedStruct do
408510
end
409511
end
410512

411-
############################################################################
412-
## Callbacks ##
413-
############################################################################
414-
415513
@doc false
416514
def __field__(mod, name, type, opts) when is_atom(name) do
417515
if mod |> Module.get_attribute(:ts_fields) |> Keyword.has_key?(name) do
@@ -437,23 +535,6 @@ defmodule TypedStruct do
437535
raise ArgumentError, "a field name must be an atom, got #{inspect(name)}"
438536
end
439537

440-
@doc false
441-
defmacro __type__(types, opts) do
442-
if Keyword.get(opts, :opaque, false) do
443-
quote bind_quoted: [types: types] do
444-
@opaque t() :: %__MODULE__{unquote_splicing(types)}
445-
end
446-
else
447-
quote bind_quoted: [types: types] do
448-
@type t() :: %__MODULE__{unquote_splicing(types)}
449-
end
450-
end
451-
end
452-
453-
############################################################################
454-
## Helpers ##
455-
############################################################################
456-
457538
# Makes the type nullable if the key is not enforced.
458539
defp type_for(type, false), do: type
459540
defp type_for(type, _), do: quote(do: unquote(type) | nil)

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule TypedStruct.MixProject do
22
use Mix.Project
33

4-
@version "0.2.0"
4+
@version "0.2.1"
55
@repo_url "https://github.com/ejpcmac/typed_struct"
66

77
def project do

test/typed_struct_test.exs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ defmodule TypedStructTest do
4343
def enforce_keys, do: @enforce_keys
4444
end
4545

46+
defmodule TestModule do
47+
use TypedStruct
48+
49+
typedstruct module: Struct do
50+
field :field, term()
51+
end
52+
end
53+
4654
@bytecode bytecode
4755
@bytecode_opaque bytecode_opaque
4856

@@ -138,6 +146,10 @@ defmodule TypedStructTest do
138146
assert type1 == type2
139147
end
140148

149+
test "generates the struct in a submodule if `module: ModuleName` is set" do
150+
assert TestModule.Struct.__struct__() == %TestModule.Struct{field: nil}
151+
end
152+
141153
############################################################################
142154
## Problems ##
143155
############################################################################

0 commit comments

Comments
 (0)