Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/elixir/lib/kernel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5589,6 +5589,9 @@ defmodule Kernel do
defstruct name: nil, age: 10 + 11
end
`@enforce_keys` must be set to an atom or a list of unique atoms,
all of which must name fields defined by `defstruct/1`
Now trying to build a struct without the name key will fail:
%User{age: 21}
Expand Down
19 changes: 5 additions & 14 deletions lib/elixir/lib/kernel/utils.ex
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,8 @@ defmodule Kernel.Utils do
# TODO: Make it raise on v2.0
warn_on_duplicate_struct_key(:lists.keysort(1, fields), env)

foreach = fn
key when is_atom(key) ->
:ok

key ->
raise ArgumentError, "keys given to @enforce_keys must be atoms, got: #{inspect(key)}"
end

:lists.foreach(foreach, enforce_keys)
struct = :maps.from_list([__struct__: module] ++ fields)
field_map = :maps.from_list(fields)
struct = :maps.put(:__struct__, module, field_map)
escaped_struct = :elixir_quote.escape(struct, {:struct, module}, false)

body =
Expand Down Expand Up @@ -215,7 +207,7 @@ defmodule Kernel.Utils do
end
end

case enforce_keys -- :maps.keys(struct) do
case enforce_keys -- :maps.keys(field_map) do
[] ->
mapper = fn {key, val} ->
%{field: key, default: val, required: :lists.member(key, enforce_keys)}
Expand All @@ -225,10 +217,9 @@ defmodule Kernel.Utils do
derive = :lists.map(fn {_, value} -> value end, :ets.take(bag, {:accumulate, :derive}))
{struct, :lists.reverse(derive), escaped_struct, quote(do: kv), body}

error_keys ->
invalid_keys ->
raise ArgumentError,
"@enforce_keys required keys (#{inspect(error_keys)}) that are not defined in defstruct: " <>
"#{inspect(fields)}"
"unknown or duplicate keys given to @enforce_keys, got: #{inspect(invalid_keys)}"
end
end

Expand Down
18 changes: 1 addition & 17 deletions lib/elixir/test/elixir/kernel/errors_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ defmodule Kernel.ErrorsTest do
end
end

test "good struct" do
test "valid struct" do
defmodule GoodStruct do
defstruct name: "john"
end
Expand All @@ -415,22 +415,6 @@ defmodule Kernel.ErrorsTest do
~c"%#{GoodStruct}{age: 27} = %{}"
)
end

test "enforce @enforce_keys" do
defmodule EnforceKeys do
@enforce_keys [:foo]
defstruct(foo: nil)
end

assert_raise ArgumentError,
"@enforce_keys required keys ([:fo, :bar]) that are not defined in defstruct: [foo: nil]",
fn ->
defmodule EnforceKeysError do
@enforce_keys [:foo, :fo, :bar]
defstruct(foo: nil)
end
end
end
end

test "invalid unquote" do
Expand Down
47 changes: 36 additions & 11 deletions lib/elixir/test/elixir/map_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -422,17 +422,42 @@ defmodule MapTest do
end
end

test "defstruct raises on invalid enforce_keys" do
message = "keys given to @enforce_keys must be atoms, got: \"foo\""

assert_raise ArgumentError, message, fn ->
Code.eval_string("""
defmodule TestMod do
@enforce_keys "foo"
defstruct [:foo]
end
""")
end
test "defstruct raises on unknown or duplicate enforce_keys" do
assert_raise ArgumentError,
"unknown or duplicate keys given to @enforce_keys, got: [\"foo\"]",
fn ->
defmodule TestMod do
@enforce_keys "foo"
defstruct [:foo]
end
end

assert_raise ArgumentError,
"unknown or duplicate keys given to @enforce_keys, got: [:foo, :bar]",
fn ->
defmodule TestMod do
@enforce_keys [:foo, :bar, :foo, :bar]
defstruct [:foo, :bar]
end
end

assert_raise ArgumentError,
"unknown or duplicate keys given to @enforce_keys, got: [:unknown]",
fn ->
defmodule TestMod do
@enforce_keys [:unknown]
defstruct [:foo]
end
end

assert_raise ArgumentError,
"unknown or duplicate keys given to @enforce_keys, got: [:__struct__]",
fn ->
defmodule TestMod do
@enforce_keys [:__struct__]
defstruct [:foo]
end
end
end

test "struct always expands context module" do
Expand Down
Loading