crash with `protocol Enumerable not implemented` error
marcandre opened this issue · comments
Marc-André Lafortune commented
Following the fixes in last version, I bravely continued on my quest but quickly reached an impasse.
defmodule EmptyMix do
defstruct [:name]
use TypeCheck, enable_runtime_checks: Mix.env() != :prod
@type! t :: %__MODULE__{name: String.t()}
@spec! hello(%{String.t() => any} | t()) :: atom
def hello(_struct), do: :ok
end
# Test:
defmodule EmptyMixTest do
use ExUnit.Case
doctest EmptyMix
test "this works" do
assert EmptyMix.hello(%{"a" => :b}) == :ok
end
test "this raises an error do
assert EmptyMix.hello(%EmptyMix{name: "X"}) == :ok
end
end
Error is:
** (Protocol.UndefinedError) protocol Enumerable not implemented for %EmptyMix{name: "X"} of type EmptyMix (a struct). This protocol is implemented for the following type(s): Date.Range, File.Stream, Function, GenEvent.Stream, HashDict, HashSet, IO.Stream, Jason.OrderedObject, List, Map, MapSet, Range, Stream, StreamData
code: assert EmptyMix.hello(%EmptyMix{name: "X"}) == :ok
stacktrace:
(elixir 1.14.0) lib/enum.ex:1: Enumerable.impl_for!/1
(elixir 1.14.0) lib/enum.ex:166: Enumerable.reduce/3
(elixir 1.14.0) lib/enum.ex:2514: Enum.reduce_while/3
(empty_mix 0.1.0) lib/type_check/spec.ex:54: EmptyMix.hello/1
test/empty_mix_test.exs:10: (test)
Qqwy / Marten commented
The error occurs when passing a struct to a union type of which a non-struct map is one of the possibilities.
This will result in the first 'is_map' check being true, but then when calling Enum.reduce_while
to check for the keys, balk at there not being an Enumerable
implementation.
Either we need Enum.to_list
:maps.to_list
in some places before iterating over the map key-value pairs, or we need a defensive check to ensure that a struct is not allowed to reach that point.