OTP-21.1 vs OTP-22 - same code different result

Hi, I am a little bit confused.

I have this code snippet.

defmodule Foo do
  def bar(x)
      when is_map(x) or
             is_tuple(x) or
             is_pid(x) or
             is_port(x) or
             is_reference(x) or
             is_function(x) or
             (is_bitstring(x) and not is_binary(x)) do
    IO.puts("one: #{x}")
  end

  def bar(x) do
    IO.puts("two: #{x}")
  end
end

Foo.bar("hello")

If I run this with OTP-21.1 and OTP-22 I get different results.

iex script.exs
Erlang/OTP 22 [erts-10.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

one: hello
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
iex script.exs
Erlang/OTP 21 [erts-10.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

two: hello
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>

I have no clue why.

I am using asdf, maybe I do something wrong when I am changing the versions.

5 Likes

Could you please check the results of is_binary("hello") and is_bitstring("hello") on each version?

Both in both versions true.

I hadn’t expect that…

So with other words, in 22 some of these will print true, while its wont in 21…

x = "hello"
IO.inpsect is_map(x), label: :map
IO.inpsect is_tuple(x), label: :tuple
IO.inspect is_pid(x), label: :pid
IO.inspect is_port(x), label: :port
IO.inspect is_reference(x), label: :ref
IO.inspect is_function(x), label: :fun

I don’t know. I have changed the snippet now.

defmodule Foo do
  def bar(x) when is_bitstring(x) and not is_binary(x) do
    IO.puts("one: #{x}")
  end

  def bar(x) do
    IO.puts("two: #{x}")
  end
end

Foo.bar("hello")

x = "hello"
guard = is_bitstring(x) and not is_binary(x)
IO.inspect(guard, label: :guard)

and get this

Erlang/OTP 21 [erts-10.1] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

two: hello
guard: false
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
Erlang/OTP 22 [erts-10.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

one: hello
guard: false
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
1 Like

Could you please tell us the result of quote do is_bitstring(x) and not is_binary(x) end?

I think its not an OTP issue, but rather elixir, a slight change in parsing behaviour between 1.8.1 and 1.8.2 of elixir…

1 Like

(and/or maybe do a quick install of 1.8.1 and see what results are…)

As I have seen both time the unexpected result of one: hello with a local test on elixir 1.8.1 and 1.8.2, both OTP-22, I’m now trying to reproduce something similar on pure erlang.

The qzote is the same for both versions.

{:and, [context: Elixir, import: Kernel],
 [
   {:is_bitstring, [context: Elixir, import: Kernel], [{:x, [], Elixir}]},
   {:not, [context: Elixir, import: Kernel],
    [{:is_binary, [context: Elixir, import: Kernel], [{:x, [], Elixir}]}]}
 ]}
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)
{:and, [context: Elixir, import: Kernel],
 [
   {:is_bitstring, [context: Elixir, import: Kernel], [{:x, [], Elixir}]},
   {:not, [context: Elixir, import: Kernel],
    [{:is_binary, [context: Elixir, import: Kernel], [{:x, [], Elixir}]}]}
 ]}
Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)

This seems most likely. You need to be using an Elixir that was compiled with the same version of OTP that you are using at runtime. That means switching between elixir 1.8.1-otp22 and elixir 1.8.1 as you switch OTP. Is that what you are doing? You are definitely getting the wrong result on OTP 22.

This is not true. An elixir compiled on OTP 20 can work well on an OTP 21 or 22 and usually should not create any unwanted effects besides not using features that are available on the current platform.

Can’t reproduce with equivalent Erlang code for any minor release since 21.1.

So it really seems to be related to how you switch versions, could you tell us how you do it exactly?

1 Like

Like I said, I’m doing it with asdf.

$> asdf current elixir
1.8.2-otp-22 (set by /Users/kruse/.tool-versions)
$> asdf current erlang
22.0 (set by /Users/kruse/.tool-versions)
# change version
$> asdf local elixir 1.8.1
$> asdf local erlang 21.1

After changing the version, how do you run your program then?

1 Like

It is just a script and I run it with iex script.exs.

If I change

def bar(x) when is_bitstring(x) and (not is_binary(x)) do

to

def bar(x) when (not is_binary(x)) and is_bitstring(x) do

everything runs as expected.

1 Like

@Marcus, thanks for noticing this bug.

It is a bug in an optimization pass in the new compiler in OTP 22. I am not sure what I was thinking when I wrote that code, and we didn’t catch it in our internal tests.

This pull request fixes the bug.

26 Likes

Awesome! Which OTP version will include the fix? 22.0.2?

You gotta love those reflexes! :heart: :purple_heart:

2 Likes

Yes, it will probably be in 22.0.2.

1 Like