Skip to content

Commit 166685c

Browse files
author
José Valim
committed
Automatically define types for structs
1 parent f60bdb8 commit 166685c

File tree

2 files changed

+25
-63
lines changed

2 files changed

+25
-63
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 19 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3112,22 +3112,6 @@ defmodule Kernel do
31123112
31133113
user.update_age(fn(old) -> old + 1 end)
31143114
3115-
## Types
3116-
3117-
Every record defines a type named `t` that can be accessed in typespecs.
3118-
Those types can be specified inside the record definition:
3119-
3120-
defrecord User do
3121-
record_type name: string, age: integer
3122-
end
3123-
3124-
All fields without a specified type are assumed to have type `term`.
3125-
3126-
Assuming the `User` record defined above, it could be used in typespecs
3127-
as follow:
3128-
3129-
@spec handle_user(User.t) :: boolean()
3130-
31313115
## Runtime introspection
31323116
31333117
At runtime, developers can use `__record__` to get information
@@ -3148,31 +3132,6 @@ defmodule Kernel do
31483132
User.__record__(:index, :unknown)
31493133
#=> nil
31503134
3151-
## Compile-time introspection
3152-
3153-
At compile time, one can access the following information about the record
3154-
from within the record module:
3155-
3156-
* `@record_fields` — a keyword list of record fields with defaults
3157-
* `@record_types` — a keyword list of record fields with types
3158-
3159-
For example:
3160-
3161-
defrecord Foo, bar: nil do
3162-
record_type bar: nil | integer
3163-
IO.inspect @record_fields
3164-
IO.inspect @record_types
3165-
end
3166-
3167-
Prints out:
3168-
3169-
[bar: nil]
3170-
[bar: {:|,[line: ...],[nil,{:integer,[line: ...],nil}]}]
3171-
3172-
Where the last line is a quoted representation of
3173-
3174-
[bar: nil | integer]
3175-
31763135
"""
31773136
defmacro defrecord(name, fields, do_block \\ []) do
31783137
case is_list(fields) and Keyword.get(fields, :do, false) do
@@ -3226,20 +3185,6 @@ defmodule Kernel do
32263185
32273186
state() #=> { MyServer, nil }
32283187
3229-
## Types
3230-
3231-
`defrecordp` allows a developer to generate a type
3232-
automatically by simply providing a type to its fields.
3233-
The following definition:
3234-
3235-
defrecordp :user,
3236-
name: "José" :: binary,
3237-
age: 25 :: integer
3238-
3239-
Will generate the following type:
3240-
3241-
@typep user_t :: { :user, binary, integer }
3242-
32433188
"""
32443189
defmacro defrecordp(name, tag \\ nil, fields) do
32453190
Record.Deprecated.defrecordp(name, Macro.expand(tag, __CALLER__), fields)
@@ -3254,8 +3199,8 @@ defmodule Kernel do
32543199
32553200
To define a struct, a developer needs to only define
32563201
a function named `__struct__/0` that returns a map with the
3257-
structs field. This macro is a convenience for doing such
3258-
function and a type `t` in one pass.
3202+
structs field. This macro is a convenience for defining such
3203+
function, with the addition of a type `t`.
32593204
32603205
For more information about structs, please check
32613206
`Kernel.SpecialForms.%/2`.
@@ -3282,19 +3227,32 @@ defmodule Kernel do
32823227
defstruct my_fields
32833228
end
32843229
3230+
## Types
3231+
3232+
`defstruct` automatically generates a type `t` unless one exists.
3233+
The following definition:
3234+
3235+
defmodule User do
3236+
defstruct name: "José" :: String.t,
3237+
age: 25 :: integer
3238+
end
3239+
3240+
Generates a type as follows:
3241+
3242+
@type t :: %User{name: String.t, age: integer}
3243+
3244+
In case a struct does not declare a field type, it defaults to `term`.
32853245
"""
32863246
defmacro defstruct(kv) do
32873247
kv = Macro.escape(kv, unquote: true)
32883248
quote bind_quoted: [kv: kv] do
32893249
# Expand possible macros that return KVs.
32903250
kv = Macro.expand(kv, __ENV__)
3291-
3292-
# TODO: Use those types once we support maps typespecs.
3293-
{ fields, _types } = Record.Backend.split_fields_and_types(:defstruct, kv)
3251+
{ fields, types } = Record.Backend.split_fields_and_types(:defstruct, kv)
32943252

32953253
if :code.ensure_loaded(Kernel.Typespec) == { :module, Kernel.Typespec } and
32963254
not Kernel.Typespec.defines_type?(__MODULE__, :t, 0) do
3297-
@type t :: map
3255+
@type t :: %{ unquote_splicing(types), __struct__: __MODULE__ }
32983256
end
32993257

33003258
def __struct__() do

lib/elixir/test/elixir/kernel/typespec_test.exs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,15 @@ defmodule Kernel.TypespecTest do
271271

272272
test "@type from structs" do
273273
types = test_module do
274-
defstruct name: nil, age: 0
274+
defstruct name: nil, age: 0 :: non_neg_integer
275275
@type
276276
end
277277

278-
assert [{:t, {:type, _, :map, []}, []}] = types
278+
assert [{:t, {:type, _, :map, [
279+
{:type, _, :map_field_assoc, {:atom, _, :name}, {:type, _, :term, []}},
280+
{:type, _, :map_field_assoc, {:atom, _, :age}, {:type, _, :non_neg_integer, []}},
281+
{:type, _, :map_field_assoc, {:atom, _, :__struct__}, {:atom, _, Kernel.TypespecTest.T}}
282+
]}, []}] = types
279283
end
280284

281285
test "@type unquote fragment" do

0 commit comments

Comments
 (0)