Skip to content

Commit ab53867

Browse files
committed
Add :records, :binaries and :char_lists options to inspect/2
Deprecate :raw in favor of :records
1 parent 691a9b8 commit ab53867

File tree

13 files changed

+136
-56
lines changed

13 files changed

+136
-56
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* Deprecations
1919
* [Kernel] `is_alive/0` is deprecated in favor of `Node.alive?`
2020
* [Kernel] `Kernel.inspect/2` with `Inspect.Opts[]` is deprecated in favor of `Inspect.Algebra.to_doc/2`
21+
* [Kernel] `Kernel.inspect/2` with `:raw` option is deprecated, use `:records` option instead
2122

2223
* Backwards incompatible changes
2324

lib/elixir/lib/inspect.ex

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import Kernel, except: [inspect: 1]
22
import Inspect.Algebra
33

4-
defrecord Inspect.Opts, raw: false, limit: 50, pretty: false, width: 80
4+
defrecord Inspect.Opts,
5+
records: true,
6+
binaries: :infer,
7+
char_lists: :infer,
8+
limit: 50,
9+
pretty: false,
10+
width: 80
511

612
defprotocol Inspect do
713
@moduledoc """
@@ -43,8 +49,8 @@ defprotocol Inspect do
4349
## Error handling
4450
4551
In case there is an error while your structure is being inspected,
46-
Elixir will automatically default to the raw inspecting. You can
47-
however access the underlying error by invoking the Inspect
52+
Elixir will automatically fall back to tuple inspection for records.
53+
You can however access the underlying error by invoking the Inspect
4854
implementation directly. For example, to test Inspect.HashSet above,
4955
you just need to do:
5056
@@ -158,8 +164,8 @@ defimpl Inspect, for: BitString do
158164
"<<0, 1, 2>>"
159165
160166
"""
161-
def inspect(thing, opts) when is_binary(thing) do
162-
if String.printable?(thing) do
167+
def inspect(thing, Inspect.Opts[binaries: bins] = opts) when is_binary(thing) do
168+
if bins == :as_strings or (bins == :infer and String.printable?(thing)) do
163169
<< ?", escape(thing, ?") :: binary, ?" >>
164170
else
165171
inspect_bitstring(thing, opts)
@@ -213,11 +219,29 @@ defimpl Inspect, for: BitString do
213219
defp escape(<<?\v, t :: binary>>, char, binary) do
214220
escape(t, char, << binary :: binary, ?\\, ?v >>)
215221
end
222+
defp escape(<<h :: utf8, t :: binary>>, char, binary) do
223+
head = << h :: utf8 >>
224+
if String.printable?(head) do
225+
escape(t, char, append(head, binary))
226+
else
227+
<< byte :: size(8), h :: binary >> = head
228+
t = << h :: binary, t :: binary >>
229+
escape(t, char, << binary :: binary, octify(byte) :: binary >>)
230+
end
231+
end
216232
defp escape(<<h, t :: binary>>, char, binary) do
217-
escape(t, char, << binary :: binary, h >>)
233+
escape(t, char, << binary :: binary, octify(h) :: binary >>)
218234
end
219235
defp escape(<<>>, _char, binary), do: binary
220236

237+
defp octify(byte) do
238+
<< hi :: size(2), mi :: size(3), lo :: size(3) >> = << byte >>
239+
<< ?\\, ?0 + hi, ?0 + mi, ?0 + lo >>
240+
end
241+
242+
defp append(<<h, t :: binary>>, binary), do: append(t, << binary :: binary, h >>)
243+
defp append(<<>>, binary), do: binary
244+
221245
## Bitstrings
222246

223247
defp inspect_bitstring(bitstring, Inspect.Opts[] = opts) do
@@ -270,11 +294,11 @@ defimpl Inspect, for: List do
270294

271295
def inspect([], _opts), do: "[]"
272296

273-
def inspect(thing, Inspect.Opts[] = opts) do
297+
def inspect(thing, Inspect.Opts[char_lists: lists] = opts) do
274298
cond do
275-
:io_lib.printable_list(thing) ->
299+
lists == :as_char_lists or (lists == :infer and :io_lib.printable_list(thing)) ->
276300
<< ?', Inspect.BitString.escape(String.from_char_list!(thing), ?') :: binary, ?' >>
277-
keyword?(thing) && not opts.raw ->
301+
keyword?(thing) ->
278302
surround_many("[", thing, "]", opts.limit, &keyword(&1, opts))
279303
true ->
280304
surround_many("[", thing, "]", opts.limit, &to_doc(&1, opts))
@@ -322,20 +346,24 @@ defimpl Inspect, for: Tuple do
322346

323347
def inspect({}, _opts), do: "{}"
324348

325-
def inspect(tuple, opts) do
326-
unless opts.raw do
349+
def inspect(tuple, Inspect.Opts[] = opts) do
350+
if opts.records do
327351
record_inspect(tuple, opts)
328-
end || surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
352+
else
353+
surround_many("{", tuple_to_list(tuple), "}", opts.limit, &to_doc(&1, opts))
354+
end
329355
end
330356

331357
## Helpers
332358

333-
defp record_inspect(record, opts) do
359+
defp record_inspect(record, Inspect.Opts[] = opts) do
334360
[name|tail] = tuple_to_list(record)
335361

336362
if is_atom(name) && (fields = record_fields(name)) && (length(fields) == size(record) - 1) do
337363
surround_record(name, fields, tail, opts)
338-
end || surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
364+
else
365+
surround_many("{", [name|tail], "}", opts.limit, &to_doc(&1, opts))
366+
end
339367
end
340368

341369
defp record_fields(name) do
@@ -350,7 +378,7 @@ defimpl Inspect, for: Tuple do
350378
end
351379
end
352380

353-
defp surround_record(name, fields, tail, opts) do
381+
defp surround_record(name, fields, tail, Inspect.Opts[] = opts) do
354382
concat(
355383
Inspect.Atom.inspect(name, opts),
356384
surround_many("[", zip_fields(fields, tail), "]", opts.limit, &keyword(&1, opts))
@@ -421,8 +449,8 @@ defimpl Inspect, for: Regex do
421449
concat ["%r", to_doc(Regex.source(regex), opts), Regex.opts(regex)]
422450
end
423451

424-
def inspect(other, opts) do
425-
to_doc(other, opts.raw(true))
452+
def inspect(other, Inspect.Opts[] = opts) do
453+
to_doc(other, opts.records(false))
426454
end
427455
end
428456

lib/elixir/lib/inspect/algebra.ex

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,18 @@ defmodule Inspect.Algebra do
106106
"""
107107
@spec to_doc(any, Inspect.Opts.t) :: t
108108
def to_doc(arg, opts) when is_record(opts, Inspect.Opts) do
109-
case is_tuple(arg) do
110-
true ->
111-
case elem(opts, 1) do
112-
true -> Inspect.Tuple.inspect(arg, opts)
113-
false ->
114-
try do
115-
Inspect.inspect(arg, opts)
116-
catch
117-
_, _ -> Inspect.Tuple.inspect(arg, opts)
118-
end
109+
if is_tuple(arg) do
110+
if elem(opts, Inspect.Opts.__record__(:index, :records)) do
111+
try do
112+
Inspect.inspect(arg, opts)
113+
catch
114+
_, _ -> Inspect.Tuple.inspect(arg, opts)
119115
end
120-
false ->
121-
Inspect.inspect(arg, opts)
116+
else
117+
Inspect.Tuple.inspect(arg, opts)
118+
end
119+
else
120+
Inspect.inspect(arg, opts)
122121
end
123122
end
124123

lib/elixir/lib/kernel.ex

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1710,8 +1710,20 @@ defmodule Kernel do
17101710
17111711
The following options are supported:
17121712
1713-
* `:raw` - when true, record tuples are not formatted by the inspect protocol,
1714-
but are printed as just tuples, defaults to false;
1713+
* `:records` - when false, records are not formatted by the inspect protocol,
1714+
they are instead printed as just tuples, defaults to true;
1715+
1716+
* `:binaries` - when `:as_strings` all binaries will be printed as strings,
1717+
non-printable bytes will be escaped; when `:as_binaries` all
1718+
binaries will be printed in bit syntax; when the default
1719+
`:infer`, the binary will be printed as a string if it is
1720+
printable, otherwise in bit syntax;
1721+
1722+
* `:char_lists` - when `:as_char_lists` all lists will be printed as char lists,
1723+
non-printable elements will be escaped; when `:as_lists` all
1724+
lists will be printed as lists; when the default `:infer`, the
1725+
list will be printed as a char list if it is printable,
1726+
otherwise as list;
17151727
17161728
* `:limit` - limits the number of items that are printed for tuples, bitstrings,
17171729
and lists, does not apply to strings nor char lists, defaults to 50;
@@ -1732,9 +1744,18 @@ defmodule Kernel do
17321744
iex> inspect(ArgumentError[])
17331745
"ArgumentError[message: \"argument error\"]"
17341746
1735-
iex> inspect(ArgumentError[], raw: true)
1747+
iex> inspect(ArgumentError[], records: false)
17361748
"{ArgumentError, :__exception__, \"argument error\"}"
17371749
1750+
iex> inspect("josé" <> <<0>>)
1751+
"<<106, 111, 115, 195, 169, 0>>"
1752+
1753+
iex> inspect("josé" <> <<0>>, binaries: :as_strings)
1754+
"\"josé\\000\""
1755+
1756+
iex> inspect("josé", binaries: :as_binaries)
1757+
"<<106, 111, 115, 195, 169>>"
1758+
17381759
Note that the inspect protocol does not necessarily return a valid
17391760
representation of an Elixir term. In such cases, the inspected result
17401761
must start with `#`. For example, inspecting a function will return:
@@ -1752,6 +1773,11 @@ defmodule Kernel do
17521773
end
17531774

17541775
def inspect(arg, opts) when is_list(opts) do
1776+
unless nil?(raw = opts[:raw]) do
1777+
IO.write "Kernel.inspect/2 with :raw option is deprecated, please use :records instead\n#{Exception.format_stacktrace}"
1778+
opts = opts ++ [records: !raw]
1779+
end
1780+
17551781
opts = Inspect.Opts.new(opts)
17561782
limit = case opts.pretty do
17571783
true -> opts.width
@@ -2956,7 +2982,7 @@ defmodule Kernel do
29562982
the record name, we can get the raw record representation as
29572983
follows:
29582984
2959-
inspect User.new, raw: true
2985+
inspect User.new, records: false
29602986
#=> { User, nil, 0 }
29612987
29622988
In addition to defining readers and writers for each attribute, Elixir also

lib/elixir/lib/macro.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ defmodule Macro do
417417
end
418418

419419
# All other structures
420-
def to_string(other, fun), do: fun.(other, inspect(other, raw: true))
420+
def to_string(other, fun), do: fun.(other, inspect(other, records: false))
421421

422422
# Block keywords
423423
@kw_keywords [:do, :catch, :rescue, :after, :else]
@@ -427,7 +427,7 @@ defmodule Macro do
427427
end
428428
defp kw_blocks?(_), do: false
429429

430-
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, raw: true)
430+
defp module_to_string(atom, _fun) when is_atom(atom), do: inspect(atom, records: false)
431431
defp module_to_string(other, fun), do: call_to_string(other, fun)
432432

433433
defp call_to_string(atom, _fun) when is_atom(atom), do: atom_to_binary(atom)

lib/elixir/lib/record.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ defmodule Record do
9696
9797
Notice that now since the record definition is accessible, Elixir
9898
shows the record nicely formatted, no longer as a simple tuple. We
99-
can get the raw formatting by passing `raw: true` to `inspect`:
99+
can get the raw formatting by passing `records: false` to `inspect`:
100100
101-
inspect user(), raw: true
101+
inspect user(), records: false
102102
{ User, "José", 25 }
103103
104104
Since working with external records is common, Elixir allows

lib/elixir/src/elixir_exp.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ expand({ _, Meta, Args } = Invalid, E) when is_list(Meta) and is_list(Args) ->
343343

344344
expand({ _, _, _ } = Tuple, E) ->
345345
compile_error([{line,0}], E#elixir_env.file, "invalid quoted expression: ~ts",
346-
['Elixir.Kernel':inspect(Tuple, [{raw,true}])]);
346+
['Elixir.Kernel':inspect(Tuple, [{records,false}])]);
347347

348348
%% Literals
349349

lib/elixir/test/elixir/inspect_test.exs

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,22 @@ defmodule Inspect.BitStringTest do
7171
assert inspect(" ゆんゆん") == "\" ゆんゆん\""
7272
end
7373

74-
test :unprintable do
75-
assert inspect(<<193>>) == "<<193>>"
74+
test :opt_infer do
75+
assert inspect(<<"eric", 193, "mj">>, binaries: :infer) == %s(<<101, 114, 105, 99, 193, 109, 106>>)
76+
assert inspect(<<"eric">>, binaries: :infer) == %s("eric")
77+
assert inspect(<<193>>, binaries: :infer) == %s(<<193>>)
78+
end
79+
80+
test :opt_as_strings do
81+
assert inspect(<<"eric", 193, "mj">>, binaries: :as_strings) == %s("eric\\301mj")
82+
assert inspect(<<"eric">>, binaries: :as_strings) == %s("eric")
83+
assert inspect(<<193>>, binaries: :as_strings) == %s("\\301")
84+
end
85+
86+
test :opt_as_binaries do
87+
assert inspect(<<"eric", 193, "mj">>, binaries: :as_binaries) == "<<101, 114, 105, 99, 193, 109, 106>>"
88+
assert inspect(<<"eric">>, binaries: :as_binaries) == "<<101, 114, 105, 99>>"
89+
assert inspect(<<193>>, binaries: :as_binaries) == "<<193>>"
7690
end
7791

7892
test :unprintable_with_opts do
@@ -154,8 +168,8 @@ defmodule Inspect.TupleTest do
154168
assert inspect({ 1, 2, 3, 4 }, limit: 3) == "{1, 2, 3, ...}"
155169
end
156170

157-
test :with_raw do
158-
assert inspect(Config.new, raw: true) == "{Inspect.TupleTest.Config, 1, []}"
171+
test :with_records_false do
172+
assert inspect(Config.new, records: false) == "{Inspect.TupleTest.Config, 1, []}"
159173
end
160174
end
161175

@@ -181,16 +195,28 @@ defmodule Inspect.ListTest do
181195
"[foo: [1, 2, 3, :bar],\n bazzz: :bat]"
182196
end
183197

184-
test :keyword_with_raw do
185-
assert inspect([a: 1], raw: true) == "[{:a, 1}]"
186-
assert inspect([a: 1, b: 2], raw: true) == "[{:a, 1}, {:b, 2}]"
187-
assert inspect([a: 1, a: 2, b: 2], raw: true) == "[{:a, 1}, {:a, 2}, {:b, 2}]"
188-
end
189-
190198
test :non_keyword do
191199
assert inspect([{ Regex, 1 }]) == "[{Regex, 1}]"
192200
end
193201

202+
test :opt_infer do
203+
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :infer) == "[101, 114, 105, 99, 0, 109, 106]"
204+
assert inspect('eric', char_lists: :infer) == "'eric'"
205+
assert inspect([0], char_lists: :infer) == "[0]"
206+
end
207+
208+
test :opt_as_strings do
209+
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :as_char_lists) == "'eric\\000mj'"
210+
assert inspect('eric', char_lists: :as_char_lists) == "'eric'"
211+
assert inspect([0], char_lists: :as_char_lists) == "'\\000'"
212+
end
213+
214+
test :opt_as_lists do
215+
assert inspect('eric' ++ [0] ++ 'mj', char_lists: :as_lists) == "[101, 114, 105, 99, 0, 109, 106]"
216+
assert inspect('eric', char_lists: :as_lists) == "[101, 114, 105, 99]"
217+
assert inspect([0], char_lists: :as_lists) == "[0]"
218+
end
219+
194220
test :non_printable do
195221
assert inspect([{:b, 1}, {:a, 1}]) == "[b: 1, a: 1]"
196222
end

lib/ex_unit/lib/ex_unit/doc_test.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ defmodule ExUnit.DocTest do
504504
# Finally, parse expected_acc.
505505
defp extract_tests([expected|lines], line, expr_acc, expected_acc, [test=Test[exprs: exprs]|t]=acc, newtest) do
506506
if expected =~ %r/^#[A-Z][\w\.]*<.*>$/ do
507-
expected = expected_acc <> "\n" <> Inspect.BitString.inspect(expected, [])
507+
expected = expected_acc <> "\n" <> inspect(expected)
508508
test = test.exprs([{ expr_acc, { :inspect, expected } } | exprs])
509509
extract_tests(lines, line, "", "", [test|t], newtest)
510510
else

lib/iex/lib/iex/options.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ defmodule IEx.Options do
1717
iex(1)> ArgumentError[]
1818
ArgumentError[message: "argument error"]
1919
20-
iex(2)> IEx.Options.set :inspect, raw: true
21-
[limit: 50, raw: false]
20+
iex(2)> IEx.Options.set :inspect, records: false
21+
[limit: 50, records: true]
2222
2323
iex(3)> ArgumentError[]
2424
{ArgumentError,:__exception__,"argument error"}

0 commit comments

Comments
 (0)