• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Qqwy / elixir-type_check / dbbce214d08708fdf06aedfc383033b2ea38b8f3

pending completion
dbbce214d08708fdf06aedfc383033b2ea38b8f3

push

github

Qqwy
Merge pull request #176 from JamesLavin/add_readme_link_to_elixirconf_eu_presentation

1120 of 1318 relevant lines covered (84.98%)

55552.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/lib/type_check/defstruct.ex
1
defmodule TypeCheck.Defstruct do
2
  @moduledoc """
3
  Implements the `defstruct!` macro.
4

5
  To use this macro:
6
  - Ensure you `use TypeCheck` in your module
7
  - Also `use TypeCheck.Defstruct`
8

9
  And now call `defstruct!/1` when you want to define a struct.
10
  """
11

12
  defmacro __using__(_opts) do
13
    # if Module.get_attribute(__CALLER__.module, TypeCheck.Options) do
14
    quote do
15
      unless Module.get_attribute(__MODULE__, TypeCheck.Options) do
16
        raise TypeCheck.CompileError, """
17
        You need to `use TypeCheck` before calling `use TypeCheck.Defstruct`.
18

19
        These are separate steps to:
20
        - Be very explicit where `@type!`, `@spec!` etc. come from.
21
        - Allow customization by passing options to `use TypeCheck`.
22
        """
23
      end
24

25
      import TypeCheck.Defstruct
26
    end
27
  end
28

29
  @doc """
30
  Defines a struct and a TypeCheck type at the same time.
31

32

33
  # Example:
34

35
      defmodule User do
36
        use TypeCheck
37
        use TypeCheck.Defstruct
38

39
        defstruct!(
40
          name: "Guest" :: String.t(),
41
          age: _ :: non_neg_integer()
42
        )
43
      end
44

45
  This is syntactic sugar for:
46

47
      defmodule User do
48
        use TypeCheck
49
        use TypeCheck.Defstruct
50

51
        @type! t() :: %User{
52
          name: String.t(),
53
          age: non_neg_integer()
54
        }
55
        @enforce_keys [:age]
56
        defstruct [:age, name: "Guest"]
57
      end
58

59
  ## Optional and required keys
60

61
  A key is considered optional if it uses the syntax
62

63
      name: default_value :: type
64

65
  A key is considered required if it uses one of the following syntaxes:
66

67
      :name :: type
68

69
      name: _ :: type
70

71
  In this case, it will be added to the `@enforce_keys` list. (c.f. `Kernel.defstruct`).
72
  """
73
  defmacro defstruct!(fields_with_types) do
74
    full_info = extract_fields(fields_with_types)
×
75

76
    type_ast = type_ast(full_info, __CALLER__)
×
77
    enforced_keys = enforced_keys(full_info)
×
78
    struct_info = struct_info(full_info)
×
79

80
    res =
×
81
      quote generated: true do
82
        @enforce_keys unquote(enforced_keys)
83
        defstruct(unquote(struct_info))
84
        unquote(type_ast)
85
      end
86

87
    res
88
    |> Macro.to_string()
89
    |> Code.format_string!()
90
    |> IO.puts()
×
91

92
    res
×
93
  end
94

95
  defp extract_fields(fields_with_types_ast) do
96
    Enum.map(fields_with_types_ast, &extract_field/1)
×
97
  end
98

99
  defp extract_field(field_ast) do
100
    case field_ast do
×
101
      # :name :: type
102
      {:"::", _, [field_name, field_type_ast]} when is_atom(field_name) ->
103
        {field_name, field_type_ast, :required}
×
104

105
      # name: _ :: type
106
      {field_name, {:"::", _, [{:_, _, _}, field_type_ast]}} when is_atom(field_name) ->
107
        {field_name, field_type_ast, :required}
×
108

109
      # name: default :: type
110
      {field_name, {:"::", _, [default_value, field_type_ast]}} when is_atom(field_name) ->
111
        {field_name, field_type_ast, {:default, default_value}}
×
112
    end
113
  end
114

115
  defp enforced_keys(full_info) do
116
    Enum.flat_map(full_info, fn
×
117
      {field_name, _, :required} ->
118
        [field_name]
×
119

120
      _ ->
121
        []
122
    end)
123
  end
124

125
  defp struct_info(full_info) do
126
    Enum.map(full_info, fn
×
127
      {field_name, _, :required} -> field_name
×
128
      {field_name, _, {:default, default_value}} -> {field_name, default_value}
×
129
    end)
130
  end
131

132
  defp type_ast(full_info, caller) do
133
    full_fields_ast =
×
134
      Enum.map(full_info, fn {field_name, field_ast, _} -> {field_name, field_ast} end)
×
135

136
    quote generated: true do
137
      TypeCheck.Macros.type!(t() :: %unquote(caller.module){unquote_splicing(full_fields_ast)})
×
138
    end
139
  end
140
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc