Skip to content

Commit a5b9c5b

Browse files
committed
Add required field back to struct info
Closes #14616. Closes #14617. Closes #14500.
1 parent fcea4b4 commit a5b9c5b

File tree

5 files changed

+44
-18
lines changed

5 files changed

+44
-18
lines changed

lib/elixir/lib/kernel/utils.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ defmodule Kernel.Utils do
217217
case enforce_keys -- :maps.keys(struct) do
218218
[] ->
219219
mapper = fn {key, val} ->
220-
%{field: key, default: val}
220+
%{field: key, default: val, required: :lists.member(key, enforce_keys)}
221221
end
222222

223223
:ets.insert(set, {{:elixir, :struct}, :lists.map(mapper, fields)})

lib/elixir/lib/macro.ex

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -861,21 +861,39 @@ defmodule Macro do
861861
end
862862

863863
@doc """
864-
Extracts the struct information (equivalent to calling
865-
`module.__info__(:struct)`).
864+
Extracts the struct information.
866865
867866
This is useful when a struct needs to be expanded at
868867
compilation time and the struct being expanded may or may
869-
not have been compiled. This function is also capable of
870-
expanding structs defined under the module being compiled.
868+
not have been compiled (including structs in the defined
869+
under the module being compiled). For compiled modules,
870+
it will invoke `module.__info__(:struct)`.
871+
871872
Calling this function also adds an export dependency on the
872873
given struct.
873874
874875
It will raise `ArgumentError` if the struct is not available.
876+
877+
## Compatibility considerations
878+
879+
This function currently returns both `:required` and `:default`
880+
entries for each field. While this naming is inconsistent
881+
(a required field should not have a default), this is done for
882+
backwards compatibility purposes.
883+
884+
In future releases, Elixir may introduce truly required struct
885+
fields, and therefore only one of required or default will be
886+
present. Your code should prepare for such scenario accordingly.
875887
"""
876888
@doc since: "1.18.0"
877889
@spec struct_info!(module(), Macro.Env.t()) ::
878-
[%{field: atom(), required: boolean(), default: term()}]
890+
[
891+
%{
892+
required(:field) => atom(),
893+
optional(:required) => boolean(),
894+
optional(:default) => term()
895+
}
896+
]
879897
def struct_info!(module, env) when is_atom(module) do
880898
case :elixir_map.maybe_load_struct_info([line: env.line], module, [], true, env) do
881899
{:ok, info} -> info

lib/elixir/lib/module.ex

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -681,7 +681,6 @@ defmodule Module do
681681
This function is generated for all modules. It's similar to `module_info/1` but
682682
includes some additional Elixir-specific information, such as struct and macro
683683
information. For documentation, see `c:Module.__info__/1`.
684-
685684
'''
686685

687686
@type definition :: {atom, arity}
@@ -721,7 +720,8 @@ defmodule Module do
721720
722721
* `:module` - the module atom name
723722
724-
* `:struct` - (since v1.14.0) if the module defines a struct and if so each field in order
723+
* `:struct` - (since v1.14.0) if the module defines a struct and if so each field in order.
724+
See `Macro.struct_info!/2` for more information
725725
726726
"""
727727
@callback __info__(:attributes) :: keyword()
@@ -731,7 +731,14 @@ defmodule Module do
731731
@callback __info__(:md5) :: binary()
732732
@callback __info__(:module) :: module()
733733
@callback __info__(:struct) ::
734-
list(%{required(:field) => atom(), optional(:default) => term()}) | nil
734+
[
735+
%{
736+
required(:field) => atom(),
737+
optional(:required) => boolean(),
738+
optional(:default) => term()
739+
}
740+
]
741+
| nil
735742

736743
@doc """
737744
Returns information about module attributes used by Elixir.

lib/elixir/test/elixir/macro_test.exs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1645,31 +1645,32 @@ defmodule MacroTest do
16451645

16461646
test "struct_info!/2 expands structs multiple levels deep" do
16471647
defmodule StructBang do
1648+
@enforce_keys [:b]
16481649
defstruct [:a, :b]
16491650

16501651
assert Macro.struct_info!(StructBang, __ENV__) == [
1651-
%{field: :a, default: nil},
1652-
%{field: :b, default: nil}
1652+
%{field: :a, default: nil, required: false},
1653+
%{field: :b, default: nil, required: true}
16531654
]
16541655

16551656
def within_function do
16561657
assert Macro.struct_info!(StructBang, __ENV__) == [
1657-
%{field: :a, default: nil},
1658-
%{field: :b, default: nil}
1658+
%{field: :a, default: nil, required: false},
1659+
%{field: :b, default: nil, required: true}
16591660
]
16601661
end
16611662

16621663
defmodule Nested do
16631664
assert Macro.struct_info!(StructBang, __ENV__) == [
1664-
%{field: :a, default: nil},
1665-
%{field: :b, default: nil}
1665+
%{field: :a, default: nil, required: false},
1666+
%{field: :b, default: nil, required: true}
16661667
]
16671668
end
16681669
end
16691670

16701671
assert Macro.struct_info!(StructBang, __ENV__) == [
1671-
%{field: :a, default: nil},
1672-
%{field: :b, default: nil}
1672+
%{field: :a, default: nil, required: false},
1673+
%{field: :b, default: nil, required: true}
16731674
]
16741675
end
16751676

lib/elixir/test/elixir/map_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ defmodule MapTest do
401401
end
402402

403403
info = Macro.struct_info!(WithBitstring, __ENV__)
404-
assert info == [%{default: <<255, 127::7>>, field: :bitstring}]
404+
assert info == [%{default: <<255, 127::7>>, field: :bitstring, required: false}]
405405
end
406406

407407
test "defstruct can only be used once in a module" do

0 commit comments

Comments
 (0)