Let’s go code spelunking! Let’s start with Code.string_to_quoted because this has the general properties we want: it takes a string, and it will error if you do #4
https://github.com/elixir-lang/elixir/blob/3b93c6f969ba3dadfbe676445c080e25a3d4161b/lib/elixir/lib/code.ex#L898
case :elixir.string_to_tokens(to_charlist(string), line, column, file, opts) do
{:ok, tokens} ->
:elixir.tokens_to_quoted(tokens, file, opts)
{:error, _error_msg} = error ->
error
end
Here we find :elixir.string_to_tokens as well as :elixir.tokens_to_quoted so let’s dig in there and see if we can find the error generating bit. Let’s pop open an iex session to see if the first function returns an error:
iex(7)> file = Keyword.get(opts, :file, "nofile")
"nofile"
iex(8)> line = Keyword.get(opts, :line, 1)
1
iex(9)> column = Keyword.get(opts, :column, 1)
1
iex(10)> string = "if (false, [do: :this, else: :that])"
"if (false, [do: :this, else: :that])"
iex(11)> :elixir.string_to_tokens(to_charlist(string), line, column, file, opts)
{:ok,
[
{:identifier, {1, 1, nil}, :if},
{:"(", {1, 4, nil}},
{false, {1, 5, nil}},
{:",", {1, 10, 0}},
{:"[", {1, 12, nil}},
{:kw_identifier, {1, 13, nil}, :do},
{:atom, {1, 17, nil}, :this},
{:",", {1, 22, 0}},
{:kw_identifier, {1, 24, nil}, :else},
{:atom, {1, 30, nil}, :that},
{:"]", {1, 35, nil}},
{:")", {1, 36, nil}}
]}
Nope, no error. This does begin to answer your question though because we can see what it gets “parsed into”.
Let’s compare this to your #3 case:
iex(13)> :elixir.string_to_tokens(to_charlist(string), line, column, file, opts)
{:ok,
[
{:paren_identifier, {1, 1, nil}, :if},
{:"(", {1, 3, nil}},
{false, {1, 4, nil}},
{:",", {1, 9, 0}},
{:"[", {1, 11, nil}},
{:kw_identifier, {1, 12, nil}, :do},
{:atom, {1, 16, nil}, :this},
{:",", {1, 21, 0}},
{:kw_identifier, {1, 23, nil}, :else},
{:atom, {1, 29, nil}, :that},
{:"]", {1, 34, nil}},
{:")", {1, 35, nil}}
]}
Aha! Note the first item is now :parent_identifier vs :identifier. This likely gets used later.
Let’s move on to the :elixir.tokens_to_quoted function then:
https://github.com/elixir-lang/elixir/blob/3b93c6f969ba3dadfbe676445c080e25a3d4161b/lib/elixir/src/elixir.erl#L362
tokens_to_quoted(Tokens, File, Opts) ->
handle_parsing_opts(File, Opts),
try elixir_parser:parse(Tokens) of
OK mostly just going to :elixir_parser so let’s go there:
https://github.com/elixir-lang/elixir/blob/3b93c6f969ba3dadfbe676445c080e25a3d4161b/lib/elixir/src/elixir_parser.yrl
Aha! This is a big old yrl file so let’s just do the easy thing and search for the word “space” and see if our error shows up:
https://github.com/elixir-lang/elixir/blob/3b93c6f969ba3dadfbe676445c080e25a3d4161b/lib/elixir/src/elixir_parser.yrl#L1133-L1136
Sure does! Let’s see when that gets called:
https://github.com/elixir-lang/elixir/blob/3b93c6f969ba3dadfbe676445c080e25a3d4161b/lib/elixir/src/elixir_parser.yrl#L503-L504
Unfortunately here is where my ability to help starts to reach its limits. I don’t particularly know or understand how yrl files work, so beyond seeing that the column values between #3 and #4 are different, I don’t super duper know how it’s arriving at that case. Probably something to do with that :paren_identifier thing. Hopefully this is a good jumping off point though!
EDIT: Made a small edit to include output from the #3 case.