• 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

96.99
/lib/type_check/type_error/default_formatter.ex
1
defmodule TypeCheck.TypeError.DefaultFormatter do
2
  @behaviour TypeCheck.TypeError.Formatter
3

4
  @spec format(TypeCheck.TypeError.problem_tuple(), TypeCheck.TypeError.location()) :: String.t()
5
  def format(problem_tuple, location \\ []) do
6
    res =
6,516✔
7
      do_format(problem_tuple)
8
      # Ensure we start with four spaces, which multi-line exception pretty-printing expects
9
      |> indent()
10
      |> indent()
11

12
    (location_string(location) <> res)
13
    |> String.trim()
6,516✔
14
  end
15

16
  defp location_string([]), do: ""
17

18
  defp location_string(location) do
19
    raw_file = location[:file]
3,196✔
20
    line = location[:line]
3,196✔
21

22
    file = String.replace_prefix(raw_file, File.cwd!() <> "/", "")
3,196✔
23
    "At #{file}:#{line}:\n"
3,196✔
24
  end
25

26
  @doc """
27
  Transforms a `problem_tuple` into a humanly-readable explanation string.
28

29
  C.f. `TypeCheck.TypeError.Formatter` for more information about problem tuples.
30
  """
31
  @spec do_format(TypeCheck.TypeError.Formatter.problem_tuple()) :: String.t()
32
  def do_format(problem_tuple)
33

34
  def do_format({%TypeCheck.Builtin.Atom{}, :no_match, _, val}) do
35
    "`#{inspect(val, inspect_value_opts())}` is not an atom."
315✔
36
  end
37

38
  def do_format({%TypeCheck.Builtin.Binary{}, :no_match, _, val}) do
39
    "`#{inspect(val, inspect_value_opts())}` is not a binary."
370✔
40
  end
41

42
  def do_format({%TypeCheck.Builtin.Bitstring{}, :no_match, _, val}) do
43
    "`#{inspect(val, inspect_value_opts())}` is not a bitstring."
272✔
44
  end
45

46
  def do_format({%TypeCheck.Builtin.SizedBitstring{}, :no_match, _, val}) do
47
    "`#{inspect(val, inspect_value_opts())}` is not a bitstring."
398✔
48
  end
49

50
  def do_format({s = %TypeCheck.Builtin.SizedBitstring{}, :wrong_size, _, val}) do
51
    cond do
307✔
52
      s.unit_size == nil ->
307✔
53
        "`#{inspect(val, inspect_value_opts())}` has a different bit_size (#{bit_size(val)}) than expected (#{s.prefix_size})."
105✔
54

55
      s.prefix_size == 0 ->
202✔
56
        "`#{inspect(val, inspect_value_opts())}` has a different bit_size (#{bit_size(val)}) than expected (_ * #{s.unit_size})."
101✔
57

58
      true ->
101✔
59
        "`#{inspect(val, inspect_value_opts())}` has a different bit_size (#{bit_size(val)}) than expected (#{s.prefix_size} + _ * #{s.unit_size})."
101✔
60
    end
61
  end
62

63
  def do_format({%TypeCheck.Builtin.Boolean{}, :no_match, _, val}) do
64
    "`#{inspect(val, inspect_value_opts())}` is not a boolean."
369✔
65
  end
66

67
  def do_format({s = %TypeCheck.Builtin.FixedList{}, :not_a_list, _, val}) do
68
    problem = "`#{inspect(val, inspect_value_opts())}` is not a list."
134✔
69
    compound_check(val, s, problem)
134✔
70
  end
71

72
  def do_format(
73
        {s = %TypeCheck.Builtin.FixedList{}, :different_length,
74
         %{expected_length: expected_length}, val}
75
      ) do
76
    problem =
83✔
77
      "`#{inspect(val, inspect_value_opts())}` has #{length(val)} elements rather than #{expected_length}."
83✔
78

79
    compound_check(val, s, problem)
83✔
80
  end
81

82
  def do_format(
83
        {s = %TypeCheck.Builtin.FixedList{}, :element_error, %{problem: problem, index: index},
84
         val}
85
      ) do
86
    compound_check(val, s, "at index #{index}:\n", do_format(problem))
53✔
87
  end
88

89
  def do_format({s = %maplike{}, :not_a_map, _, val})
90
      when maplike in [
91
             TypeCheck.Builtin.FixedMap,
92
             TypeCheck.Builtin.CompoundFixedMap,
93
             TypeCheck.Builtin.OptionalFixedMap
94
           ] do
95
    problem = "`#{inspect(val, inspect_value_opts())}` is not a map."
251✔
96
    compound_check(val, s, problem)
251✔
97
  end
98

99
  def do_format({s = %maplike{}, :missing_keys, %{keys: keys}, val})
100
      when maplike in [
101
             TypeCheck.Builtin.FixedMap,
102
             TypeCheck.Builtin.CompoundFixedMap,
103
             TypeCheck.Builtin.OptionalFixedMap
104
           ] do
105
    keys_str =
153✔
106
      keys
107
      |> Enum.map(&inspect/1)
108
      |> Enum.join(", ")
109

110
    problem =
153✔
111
      "`#{inspect(val, inspect_value_opts())}` is missing the following required key(s): `#{keys_str}`."
153✔
112

113
    compound_check(val, s, problem)
153✔
114
  end
115

116
  def do_format({s = %maplike{}, :superfluous_keys, %{keys: keys}, val})
117
      when maplike in [
118
             TypeCheck.Builtin.FixedMap,
119
             TypeCheck.Builtin.CompoundFixedMap,
120
             TypeCheck.Builtin.OptionalFixedMap
121
           ] do
122
    keys_str =
137✔
123
      keys
124
      |> Enum.map(&inspect/1)
125
      |> Enum.join(", ")
126

127
    problem =
137✔
128
      "`#{inspect(val, inspect_value_opts())}` contains the following superfluous key(s): `#{keys_str}`."
137✔
129

130
    compound_check(val, s, problem)
137✔
131
  end
132

133
  def do_format({s = %maplike{}, :value_error, %{problem: problem, key: key}, val})
134
      when maplike in [
135
             TypeCheck.Builtin.FixedMap,
136
             TypeCheck.Builtin.CompoundFixedMap,
137
             TypeCheck.Builtin.OptionalFixedMap
138
           ] do
139
    compound_check(
118✔
140
      val,
141
      s,
142
      "under key `#{inspect(key, inspect_type_opts())}`:\n",
143
      do_format(problem)
144
    )
145
  end
146

147
  def do_format({%TypeCheck.Builtin.Float{}, :no_match, _, val}) do
148
    "`#{inspect(val, inspect_value_opts())}` is not a float."
280✔
149
  end
150

151
  def do_format({%TypeCheck.Builtin.Function{param_types: list}, :no_match, _, val})
152
      when is_list(list) and is_function(val) do
153
    {:arity, arity} = Function.info(val, :arity)
×
154

155
    "`#{inspect(val, inspect_value_opts())}` (arity #{arity}) is not a function of arity `#{length(list)}`."
×
156
  end
157

158
  def do_format({%TypeCheck.Builtin.Function{}, :no_match, _, val}) do
159
    "`#{inspect(val, inspect_value_opts())}` is not a function."
201✔
160
  end
161

162
  def do_format({s = %TypeCheck.Builtin.Guarded{}, :type_failed, %{problem: problem}, val}) do
163
    compound_check(val, s, do_format(problem))
2✔
164
  end
165

166
  def do_format({s = %TypeCheck.Builtin.Guarded{}, :guard_failed, %{bindings: bindings}, val}) do
167
    guard_str =
2✔
168
      Inspect.Algebra.format(
169
        Inspect.Algebra.color(
170
          Macro.to_string(s.guard),
2✔
171
          :builtin_type,
172
          struct(Inspect.Opts, inspect_type_opts())
173
        ),
174
        80
175
      )
176

177
    problem = """
2✔
178
    `#{guard_str}` evaluated to false or nil.
2✔
179
    bound values: #{inspect(bindings, inspect_type_opts())}
180
    """
181

182
    compound_check(val, s, "type guard:\n", problem)
2✔
183
  end
184

185
  def do_format({%TypeCheck.Builtin.Integer{}, :no_match, _, val}) do
186
    "`#{inspect(val, inspect_value_opts())}` is not an integer."
509✔
187
  end
188

189
  def do_format({%TypeCheck.Builtin.PosInteger{}, :no_match, _, val}) do
190
    "`#{inspect(val, inspect_value_opts())}` is not a positive integer."
264✔
191
  end
192

193
  def do_format({%TypeCheck.Builtin.NegInteger{}, :no_match, _, val}) do
194
    "`#{inspect(val, inspect_value_opts())}` is not a negative integer."
277✔
195
  end
196

197
  def do_format({%TypeCheck.Builtin.NonNegInteger{}, :no_match, _, val}) do
198
    "`#{inspect(val, inspect_value_opts())}` is not a non-negative integer."
283✔
199
  end
200

201
  def do_format({s = %listlike{}, :not_a_list, _, val})
202
      when listlike in [TypeCheck.Builtin.List, TypeCheck.Builtin.MaybeImproperList] do
203
    compound_check(val, s, "`#{inspect(val, inspect_value_opts())}` is not a list.")
301✔
204
  end
205

206
  def do_format({s = %listlike{}, :element_error, %{problem: problem, index: index}, val})
207
      when listlike in [TypeCheck.Builtin.List, TypeCheck.Builtin.MaybeImproperList] do
208
    compound_check(val, s, "at index #{index}:\n", do_format(problem))
189✔
209
  end
210

211
  def do_format(
212
        {s = %TypeCheck.Builtin.MaybeImproperList{}, :terminator_error, %{problem: problem}, val}
213
      ) do
214
    compound_check(val, s, "at the improper terminator of the list:\n", do_format(problem))
53✔
215
  end
216

217
  def do_format({%TypeCheck.Builtin.Literal{value: expected_value}, :not_same_value, %{}, val}) do
218
    "`#{inspect(val, inspect_value_opts())}` is not the same value as `#{inspect(expected_value, inspect_type_opts())}`."
292✔
219
  end
220

221
  def do_format({s = %TypeCheck.Builtin.Map{}, :not_a_map, _, val}) do
222
    compound_check(val, s, "`#{inspect(val, inspect_value_opts())}` is not a map.")
123✔
223
  end
224

225
  def do_format({s = %maplike{}, :key_error, %{problem: problem}, val})
226
      when maplike in [TypeCheck.Builtin.Map, TypeCheck.Builtin.CompoundFixedMap] do
227
    compound_check(val, s, "key error:\n", do_format(problem))
133✔
228
  end
229

230
  def do_format({s = %TypeCheck.Builtin.Map{}, :value_error, %{problem: problem, key: key}, val}) do
231
    compound_check(
61✔
232
      val,
233
      s,
234
      "under key `#{inspect(key, inspect_type_opts())}`:\n",
235
      do_format(problem)
236
    )
237
  end
238

239
  def do_format({s = %TypeCheck.Builtin.NamedType{}, :named_type, %{problem: problem}, val}) do
240
    child_str = indent(do_format(problem))
277✔
241

242
    """
277✔
243
    `#{inspect(val, inspect_value_opts())}` does not match the definition of the named type `#{Inspect.Algebra.format(Inspect.Algebra.color(to_string(s.name), :named_type, struct(Inspect.Opts, inspect_type_opts())), 80)}`
277✔
244
    which is: `#{TypeCheck.Inspect.inspect_binary(s, [show_long_named_type: true] ++ inspect_type_opts())}`. Reason:
277✔
245
    #{child_str}
277✔
246
    """
247

248
    # compound_check(val, s, do_format(problem))
249
  end
250

251
  def do_format({%TypeCheck.Builtin.None{}, :no_match, _, val}) do
252
    "`#{inspect(val, inspect_value_opts())}` does not match `none()` (no value matches `none()`)."
272✔
253
  end
254

255
  def do_format({%TypeCheck.Builtin.Number{}, :no_match, _, val}) do
256
    "`#{inspect(val, inspect_value_opts())}` is not a number."
267✔
257
  end
258

259
  def do_format({s = %TypeCheck.Builtin.OneOf{}, :all_failed, %{problems: problems}, val}) do
260
    message =
167✔
261
      problems
262
      |> Enum.with_index()
263
      |> Enum.map(fn {problem, index} ->
264
        """
959✔
265
        #{index})
959✔
266
        #{indent(do_format(problem))}
959✔
267
        """
268
      end)
269
      |> Enum.join("\n")
270

271
    compound_check(val, s, "all possibilities failed:\n", message)
167✔
272
  end
273

274
  def do_format({%TypeCheck.Builtin.PID{}, :no_match, _, val}) do
275
    "`#{inspect(val, inspect_value_opts())}` is not a pid."
71✔
276
  end
277

278
  def do_format({%TypeCheck.Builtin.Port{}, :no_match, _, val}) do
279
    "`#{inspect(val, inspect_value_opts())}` is not a port."
264✔
280
  end
281

282
  def do_format({%TypeCheck.Builtin.Reference{}, :no_match, _, val}) do
283
    "`#{inspect(val, inspect_value_opts())}` is not a reference."
253✔
284
  end
285

286
  def do_format({s = %TypeCheck.Builtin.Range{}, :not_an_integer, _, val}) do
287
    compound_check(val, s, "`#{inspect(val, inspect_value_opts())}` is not an integer.")
184✔
288
  end
289

290
  def do_format({s = %TypeCheck.Builtin.Range{range: range}, :not_in_range, _, val}) do
291
    compound_check(
84✔
292
      val,
293
      s,
294
      "`#{inspect(val, inspect_value_opts())}` falls outside the range #{inspect(range, inspect_type_opts())}."
295
    )
296
  end
297

298
  def do_format({s = %TypeCheck.Builtin.FixedTuple{}, :not_a_tuple, _, val}) do
299
    problem = "`#{inspect(val, inspect_value_opts())}` is not a tuple."
125✔
300
    compound_check(val, s, problem)
125✔
301
  end
302

303
  def do_format(
304
        {s = %TypeCheck.Builtin.FixedTuple{}, :different_size, %{expected_size: expected_size},
305
         val}
306
      ) do
307
    problem =
87✔
308
      "`#{inspect(val, inspect_value_opts())}` has #{tuple_size(val)} elements rather than #{expected_size}."
87✔
309

310
    compound_check(val, s, problem)
87✔
311
  end
312

313
  def do_format(
314
        {s = %TypeCheck.Builtin.FixedTuple{}, :element_error, %{problem: problem, index: index},
315
         val}
316
      ) do
317
    compound_check(val, s, "at index #{index}:\n", do_format(problem))
60✔
318
  end
319

320
  def do_format({s = %TypeCheck.Builtin.Tuple{}, :no_match, _, val}) do
321
    problem = "`#{inspect(val, inspect_value_opts())}` is not a tuple."
236✔
322
    compound_check(val, s, problem)
236✔
323
  end
324

325
  def do_format(
326
        {%TypeCheck.Builtin.ImplementsProtocol{protocol: protocol_name}, :no_match, _, val}
327
      ) do
328
    "`#{inspect(val, inspect_value_opts())}` does not implement the protocol `#{protocol_name}`"
144✔
329
  end
330

331
  def do_format({s = %mod{}, :param_error, %{index: index, problem: problem}, val})
332
      when mod in [TypeCheck.Spec, TypeCheck.Builtin.Function] do
333
    # compound_check(val, s, "at parameter no. #{index + 1}:\n", do_format(problem))
334
    name = Map.get(s, :name, "#Function<...>")
254✔
335

336
    function_with_arity =
254✔
337
      IO.ANSI.format_fragment([:default_color, "#{name}/#{Enum.count(val)}", :red])
254✔
338

339
    param_spec =
254✔
340
      s.param_types |> Enum.at(index) |> TypeCheck.Inspect.inspect_binary(inspect_type_opts())
254✔
341

342
    arguments = val |> Enum.map(&inspect(&1, inspect_value_opts())) |> Enum.join(", ")
254✔
343

344
    raw_call =
254✔
345
      if mod == TypeCheck.Builtin.Function do
346
        "#{name}.(#{arguments})"
200✔
347
      else
348
        "#{name}(#{arguments})"
54✔
349
      end
350

351
    call = IO.ANSI.format_fragment([:default_color, raw_call, :red])
254✔
352

353
    value = Enum.at(val, index)
254✔
354
    value_str = inspect(value, inspect_value_opts())
254✔
355

356
    """
254✔
357
    The call to `#{function_with_arity}` failed,
254✔
358
    because parameter no. #{index + 1} does not adhere to the spec `#{param_spec}`.
254✔
359
    Rather, its value is: `#{value_str}`.
254✔
360
    Details:
361
      The call `#{call}`
254✔
362
      does not adhere to spec `#{TypeCheck.Inspect.inspect_binary(s, inspect_type_opts())}`. Reason:
254✔
363
        parameter no. #{index + 1}:
254✔
364
    #{indent(indent(indent(do_format(problem))))}
254✔
365
    """
366
  end
367

368
  def do_format({s = %mod{}, :return_error, %{problem: problem, arguments: arguments}, val})
369
      when mod in [TypeCheck.Spec, TypeCheck.Builtin.Function] do
370
    name = Map.get(s, :name, "#Function<...>")
128✔
371

372
    function_with_arity =
128✔
373
      IO.ANSI.format_fragment([:default_color, "#{name}/#{Enum.count(arguments)}", :red])
128✔
374

375
    result_spec = s.return_type |> TypeCheck.Inspect.inspect_binary(inspect_type_opts())
128✔
376

377
    arguments_str =
128✔
378
      arguments |> Enum.map(fn val -> inspect(val, inspect_value_opts()) end) |> Enum.join(", ")
229✔
379

380
    arguments_str = IO.ANSI.format_fragment([:default_color, arguments_str, :default_color])
128✔
381

382
    raw_call =
128✔
383
      if mod == TypeCheck.Builtin.Function do
384
        "#{name}.(#{arguments_str})"
101✔
385
      else
386
        "#{name}(#{arguments_str})"
27✔
387
      end
388

389
    call = IO.ANSI.format_fragment([:default_color, raw_call, :red])
128✔
390

391
    val_str = inspect(val, inspect_value_opts())
128✔
392

393
    """
128✔
394
    The call to `#{function_with_arity}` failed,
128✔
395
    because the returned result does not adhere to the spec `#{result_spec}`.
128✔
396
    Rather, its value is: `#{val_str}`.
128✔
397
    Details:
398
      The result of calling `#{call}`
128✔
399
      does not adhere to spec `#{TypeCheck.Inspect.inspect_binary(s, inspect_type_opts())}`. Reason:
128✔
400
        Returned result:
401
    #{indent(indent(indent(do_format(problem))))}
128✔
402
    """
403
  end
404

405
  defp compound_check(val, s, child_prefix \\ nil, child_problem) do
406
    child_str =
2,736✔
407
      if child_prefix do
408
        indent(child_prefix <> indent(child_problem))
836✔
409
      else
410
        indent(child_problem)
1,900✔
411
      end
412

413
    """
2,736✔
414
    `#{inspect(val, inspect_value_opts())}` does not check against `#{TypeCheck.Inspect.inspect_binary(s, inspect_type_opts())}`. Reason:
2,736✔
415
    #{child_str}
2,736✔
416
    """
417
  end
418

419
  defp indent(str) do
420
    String.replace("  " <> str, "\n", "\n  ")
18,986✔
421
  end
422

423
  defp inspect_value_opts() do
424
    # [reset_color: :red, syntax_colors: ([reset: :default_color] ++ TypeCheck.Inspect.default_colors())]
425
    color_opts =
11,379✔
426
      if IO.ANSI.enabled?() do
427
        [reset_color: :red, syntax_colors: [reset: :red] ++ TypeCheck.Inspect.default_colors()]
×
428
      else
429
        []
430
      end
431

432
    [limit: 5] ++ color_opts
11,379✔
433
  end
434

435
  defp inspect_type_opts() do
436
    if IO.ANSI.enabled?() do
4,613✔
437
      [reset_color: :red, syntax_colors: [reset: :red] ++ TypeCheck.Inspect.default_colors()]
×
438
    else
439
      []
440
    end
441
  end
442
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