Skip to content

Crash conforming keyword list containing function value #93

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions