1defprotocol IEx.Info do
2  @fallback_to_any true
3
4  @spec info(term()) :: [{info_name :: String.Chars.t(), info :: String.t()}]
5  def info(term)
6end
7
8defimpl IEx.Info, for: Tuple do
9  def info(_tuple) do
10    [
11      {"Data type", "Tuple"},
12      {"Reference modules", "Tuple"}
13    ]
14  end
15end
16
17defimpl IEx.Info, for: Atom do
18  def info(atom) do
19    specific_info =
20      cond do
21        module?(atom) ->
22          info_module(atom)
23
24        match?("Elixir." <> _, Atom.to_string(atom)) ->
25          info_module_like_atom(atom)
26
27        true ->
28          info_atom(atom)
29      end
30
31    description =
32      if atom == IEx.dont_display_result() do
33        description = """
34        This atom is returned by IEx when a function that should not print its
35        return value on screen is executed.
36        """
37
38        [{"Description", description}]
39      else
40        []
41      end
42
43    [{"Data type", "Atom"}] ++ description ++ specific_info
44  end
45
46  defp module?(atom) do
47    case :code.get_object_code(atom) do
48      :error ->
49        Code.ensure_loaded?(atom)
50
51      {^atom, beam, _path} ->
52        info = :beam_lib.info(beam)
53        Keyword.fetch(info, :module) == {:ok, atom}
54    end
55  end
56
57  defp info_module(mod) do
58    extra =
59      case Code.fetch_docs(mod) do
60        {:docs_v1, _, _, _, %{}, _, _} -> "Use h(#{inspect(mod)}) to access its documentation.\n"
61        _ -> ""
62      end
63
64    mod_info = mod.module_info()
65
66    generic_info = [
67      {"Module bytecode", module_object_file(mod)},
68      {"Source", module_source_file(mod_info)},
69      {"Version", module_version(mod_info)},
70      {"Compile options", module_compile_options(mod_info)},
71      {"Description", "#{extra}Call #{inspect(mod)}.module_info() to access metadata."}
72    ]
73
74    final_info = [
75      {"Raw representation", ":" <> inspect(Atom.to_string(mod))},
76      {"Reference modules", "Module, Atom"}
77    ]
78
79    generic_info ++ protocol_info(mod) ++ final_info
80  end
81
82  defp protocol_info(mod) do
83    if function_exported?(mod, :__protocol__, 1) do
84      impls =
85        mod
86        |> Protocol.extract_impls(:code.get_path())
87        |> Enum.map_join(", ", &inspect/1)
88
89      [{"Protocol", "This module is a protocol. These data structures implement it:\n  #{impls}"}]
90    else
91      []
92    end
93  end
94
95  defp info_module_like_atom(atom) do
96    [
97      {"Raw representation", ":" <> inspect(Atom.to_string(atom))},
98      {"Reference modules", "Atom"}
99    ]
100  end
101
102  defp info_atom(_atom) do
103    [{"Reference modules", "Atom"}]
104  end
105
106  defp module_object_file(mod) do
107    default_or_apply(:code.which(mod), fn
108      [_ | _] = path -> Path.relative_to_cwd(path)
109      atom -> inspect(atom)
110    end)
111  end
112
113  defp module_version(mod_info) do
114    default_or_apply(mod_info[:attributes][:vsn], &inspect/1)
115  end
116
117  defp module_source_file(mod_info) do
118    default_or_apply(mod_info[:compile][:source], &Path.relative_to_cwd/1)
119  end
120
121  defp module_compile_options(mod_info) do
122    default_or_apply(mod_info[:compile][:options], &inspect/1)
123  end
124
125  defp default_or_apply(nil, _), do: "no value found"
126  defp default_or_apply(data, fun), do: fun.(data)
127end
128
129defimpl IEx.Info, for: List do
130  def info(list) do
131    specific_info =
132      cond do
133        list == [] -> info_list(list)
134        List.ascii_printable?(list) -> info_printable_charlist(list)
135        Keyword.keyword?(list) -> info_kw_list(list)
136        List.improper?(list) -> info_improper_list(list)
137        true -> info_list(list)
138      end
139
140    [{"Data type", "List"}] ++ specific_info
141  end
142
143  defp info_printable_charlist(charlist) do
144    description = """
145    This is a list of integers that is printed as a sequence of characters
146    delimited by single quotes because all the integers in it represent printable
147    ASCII characters. Conventionally, a list of Unicode code points is known as a
148    charlist and a list of ASCII characters is a subset of it.
149    """
150
151    [
152      {"Description", description},
153      {"Raw representation", inspect(charlist, charlists: :as_lists)},
154      {"Reference modules", "List"}
155    ]
156  end
157
158  defp info_kw_list(_kw_list) do
159    description = """
160    This is what is referred to as a "keyword list". A keyword list is a list
161    of two-element tuples where the first element of each tuple is an atom.
162    """
163
164    [{"Description", description}, {"Reference modules", "Keyword, List"}]
165  end
166
167  defp info_improper_list(_improper_list) do
168    description = """
169    This is what is referred to as an "improper list". An improper list is a
170    list which its last tail is not to an empty list. For example: [1, 2, 3]
171    is a proper list, as it is equivalent to [1, 2, 3 | []], as opposed to
172    [1, 2 | 3] which is an improper list since its last tail returns 3.
173    """
174
175    [
176      {"Description", description},
177      {"Reference modules", "List"}
178    ]
179  end
180
181  defp info_list(_list) do
182    [{"Reference modules", "List"}]
183  end
184end
185
186defimpl IEx.Info, for: BitString do
187  def info(bitstring) do
188    specific_info =
189      cond do
190        is_binary(bitstring) and String.printable?(bitstring) -> info_string(bitstring)
191        is_binary(bitstring) and String.valid?(bitstring) -> info_non_printable_string(bitstring)
192        is_binary(bitstring) -> info_binary(bitstring)
193        is_bitstring(bitstring) -> info_bitstring(bitstring)
194      end
195
196    [{"Data type", "BitString"}] ++ specific_info
197  end
198
199  defp info_string(bitstring) do
200    description = """
201    This is a string: a UTF-8 encoded binary. It's printed surrounded by
202    "double quotes" because all UTF-8 encoded code points in it are printable.
203    """
204
205    [
206      {"Byte size", byte_size(bitstring)},
207      {"Description", description},
208      {"Raw representation", inspect(bitstring, binaries: :as_binaries)},
209      {"Reference modules", "String, :binary"}
210    ]
211  end
212
213  defp info_non_printable_string(bitstring) do
214    first_non_printable = find_first_codepoint(bitstring, &(not String.printable?(&1)))
215
216    desc = """
217    This is a string: a UTF-8 encoded binary. It's printed with the `<<>>`
218    syntax (as opposed to double quotes) because it contains non-printable
219    UTF-8 encoded code points (the first non-printable code point being
220    `#{inspect(first_non_printable)}`).
221    """
222
223    [
224      {"Byte size", byte_size(bitstring)},
225      {"Description", desc},
226      {"Reference modules", "String, :binary"}
227    ]
228  end
229
230  defp info_binary(bitstring) do
231    first_non_valid = find_first_codepoint(bitstring, &(not String.valid?(&1)))
232
233    description = """
234    This is a binary: a collection of bytes. It's printed with the `<<>>`
235    syntax (as opposed to double quotes) because it is not a UTF-8 encoded
236    binary (the first invalid byte being `#{inspect(first_non_valid)}`)
237    """
238
239    [
240      {"Byte size", byte_size(bitstring)},
241      {"Description", description},
242      {"Reference modules", ":binary"}
243    ]
244  end
245
246  defp info_bitstring(bitstring) do
247    description = """
248    This is a bitstring. It's a chunk of bits that are not divisible by 8
249    (the number of bytes isn't whole).
250    """
251
252    [{"Bits size", bit_size(bitstring)}, {"Description", description}]
253  end
254
255  defp find_first_codepoint(binary, fun) do
256    binary
257    |> String.codepoints()
258    |> Enum.find(fun)
259  end
260end
261
262defimpl IEx.Info, for: Integer do
263  def info(_integer) do
264    [{"Data type", "Integer"}, {"Reference modules", "Integer"}]
265  end
266end
267
268defimpl IEx.Info, for: Float do
269  def info(_float) do
270    [{"Data type", "Float"}, {"Reference modules", "Float"}]
271  end
272end
273
274defimpl IEx.Info, for: Function do
275  def info(fun) do
276    fun_info = Function.info(fun)
277
278    specific_info =
279      if fun_info[:type] == :external and fun_info[:env] == [] do
280        info_named_fun(fun_info)
281      else
282        info_anon_fun(fun_info)
283      end
284
285    [{"Data type", "Function"}] ++ specific_info
286  end
287
288  defp info_anon_fun(fun_info) do
289    [
290      {"Type", to_string(fun_info[:type])},
291      {"Arity", fun_info[:arity]},
292      {"Description", "This is an anonymous function."}
293    ]
294  end
295
296  defp info_named_fun(fun_info) do
297    [
298      {"Type", to_string(fun_info[:type])},
299      {"Arity", fun_info[:arity]}
300    ]
301  end
302end
303
304defimpl IEx.Info, for: PID do
305  @keys [:registered_name, :links, :message_queue_len]
306
307  def info(pid) do
308    extra_info =
309      case :rpc.pinfo(pid, @keys) do
310        [_ | _] = info ->
311          [
312            {"Alive", true},
313            {"Name", process_name(info[:registered_name])},
314            {"Links", links(info[:links])},
315            {"Message queue length", info[:message_queue_len]}
316          ]
317
318        _ ->
319          [{"Alive", false}]
320      end
321
322    final_info = [
323      {"Description", "Use Process.info/1 to get more info about this process"},
324      {"Reference modules", "Process, Node"}
325    ]
326
327    [{"Data type", "PID"}] ++ extra_info ++ final_info
328  end
329
330  defp process_name([]), do: "not registered"
331  defp process_name(name), do: inspect(name)
332
333  defp links([]), do: "none"
334  defp links(links), do: Enum.map_join(links, ", ", &inspect/1)
335end
336
337defimpl IEx.Info, for: Map do
338  def info(_map) do
339    [{"Data type", "Map"}, {"Reference modules", "Map"}]
340  end
341end
342
343defimpl IEx.Info, for: Port do
344  def info(port) do
345    connected = :rpc.call(node(port), :erlang, :port_info, [port, :connected])
346
347    [
348      {"Data type", "Port"},
349      {"Open", match?({:connected, _}, connected)},
350      {"Reference modules", "Port"}
351    ]
352  end
353end
354
355defimpl IEx.Info, for: Reference do
356  def info(_ref) do
357    [{"Data type", "Reference"}]
358  end
359end
360
361defimpl IEx.Info, for: [Date, Time, NaiveDateTime] do
362  {sigil, repr} =
363    case @for do
364      Date -> {"D", "date"}
365      Time -> {"T", "time"}
366      NaiveDateTime -> {"N", ~S{"naive" datetime (that is, a datetime without a time zone)}}
367    end
368
369  def info(value) do
370    description = """
371    This is a struct representing a #{unquote(repr)}. It is commonly
372    represented using the `~#{unquote(sigil)}` sigil syntax, that is
373    defined in the `Kernel.sigil_#{unquote(sigil)}/2` macro.
374    """
375
376    [
377      {"Data type", inspect(@for)},
378      {"Description", description},
379      {"Raw representation", raw_inspect(value)},
380      {"Reference modules", inspect(@for) <> ", Calendar, Map"}
381    ]
382  end
383
384  defp raw_inspect(value) do
385    value
386    |> Inspect.Any.inspect(%Inspect.Opts{})
387    |> Inspect.Algebra.format(:infinity)
388    |> IO.iodata_to_binary()
389  end
390end
391
392defimpl IEx.Info, for: Any do
393  def info(%module{}) do
394    [
395      {"Data type", inspect(module)},
396      {"Description", "This is a struct. Structs are maps with a __struct__ key."},
397      {"Reference modules", inspect(module) <> ", Map"}
398    ]
399  end
400end
401