Skip to content

Crash conforming keyword list containing function value #93

@garthk

Description

@garthk
import Norm

key_opt_spec = {:keys, coll_of(one_of(~w"a b c"a))}
fun_opt_spec = {:fun, spec(is_function(1))}
opts_spec = coll_of(one_of([key_opt_spec, fun_opt_spec]))

conform!([{:fun, fn _ -> "auto" end}], opts_spec)

crashes with:

** (Protocol.UndefinedError) protocol Enumerable not implemented for #Function<44.97283095/1 in :erl_eval.expr/5> of type Function, only anonymous functions of arity 2 are enumerable. This protocol is implemented for the following type(s): Ecto.Adapters.SQL.Stream, Postgrex.Stream, DBConnection.Stream, DBConnection.PrepareStream, Timex.Interval, Matrex, StreamData, HashSet, Range, Map, Function, List, Stream, Date.Range, HashDict, GenEvent.Stream, MapSet, File.Stream, IO.Stream
    (elixir 1.10.4) lib/enum.ex:3731: Enumerable.Function.reduce/3
    (elixir 1.10.4) lib/enum.ex:605: Enum.count/1
    (norm 0.12.0) lib/norm/core/collection.ex:57: Norm.Conformer.Conformable.Norm.Core.Collection.check_counts/3
    (norm 0.12.0) lib/norm/core/collection.ex:18: Norm.Conformer.Conformable.Norm.Core.Collection.conform/3
    (elixir 1.10.4) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
    (elixir 1.10.4) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
    (norm 0.12.0) lib/norm/conformer.ex:99: Norm.Conformer.Conformable.Tuple.conform/3
    (elixir 1.10.4) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
    (norm 0.12.0) lib/norm/core/any_of.ex:18: Norm.Conformer.Conformable.Norm.Core.AnyOf.conform/3
    (elixir 1.10.4) lib/enum.ex:1396: Enum."-map/2-lists^map/1-0-"/2
    (norm 0.12.0) lib/norm/core/collection.ex:22: Norm.Conformer.Conformable.Norm.Core.Collection.conform/3
    (norm 0.12.0) lib/norm.ex:62: Norm.conform!/2

Enumerable.impl_for/1 for (fn _ -> "auto" end) returns Enumerable.Function, which is good enough for your check_enumerable/3 in norm/lib/norm/core/collection.ex, but when you try to actually enumerate it it'll blow up in your face:

    defp check_enumerable(input, path, _opts) do
      if Enumerable.impl_for(input) == nil do
        {:error, [Conformer.error(path, input, "not enumerable")]}
      else
        :ok
      end
    end
defimpl Enumerable, for: Function do
  def count(_function), do: {:error, __MODULE__}
  def member?(_function, _value), do: {:error, __MODULE__}
  def slice(_function), do: {:error, __MODULE__}

  def reduce(function, acc, fun) when is_function(function, 2), do: function.(acc, fun)

  def reduce(function, _acc, _fun) do
    raise Protocol.UndefinedError,
      protocol: @protocol,
      value: function,
      description: "only anonymous functions of arity 2 are enumerable"
  end
end

Further up the stack, we're trying to enumerate the function because in lib/norm/conformer.ex:99 you're trying to do this:

Conformable.conform(spec, elem(input, i), path ++ [I])

… which, if you expand the arguments, is:

Norm.Conformer.Conformable.conform(
  #Norm.CollOf<#Norm.OneOf<[:a, :b, :c]>>,
  #Function<44.97283095/1 in :erl_eval.expr/5>,
  [0, 1]
)

I lack the time to figure out why you're trying to conform the :fun to the spec of the :key. I'm going to have to just rip the @contract off for now, as it's preventing routine maintenance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions