# SPDX-FileCopyrightText: 2019 ash contributors # # SPDX-License-Identifier: MIT defmodule Ash.Query.Function.If do @moduledoc """ If predicate is truthy, then the second argument is returned, otherwise the third. """ use Ash.Query.Function, name: :if, no_inspect?: true import Ash.Expr, only: [expr?: 1] def args, do: [[:boolean, :any], [:boolean, :any, :same]] def returns, do: [:same, :same] def evaluate_nil_inputs?, do: true def new([condition, block]) do args = if Keyword.keyword?(block) && Keyword.has_key?(block, :do) do if Keyword.has_key?(block, :else) do [condition, block[:do], block[:else]] else [condition, block[:do], nil] end else [condition, block, nil] end new(args) end def new([true, block, _else_block]), do: {:ok, block} def new([false, _block, else_block]), do: {:ok, else_block} def new([nil, _block, else_block]), do: {:ok, else_block} def new([condition, block, else_block]) do super([condition, block, else_block]) end def can_return_nil?(%{arguments: [_ | rest]}) do Enum.any?(rest, &Ash.Expr.can_return_nil?/1) end def evaluate(%{arguments: [true, when_true, _]}), do: {:known, when_true} def evaluate(%{arguments: [false, _, when_false]}), do: {:known, when_false} def evaluate(%{arguments: [nil, _, when_false]}), do: {:known, when_false} def evaluate(%{arguments: [_, when_true, _]}), do: {:known, when_true} def partial_evaluate(%{arguments: [false, _, when_false]}), do: {:ok, when_false} def partial_evaluate(%{arguments: [nil, _, when_false]}), do: {:ok, when_false} def partial_evaluate(%{arguments: [condition, when_true, _]} = fun) do if expr?(condition) do {:ok, fun} else {:ok, when_true} end end defimpl Inspect do import Inspect.Algebra def inspect(%{arguments: [condition, when_true, when_false]}, opts) do with {:ok, required_expr} <- is_thing_is_nil(condition), true <- required_expr == when_false, true <- is_required_error(when_true) do concat(["required!(", to_doc(required_expr, opts), ")"]) else _ -> {conds, final} = extract_cases(when_false) case conds do [] -> if is_nil(final) do concat([ nest( concat([ group(concat(["if ", to_doc(condition, opts), " do"])), line(), to_doc(when_true, opts) ]), 2 ), line(), "end" ]) else concat([ nest( concat([ group(concat(["if ", to_doc(condition, opts), " do"])), line(), to_doc(when_true, opts) ]), 2 ), line(), "else", nest( concat([ line(), to_doc(when_false, opts) ]), 2 ), line(), "end" ]) end conds -> conds = [{condition, when_true} | conds ++ [{true, final}]] concat( [ "cond do" ] ++ Enum.flat_map(conds, fn {cond, when_true} -> [ nest(concat([line(), to_doc(cond, opts), " ->"]), 2), nest(concat([line(), to_doc(when_true, opts)]), 4) ] end) ++ [ line(), "end" ] ) end end end defp is_thing_is_nil(%{name: :is_nil, args: [thing]}) do {:ok, thing} end defp is_thing_is_nil(%{name: :is_nil, arguments: [thing]}) do {:ok, thing} end defp is_thing_is_nil(%{operator: :is_nil, left: thing, right: true}) do {:ok, thing} end defp is_thing_is_nil(_) do :error end defp is_required_error(%{name: :error, args: [Ash.Error.Changes.Required | _]}), do: true defp is_required_error(%{name: :error, arguments: [Ash.Error.Changes.Required | _]}), do: true defp is_required_error(_), do: false defp extract_cases(other, list_acc \\ []) defp extract_cases(%{name: :if, arguments: [condition, when_true, when_false]}, list_acc) do extract_cases(when_false, [{condition, when_true} | list_acc]) end defp extract_cases(%{name: :if, args: [condition, blocks]}, list_acc) do {:ok, if_func} = Ash.Query.Function.If.new([condition, blocks]) extract_cases(if_func, list_acc) end defp extract_cases(other, list_acc) do {Enum.reverse(list_acc), other} end end end