What’s going on here?

This time it’s not so hard to see: ++ only validates that its first argument is a list. The second argument can be anything, and whatever it is will be added as the tail to the generated list. Check this out…

5> [1,2,3] ++ something.

[1,2,3|something]

6>

It’s just how ++ works. What Fede found is just an edge case where the resulting list has no head, and therefore it’s only its tail. It’s as if we would have constructed [head|something] and requested its tail…

7> tl([head|something]).

something

8>

So, mystery solved. But to be fair, the only slightly relevant documentation I could find about this was the following one:

The list concatenation operator ++ appends its second argument to its first and returns the resulting list.

It might be a good thing to add some more implementation/spec details to the Erlang docs.

In any case, typer (and consequently dialyzer) seems to be aware of all this. Check this module annotated by typer with specs: