From 174d0b3b898e4abf91d1ad7a4f083f86dd1c6efa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 6 Apr 2025 20:51:45 +0200 Subject: [PATCH 01/13] Simplify fitting code --- lib/elixir/lib/code/formatter.ex | 39 ++++++--- lib/elixir/lib/inspect/algebra.ex | 141 ++++++++++++++++-------------- 2 files changed, 101 insertions(+), 79 deletions(-) diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index ec4730b8f7c..55d857f62d8 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -617,9 +617,12 @@ defmodule Code.Formatter do end doc = - with_next_break_fits(next_break_fits?(right_arg, state), right, fn right -> - concat(group(left), group(nest(glue(op, group(right)), 2, :break))) - end) + concat( + group(left), + with_next_break_fits(next_break_fits?(right_arg, state), right, fn right -> + nest(glue(op, right), 2, :break) + end) + ) {doc, state} end @@ -818,9 +821,8 @@ defmodule Code.Formatter do {" " <> op_string, with_next_break_fits(next_break_fits?, right, fn right -> - right = nest(concat(break(), group(right)), nesting, :break) - right = if eol?, do: force_unfit(right), else: right - group(right) + right = nest(concat(break(), right), nesting, :break) + if eol?, do: force_unfit(right), else: right end)} end @@ -1800,10 +1802,20 @@ defmodule Code.Formatter do doc = case args do - [_ | _] -> concat_to_last_group(doc, ",") - [] when last_arg_mode == :force_comma -> concat_to_last_group(doc, ",") - [] when last_arg_mode == :next_break_fits -> next_break_fits(doc, :enabled) - [] when last_arg_mode == :none -> doc + [_ | _] -> + concat_to_last_group(doc, ",") + + [] when last_arg_mode == :force_comma -> + concat_to_last_group(doc, ",") + + [] when last_arg_mode == :next_break_fits -> + doc + |> ungroup_if_group() + |> group() + |> next_break_fits(:enabled) + + [] when last_arg_mode == :none -> + doc end {{doc, @empty, 1}, state} @@ -2321,11 +2333,16 @@ defmodule Code.Formatter do defp with_next_break_fits(condition, doc, fun) do if condition do doc + |> group() |> next_break_fits(:enabled) |> fun.() + |> group() |> next_break_fits(:disabled) else - fun.(doc) + doc + |> group() + |> fun.() + |> group() end end diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index ea44c037b28..9a1a2bab155 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -787,6 +787,11 @@ defmodule Inspect.Algebra do @spec next_break_fits(t, :enabled | :disabled) :: doc_fits def next_break_fits(doc, mode \\ @next_break_fits) when is_doc(doc) and mode in [:enabled, :disabled] do + case doc do + doc_group(_, _) -> :ok + _ -> IO.warn("next_break_fits/2 expect a group as document") + end + doc_fits(doc, mode) end @@ -1019,17 +1024,20 @@ defmodule Inspect.Algebra do # # * flat - represents a document with breaks as flats (a break may fit, as it may break) # * break - represents a document with breaks as breaks (a break always fits, since it breaks) + # + # These other two modes only affect fitting: + # # * flat_no_break - represents a document with breaks as flat not allowed to enter in break mode # * break_no_flat - represents a document with breaks as breaks not allowed to enter in flat mode # @typep mode :: :flat | :flat_no_break | :break | :break_no_flat - @spec fits( + @spec fits?( width :: non_neg_integer() | :infinity, column :: non_neg_integer(), break? :: boolean(), entries - ) :: :fit | :no_fit | :break_next + ) :: boolean() when entries: maybe_improper_list( {integer(), mode(), t()} | :group_over, @@ -1041,79 +1049,71 @@ defmodule Inspect.Algebra do # # In case we have groups and the group fits, we need to consider the group # parent without the child breaks, hence {:tail, b?, t} below. - defp fits(w, k, b?, _) when k > w and b?, do: :no_fit - defp fits(_, _, _, []), do: :fit - defp fits(w, k, _, {:tail, b?, t}), do: fits(w, k, b?, t) + defp fits?(w, k, b?, _) when k > w and b?, do: false + defp fits?(_, _, _, []), do: true + defp fits?(w, k, _, {:tail, b?, t}), do: fits?(w, k, b?, t) - ## Flat no break - - defp fits(w, k, b?, [{i, _, doc_fits(x, :disabled)} | t]), - do: fits(w, k, b?, [{i, :flat_no_break, x} | t]) + ## Group over + # If we get to the end of the group and if fits, it is because + # something already broke elsewhere, so we can consider the group + # fits. This only appears when checking if a flex break and fitting. - defp fits(w, k, b?, [{i, :flat_no_break, doc_fits(x, _)} | t]), - do: fits(w, k, b?, [{i, :flat_no_break, x} | t]) + defp fits?(_w, _k, b?, [:group_over | _]), + do: b? - ## Breaks no flat + ## Flat no break - defp fits(w, k, b?, [{i, _, doc_fits(x, :enabled)} | t]), - do: fits(w, k, b?, [{i, :break_no_flat, x} | t]) + defp fits?(w, k, b?, [{i, _, doc_fits(x, :disabled)} | t]), + do: fits?(w, k, b?, [{i, :flat_no_break, x} | t]) - defp fits(w, k, b?, [{i, :break_no_flat, doc_force(x)} | t]), - do: fits(w, k, b?, [{i, :break_no_flat, x} | t]) + defp fits?(w, k, b?, [{i, :flat_no_break, doc_fits(x, _)} | t]), + do: fits?(w, k, b?, [{i, :flat_no_break, x} | t]) - defp fits(w, k, b?, [{i, :break_no_flat, x} | t]) - when x == :doc_line or (is_tuple(x) and elem(x, 0) == :doc_break) do - case fits(w, k, b?, [{i, :flat, x} | t]) do - :no_fit -> :break_next - fits -> fits - end - end + ## Breaks no flat - ## Group over - # If we get to the end of the group and if fits, it is because - # something already broke elsewhere, so we can consider the group - # fits. This only appears when checking if a flex break fits. + defp fits?(w, k, b?, [{i, _, doc_fits(x, :enabled)} | t]), + do: fits?(w, k, b?, [{i, :break_no_flat, x} | t]) - defp fits(_w, _k, true, [:group_over | _]), - do: :fit + defp fits?(w, k, b?, [{i, :break_no_flat, doc_force(x)} | t]), + do: fits?(w, k, b?, [{i, :break_no_flat, x} | t]) - defp fits(w, k, b?, [:group_over | t]), - do: fits(w, k, b?, t) + defp fits?(_, _, _, [{_, :break_no_flat, doc_break(_, _)} | _]), do: true + defp fits?(_, _, _, [{_, :break_no_flat, :doc_line} | _]), do: true ## Breaks - defp fits(_, _, _, [{_, :break, doc_break(_, _)} | _]), do: :fit - defp fits(_, _, _, [{_, :break, :doc_line} | _]), do: :fit + defp fits?(_, _, _, [{_, :break, doc_break(_, _)} | _]), do: true + defp fits?(_, _, _, [{_, :break, :doc_line} | _]), do: true - defp fits(w, k, b?, [{i, :break, doc_group(x, _)} | t]), - do: fits(w, k, b?, [{i, :flat, x} | {:tail, b?, t}]) + defp fits?(w, k, b?, [{i, :break, doc_group(x, _)} | t]), + do: fits?(w, k, b?, [{i, :flat, x} | {:tail, b?, t}]) ## Catch all - defp fits(w, _, _, [{i, _, :doc_line} | t]), do: fits(w, i, false, t) - defp fits(w, k, b?, [{_, _, :doc_nil} | t]), do: fits(w, k, b?, t) - defp fits(w, _, b?, [{i, _, doc_collapse(_)} | t]), do: fits(w, i, b?, t) - defp fits(w, k, b?, [{i, m, doc_color(x, _)} | t]), do: fits(w, k, b?, [{i, m, x} | t]) - defp fits(w, k, b?, [{_, _, doc_string(_, l)} | t]), do: fits(w, k + l, b?, t) - defp fits(w, k, b?, [{_, _, s} | t]) when is_binary(s), do: fits(w, k + byte_size(s), b?, t) - defp fits(_, _, _, [{_, _, doc_force(_)} | _]), do: :no_fit - defp fits(w, k, _, [{_, _, doc_break(s, _)} | t]), do: fits(w, k + byte_size(s), true, t) - defp fits(w, k, b?, [{i, m, doc_nest(x, _, :break)} | t]), do: fits(w, k, b?, [{i, m, x} | t]) + defp fits?(w, _, _, [{i, _, :doc_line} | t]), do: fits?(w, i, false, t) + defp fits?(w, k, b?, [{_, _, :doc_nil} | t]), do: fits?(w, k, b?, t) + defp fits?(w, _, b?, [{i, _, doc_collapse(_)} | t]), do: fits?(w, i, b?, t) + defp fits?(w, k, b?, [{i, m, doc_color(x, _)} | t]), do: fits?(w, k, b?, [{i, m, x} | t]) + defp fits?(w, k, b?, [{_, _, doc_string(_, l)} | t]), do: fits?(w, k + l, b?, t) + defp fits?(w, k, b?, [{_, _, s} | t]) when is_binary(s), do: fits?(w, k + byte_size(s), b?, t) + defp fits?(_, _, _, [{_, _, doc_force(_)} | _]), do: false + defp fits?(w, k, _, [{_, _, doc_break(s, _)} | t]), do: fits?(w, k + byte_size(s), true, t) + defp fits?(w, k, b?, [{i, m, doc_nest(x, _, :break)} | t]), do: fits?(w, k, b?, [{i, m, x} | t]) - defp fits(w, k, b?, [{i, m, doc_nest(x, j, _)} | t]), - do: fits(w, k, b?, [{apply_nesting(i, k, j), m, x} | t]) + defp fits?(w, k, b?, [{i, m, doc_nest(x, j, _)} | t]), + do: fits?(w, k, b?, [{apply_nesting(i, k, j), m, x} | t]) - defp fits(w, k, b?, [{i, m, doc_cons(x, y)} | t]), - do: fits(w, k, b?, [{i, m, x}, {i, m, y} | t]) + defp fits?(w, k, b?, [{i, m, doc_cons(x, y)} | t]), + do: fits?(w, k, b?, [{i, m, x}, {i, m, y} | t]) - defp fits(w, k, b?, [{i, m, doc_group(x, _)} | t]), - do: fits(w, k, b?, [{i, m, x} | {:tail, b?, t}]) + defp fits?(w, k, b?, [{i, m, doc_group(x, _)} | t]), + do: fits?(w, k, b?, [{i, m, x} | {:tail, b?, t}]) - defp fits(w, k, b?, [{i, m, doc_limit(x, :infinity)} | t]) when w != :infinity, - do: fits(:infinity, k, b?, [{i, :flat, x}, {i, m, doc_limit(empty(), w)} | t]) + defp fits?(w, k, b?, [{i, m, doc_limit(x, :infinity)} | t]) when w != :infinity, + do: fits?(:infinity, k, b?, [{i, :flat, x}, {i, m, doc_limit(empty(), w)} | t]) - defp fits(_w, k, b?, [{i, m, doc_limit(x, w)} | t]), - do: fits(w, k, b?, [{i, m, x} | t]) + defp fits?(_w, k, b?, [{i, m, doc_limit(x, w)} | t]), + do: fits?(w, k, b?, [{i, m, x} | t]) @spec format( width :: non_neg_integer() | :infinity, @@ -1129,17 +1129,13 @@ defmodule Inspect.Algebra do defp format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, k + byte_size(s), t)] defp format(w, k, [{i, m, doc_force(x)} | t]), do: format(w, k, [{i, m, x} | t]) - defp format(w, k, [{i, :flat_no_break, doc_fits(x, :enabled)} | t]), - do: format(w, k, [{i, :break_no_flat, x} | t]) - - defp format(w, k, [{i, m, doc_fits(x, _)} | t]), do: format(w, k, [{i, m, x} | t]) defp format(w, _, [{i, _, doc_collapse(max)} | t]), do: collapse(format(w, i, t), max, 0, i) # Flex breaks are conditional to the document and the mode defp format(w, k, [{i, m, doc_break(s, :flex)} | t]) do k = k + byte_size(s) - if w == :infinity or m == :flat or fits(w, k, true, t) != :no_fit do + if w == :infinity or m == :flat or fits?(w, k, true, t) do [s | format(w, k, t)] else [indent(i) | format(w, i, t)] @@ -1173,20 +1169,29 @@ defmodule Inspect.Algebra do format(w, k, [{i, :break, x} | t]) end - defp format(w, k, [{i, :break_no_flat, doc_group(x, _)} | t]) do - format(w, k, [{i, :break, x} | t]) - end - defp format(w, k, [{i, _, doc_group(x, _)} | t]) do - fits = if w == :infinity, do: :fit, else: fits(w, k, false, [{i, :flat, x}]) + if w == :infinity or fits?(w, k, false, [{i, :flat, x}]) do + format(w, k, [{i, :flat, x} | t]) + else + format(w, k, [{i, :break, x}, :group_over | t]) + end + end - case fits do - :fit -> format(w, k, [{i, :flat, x} | t]) - :no_fit -> format(w, k, [{i, :break, x}, :group_over | t]) - :break_next -> format(w, k, [{i, :flat_no_break, x}, :group_over | t]) + # Next break fits needs to do the fitting decision + # if it is in flat mode, as it may be the one responsible + # for the flat mode. + defp format(w, k, [{i, :flat, doc_fits(doc_group(x, _), :enabled)} | t]) do + if w == :infinity or fits?(w, k, false, [{i, :flat, x} | t]) do + format(w, k, [{i, :flat, x} | t]) + else + format(w, k, [{i, :break, x}, :group_over | t]) end end + defp format(w, k, [{i, m, doc_fits(x, _)} | t]) do + format(w, k, [{i, m, x} | t]) + end + # Limit is set to infinity and then reverts defp format(w, k, [{i, m, doc_limit(x, :infinity)} | t]) when w != :infinity do format(:infinity, k, [{i, :flat, x}, {i, m, doc_limit(empty(), w)} | t]) From c76e11e3e2fdf91b50968939ca512ef5b036c6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 6 Apr 2025 21:37:02 +0200 Subject: [PATCH 02/13] Refactor --- lib/elixir/lib/code/formatter.ex | 23 ++----- lib/elixir/lib/inspect/algebra.ex | 105 ++++++++++++++++++++---------- 2 files changed, 78 insertions(+), 50 deletions(-) diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index 55d857f62d8..49d0b234276 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -1264,7 +1264,7 @@ defmodule Code.Formatter do args_doc = if skip_parens? do left_doc - |> concat(next_break_fits(group(right_doc, :inherit), :enabled)) + |> concat(group(right_doc, :flex)) |> nest(:cursor, :break) else right_doc = @@ -1272,8 +1272,7 @@ defmodule Code.Formatter do |> nest(2, :break) |> concat(break("")) |> concat(")") - |> group(:inherit) - |> next_break_fits(:enabled) + |> group(:flex) concat(nest(left_doc, 2, :break), right_doc) end @@ -1316,13 +1315,11 @@ defmodule Code.Formatter do |> concat(args_doc) |> nest(2) |> concat(extra) - |> group() skip_parens? -> " " |> concat(args_doc) |> concat(extra) - |> group() true -> "(" @@ -1330,13 +1327,12 @@ defmodule Code.Formatter do |> nest(2, :break) |> concat(args_doc) |> concat(extra) - |> group() end if next_break_fits? do - {next_break_fits(doc, :disabled), state} + {group(doc, :strict), state} else - {doc, state} + {group(doc), state} end end @@ -1809,10 +1805,7 @@ defmodule Code.Formatter do concat_to_last_group(doc, ",") [] when last_arg_mode == :next_break_fits -> - doc - |> ungroup_if_group() - |> group() - |> next_break_fits(:enabled) + doc |> ungroup_if_group() |> group(:flex) [] when last_arg_mode == :none -> doc @@ -2333,11 +2326,9 @@ defmodule Code.Formatter do defp with_next_break_fits(condition, doc, fun) do if condition do doc - |> group() - |> next_break_fits(:enabled) + |> group(:flex) |> fun.() - |> group() - |> next_break_fits(:disabled) + |> group(:strict) else doc |> group() diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 9a1a2bab155..ce53f2eb256 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -247,7 +247,6 @@ defmodule Inspect.Algebra do @container_separator "," @tail_separator " |" @newline "\n" - @next_break_fits :enabled # Functional interface to "doc" records @@ -259,7 +258,6 @@ defmodule Inspect.Algebra do | doc_collapse | doc_color | doc_cons - | doc_fits | doc_force | doc_group | doc_nest @@ -291,16 +289,11 @@ defmodule Inspect.Algebra do quote do: {:doc_break, unquote(break), unquote(mode)} end - @typep doc_group :: {:doc_group, t, :inherit | :self} + @typep doc_group :: {:doc_group, t, :inherit | :self | :flex | :strict} defmacrop doc_group(group, mode) do quote do: {:doc_group, unquote(group), unquote(mode)} end - @typep doc_fits :: {:doc_fits, t, :enabled | :disabled} - defmacrop doc_fits(group, mode) do - quote do: {:doc_fits, unquote(group), unquote(mode)} - end - @typep doc_force :: {:doc_force, t} defmacrop doc_force(group) do quote do: {:doc_force, unquote(group)} @@ -321,7 +314,6 @@ defmodule Inspect.Algebra do :doc_collapse, :doc_color, :doc_cons, - :doc_fits, :doc_force, :doc_group, :doc_nest, @@ -784,15 +776,15 @@ defmodule Inspect.Algebra do """ @doc since: "1.6.0" - @spec next_break_fits(t, :enabled | :disabled) :: doc_fits - def next_break_fits(doc, mode \\ @next_break_fits) + @spec next_break_fits(t, :enabled | :disabled) :: doc_group + def next_break_fits(doc, mode \\ :enabled) when is_doc(doc) and mode in [:enabled, :disabled] do + mode = if mode == :enabled, do: :flex, else: :strict + case doc do - doc_group(_, _) -> :ok - _ -> IO.warn("next_break_fits/2 expect a group as document") + doc_group(doc, _) -> doc_group(doc, mode) + _ -> doc_group(doc, mode) end - - doc_fits(doc, mode) end @doc """ @@ -877,10 +869,21 @@ defmodule Inspect.Algebra do Returns a group containing the specified document `doc`. Documents in a group are attempted to be rendered together - to the best of the renderer ability. + to the best of the renderer ability. There are four types + of groups, described below. + + ## Group modes + + * `:self` - the group breaks if it doesn't fit the width + as a whole - The group mode can also be set to `:inherit`, which means it - automatically breaks if the parent group has broken too. + * `:flex` - the group breaks if part of it fits. This option + disables the `force_unfit/1` document. Any `:self` group + within behaves as `:flex` + + * `:strict` - the group breaks if it doesn't fit the width + as a whole and amy group within behaves as `:strict` + (in other words, this group disables any `:flex` inside) ## Examples @@ -907,8 +910,40 @@ defmodule Inspect.Algebra do iex> Inspect.Algebra.format(doc, 6) ["Hello,", "\n", "A", "\n", "B"] + ## Mode examples + + The different groups modes are used by Elixir's code formatter + to avoid breaking code at some specific locations. For example, + consider this code: + + some_function_call(%{..., key: value, ...}) + + Now imagine that this code does not fit its line. The code + formatter introduces breaks inside `(` and `)` and inside + `%{` and `}`, each within their own group. Therefore the + document would break as: + + some_function_call( + %{ + ..., + key: value, + ... + } + ) + + To address this, the formatter marks the outer group as flex. + This means the first break will consider the group which contains + the `(...)` as fit, skipping their new lines. So overall the code + is formatted as: + + some_function_call(%{ + ..., + key: value, + ... + }) + """ - @spec group(t, :self | :inherit) :: doc_group + @spec group(t, :self | :inherit | :flex | :strict) :: doc_group def group(doc, mode \\ :self) when is_doc(doc) do doc_group(doc, mode) end @@ -1063,15 +1098,18 @@ defmodule Inspect.Algebra do ## Flat no break - defp fits?(w, k, b?, [{i, _, doc_fits(x, :disabled)} | t]), + defp fits?(w, k, b?, [{i, _, doc_group(x, :strict)} | t]), do: fits?(w, k, b?, [{i, :flat_no_break, x} | t]) - defp fits?(w, k, b?, [{i, :flat_no_break, doc_fits(x, _)} | t]), + defp fits?(w, k, b?, [{i, :flat_no_break, doc_group(x, _)} | t]), do: fits?(w, k, b?, [{i, :flat_no_break, x} | t]) ## Breaks no flat - defp fits?(w, k, b?, [{i, _, doc_fits(x, :enabled)} | t]), + defp fits?(w, k, b?, [{i, _, doc_group(x, :flex)} | t]), + do: fits?(w, k, b?, [{i, :break_no_flat, x} | t]) + + defp fits?(w, k, b?, [{i, :break_no_flat, doc_group(x, _)} | t]), do: fits?(w, k, b?, [{i, :break_no_flat, x} | t]) defp fits?(w, k, b?, [{i, :break_no_flat, doc_force(x)} | t]), @@ -1169,29 +1207,26 @@ defmodule Inspect.Algebra do format(w, k, [{i, :break, x} | t]) end - defp format(w, k, [{i, _, doc_group(x, _)} | t]) do - if w == :infinity or fits?(w, k, false, [{i, :flat, x}]) do + defp format(w, k, [{i, :break, doc_group(x, :flex)} | t]) do + format(w, k, [{i, :break, x} | t]) + end + + defp format(w, k, [{i, :flat, doc_group(x, :flex)} | t]) do + if w == :infinity or fits?(w, k, false, [{i, :flat, x} | t]) do format(w, k, [{i, :flat, x} | t]) else format(w, k, [{i, :break, x}, :group_over | t]) end end - # Next break fits needs to do the fitting decision - # if it is in flat mode, as it may be the one responsible - # for the flat mode. - defp format(w, k, [{i, :flat, doc_fits(doc_group(x, _), :enabled)} | t]) do - if w == :infinity or fits?(w, k, false, [{i, :flat, x} | t]) do + defp format(w, k, [{i, _, doc_group(x, _)} | t]) do + if w == :infinity or fits?(w, k, false, [{i, :flat, x}]) do format(w, k, [{i, :flat, x} | t]) else format(w, k, [{i, :break, x}, :group_over | t]) end end - defp format(w, k, [{i, m, doc_fits(x, _)} | t]) do - format(w, k, [{i, m, x} | t]) - end - # Limit is set to infinity and then reverts defp format(w, k, [{i, m, doc_limit(x, :infinity)} | t]) when w != :infinity do format(:infinity, k, [{i, :flat, x}, {i, m, doc_limit(empty(), w)} | t]) @@ -1218,5 +1253,7 @@ defmodule Inspect.Algebra do defp apply_nesting(i, _, j), do: i + j defp indent(0), do: @newline - defp indent(i), do: @newline <> :binary.copy(" ", i) + + defp indent(i), + do: @newline <> :binary.copy(" ", i) end From 447fc8eb2e1095c98f6e1c4557d8965c401a8ca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 6 Apr 2025 22:28:53 +0200 Subject: [PATCH 03/13] mix format --- lib/elixir/lib/calendar/date_range.ex | 8 ++++++-- lib/elixir/lib/inspect/algebra.ex | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/elixir/lib/calendar/date_range.ex b/lib/elixir/lib/calendar/date_range.ex index 986070f8ed6..a92b9d124f5 100644 --- a/lib/elixir/lib/calendar/date_range.ex +++ b/lib/elixir/lib/calendar/date_range.ex @@ -171,8 +171,12 @@ defmodule Date.Range do when step < 0 and first_days < last_days, do: 0 - defp size(%Date.Range{first_in_iso_days: first_days, last_in_iso_days: last_days, step: step}), - do: abs(div(last_days - first_days, step)) + 1 + defp size(%Date.Range{ + first_in_iso_days: first_days, + last_in_iso_days: last_days, + step: step + }), + do: abs(div(last_days - first_days, step)) + 1 # TODO: Remove me on v2.0 defp size( diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index ce53f2eb256..44828d5a94b 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -1213,7 +1213,7 @@ defmodule Inspect.Algebra do defp format(w, k, [{i, :flat, doc_group(x, :flex)} | t]) do if w == :infinity or fits?(w, k, false, [{i, :flat, x} | t]) do - format(w, k, [{i, :flat, x} | t]) + format(w, k, [{i, :flat, x}, :group_over | t]) else format(w, k, [{i, :break, x}, :group_over | t]) end @@ -1221,7 +1221,7 @@ defmodule Inspect.Algebra do defp format(w, k, [{i, _, doc_group(x, _)} | t]) do if w == :infinity or fits?(w, k, false, [{i, :flat, x}]) do - format(w, k, [{i, :flat, x} | t]) + format(w, k, [{i, :flat, x}, :group_over | t]) else format(w, k, [{i, :break, x}, :group_over | t]) end From c2708c3ad6f06a57d46686f49fcf6a259b872985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 6 Apr 2025 23:12:24 +0200 Subject: [PATCH 04/13] More --- lib/elixir/lib/inspect/algebra.ex | 112 +++++++++++------------------- 1 file changed, 40 insertions(+), 72 deletions(-) diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 44828d5a94b..4a83c0b3449 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -237,8 +237,8 @@ defmodule Inspect.Algebra do Flex breaks, however, are re-evaluated on every occurrence and may still be rendered flat. See `break/1` and `flex_break/1` for more information. - This implementation also adds `force_unfit/1` and `next_break_fits/2` which - give more control over the document fitting. + This implementation also adds `force_unfit/1` and optimistic/pessimistic + groups which give more control over the document fitting. [0]: https://lindig.github.io/papers/strictly-pretty-2000.pdf @@ -289,7 +289,7 @@ defmodule Inspect.Algebra do quote do: {:doc_break, unquote(break), unquote(mode)} end - @typep doc_group :: {:doc_group, t, :inherit | :self | :flex | :strict} + @typep doc_group :: {:doc_group, t, :normal | :optimistic | :pessimistic | :inherit} defmacrop doc_group(group, mode) do quote do: {:doc_group, unquote(group), unquote(mode)} end @@ -736,50 +736,13 @@ defmodule Inspect.Algebra do @doc """ Considers the next break as fit. - - `mode` can be `:enabled` or `:disabled`. When `:enabled`, - it will consider the document as fit as soon as it finds - the next break, effectively cancelling the break. It will - also ignore any `force_unfit/1` in search of the next break. - - When disabled, it behaves as usual and it will ignore - any further `next_break_fits/2` instruction. - - ## Examples - - This is used by Elixir's code formatter to avoid breaking - code at some specific locations. For example, consider this - code: - - some_function_call(%{..., key: value, ...}) - - Now imagine that this code does not fit its line. The code - formatter introduces breaks inside `(` and `)` and inside - `%{` and `}`. Therefore the document would break as: - - some_function_call( - %{ - ..., - key: value, - ... - } - ) - - The formatter wraps the algebra document representing the - map in `next_break_fits/1` so the code is formatted as: - - some_function_call(%{ - ..., - key: value, - ... - }) - """ - @doc since: "1.6.0" + # TODO: Deprecate me on Elixir v1.23 + @doc deprecated: "Pass the optimistic/pessimistic type to group/2 instead" @spec next_break_fits(t, :enabled | :disabled) :: doc_group def next_break_fits(doc, mode \\ :enabled) when is_doc(doc) and mode in [:enabled, :disabled] do - mode = if mode == :enabled, do: :flex, else: :strict + mode = if mode == :enabled, do: :optimistic, else: :pessimistic case doc do doc_group(doc, _) -> doc_group(doc, mode) @@ -869,21 +832,24 @@ defmodule Inspect.Algebra do Returns a group containing the specified document `doc`. Documents in a group are attempted to be rendered together - to the best of the renderer ability. There are four types - of groups, described below. + to the best of the renderer ability. If there are `break/1`s + in the group and the group does not fit the given width, + the breaks are converted into lines. Otherwise the breaks + are rendered as text based on their string contents. + + There are three types of groups, described next. ## Group modes - * `:self` - the group breaks if it doesn't fit the width - as a whole + * `:normal` - the group fits if it fits within the given width - * `:flex` - the group breaks if part of it fits. This option - disables the `force_unfit/1` document. Any `:self` group - within behaves as `:flex` + * `:optimistic` - the group fits if it fits within the given + width. However, when nested within another group, the parent + group will assume this group fits as long as it has a single + break, even ignoring any `force_unfit/1` document - * `:strict` - the group breaks if it doesn't fit the width - as a whole and amy group within behaves as `:strict` - (in other words, this group disables any `:flex` inside) + * `:pessimistic` - the group fits if it fits within the given + width. However it disables any optimistic group within it ## Examples @@ -931,9 +897,9 @@ defmodule Inspect.Algebra do } ) - To address this, the formatter marks the outer group as flex. - This means the first break will consider the group which contains - the `(...)` as fit, skipping their new lines. So overall the code + To address this, the formatter marks the inner group as optimistic. + This means the first group, which is `(...)` will consider the document + fits and avoids adding breaks around the parens. So overall the code is formatted as: some_function_call(%{ @@ -943,9 +909,19 @@ defmodule Inspect.Algebra do }) """ - @spec group(t, :self | :inherit | :flex | :strict) :: doc_group - def group(doc, mode \\ :self) when is_doc(doc) do - doc_group(doc, mode) + @spec group(t, :normal | :optimistic | :pessimistic) :: doc_group + def group(doc, mode \\ :normal) when is_doc(doc) do + doc_group( + doc, + case mode do + # TODO: Deprecate :self and :inherit on Elixir v1.23 + :self -> :normal + :inherit -> :inherit + :flex -> :optimistic + :strict -> :pessimistic + mode -> mode + end + ) end @doc ~S""" @@ -1098,7 +1074,7 @@ defmodule Inspect.Algebra do ## Flat no break - defp fits?(w, k, b?, [{i, _, doc_group(x, :strict)} | t]), + defp fits?(w, k, b?, [{i, _, doc_group(x, :pessimistic)} | t]), do: fits?(w, k, b?, [{i, :flat_no_break, x} | t]) defp fits?(w, k, b?, [{i, :flat_no_break, doc_group(x, _)} | t]), @@ -1106,10 +1082,7 @@ defmodule Inspect.Algebra do ## Breaks no flat - defp fits?(w, k, b?, [{i, _, doc_group(x, :flex)} | t]), - do: fits?(w, k, b?, [{i, :break_no_flat, x} | t]) - - defp fits?(w, k, b?, [{i, :break_no_flat, doc_group(x, _)} | t]), + defp fits?(w, k, b?, [{i, _, doc_group(x, :optimistic)} | t]), do: fits?(w, k, b?, [{i, :break_no_flat, x} | t]) defp fits?(w, k, b?, [{i, :break_no_flat, doc_force(x)} | t]), @@ -1203,15 +1176,12 @@ defmodule Inspect.Algebra do format(w, k, t) end + # TODO: Deprecate me in Elixir v1.23 defp format(w, k, [{i, :break, doc_group(x, :inherit)} | t]) do format(w, k, [{i, :break, x} | t]) end - defp format(w, k, [{i, :break, doc_group(x, :flex)} | t]) do - format(w, k, [{i, :break, x} | t]) - end - - defp format(w, k, [{i, :flat, doc_group(x, :flex)} | t]) do + defp format(w, k, [{i, :flat, doc_group(x, :optimistic)} | t]) do if w == :infinity or fits?(w, k, false, [{i, :flat, x} | t]) do format(w, k, [{i, :flat, x}, :group_over | t]) else @@ -1253,7 +1223,5 @@ defmodule Inspect.Algebra do defp apply_nesting(i, _, j), do: i + j defp indent(0), do: @newline - - defp indent(i), - do: @newline <> :binary.copy(" ", i) + defp indent(i), do: @newline <> :binary.copy(" ", i) end From cc73fab9b852942e6ccbeda6d6091be22ebca2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 6 Apr 2025 23:14:12 +0200 Subject: [PATCH 05/13] Done --- lib/elixir/lib/code/formatter.ex | 12 ++++++------ lib/elixir/lib/inspect/algebra.ex | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex index 49d0b234276..7e1ea6ca876 100644 --- a/lib/elixir/lib/code/formatter.ex +++ b/lib/elixir/lib/code/formatter.ex @@ -1264,7 +1264,7 @@ defmodule Code.Formatter do args_doc = if skip_parens? do left_doc - |> concat(group(right_doc, :flex)) + |> concat(group(right_doc, :optimistic)) |> nest(:cursor, :break) else right_doc = @@ -1272,7 +1272,7 @@ defmodule Code.Formatter do |> nest(2, :break) |> concat(break("")) |> concat(")") - |> group(:flex) + |> group(:optimistic) concat(nest(left_doc, 2, :break), right_doc) end @@ -1330,7 +1330,7 @@ defmodule Code.Formatter do end if next_break_fits? do - {group(doc, :strict), state} + {group(doc, :pessimistic), state} else {group(doc), state} end @@ -1805,7 +1805,7 @@ defmodule Code.Formatter do concat_to_last_group(doc, ",") [] when last_arg_mode == :next_break_fits -> - doc |> ungroup_if_group() |> group(:flex) + doc |> ungroup_if_group() |> group(:optimistic) [] when last_arg_mode == :none -> doc @@ -2326,9 +2326,9 @@ defmodule Code.Formatter do defp with_next_break_fits(condition, doc, fun) do if condition do doc - |> group(:flex) + |> group(:optimistic) |> fun.() - |> group(:strict) + |> group(:pessimistic) else doc |> group() diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 4a83c0b3449..70909bedeef 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -917,8 +917,6 @@ defmodule Inspect.Algebra do # TODO: Deprecate :self and :inherit on Elixir v1.23 :self -> :normal :inherit -> :inherit - :flex -> :optimistic - :strict -> :pessimistic mode -> mode end ) From b676f2c055518639ab59535354cf66a4ddac73b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Sun, 6 Apr 2025 23:34:00 +0200 Subject: [PATCH 06/13] Tests --- lib/elixir/lib/inspect/algebra.ex | 3 +- .../test/elixir/inspect/algebra_test.exs | 36 ++++++------------- 2 files changed, 12 insertions(+), 27 deletions(-) diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 70909bedeef..7351dca4df0 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -846,7 +846,8 @@ defmodule Inspect.Algebra do * `:optimistic` - the group fits if it fits within the given width. However, when nested within another group, the parent group will assume this group fits as long as it has a single - break, even ignoring any `force_unfit/1` document + break, even if the optimistic group has a `force_unfit/1` + document within it * `:pessimistic` - the group fits if it fits within the given width. However it disables any optimistic group within it diff --git a/lib/elixir/test/elixir/inspect/algebra_test.exs b/lib/elixir/test/elixir/inspect/algebra_test.exs index faaa174655a..19ae0f1c54a 100644 --- a/lib/elixir/test/elixir/inspect/algebra_test.exs +++ b/lib/elixir/test/elixir/inspect/algebra_test.exs @@ -196,27 +196,14 @@ defmodule Inspect.AlgebraTest do test "group doc" do # Consistent with definitions - assert group("ab") == {:doc_group, "ab", :self} - assert group(empty()) == {:doc_group, empty(), :self} + assert group("ab") == {:doc_group, "ab", :normal} + assert group(empty()) == {:doc_group, empty(), :normal} # Consistent formatting doc = concat(glue(glue(glue("hello", "a"), "b"), "c"), "d") assert render(group(doc), 5) == "hello\na\nb\ncd" end - test "group doc with inherit" do - # Consistent with definitions - assert group("ab", :inherit) == {:doc_group, "ab", :inherit} - assert group(empty(), :inherit) == {:doc_group, empty(), :inherit} - - # Consistent formatting - doc = concat(glue(glue(group(glue("a", "b"), :self), "c"), "d"), "hello") - assert render(group(doc), 5) == "a b\nc\ndhello" - - doc = concat(glue(glue(group(glue("a", "b"), :inherit), "c"), "d"), "hello") - assert render(group(doc), 5) == "a\nb\nc\ndhello" - end - test "no limit doc" do doc = no_limit(group(glue(glue("hello", "a"), "b"))) assert render(doc, 5) == "hello a b" @@ -258,19 +245,16 @@ defmodule Inspect.AlgebraTest do assert force_unfit("ab") == {:doc_force, "ab"} assert force_unfit(empty()) == {:doc_force, empty()} - # Consistent with definitions - assert next_break_fits("ab") == {:doc_fits, "ab", :enabled} - assert next_break_fits(empty()) == {:doc_fits, empty(), :enabled} - assert next_break_fits("ab", :disabled) == {:doc_fits, "ab", :disabled} - assert next_break_fits(empty(), :disabled) == {:doc_fits, empty(), :disabled} - # Consistent formatting - doc = force_unfit(concat(glue(glue(glue("hello", "a"), "b"), "c"), "d")) - assert render(doc, 20) == "hello\na\nb\ncd" - assert render(next_break_fits(doc, :enabled), 20) == "hello a b cd" + doc = force_unfit(glue(glue("hello", "a"), "b")) + assert render(doc, 20) == "hello\na\nb" + assert render(doc |> glue("c") |> group(), 20) == "hello\na\nb\nc" + + assert render(doc |> group(:optimistic) |> glue("c") |> group(), 20) == + "hello\na\nb c" - assert render(next_break_fits(next_break_fits(doc, :enabled), :disabled), 20) == - "hello\na\nb\ncd" + assert render(doc |> group(:optimistic) |> glue("c") |> group(:pessimistic), 20) == + "hello\na\nb c" end test "formatting groups with lines" do From c11b2e9210dd5d8a631d0c733670e5b7a1f3d0d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 7 Apr 2025 09:15:04 +0200 Subject: [PATCH 07/13] Update lib/elixir/lib/inspect/algebra.ex --- lib/elixir/lib/inspect/algebra.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 7351dca4df0..61564774f81 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -918,7 +918,7 @@ defmodule Inspect.Algebra do # TODO: Deprecate :self and :inherit on Elixir v1.23 :self -> :normal :inherit -> :inherit - mode -> mode + mode when mode in [:normal, :optimistic, :pessimistic] -> mode end ) end From 4e459f097f96e45c548f51886c5b5a386ff17e6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 7 Apr 2025 09:29:19 +0200 Subject: [PATCH 08/13] Fixes --- lib/elixir/test/elixir/inspect/algebra_test.exs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/elixir/test/elixir/inspect/algebra_test.exs b/lib/elixir/test/elixir/inspect/algebra_test.exs index 19ae0f1c54a..ab3bc8ab00a 100644 --- a/lib/elixir/test/elixir/inspect/algebra_test.exs +++ b/lib/elixir/test/elixir/inspect/algebra_test.exs @@ -248,13 +248,15 @@ defmodule Inspect.AlgebraTest do # Consistent formatting doc = force_unfit(glue(glue("hello", "a"), "b")) assert render(doc, 20) == "hello\na\nb" - assert render(doc |> glue("c") |> group(), 20) == "hello\na\nb\nc" - assert render(doc |> group(:optimistic) |> glue("c") |> group(), 20) == - "hello\na\nb c" + assert render(doc |> glue("c") |> group(), 20) == + "hello\na\nb\nc" - assert render(doc |> group(:optimistic) |> glue("c") |> group(:pessimistic), 20) == + assert render(doc |> group(:optimistic) |> group() |> glue("c"), 20) == "hello\na\nb c" + + assert render(doc |> group(:optimistic) |> group(:pessimistic) |> glue("c"), 20) == + "hello\na\nb\nc" end test "formatting groups with lines" do From ccc9bdc6e2a9e88fbd5096de2843ed249ff0d4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 7 Apr 2025 09:33:39 +0200 Subject: [PATCH 09/13] Keep fixes --- lib/elixir/lib/inspect/algebra.ex | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 61564774f81..f4c6d77de5c 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -258,6 +258,7 @@ defmodule Inspect.Algebra do | doc_collapse | doc_color | doc_cons + | doc_fits | doc_force | doc_group | doc_nest @@ -294,6 +295,11 @@ defmodule Inspect.Algebra do quote do: {:doc_group, unquote(group), unquote(mode)} end + @typep doc_fits :: {:doc_fits, t, :enabled | :disabled} + defmacrop doc_fits(group, mode) do + quote do: {:doc_fits, unquote(group), unquote(mode)} + end + @typep doc_force :: {:doc_force, t} defmacrop doc_force(group) do quote do: {:doc_force, unquote(group)} @@ -314,6 +320,7 @@ defmodule Inspect.Algebra do :doc_collapse, :doc_color, :doc_cons, + :doc_fits, :doc_force, :doc_group, :doc_nest, @@ -742,12 +749,7 @@ defmodule Inspect.Algebra do @spec next_break_fits(t, :enabled | :disabled) :: doc_group def next_break_fits(doc, mode \\ :enabled) when is_doc(doc) and mode in [:enabled, :disabled] do - mode = if mode == :enabled, do: :optimistic, else: :pessimistic - - case doc do - doc_group(doc, _) -> doc_group(doc, mode) - _ -> doc_group(doc, mode) - end + doc_fits(doc, mode) end @doc """ @@ -1073,6 +1075,12 @@ defmodule Inspect.Algebra do ## Flat no break + defp fits?(w, k, b?, [{i, _, doc_fits(x, :disabled)} | t]), + do: fits?(w, k, b?, [{i, :flat_no_break, x} | t]) + + defp fits?(w, k, b?, [{i, :flat_no_break, doc_fits(x, _)} | t]), + do: fits?(w, k, b?, [{i, :flat_no_break, x} | t]) + defp fits?(w, k, b?, [{i, _, doc_group(x, :pessimistic)} | t]), do: fits?(w, k, b?, [{i, :flat_no_break, x} | t]) @@ -1081,6 +1089,9 @@ defmodule Inspect.Algebra do ## Breaks no flat + defp fits?(w, k, b?, [{i, _, doc_fits(x, :enabled)} | t]), + do: fits?(w, k, b?, [{i, :break_no_flat, x} | t]) + defp fits?(w, k, b?, [{i, _, doc_group(x, :optimistic)} | t]), do: fits?(w, k, b?, [{i, :break_no_flat, x} | t]) @@ -1138,7 +1149,7 @@ defmodule Inspect.Algebra do defp format(w, k, [{_, _, doc_string(s, l)} | t]), do: [s | format(w, k + l, t)] defp format(w, k, [{_, _, s} | t]) when is_binary(s), do: [s | format(w, k + byte_size(s), t)] defp format(w, k, [{i, m, doc_force(x)} | t]), do: format(w, k, [{i, m, x} | t]) - + defp format(w, k, [{i, m, doc_fits(x, _)} | t]), do: format(w, k, [{i, m, x} | t]) defp format(w, _, [{i, _, doc_collapse(max)} | t]), do: collapse(format(w, i, t), max, 0, i) # Flex breaks are conditional to the document and the mode From 0bd0520c8821b9a7aaa5aee58f1f5eb2488f54e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 7 Apr 2025 09:38:06 +0200 Subject: [PATCH 10/13] Better tests --- lib/elixir/test/elixir/inspect/algebra_test.exs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/elixir/test/elixir/inspect/algebra_test.exs b/lib/elixir/test/elixir/inspect/algebra_test.exs index ab3bc8ab00a..b01cd43ebef 100644 --- a/lib/elixir/test/elixir/inspect/algebra_test.exs +++ b/lib/elixir/test/elixir/inspect/algebra_test.exs @@ -252,11 +252,11 @@ defmodule Inspect.AlgebraTest do assert render(doc |> glue("c") |> group(), 20) == "hello\na\nb\nc" - assert render(doc |> group(:optimistic) |> group() |> glue("c"), 20) == - "hello\na\nb c" + assert render(doc |> group(:optimistic) |> glue("c") |> group() |> glue("d"), 20) == + "hello\na\nb c d" - assert render(doc |> group(:optimistic) |> group(:pessimistic) |> glue("c"), 20) == - "hello\na\nb\nc" + assert render(doc |> group(:optimistic) |> glue("c") |> group(:pessimistic) |> glue("d"), 20) == + "hello\na\nb c\nd" end test "formatting groups with lines" do From f2cb58b3c8d4f7d8696b4fdd3ea3d9be392a429f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 7 Apr 2025 09:42:40 +0200 Subject: [PATCH 11/13] More tests --- lib/elixir/test/elixir/inspect/algebra_test.exs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/elixir/test/elixir/inspect/algebra_test.exs b/lib/elixir/test/elixir/inspect/algebra_test.exs index b01cd43ebef..d1514f38d14 100644 --- a/lib/elixir/test/elixir/inspect/algebra_test.exs +++ b/lib/elixir/test/elixir/inspect/algebra_test.exs @@ -204,6 +204,20 @@ defmodule Inspect.AlgebraTest do assert render(group(doc), 5) == "hello\na\nb\ncd" end + test "group modes doc" do + doc = glue(glue("hello", "a"), "b") + assert render(doc, 10) == "hello a b" + + assert render(doc |> glue("c") |> group(), 10) == + "hello\na\nb\nc" + + assert render(doc |> group(:optimistic) |> glue("c") |> group() |> glue("d"), 10) == + "hello\na\nb c d" + + assert render(doc |> group(:optimistic) |> glue("c") |> group(:pessimistic) |> glue("d"), 10) == + "hello\na\nb c\nd" + end + test "no limit doc" do doc = no_limit(group(glue(glue("hello", "a"), "b"))) assert render(doc, 5) == "hello a b" From 6ab3b551b24cc4f472f5adb688d9028f18f0ee87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 7 Apr 2025 09:50:29 +0200 Subject: [PATCH 12/13] More docs --- lib/elixir/lib/inspect/algebra.ex | 8 +++++++- lib/elixir/test/elixir/inspect/algebra_test.exs | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index f4c6d77de5c..9ef6bc9a912 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -849,7 +849,13 @@ defmodule Inspect.Algebra do width. However, when nested within another group, the parent group will assume this group fits as long as it has a single break, even if the optimistic group has a `force_unfit/1` - document within it + document within it. Overall, this has an effect similar + to swapping the order groups break. For example, if you have + a `parent_group(child_group)` and they do not fit, the parent + will convert breaks into newlines, hoping the child group fits. + However, if the child group is optimistic, then the parent can + assume that it will fit, leaving the overall fitting decision + to the child * `:pessimistic` - the group fits if it fits within the given width. However it disables any optimistic group within it diff --git a/lib/elixir/test/elixir/inspect/algebra_test.exs b/lib/elixir/test/elixir/inspect/algebra_test.exs index d1514f38d14..e969bd0b35e 100644 --- a/lib/elixir/test/elixir/inspect/algebra_test.exs +++ b/lib/elixir/test/elixir/inspect/algebra_test.exs @@ -211,6 +211,9 @@ defmodule Inspect.AlgebraTest do assert render(doc |> glue("c") |> group(), 10) == "hello\na\nb\nc" + assert render(doc |> group() |> glue("c") |> group() |> glue("d"), 10) == + "hello a b\nc\nd" + assert render(doc |> group(:optimistic) |> glue("c") |> group() |> glue("d"), 10) == "hello\na\nb c d" From 72e557ad467b4b8f0ade7beab5776de93d5d7e21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Mon, 7 Apr 2025 09:56:17 +0200 Subject: [PATCH 13/13] More --- lib/elixir/lib/inspect/algebra.ex | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex index 9ef6bc9a912..d51310e665d 100644 --- a/lib/elixir/lib/inspect/algebra.ex +++ b/lib/elixir/lib/inspect/algebra.ex @@ -852,10 +852,10 @@ defmodule Inspect.Algebra do document within it. Overall, this has an effect similar to swapping the order groups break. For example, if you have a `parent_group(child_group)` and they do not fit, the parent - will convert breaks into newlines, hoping the child group fits. - However, if the child group is optimistic, then the parent can - assume that it will fit, leaving the overall fitting decision - to the child + converts breaks into newlines first, allowing the child to compute + if it fits. However, if the child group is optimistic and it + has breaks, then the parent assumes it fits, leaving the overall + fitting decision to the child * `:pessimistic` - the group fits if it fits within the given width. However it disables any optimistic group within it