@@ -5,6 +5,13 @@ defmodule TypedStructor do
5
5
|> String . split ( "<!-- MODULEDOC -->" , parts: 2 )
6
6
|> Enum . fetch! ( 1 )
7
7
8
+ @ built_in_definers [
9
+ defstruct: TypedStructor.Definer.Defstruct ,
10
+ defexception: TypedStructor.Definer.Defexception ,
11
+ defrecord: TypedStructor.Definer.Defrecord ,
12
+ defrecordp: TypedStructor.Definer.Defrecordp
13
+ ]
14
+
8
15
defmacro __using__ ( _opts ) do
9
16
quote do
10
17
import TypedStructor , only: [ typed_structor: 1 , typed_structor: 2 ]
@@ -28,24 +35,28 @@ defmodule TypedStructor do
28
35
The available definers are:
29
36
- `:defstruct`, which defines a struct and a type for a given definition
30
37
- `:defexception`, which defines an exception and a type for a given definition
38
+ - `:defrecord`, which defines record macros and a type for a given definition
39
+ - `:defrecordp`, which defines private record macros and a type for a given definition
31
40
32
41
### `:defstruct` options
33
42
34
- * `:define_struct` - if `false`, the type will be defined, but the struct will not be defined. Defaults to `true`.
43
+ #{ TypedStructor.Definer.Defstruct . __additional_options__ ( ) }
35
44
36
45
### `:defexception` options
37
46
38
- * `:define_struct` - if `false`, the type will be defined, but the exception struct will not be defined. Defaults to `true`.
47
+ #{ TypedStructor.Definer.Defexception . __additional_options__ ( ) }
48
+
49
+ ### `:defrecord` and `:defrecordp` options
50
+
51
+ #{ TypedStructor.Definer.Defrecord . __additional_options__ ( ) }
39
52
40
53
### custom definer
41
54
42
55
defmodule MyStruct do
43
- # you must require the definer module to use its define/1 macro
44
- require MyDefiner
45
-
46
56
use TypedStructor
47
57
48
- typed_structor definer: &MyDefiner.define/1 do
58
+ typed_structor definer: MyDefiner do
59
+
49
60
field :name, String.t()
50
61
field :age, integer()
51
62
end
@@ -91,7 +102,7 @@ defmodule TypedStructor do
91
102
defmacro typed_structor ( options \\ [ ] , do: block ) when is_list ( options ) do
92
103
case Keyword . pop ( options , :module ) do
93
104
{ nil , options } ->
94
- __typed_structor__ ( __CALLER__ . module , options , block )
105
+ __typed_structor__ ( __CALLER__ , options , block )
95
106
96
107
{ module , options } ->
97
108
quote do
@@ -106,16 +117,18 @@ defmodule TypedStructor do
106
117
end
107
118
end
108
119
109
- defp __typed_structor__ ( mod , options , block ) do
110
- Module . register_attribute ( mod , :__ts_options__ , accumulate: false )
111
- Module . register_attribute ( mod , :__ts_struct_fields__ , accumulate: true )
112
- Module . register_attribute ( mod , :__ts_struct_parameters__ , accumulate: true )
113
- Module . register_attribute ( mod , :__ts_struct_plugins__ , accumulate: true )
114
- Module . register_attribute ( mod , :__ts_definition____ , accumulate: false )
120
+ defp __typed_structor__ ( caller , options , block ) when is_list ( options ) do
121
+ case fetch_definer! ( caller , options ) do
122
+ :error -> :ok
123
+ { :ok , definer } -> Module . put_attribute ( caller . module , :__ts_definer__ , definer )
124
+ end
115
125
116
- quote do
117
- @ __ts_options__ unquote ( options )
126
+ Module . register_attribute ( caller . module , :__ts_struct_fields__ , accumulate: true )
127
+ Module . register_attribute ( caller . module , :__ts_struct_parameters__ , accumulate: true )
128
+ Module . register_attribute ( caller . module , :__ts_struct_plugins__ , accumulate: true )
129
+ Module . register_attribute ( caller . module , :__ts_definition____ , accumulate: false )
118
130
131
+ quote do
119
132
# create a lexical scope
120
133
try do
121
134
import TypedStructor ,
@@ -132,15 +145,14 @@ defmodule TypedStructor do
132
145
try do
133
146
definition =
134
147
TypedStructor . __call_plugins_before_definitions__ ( % TypedStructor.Definition {
135
- options: @ __ts_options__ ,
148
+ options: unquote ( options ) ,
136
149
fields: Enum . reverse ( @ __ts_struct_fields__ ) ,
137
150
parameters: Enum . reverse ( @ __ts_struct_parameters__ )
138
151
} )
139
152
140
153
@ __ts_definition__ definition
141
154
after
142
155
# cleanup
143
- Module . delete_attribute ( __MODULE__ , :__ts_options__ )
144
156
Module . delete_attribute ( __MODULE__ , :__ts_struct_fields__ )
145
157
Module . delete_attribute ( __MODULE__ , :__ts_struct_parameters__ )
146
158
end
@@ -154,10 +166,30 @@ defmodule TypedStructor do
154
166
# cleanup
155
167
Module . delete_attribute ( __MODULE__ , :__ts_struct_plugins__ )
156
168
Module . delete_attribute ( __MODULE__ , :__ts_definition__ )
169
+ Module . delete_attribute ( __MODULE__ , :__ts_definer__ )
157
170
end
158
171
end
159
172
end
160
173
174
+ defp fetch_definer! ( caller , options ) when is_list ( options ) do
175
+ case Keyword . fetch ( options , :definer ) do
176
+ :error ->
177
+ :error
178
+
179
+ { :ok , definer } ->
180
+ case Macro . expand ( definer , caller ) do
181
+ built_in_or_mod when is_atom ( built_in_or_mod ) ->
182
+ { :ok , built_in_or_mod }
183
+
184
+ other ->
185
+ raise ArgumentError , """
186
+ Definer must be one of :defstruct, :defexception, :defrecord, :defrecordp or a module that defines a `define/1` macro,
187
+ got: #{ inspect ( other ) }
188
+ """
189
+ end
190
+ end
191
+ end
192
+
161
193
# register global plugins
162
194
defp register_global_plugins do
163
195
:typed_structor
@@ -219,7 +251,7 @@ defmodule TypedStructor do
219
251
options = Keyword . merge ( options , name: name , type: Macro . escape ( type ) )
220
252
221
253
quote do
222
- @ __ts_struct_fields__ Keyword . merge ( @ __ts_options__ , unquote ( options ) )
254
+ @ __ts_struct_fields__ unquote ( options )
223
255
end
224
256
end
225
257
@@ -272,21 +304,30 @@ defmodule TypedStructor do
272
304
end
273
305
274
306
defmacro __define__ ( definition ) do
275
- quote bind_quoted: [ definition: definition ] do
276
- case Keyword . get ( definition . options , :definer , :defstruct ) do
277
- :defstruct ->
278
- require TypedStructor.Definer.Defstruct
279
- # credo:disable-for-next-line Credo.Check.Design.AliasUsage
280
- TypedStructor.Definer.Defstruct . define ( definition )
281
-
282
- :defexception ->
283
- require TypedStructor.Definer.Defexception
284
- # credo:disable-for-next-line Credo.Check.Design.AliasUsage
285
- TypedStructor.Definer.Defexception . define ( definition )
286
-
287
- fun when is_function ( fun ) ->
288
- then ( definition , fun )
307
+ definer = Module . get_attribute ( __CALLER__ . module , :__ts_definer__ , :defstruct )
308
+ definer_mod = Keyword . get ( @ built_in_definers , definer , definer )
309
+
310
+ quote do
311
+ case Keyword . fetch ( unquote ( definition ) . options , :definer ) do
312
+ :error ->
313
+ :ok
314
+
315
+ { :ok , unquote ( definer ) } ->
316
+ :ok
317
+
318
+ { :ok , other } ->
319
+ IO . warn ( """
320
+ The definer option set in the `typed_structor` block is different from the definer option in the definition.
321
+ We will ignore the definer option in the definition and use the one set in the `typed_structor` block.
322
+
323
+ Note: The definer option in the definition may be changed by a plugin.
324
+
325
+ The effective definer is: #{ inspect ( unquote ( definer ) ) } , the ignored definer from the definition is: #{ inspect ( other ) } .
326
+ """ )
289
327
end
328
+
329
+ require unquote ( definer_mod )
330
+ unquote ( definer_mod ) . define ( unquote ( definition ) )
290
331
end
291
332
end
292
333
0 commit comments