Packages
ash
3.4.2
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/query/boolean_expression.ex
defmodule Ash.Query.BooleanExpression do
@moduledoc "Represents a boolean expression"
@dialyzer {:nowarn_function, optimized_new: 3}
defstruct [:op, :left, :right]
alias Ash.Query.Operator.{Eq, In, NotEq}
alias Ash.Query.Ref
def new(_, nil, nil), do: false
def new(:or, left, nil), do: left
def new(:or, nil, right), do: right
def new(:and, _, nil), do: false
def new(:and, nil, _), do: false
def new(op, left, right) do
%__MODULE__{op: op, left: left, right: right}
end
# In many cases we could actually just return `true/false` directly because we know
# statements are contradictions. However, that would likely confuse users. For example:
# `Ash.Query.filter(Resource, x == 1 and x in [2, 3])` becomes `#Ash.Query<filter: false>`
# We may want to go down this route some day, but for now we simply use this to combine
# statements where possible, which helps with authorization logic that leverages the query
# For example, `x in [1, 2] or x == 3` becomes `x in [1, 2, 3]`, and `x in [1, 2, 3] and x != 1`
# becomes `x in [2, 3]`
def optimized_new(_, nil, nil), do: false
def optimized_new(:and, false, _), do: false
def optimized_new(:and, _, false), do: false
def optimized_new(:and, nil, _), do: false
def optimized_new(:and, _, nil), do: false
def optimized_new(:and, true, right), do: right
def optimized_new(:and, left, true), do: left
def optimized_new(:or, true, _), do: true
def optimized_new(:or, _, true), do: true
def optimized_new(:or, nil, right), do: right
def optimized_new(:or, left, nil), do: left
def optimized_new(
op,
%__MODULE__{op: op} = left_expr,
%__MODULE__{
op: op,
left: left,
right: right
}
) do
optimized_new(op, optimized_new(op, left_expr, left), right)
end
def optimized_new(op, %__MODULE__{} = left, %__MODULE__{} = right) do
do_new(op, left, right)
end
def optimized_new(op, left, %__MODULE__{} = right) do
optimized_new(op, right, left)
end
def optimized_new(op, %In{} = left, %NotEq{} = right) do
optimized_new(op, right, left)
end
def optimized_new(op, %In{} = left, %Eq{} = right) do
optimized_new(op, right, left)
end
def optimized_new(op, %Eq{right: %Ref{}} = left, right) do
do_new(op, left, right)
end
def optimized_new(op, left, %Eq{right: %Ref{}} = right) do
do_new(op, left, right)
end
def optimized_new(op, %NotEq{right: %Ref{}} = left, right) do
do_new(op, left, right)
end
def optimized_new(op, left, %NotEq{right: %Ref{}} = right) do
do_new(op, left, right)
end
def optimized_new(
:or,
%Eq{left: left, right: value} = left_op,
%In{left: left, right: %MapSet{} = mapset} = right
) do
if can_optimize?(value) do
%{right | right: MapSet.put(mapset, value)}
else
do_new(:or, left_op, right)
end
end
def optimized_new(
:and,
%Eq{left: left, right: value} = left_expr,
%In{left: left, right: %MapSet{} = mapset} = right
) do
if can_optimize?(value) do
if MapSet.member?(mapset, value) do
left_expr
else
do_new(:and, left_expr, right)
end
else
do_new(:and, left_expr, right)
end
end
def optimized_new(
:and,
%NotEq{left: left, right: value} = left_op,
%In{left: left, right: %MapSet{} = mapset} = right_expr
) do
if can_optimize?(value) do
%{right_expr | right: MapSet.delete(mapset, value)}
else
do_new(:and, left_op, right_expr)
end
end
def optimized_new(
op,
%NotEq{} = left,
%Eq{} = right
) do
optimized_new(op, right, left)
end
def optimized_new(
:or,
%Eq{left: left, right: left_value} = left_expr,
%Eq{left: left, right: right_value} = right_expr
) do
if can_optimize?(left_value) && can_optimize?(right_value) do
%In{left: left, right: MapSet.new([left_value, right_value])}
else
do_new(:or, left_expr, right_expr)
end
end
def optimized_new(
:and,
%Eq{left: left, right: left_value} = left_expr,
%Eq{left: left, right: right_value} = right_expr
) do
if can_optimize?(left_value) && can_optimize?(right_value) && left_value == right_value do
left_expr
else
do_new(:and, left_expr, right_expr)
end
end
def optimized_new(
:and,
%NotEq{left: left, right: left_value} = left_expr,
%NotEq{left: left, right: right_value} = right_expr
) do
if can_optimize?(left_value) && can_optimize?(right_value) && left_value == right_value do
left_value
else
do_new(:and, left_expr, right_expr)
end
end
def optimized_new(
:or,
%In{left: left, right: %MapSet{} = left_values},
%In{left: left, right: %MapSet{} = right_values} = right_expr
) do
%{right_expr | right: MapSet.union(left_values, right_values)}
end
def optimized_new(
:and,
%In{left: left, right: left_values} = left_expr,
%In{left: left, right: right_values} = right_expr
) do
if can_optimize?(left_values) && can_optimize?(right_values) do
intersection = MapSet.intersection(left_values, right_values)
case MapSet.size(intersection) do
0 -> false
1 -> %Eq{left: left, right: Enum.at(intersection, 0)}
_ -> %{right_expr | right: intersection}
end
else
do_new(:and, left_expr, right_expr)
end
end
def optimized_new(
:or,
%__MODULE__{op: :or, left: left, right: right} = left_expr,
right_expr
) do
case right_expr do
%In{} = in_op ->
with {:left, nil} <- {:left, Ash.Filter.find(left, &simplify?(&1, in_op), true, false)},
{:right, nil} <- {:right, Ash.Filter.find(right, &simplify?(&1, in_op), true, false)} do
do_new(:or, left_expr, in_op)
else
{:left, _} ->
%{left_expr | left: optimized_new(:or, left, in_op)}
{:right, _} ->
%{left_expr | right: optimized_new(:or, right, in_op)}
end
%Eq{} = eq_op ->
with {:left, nil} <- {:left, Ash.Filter.find(left, &simplify?(&1, eq_op), true, false)},
{:right, nil} <- {:right, Ash.Filter.find(right, &simplify?(&1, eq_op), true, false)} do
do_new(:or, left_expr, eq_op)
else
{:left, _} ->
%{left_expr | left: optimized_new(:or, left, eq_op)}
{:right, _} ->
%{left_expr | right: optimized_new(:or, right, eq_op)}
end
_ ->
do_new(:or, left_expr, right_expr)
end
end
def optimized_new(
:and,
%__MODULE__{op: :and, left: left, right: right} = left_expr,
right_expr
) do
case right_expr do
%In{} = in_op ->
with {:left, nil} <- {:left, Ash.Filter.find(left, &simplify?(&1, in_op), false, true)},
{:right, nil} <- {:right, Ash.Filter.find(right, &simplify?(&1, in_op), false, true)} do
do_new(:and, left_expr, in_op)
else
{:left, _} ->
%{left_expr | left: optimized_new(:and, left, in_op)}
{:right, _} ->
%{left_expr | right: optimized_new(:and, right, in_op)}
end
%Eq{} = eq_op ->
with {:left, nil} <- {:left, Ash.Filter.find(left, &simplify?(&1, eq_op), false, true)},
{:right, nil} <- {:right, Ash.Filter.find(right, &simplify?(&1, eq_op), false, true)} do
do_new(:and, left_expr, eq_op)
else
{:left, _} ->
%{left_expr | left: optimized_new(:and, left, eq_op)}
{:right, _} ->
%{left_expr | right: optimized_new(:and, right, eq_op)}
end
_ ->
do_new(:and, left_expr, right_expr)
end
end
def optimized_new(op, left, right) do
do_new(op, left, right)
end
defp simplify?(%NotEq{} = left, %In{} = right), do: simplify?(right, left)
defp simplify?(%NotEq{} = left, %Eq{} = right), do: simplify?(right, left)
defp simplify?(%Eq{} = left, %In{} = right), do: simplify?(right, left)
defp simplify?(%Eq{right: %Ref{}}, _), do: false
defp simplify?(_, %Eq{right: %Ref{}}), do: false
defp simplify?(%Eq{left: left, right: left_right}, %Eq{left: left, right: right_right}) do
can_optimize?(left_right) && can_optimize?(right_right)
end
defp simplify?(%NotEq{right: %Ref{}}, _), do: false
defp simplify?(_, %NotEq{right: %Ref{}}), do: false
defp simplify?(%NotEq{left: left, right: left_right}, %NotEq{left: left, right: right_right}) do
can_optimize?(left_right) && can_optimize?(right_right)
end
defp simplify?(
%Eq{left: left, right: left_right},
%In{left: left, right: %MapSet{}}
) do
can_optimize?(left_right)
end
defp simplify?(
%NotEq{left: left, right: left_right},
%In{left: left, right: %MapSet{}}
) do
can_optimize?(left_right)
end
defp simplify?(
%Eq{left: left, right: left_right},
%NotEq{left: left, right: %MapSet{}}
) do
can_optimize?(left_right)
end
defp simplify?(_, _), do: false
defp can_optimize?(value) when is_list(value) do
Enum.all?(value, &can_optimize?/1)
end
defp can_optimize?(map) when is_map(map) and not is_struct(map) do
Enum.all?(map, fn {key, val} ->
can_optimize?(key) && can_optimize?(val)
end)
end
defp can_optimize?(%MapSet{} = mapset) do
Enum.all?(mapset, &can_optimize?/1)
end
defp can_optimize?(value) do
!Ash.Expr.expr?(value)
end
defp do_new(op, left, right) do
if left == right do
left
else
%__MODULE__{
op: op,
left: left,
right: right
}
end
end
end
defimpl Inspect, for: Ash.Query.BooleanExpression do
import Inspect.Algebra
import Ash.Query.InspectHelpers
def inspect(
%{left: left, right: right, op: op},
opts
) do
container_type = container_type(opts)
opts = put_container_type(opts, op)
if container_type && op != container_type do
concat(["(", to_doc(left, opts), " ", to_string(op), " ", to_doc(right, opts), ")"])
else
concat([to_doc(left, opts), " ", to_string(op), " ", to_doc(right, opts)])
end
end
end