Skip to content

Compilation freezes #13742

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
hissssst opened this issue Jul 28, 2024 · 2 comments
Closed

Compilation freezes #13742

hissssst opened this issue Jul 28, 2024 · 2 comments

Comments

@hissssst
Copy link
Contributor

Elixir and Erlang/OTP versions

Erlang/OTP 27 [erts-15.0] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]

Elixir 1.17.2 (compiled with Erlang/OTP 27)

Operating system

linux

Current behavior

Create an empty project, insert this code and try to compile it by calling mix. Compilation will freeze

defmodule Xxx do
  def hello do
    variable = :x
    fn
      :delete, {x, function} ->
        try do
          {:ok,
           x
           |> case do
             %{x: value} = map ->
               %{
                 map
                 | x:
                     value
                     |> case do
                       %{^variable => value} = map ->
                         case function.(value) do
                           {:ok, new_value} -> %{map | variable => new_value}
                           :delete_me -> Map.delete(map, variable)
                           :error -> throw(:path_not_found)
                         end

                       [{_, _} | _] = keyword when is_atom(variable) ->
                         Pathex.Builder.SimpleDeleter.keyword_update(keyword, variable, function)

                       list when is_list(list) and is_integer(variable) and variable >= 0 ->
                         if variable >= length(list) do
                           throw(:path_not_found)
                         else
                           case function.(:lists.nth(variable + 1, list)) do
                             {:ok, new_value} -> List.replace_at(list, variable, new_value)
                             :delete_me -> List.delete_at(list, variable)
                             :error -> throw(:path_not_found)
                           end
                         end

                       list when is_list(list) and is_integer(variable) and variable < 0 ->
                         index = length(list) + variable

                         if index < 0 do
                           throw(:path_not_found)
                         else
                           case function.(:lists.nth(index + 1, list)) do
                             {:ok, new_value} -> List.replace_at(list, index, new_value)
                             :delete_me -> List.delete_at(list, index)
                             :error -> throw(:path_not_found)
                           end
                         end

                       tuple
                       when is_tuple(tuple) and is_integer(variable) and variable >= 0 and
                              tuple_size(tuple) > variable ->
                         index = variable + 1

                         case function.(:erlang.element(index, tuple)) do
                           {:ok, new_value} -> :erlang.setelement(index, tuple, new_value)
                           :delete_me -> :erlang.delete_element(index, tuple)
                           :error -> throw(:path_not_found)
                         end

                       tuple
                       when is_tuple(tuple) and is_integer(variable) and variable < 0 and
                              tuple_size(tuple) >= -variable ->
                         index = tuple_size(tuple) + variable + 1

                         case function.(:erlang.element(index, tuple)) do
                           {:ok, new_value} -> :erlang.setelement(index, tuple, new_value)
                           :delete_me -> :erlang.delete_element(index, tuple)
                           :error -> throw(:path_not_found)
                         end

                       _ ->
                         throw(:path_not_found)
                     end
               }

             [{a, _} | _] = keyword when is_atom(a) ->
               Pathex.Builder.Setter.keyword_update(keyword, :x, fn x ->
                 x
                 |> case do
                   %{^variable => value} = map ->
                     case function.(value) do
                       {:ok, new_value} -> %{map | variable => new_value}
                       :delete_me -> Map.delete(map, variable)
                       :error -> throw(:path_not_found)
                     end

                   [{_, _} | _] = keyword when is_atom(variable) ->
                     Pathex.Builder.SimpleDeleter.keyword_update(keyword, variable, function)

                   list when is_list(list) and is_integer(variable) and variable >= 0 ->
                     if variable >= length(list) do
                       throw(:path_not_found)
                     else
                       case function.(:lists.nth(variable + 1, list)) do
                         {:ok, new_value} -> List.replace_at(list, variable, new_value)
                         :delete_me -> List.delete_at(list, variable)
                         :error -> throw(:path_not_found)
                       end
                     end

                   list when is_list(list) and is_integer(variable) and variable < 0 ->
                     index = length(list) + variable

                     if index < 0 do
                       throw(:path_not_found)
                     else
                       case function.(:lists.nth(index + 1, list)) do
                         {:ok, new_value} -> List.replace_at(list, index, new_value)
                         :delete_me -> List.delete_at(list, index)
                         :error -> throw(:path_not_found)
                       end
                     end

                   tuple
                   when is_tuple(tuple) and is_integer(variable) and variable >= 0 and
                          tuple_size(tuple) > variable ->
                     index = variable + 1

                     case function.(:erlang.element(index, tuple)) do
                       {:ok, new_value} -> :erlang.setelement(index, tuple, new_value)
                       :delete_me -> :erlang.delete_element(index, tuple)
                       :error -> throw(:path_not_found)
                     end

                   tuple
                   when is_tuple(tuple) and is_integer(variable) and variable < 0 and
                          tuple_size(tuple) >= -variable ->
                     index = tuple_size(tuple) + variable + 1

                     case function.(:erlang.element(index, tuple)) do
                       {:ok, new_value} -> :erlang.setelement(index, tuple, new_value)
                       :delete_me -> :erlang.delete_element(index, tuple)
                       :error -> throw(:path_not_found)
                     end

                   _ ->
                     throw(:path_not_found)
                 end
               end)

             _ ->
               throw(:path_not_found)
           end}
        catch
          :path_not_found -> :error
        end

      :inspect, _ ->
        {:path, [], [{:/, [], [:x, variable]}]}

      :update, {x, function} ->
        try do
          {:ok,
           x
           |> case do
             %{x: value} = map ->
               %{
                 map
                 | x:
                     value
                     |> case do
                       %{^variable => value} = map ->
                         %{
                           map
                           | variable =>
                               value
                               |> (function.()
                                   |> case do
                                     {:ok, value} -> value
                                     :error -> throw(:path_not_found)
                                   end)
                         }

                       keyword when is_list(keyword) and is_atom(variable) ->
                         Pathex.Builder.Setter.keyword_update(keyword, variable, fn x ->
                           x
                           |> (function.()
                               |> case do
                                 {:ok, value} -> value
                                 :error -> throw(:path_not_found)
                               end)
                         end)

                       list when is_list(list) and is_integer(variable) and variable >= 0 ->
                         if variable >= length(list) do
                           throw(:path_not_found)
                         else
                           List.update_at(list, variable, fn x ->
                             x
                             |> (function.()
                                 |> case do
                                   {:ok, value} -> value
                                   :error -> throw(:path_not_found)
                                 end)
                           end)
                         end

                       list when is_list(list) and is_integer(variable) and variable < 0 ->
                         if -variable > length(list) do
                           throw(:path_not_found)
                         else
                           List.update_at(list, variable, fn x ->
                             x
                             |> (function.()
                                 |> case do
                                   {:ok, value} -> value
                                   :error -> throw(:path_not_found)
                                 end)
                           end)
                         end

                       tuple
                       when is_tuple(tuple) and is_integer(variable) and variable >= 0 and
                              tuple_size(tuple) > variable ->
                         indexplusone = variable + 1

                         val =
                           indexplusone
                           |> :erlang.element(tuple)
                           |> (function.()
                               |> case do
                                 {:ok, value} -> value
                                 :error -> throw(:path_not_found)
                               end)

                         :erlang.setelement(indexplusone, tuple, val)

                       tuple
                       when is_tuple(tuple) and is_integer(variable) and variable < 0 and
                              tuple_size(tuple) >= -variable ->
                         index = tuple_size(tuple) + variable + 1

                         val =
                           :erlang.element(index, tuple)
                           |> (function.()
                               |> case do
                                 {:ok, value} -> value
                                 :error -> throw(:path_not_found)
                               end)

                         :erlang.setelement(index, tuple, val)

                       _ ->
                         throw(:path_not_found)
                     end
               }

             [{a, _} | _] = keyword when is_atom(a) ->
               Pathex.Builder.Setter.keyword_update(keyword, :x, fn x ->
                 x
                 |> case do
                   %{^variable => value} = map ->
                     %{
                       map
                       | variable =>
                           value
                           |> (function.()
                               |> case do
                                 {:ok, value} -> value
                                 :error -> throw(:path_not_found)
                               end)
                     }

                   keyword when is_list(keyword) and is_atom(variable) ->
                     Pathex.Builder.Setter.keyword_update(keyword, variable, fn x ->
                       x
                       |> (function.()
                           |> case do
                             {:ok, value} -> value
                             :error -> throw(:path_not_found)
                           end)
                     end)

                   list when is_list(list) and is_integer(variable) and variable >= 0 ->
                     if variable >= length(list) do
                       throw(:path_not_found)
                     else
                       List.update_at(list, variable, fn x ->
                         x
                         |> (function.()
                             |> case do
                               {:ok, value} -> value
                               :error -> throw(:path_not_found)
                             end)
                       end)
                     end

                   list when is_list(list) and is_integer(variable) and variable < 0 ->
                     if -variable > length(list) do
                       throw(:path_not_found)
                     else
                       List.update_at(list, variable, fn x ->
                         x
                         |> (function.()
                             |> case do
                               {:ok, value} -> value
                               :error -> throw(:path_not_found)
                             end)
                       end)
                     end

                   tuple
                   when is_tuple(tuple) and is_integer(variable) and variable >= 0 and
                          tuple_size(tuple) > variable ->
                     indexplusone = variable + 1

                     val =
                       indexplusone
                       |> :erlang.element(tuple)
                       |> (function.()
                           |> case do
                             {:ok, value} -> value
                             :error -> throw(:path_not_found)
                           end)

                     :erlang.setelement(indexplusone, tuple, val)

                   tuple
                   when is_tuple(tuple) and is_integer(variable) and variable < 0 and
                          tuple_size(tuple) >= -variable ->
                     index = tuple_size(tuple) + variable + 1

                     val =
                       :erlang.element(index, tuple)
                       |> (function.()
                           |> case do
                             {:ok, value} -> value
                             :error -> throw(:path_not_found)
                           end)

                     :erlang.setelement(index, tuple, val)

                   _ ->
                     throw(:path_not_found)
                 end
               end)

             _ ->
               throw(:path_not_found)
           end}
        catch
          :path_not_found -> :error
        end

      :view, {x, function} ->
        x
        |> case do
          %{x: x} ->
            case x do
              %{^variable => x} ->
                function.(x)

              kwd when is_list(kwd) and is_atom(variable) ->
                with {:ok, value} <- Keyword.fetch(kwd, variable) do
                  function.(value)
                end

              list when is_list(list) and is_integer(variable) ->
                case Enum.at(list, variable, :__pathex_var_not_found__) do
                  :__pathex_var_not_found__ -> :error
                  value -> function.(value)
                end

              tuple
              when is_tuple(tuple) and is_integer(variable) and variable >= 0 and
                     tuple_size(tuple) > variable ->
                function.(elem(tuple, variable))

              tuple
              when is_tuple(tuple) and is_integer(variable) and variable < 0 and
                     tuple_size(tuple) >= -variable ->
                index = tuple_size(tuple) + variable + 1
                function.(:erlang.element(index, tuple))

              _ ->
                :error
            end

          kwd when is_list(kwd) ->
            with {:ok, value} <- Keyword.fetch(kwd, :x) do
              case value do
                %{^variable => x} ->
                  function.(x)

                kwd when is_list(kwd) and is_atom(variable) ->
                  with {:ok, value} <- Keyword.fetch(kwd, variable) do
                    function.(value)
                  end

                list when is_list(list) and is_integer(variable) ->
                  case Enum.at(list, variable, :__pathex_var_not_found__) do
                    :__pathex_var_not_found__ -> :error
                    value -> function.(value)
                  end

                tuple
                when is_tuple(tuple) and is_integer(variable) and variable >= 0 and
                       tuple_size(tuple) > variable ->
                  function.(elem(tuple, variable))

                tuple
                when is_tuple(tuple) and is_integer(variable) and variable < 0 and
                       tuple_size(tuple) >= -variable ->
                  index = tuple_size(tuple) + variable + 1
                  function.(:erlang.element(index, tuple))

                _ ->
                  :error
              end
            end

          _ ->
            :error
        end

      :force_update, {x, function, default} ->
        try do
          {:ok,
           x
           |> case do
             %{x: value} = map ->
               %{
                 map
                 | x:
                     value
                     |> case do
                       %{^variable => value} = map ->
                         %{
                           map
                           | variable =>
                               value
                               |> (function.()
                                   |> case do
                                     {:ok, value} -> value
                                     :error -> throw(:path_not_found)
                                   end)
                         }

                       keyword when is_list(keyword) and is_atom(variable) ->
                         Keyword.update(keyword, variable, default, fn val ->
                           val
                           |> (function.()
                               |> case do
                                 {:ok, value} -> value
                                 :error -> throw(:path_not_found)
                               end)
                         end)

                       list when is_list(list) and variable == -1 ->
                         [default | list]

                       list when is_list(list) and is_integer(variable) and variable < 0 ->
                         length = length(list)

                         if -variable > length do
                           len = max(-variable - 1 - length, 0)
                           [default | List.duplicate(nil, len)] ++ list
                         else
                           List.update_at(list, variable, fn x ->
                             x
                             |> (function.()
                                 |> case do
                                   {:ok, value} -> value
                                   :error -> throw(:path_not_found)
                                 end)
                           end)
                         end

                       list when is_list(list) and is_integer(variable) ->
                         length = length(list)

                         if variable > length do
                           len = max(variable - length, 0)
                           list ++ List.duplicate(nil, len) ++ [default]
                         else
                           List.update_at(list, variable, fn x ->
                             x
                             |> (function.()
                                 |> case do
                                   {:ok, value} -> value
                                   :error -> throw(:path_not_found)
                                 end)
                           end)
                         end

                       tuple
                       when is_tuple(tuple) and is_integer(variable) and variable >= 0 and
                              tuple_size(tuple) <= variable ->
                         len = max(variable - tuple_size(tuple), 0)
                         List.to_tuple(Tuple.to_list(tuple) ++ List.duplicate(nil, len) ++ [default])

                       tuple
                       when is_tuple(tuple) and is_integer(variable) and variable < 0 and
                              tuple_size(tuple) < -variable ->
                         len = max(-variable - tuple_size(tuple) - 1, 0)
                         List.to_tuple([default | List.duplicate(nil, len)] ++ Tuple.to_list(tuple))

                       tuple when is_tuple(tuple) and is_integer(variable) and variable < 0 ->
                         index = tuple_size(tuple) + variable + 1

                         val =
                           :erlang.element(index, tuple)
                           |> (function.()
                               |> case do
                                 {:ok, value} -> value
                                 :error -> throw(:path_not_found)
                               end)

                         :erlang.setelement(index, tuple, val)

                       tuple when is_tuple(tuple) and is_integer(variable) and variable >= 0 ->
                         indexplusone = variable + 1

                         val =
                           indexplusone
                           |> :erlang.element(tuple)
                           |> (function.()
                               |> case do
                                 {:ok, value} -> value
                                 :error -> throw(:path_not_found)
                               end)

                         :erlang.setelement(indexplusone, tuple, val)

                       %{} = other ->
                         Map.put(other, variable, default)

                       kwd when is_list(kwd) ->
                         [{variable, default} | kwd]

                       l when is_list(l) and variable == -1 ->
                         [default | l]

                       l when is_list(l) and is_integer(variable) and variable >= 0 ->
                         len = max(variable - length(l), 0)
                         l ++ List.duplicate(nil, len) ++ [default]

                       l when is_list(l) and is_integer(variable) ->
                         len = max(-variable - 1 - length(l), 0)
                         [default | List.duplicate(nil, len)] ++ l

                       t when is_tuple(t) and is_integer(variable) and variable >= 0 ->
                         len = variable - tuple_size(t)
                         List.to_tuple(Tuple.to_list(t) ++ List.duplicate(nil, len) ++ [default])

                       t when is_tuple(t) and is_integer(variable) ->
                         len = -variable - tuple_size(t) - 1
                         List.to_tuple([default | List.duplicate(nil, len)] ++ Tuple.to_list(t))

                       _ ->
                         throw(:path_not_found)
                     end
               }

             keyword when is_list(keyword) ->
               Keyword.update(keyword, :x, %{variable => default}, fn val ->
                 val
                 |> case do
                   %{^variable => value} = map ->
                     %{
                       map
                       | variable =>
                           value
                           |> (function.()
                               |> case do
                                 {:ok, value} -> value
                                 :error -> throw(:path_not_found)
                               end)
                     }

                   keyword when is_list(keyword) and is_atom(variable) ->
                     Keyword.update(keyword, variable, default, fn val ->
                       val
                       |> (function.()
                           |> case do
                             {:ok, value} -> value
                             :error -> throw(:path_not_found)
                           end)
                     end)

                   list when is_list(list) and variable == -1 ->
                     [default | list]

                   list when is_list(list) and is_integer(variable) and variable < 0 ->
                     length = length(list)

                     if -variable > length do
                       len = max(-variable - 1 - length, 0)
                       [default | List.duplicate(nil, len)] ++ list
                     else
                       List.update_at(list, variable, fn x ->
                         x
                         |> (function.()
                             |> case do
                               {:ok, value} -> value
                               :error -> throw(:path_not_found)
                             end)
                       end)
                     end

                   list when is_list(list) and is_integer(variable) ->
                     length = length(list)

                     if variable > length do
                       len = max(variable - length, 0)
                       list ++ List.duplicate(nil, len) ++ [default]
                     else
                       List.update_at(list, variable, fn x ->
                         x
                         |> (function.()
                             |> case do
                               {:ok, value} -> value
                               :error -> throw(:path_not_found)
                             end)
                       end)
                     end

                   tuple
                   when is_tuple(tuple) and is_integer(variable) and variable >= 0 and
                          tuple_size(tuple) <= variable ->
                     len = max(variable - tuple_size(tuple), 0)
                     List.to_tuple(Tuple.to_list(tuple) ++ List.duplicate(nil, len) ++ [default])

                   tuple
                   when is_tuple(tuple) and is_integer(variable) and variable < 0 and
                          tuple_size(tuple) < -variable ->
                     len = max(-variable - tuple_size(tuple) - 1, 0)
                     List.to_tuple([default | List.duplicate(nil, len)] ++ Tuple.to_list(tuple))

                   tuple when is_tuple(tuple) and is_integer(variable) and variable < 0 ->
                     index = tuple_size(tuple) + variable + 1

                     val =
                       :erlang.element(index, tuple)
                       |> (function.()
                           |> case do
                             {:ok, value} -> value
                             :error -> throw(:path_not_found)
                           end)

                     :erlang.setelement(index, tuple, val)

                   tuple when is_tuple(tuple) and is_integer(variable) and variable >= 0 ->
                     indexplusone = variable + 1

                     val =
                       indexplusone
                       |> :erlang.element(tuple)
                       |> (function.()
                           |> case do
                             {:ok, value} -> value
                             :error -> throw(:path_not_found)
                           end)

                     :erlang.setelement(indexplusone, tuple, val)

                   %{} = other ->
                     Map.put(other, variable, default)

                   kwd when is_list(kwd) ->
                     [{variable, default} | kwd]

                   l when is_list(l) and variable == -1 ->
                     [default | l]

                   l when is_list(l) and is_integer(variable) and variable >= 0 ->
                     len = max(variable - length(l), 0)
                     l ++ List.duplicate(nil, len) ++ [default]

                   l when is_list(l) and is_integer(variable) ->
                     len = max(-variable - 1 - length(l), 0)
                     [default | List.duplicate(nil, len)] ++ l

                   t when is_tuple(t) and is_integer(variable) and variable >= 0 ->
                     len = variable - tuple_size(t)
                     List.to_tuple(Tuple.to_list(t) ++ List.duplicate(nil, len) ++ [default])

                   t when is_tuple(t) and is_integer(variable) ->
                     len = -variable - tuple_size(t) - 1
                     List.to_tuple([default | List.duplicate(nil, len)] ++ Tuple.to_list(t))

                   _ ->
                     throw(:path_not_found)
                 end
               end)

             %{} = other ->
               Map.put(other, :x, %{variable => default})

             kwd when is_list(kwd) ->
               [{:x, %{variable => default}} | kwd]

             _ ->
               throw(:path_not_found)
           end}
        catch
          :path_not_found -> :error
        end
    end
  end
end

Expected behavior

This code doesn't freeze the compilation and is compiled successfully (as in 1.16, for example)

@josevalim
Copy link
Member

Did you also upgrade your Erlang/OTP version when comparing to 1.16? From an initial exploration it looks like an Erlang/OTP bug.

@josevalim
Copy link
Member

Ignore me, it is indeed on Elixir itself. :)

josevalim added a commit that referenced this issue Jul 28, 2024
Otherwise, the larger the context, the more expensive
grouping the warnings would get.

Closes #13742
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants