Skip to content

Commit da0a956

Browse files
author
José Valim
committed
Allow types to be given to defrecordp
Part of #1288
1 parent b2541a1 commit da0a956

File tree

5 files changed

+85
-14
lines changed

5 files changed

+85
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* [Mix] Implement `Mix.Version` for basic versioniong semantics
1010
* [Mix] Support creation and installation of archives (.ez files)
1111
* [Mix] `github: ...` shortcut now uses the faster `git` schema instead of `https`
12+
* [Record] Allow types to be given to `defrecordp`
1213

1314
* bug fix
1415
* [Kernel] The elixir executable on Windows now supports the same options as the UNIX one

lib/elixir/lib/kernel.ex

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,21 @@ defmodule Kernel do
14891489
user(name: name) = record
14901490
name #=> "José"
14911491
1492+
## Types
1493+
1494+
`defrecordp` automatically generates an opaque time based
1495+
on the record name and the types can be modified too. The
1496+
following definition:
1497+
1498+
defrecordp :user,
1499+
name: "José" :: binary,
1500+
age: 25 :: integer
1501+
1502+
Will generate the following type:
1503+
1504+
@opaque user :: { :user, binary, integer }
1505+
1506+
All the fields without a specified type are assumed to have type `term`.
14921507
"""
14931508
defmacro defrecordp(name, fields) when is_atom(name) do
14941509
Record.defrecordp(name, fields)

lib/elixir/lib/record.ex

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -127,21 +127,28 @@ defmodule Record do
127127
## Types
128128
129129
Every record defines a type named `t` that can be accessed in typespecs.
130-
For example, assuming the `Config` record defined above, it could be used
131-
in typespecs as follow:
130+
Those types can be passed at the moment the record is defined:
132131
133-
@spec handle_config(Config.t) :: boolean()
132+
defrecord User,
133+
name: "" :: string,
134+
age: 0 :: integer
134135
135-
Inside the record definition, a developer can define his own types too:
136+
All the fields without a specified type are assumed to have type `term`.
137+
138+
Assuming the `User` record defined above, it could be used in typespecs
139+
as follow:
140+
141+
@spec handle_user(User.t) :: boolean()
142+
143+
If the developer wants to define their own types to be used with the
144+
record, Elixir allows a more lengthy definition with the help of the
145+
`record_type` macro:
136146
137147
defrecord Config, counter: 0, failures: [] do
138148
@type kind :: term
139149
record_type counter: integer, failures: [kind]
140150
end
141151
142-
When defining a type, all the fields not mentioned in the type are
143-
assumed to have type `term`.
144-
145152
## Importing records
146153
147154
It is also possible to import a public record (a record, defined using
@@ -173,29 +180,50 @@ defmodule Record do
173180
"""
174181
def defrecord(name, values, opts) do
175182
block = Keyword.get(opts, :do, nil)
183+
{ fields, types } = record_split(values)
176184
177185
quote do
178-
unquoted_values = unquote(values)
186+
unquoted_fields = unquote(fields)
179187
180188
defmodule unquote(name) do
181189
@moduledoc false
182190
import Elixir.Record.DSL
183191
184192
@record_fields []
185-
@record_types []
193+
@record_types unquote(types)
186194
187-
# Reassign values to inner scope to
195+
# Reassign fields to inner scope to
188196
# avoid conflicts in nested records
189-
values = unquoted_values
197+
fields = unquoted_fields
190198
191-
Elixir.Record.deffunctions(values, __ENV__)
199+
Elixir.Record.deffunctions(fields, __ENV__)
192200
value = unquote(block)
193-
Elixir.Record.deftypes(values, @record_types, __ENV__)
201+
Elixir.Record.deftypes(fields, @record_types, __ENV__)
194202
value
195203
end
196204
end
197205
end
198206
207+
defp record_split(fields) when is_list(fields) do
208+
record_split(fields, [], [])
209+
end
210+
211+
defp record_split(other) do
212+
{ other, [] }
213+
end
214+
215+
defp record_split([{ field, { :::, _, [default, type] }}|t], defaults, types) do
216+
record_split t, [{ field, default }|defaults], [{ field, Macro.escape(type) }|types]
217+
end
218+
219+
defp record_split([other|t], defaults, types) do
220+
record_split t, [other|defaults], types
221+
end
222+
223+
defp record_split([], defaults, types) do
224+
{ Enum.reverse(defaults), types }
225+
end
226+
199227
@doc """
200228
Import public record definition as a set of private macros (as defined by defrecordp/2)
201229

@@ -224,12 +252,27 @@ defmodule Record do
224252
in `values`. This is invoked directly by `Kernel.defrecordp`,
225253
so check it for more information and documentation.
226254
"""
227-
def defrecordp(name, fields) do
255+
def defrecordp(name, fields) when is_atom(name) and is_list(fields) do
256+
{ fields, types } = recordp_split(fields, [], [])
257+
228258
quote do
229259
Record.defmacros(unquote(name), unquote(fields), __ENV__)
260+
@opaque unquote(name)() :: { unquote(name), unquote_splicing(types) }
230261
end
231262
end
232263

264+
defp recordp_split([{ field, { :::, _, [default, type] }}|t], defaults, types) do
265+
recordp_split t, [{ field, default }|defaults], [type|types]
266+
end
267+
268+
defp recordp_split([other|t], defaults, types) do
269+
recordp_split t, [other|defaults], [quote(do: term)|types]
270+
end
271+
272+
defp recordp_split([], defaults, types) do
273+
{ Enum.reverse(defaults), Enum.reverse(types) }
274+
end
275+
233276
@doc """
234277
Defines record functions skipping the module definition.
235278
This is called directly by `defrecord`. It expects the record

lib/elixir/test/elixir/record_test.exs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ defrecord RecordTest.WithRecordType, a: 0, b: 1 do
4949
record_type a: integer
5050
end
5151

52+
defrecord RecordTest.WithInlineType, a: nil :: atom, b: 1 :: integer
53+
5254
defmodule RecordTest.Macros do
5355
defrecordp :_user, name: "José", age: 25
5456

lib/elixir/test/elixir/typespec_test.exs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,16 @@ defmodule Typespec.TypeTest do
259259
assert [{:mytype, _, []}, {:mytype1, _, []}] = types
260260
end
261261

262+
test "@type from defrecordp" do
263+
types = test_module do
264+
defrecordp :user, name: nil, age: 0 :: integer
265+
@opaque
266+
end
267+
268+
assert [{:user, {:type, _, :tuple,
269+
[{:atom, _, :user}, {:type, _, :term, []}, {:type, _, :integer, []}]}, []}] = types
270+
end
271+
262272
test "defines_type?" do
263273
test_module do
264274
@type mytype :: tuple

0 commit comments

Comments
 (0)