Macro error (invalid literal) inside <<>>

I want to do something like this

defmodule Test do
  defmacro bool_to_int(x) do
    quote do
      case unquote(x) do
        true -> 1
        false -> 0
      end
    end
  end

  defmacro encode_flag(b) do
    quote(do: [bool_to_int(unquote(b))::1])
  end

  def encode(final?, data) do
    <<encode_flag(final?), data::bits>>
  end
end

But it raises

** (CompileError) iex:35: invalid literal [case(final?) do
  true ->
    1
  false ->
    0
end :: 1] in <<>>
    (elixir) src/elixir_bitstring.erl:32: :elixir_bitstring.expand/6
    (elixir) src/elixir_bitstring.erl:12: :elixir_bitstring.expand/4
    iex:34: (module)

Is there a way around this? I’d like to have a helper macro to easily encode flags into bitstrings.

And since <<(if true, do: 1, else: 0)::1>> works fine, I guess it should be possible.

remove the wrapping list from encode_flag/1

defmodule Test do
  defmacro bool_to_int(x) do
    quote do
      case unquote(x) do
        true -> 1
        false -> 0
      end
    end
  end

  defmacro encode_flag(b) do
    quote(do: bool_to_int(unquote(b))::1)
  end

  def encode(final?, data) do
    <<encode_flag(final?), data::bits>>
  end
end

raises

** (CompileError) iex:37: undefined function ::/2
    (stdlib) lists.erl:1338: :lists.foreach/2
    (stdlib) erl_eval.erl:670: :erl_eval.do_apply/6
    (iex) lib/iex/evaluator.ex:219: IEx.Evaluator.handle_eval/5

or have I misunderstood what you meant?

Nope, you understood correctly. But as it seems, I have made some wrong assumptions about expansion order here. <<>> seems to not expand before scanning its elements…

but since you can nest binaries the following should do:

defmacro encode_flag(b) do
  quote(do: <<bool_to_int(unquote(b))::1>>)
end
1 Like