Skip to content

Commit

Permalink
Raise on left-over __cursor__
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed May 10, 2024
1 parent 9519a72 commit 203baf3
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 107 deletions.
9 changes: 9 additions & 0 deletions lib/elixir/lib/kernel/special_forms.ex
Expand Up @@ -1756,6 +1756,15 @@ defmodule Kernel.SpecialForms do
"""
defmacro unquote(:__block__)(args), do: error!([args])

@doc """
Internal special form for cursor position.
This is the special form used whenever we need to represent
the cursor position in Elixir's AST. See `Code.Fragment` for
more information.
"""
defmacro unquote(:__cursor__)(args), do: error!([args])

@doc """
Capture operator. Captures or creates an anonymous function.
Expand Down
4 changes: 4 additions & 0 deletions lib/elixir/src/elixir_expand.erl
Expand Up @@ -152,6 +152,8 @@ expand({{'.', DotMeta, [{'__ENV__', Meta, Atom}, Field]}, CallMeta, []}, S, E)
true -> {maps:get(Field, Env), S, E};
false -> {{{'.', DotMeta, [escape_map(Env), Field]}, CallMeta, []}, S, E}
end;
expand({'__cursor__', Meta, Args}, _S, E) when is_list(Args) ->
file_error(Meta, E, ?MODULE, '__cursor__');

%% Quote

Expand Down Expand Up @@ -1302,6 +1304,8 @@ format_error({parens_map_lookup, Map, Field, Context}) ->
[Context, 'Elixir.Macro':to_string(Map), Field]);
format_error({super_in_genserver, {Name, Arity}}) ->
io_lib:format("calling super for GenServer callback ~ts/~B is deprecated", [Name, Arity]);
format_error('__cursor__') ->
"reserved special form __cursor__ cannot be expanded, it is used exclusively to annotate ASTs";
format_error({parallel_bitstring_match, Expr}) ->
Message =
"binary patterns cannot be matched in parallel using \"=\", excess pattern: ~ts",
Expand Down
3 changes: 2 additions & 1 deletion lib/elixir/src/elixir_import.erl
Expand Up @@ -260,7 +260,9 @@ special_form('%', 2) -> true;
special_form('|', 2) -> true;
special_form('.', 2) -> true;
special_form('::', 2) -> true;
special_form('__aliases__', _) -> true;
special_form('__block__', _) -> true;
special_form('__cursor__', _) -> true;
special_form('->', _) -> true;
special_form('<<>>', _) -> true;
special_form('{}', _) -> true;
Expand All @@ -276,7 +278,6 @@ special_form('__CALLER__', 0) -> true;
special_form('__STACKTRACE__', 0) -> true;
special_form('__MODULE__', 0) -> true;
special_form('__DIR__', 0) -> true;
special_form('__aliases__', _) -> true;
special_form('quote', 1) -> true;
special_form('quote', 2) -> true;
special_form('unquote', 1) -> true;
Expand Down
216 changes: 110 additions & 106 deletions lib/elixir/test/elixir/code_fragment_test.exs
Expand Up @@ -1113,163 +1113,167 @@ defmodule CodeFragmentTest do
end

describe "container_cursor_to_quoted/2" do
def s2q(arg, opts \\ []), do: Code.string_to_quoted(arg, opts)
def cc2q(arg, opts \\ []), do: CF.container_cursor_to_quoted(arg, opts)
def s2q!(arg, opts \\ []), do: Code.string_to_quoted!(arg, opts)

def cc2q!(arg, opts \\ []) do
{:ok, res} = CF.container_cursor_to_quoted(arg, opts)
res
end

test "completes terminators" do
assert cc2q("(") == s2q("(__cursor__())")
assert cc2q("[") == s2q("[__cursor__()]")
assert cc2q("{") == s2q("{__cursor__()}")
assert cc2q("<<") == s2q("<<__cursor__()>>")
assert cc2q("foo do") == s2q("foo do __cursor__() end")
assert cc2q("foo do true else") == s2q("foo do true else __cursor__() end")
assert cc2q!("(") == s2q!("(__cursor__())")
assert cc2q!("[") == s2q!("[__cursor__()]")
assert cc2q!("{") == s2q!("{__cursor__()}")
assert cc2q!("<<") == s2q!("<<__cursor__()>>")
assert cc2q!("foo do") == s2q!("foo do __cursor__() end")
assert cc2q!("foo do true else") == s2q!("foo do true else __cursor__() end")
end

test "inside interpolation" do
assert cc2q(~S|"foo #{(|) == s2q(~S|"foo #{(__cursor__())}"|)
assert cc2q(~S|"foo #{"bar #{{|) == s2q(~S|"foo #{"bar #{{__cursor__()}}"}"|)
assert cc2q!(~S|"foo #{(|) == s2q!(~S|"foo #{(__cursor__())}"|)
assert cc2q!(~S|"foo #{"bar #{{|) == s2q!(~S|"foo #{"bar #{{__cursor__()}}"}"|)
end

test "keeps operators" do
assert cc2q("1 + 2") == s2q("1 + __cursor__()")
assert cc2q("&foo") == s2q("&__cursor__()")
assert cc2q("&foo/") == s2q("&foo/__cursor__()")
assert cc2q!("1 + 2") == s2q!("1 + __cursor__()")
assert cc2q!("&foo") == s2q!("&__cursor__()")
assert cc2q!("&foo/") == s2q!("&foo/__cursor__()")
end

test "keeps function calls without parens" do
assert cc2q("alias") == s2q("__cursor__()")
assert cc2q("alias ") == s2q("alias __cursor__()")
assert cc2q("alias foo") == s2q("alias __cursor__()")
assert cc2q("alias Foo.Bar") == s2q("alias __cursor__()")
assert cc2q("alias Foo.Bar,") == s2q("alias Foo.Bar, __cursor__()")
assert cc2q("alias Foo.Bar, as: ") == s2q("alias Foo.Bar, as: __cursor__()")
assert cc2q!("alias") == s2q!("__cursor__()")
assert cc2q!("alias ") == s2q!("alias __cursor__()")
assert cc2q!("alias foo") == s2q!("alias __cursor__()")
assert cc2q!("alias Foo.Bar") == s2q!("alias __cursor__()")
assert cc2q!("alias Foo.Bar,") == s2q!("alias Foo.Bar, __cursor__()")
assert cc2q!("alias Foo.Bar, as: ") == s2q!("alias Foo.Bar, as: __cursor__()")
end

test "do-end blocks" do
assert cc2q("foo do baz") == s2q("foo do __cursor__() end")
assert cc2q("foo do bar; baz") == s2q("foo do bar; __cursor__() end")
assert cc2q("foo do bar\nbaz") == s2q("foo do bar\n__cursor__() end")
assert cc2q!("foo do baz") == s2q!("foo do __cursor__() end")
assert cc2q!("foo do bar; baz") == s2q!("foo do bar; __cursor__() end")
assert cc2q!("foo do bar\nbaz") == s2q!("foo do bar\n__cursor__() end")

assert cc2q("foo(bar do baz") == s2q("foo(bar do __cursor__() end)")
assert cc2q("foo(bar do baz ") == s2q("foo(bar do baz(__cursor__()) end)")
assert cc2q("foo(bar do baz(") == s2q("foo(bar do baz(__cursor__()) end)")
assert cc2q("foo(bar do baz bat,") == s2q("foo(bar do baz(bat, __cursor__()) end)")
assert cc2q!("foo(bar do baz") == s2q!("foo(bar do __cursor__() end)")
assert cc2q!("foo(bar do baz ") == s2q!("foo(bar do baz(__cursor__()) end)")
assert cc2q!("foo(bar do baz(") == s2q!("foo(bar do baz(__cursor__()) end)")
assert cc2q!("foo(bar do baz bat,") == s2q!("foo(bar do baz(bat, __cursor__()) end)")

assert {:error, {_, "syntax error before: ", "'end'"}} = cc2q("foo(bar do baz, bat")
assert {:error, {_, "syntax error before: ", "'end'"}} =
CF.container_cursor_to_quoted("foo(bar do baz, bat")
end

test "keyword lists" do
assert cc2q("[bar: ") == s2q("[bar: __cursor__()]")
assert cc2q("[bar: baz,") == s2q("[bar: baz, __cursor__()]")
assert cc2q("[arg, bar: baz,") == s2q("[arg, bar: baz, __cursor__()]")
assert cc2q("[arg: val, bar: baz,") == s2q("[arg: val, bar: baz, __cursor__()]")

assert cc2q("{arg, bar: ") == s2q("{arg, bar: __cursor__()}")
assert cc2q("{arg, bar: baz,") == s2q("{arg, bar: baz, __cursor__()}")
assert cc2q("{arg: val, bar: baz,") == s2q("{arg: val, bar: baz, __cursor__()}")

assert cc2q("foo(bar: ") == s2q("foo(bar: __cursor__())")
assert cc2q("foo(bar: baz,") == s2q("foo([bar: baz, __cursor__()])")
assert cc2q("foo(arg, bar: ") == s2q("foo(arg, bar: __cursor__())")
assert cc2q("foo(arg, bar: baz,") == s2q("foo(arg, [bar: baz, __cursor__()])")
assert cc2q("foo(arg: val, bar: ") == s2q("foo(arg: val, bar: __cursor__())")
assert cc2q("foo(arg: val, bar: baz,") == s2q("foo([arg: val, bar: baz, __cursor__()])")

assert cc2q("foo bar: ") == s2q("foo(bar: __cursor__())")
assert cc2q("foo bar: baz,") == s2q("foo([bar: baz, __cursor__()])")
assert cc2q("foo arg, bar: ") == s2q("foo(arg, bar: __cursor__())")
assert cc2q("foo arg, bar: baz,") == s2q("foo(arg, [bar: baz, __cursor__()])")
assert cc2q("foo arg: val, bar: ") == s2q("foo(arg: val, bar: __cursor__())")
assert cc2q("foo arg: val, bar: baz,") == s2q("foo([arg: val, bar: baz, __cursor__()])")
assert cc2q!("[bar: ") == s2q!("[bar: __cursor__()]")
assert cc2q!("[bar: baz,") == s2q!("[bar: baz, __cursor__()]")
assert cc2q!("[arg, bar: baz,") == s2q!("[arg, bar: baz, __cursor__()]")
assert cc2q!("[arg: val, bar: baz,") == s2q!("[arg: val, bar: baz, __cursor__()]")

assert cc2q!("{arg, bar: ") == s2q!("{arg, bar: __cursor__()}")
assert cc2q!("{arg, bar: baz,") == s2q!("{arg, bar: baz, __cursor__()}")

assert cc2q!("foo(bar: ") == s2q!("foo(bar: __cursor__())")
assert cc2q!("foo(bar: baz,") == s2q!("foo([bar: baz, __cursor__()])")
assert cc2q!("foo(arg, bar: ") == s2q!("foo(arg, bar: __cursor__())")
assert cc2q!("foo(arg, bar: baz,") == s2q!("foo(arg, [bar: baz, __cursor__()])")
assert cc2q!("foo(arg: val, bar: ") == s2q!("foo(arg: val, bar: __cursor__())")
assert cc2q!("foo(arg: val, bar: baz,") == s2q!("foo([arg: val, bar: baz, __cursor__()])")

assert cc2q!("foo bar: ") == s2q!("foo(bar: __cursor__())")
assert cc2q!("foo bar: baz,") == s2q!("foo([bar: baz, __cursor__()])")
assert cc2q!("foo arg, bar: ") == s2q!("foo(arg, bar: __cursor__())")
assert cc2q!("foo arg, bar: baz,") == s2q!("foo(arg, [bar: baz, __cursor__()])")
assert cc2q!("foo arg: val, bar: ") == s2q!("foo(arg: val, bar: __cursor__())")
assert cc2q!("foo arg: val, bar: baz,") == s2q!("foo([arg: val, bar: baz, __cursor__()])")
end

test "maps and structs" do
assert cc2q("%") == s2q("__cursor__()")
assert cc2q("%{") == s2q("%{__cursor__()}")
assert cc2q("%{bar:") == s2q("%{__cursor__()}")
assert cc2q("%{bar: ") == s2q("%{bar: __cursor__()}")
assert cc2q("%{bar: baz,") == s2q("%{bar: baz, __cursor__()}")
assert cc2q("%{foo | ") == s2q("%{foo | __cursor__()}")
assert cc2q("%{foo | bar:") == s2q("%{foo | __cursor__()}")
assert cc2q("%{foo | bar: ") == s2q("%{foo | bar: __cursor__()}")
assert cc2q("%{foo | bar: baz,") == s2q("%{foo | bar: baz, __cursor__()}")

assert cc2q("%Foo") == s2q("__cursor__()")
assert cc2q("%Foo{") == s2q("%Foo{__cursor__()}")
assert cc2q("%Foo{bar: ") == s2q("%Foo{bar: __cursor__()}")
assert cc2q("%Foo{bar: baz,") == s2q("%Foo{bar: baz, __cursor__()}")
assert cc2q("%Foo{foo | ") == s2q("%Foo{foo | __cursor__()}")
assert cc2q("%Foo{foo | bar:") == s2q("%Foo{foo | __cursor__()}")
assert cc2q("%Foo{foo | bar: ") == s2q("%Foo{foo | bar: __cursor__()}")
assert cc2q("%Foo{foo | bar: baz,") == s2q("%Foo{foo | bar: baz, __cursor__()}")
assert cc2q!("%") == s2q!("__cursor__()")
assert cc2q!("%{") == s2q!("%{__cursor__()}")
assert cc2q!("%{bar:") == s2q!("%{__cursor__()}")
assert cc2q!("%{bar: ") == s2q!("%{bar: __cursor__()}")
assert cc2q!("%{bar: baz,") == s2q!("%{bar: baz, __cursor__()}")
assert cc2q!("%{foo | ") == s2q!("%{foo | __cursor__()}")
assert cc2q!("%{foo | bar:") == s2q!("%{foo | __cursor__()}")
assert cc2q!("%{foo | bar: ") == s2q!("%{foo | bar: __cursor__()}")
assert cc2q!("%{foo | bar: baz,") == s2q!("%{foo | bar: baz, __cursor__()}")

assert cc2q!("%Foo") == s2q!("__cursor__()")
assert cc2q!("%Foo{") == s2q!("%Foo{__cursor__()}")
assert cc2q!("%Foo{bar: ") == s2q!("%Foo{bar: __cursor__()}")
assert cc2q!("%Foo{bar: baz,") == s2q!("%Foo{bar: baz, __cursor__()}")
assert cc2q!("%Foo{foo | ") == s2q!("%Foo{foo | __cursor__()}")
assert cc2q!("%Foo{foo | bar:") == s2q!("%Foo{foo | __cursor__()}")
assert cc2q!("%Foo{foo | bar: ") == s2q!("%Foo{foo | bar: __cursor__()}")
assert cc2q!("%Foo{foo | bar: baz,") == s2q!("%Foo{foo | bar: baz, __cursor__()}")
end

test "binaries" do
assert cc2q("<<") == s2q("<<__cursor__()>>")
assert cc2q("<<foo") == s2q("<<__cursor__()>>")
assert cc2q("<<foo, bar") == s2q("<<foo, __cursor__()>>")
assert cc2q("<<foo, bar::baz") == s2q("<<foo, bar::__cursor__()>>")
assert cc2q!("<<") == s2q!("<<__cursor__()>>")
assert cc2q!("<<foo") == s2q!("<<__cursor__()>>")
assert cc2q!("<<foo, bar") == s2q!("<<foo, __cursor__()>>")
assert cc2q!("<<foo, bar::baz") == s2q!("<<foo, bar::__cursor__()>>")
end

test "removes tokens until opening" do
assert cc2q("(123") == s2q("(__cursor__())")
assert cc2q("[foo") == s2q("[__cursor__()]")
assert cc2q("{'foo'") == s2q("{__cursor__()}")
assert cc2q("foo do :atom") == s2q("foo do __cursor__() end")
assert cc2q("foo(:atom") == s2q("foo(__cursor__())")
assert cc2q!("(123") == s2q!("(__cursor__())")
assert cc2q!("[foo") == s2q!("[__cursor__()]")
assert cc2q!("{'foo'") == s2q!("{__cursor__()}")
assert cc2q!("foo do :atom") == s2q!("foo do __cursor__() end")
assert cc2q!("foo(:atom") == s2q!("foo(__cursor__())")
end

test "removes tokens until comma" do
assert cc2q("[bar, 123") == s2q("[bar, __cursor__()]")
assert cc2q("{bar, 'foo'") == s2q("{bar, __cursor__()}")
assert cc2q("<<bar, \"sample\"") == s2q("<<bar, __cursor__()>>")
assert cc2q("foo(bar, :atom") == s2q("foo(bar, __cursor__())")
assert cc2q("foo bar, :atom") == s2q("foo(bar, __cursor__())")
assert cc2q!("[bar, 123") == s2q!("[bar, __cursor__()]")
assert cc2q!("{bar, 'foo'") == s2q!("{bar, __cursor__()}")
assert cc2q!("<<bar, \"sample\"") == s2q!("<<bar, __cursor__()>>")
assert cc2q!("foo(bar, :atom") == s2q!("foo(bar, __cursor__())")
assert cc2q!("foo bar, :atom") == s2q!("foo(bar, __cursor__())")
end

test "removes anonymous functions" do
assert cc2q("(fn") == s2q("(__cursor__())")
assert cc2q("(fn x") == s2q("(__cursor__())")
assert cc2q("(fn x ->") == s2q("(__cursor__())")
assert cc2q("(fn x -> x") == s2q("(__cursor__())")
assert cc2q("(fn x, y -> x + y") == s2q("(__cursor__())")
assert cc2q("(fn x, y -> x + y end") == s2q("(__cursor__())")
assert cc2q!("(fn") == s2q!("(__cursor__())")
assert cc2q!("(fn x") == s2q!("(__cursor__())")
assert cc2q!("(fn x ->") == s2q!("(__cursor__())")
assert cc2q!("(fn x -> x") == s2q!("(__cursor__())")
assert cc2q!("(fn x, y -> x + y") == s2q!("(__cursor__())")
assert cc2q!("(fn x, y -> x + y end") == s2q!("(__cursor__())")
end

test "removes closed terminators" do
assert cc2q("foo([1, 2, 3]") == s2q("foo(__cursor__())")
assert cc2q("foo({1, 2, 3}") == s2q("foo(__cursor__())")
assert cc2q("foo((1, 2, 3)") == s2q("foo(__cursor__())")
assert cc2q("foo(<<1, 2, 3>>") == s2q("foo(__cursor__())")
assert cc2q("foo(bar do :done end") == s2q("foo(__cursor__())")
assert cc2q!("foo([1, 2, 3]") == s2q!("foo(__cursor__())")
assert cc2q!("foo({1, 2, 3}") == s2q!("foo(__cursor__())")
assert cc2q!("foo((1, 2, 3)") == s2q!("foo(__cursor__())")
assert cc2q!("foo(<<1, 2, 3>>") == s2q!("foo(__cursor__())")
assert cc2q!("foo(bar do :done end") == s2q!("foo(__cursor__())")
end

test "incomplete expressions" do
assert cc2q("foo(123, :") == s2q("foo(123, __cursor__())")
assert cc2q("foo(123, %") == s2q("foo(123, __cursor__())")
assert cc2q("foo(123, 0x") == s2q("foo(123, __cursor__())")
assert cc2q("foo(123, ~") == s2q("foo(123, __cursor__())")
assert cc2q("foo(123, ~r") == s2q("foo(123, __cursor__())")
assert cc2q("foo(123, ~r/") == s2q("foo(123, __cursor__())")
assert cc2q!("foo(123, :") == s2q!("foo(123, __cursor__())")
assert cc2q!("foo(123, %") == s2q!("foo(123, __cursor__())")
assert cc2q!("foo(123, 0x") == s2q!("foo(123, __cursor__())")
assert cc2q!("foo(123, ~") == s2q!("foo(123, __cursor__())")
assert cc2q!("foo(123, ~r") == s2q!("foo(123, __cursor__())")
assert cc2q!("foo(123, ~r/") == s2q!("foo(123, __cursor__())")
end

test "no warnings" do
assert cc2q(~s"?\\ ") == s2q("__cursor__()")
assert cc2q(~s"{fn -> end, ") == s2q("{fn -> nil end, __cursor__()}")
assert cc2q!(~s"?\\ ") == s2q!("__cursor__()")
assert cc2q!(~s"{fn -> end, ") == s2q!("{fn -> nil end, __cursor__()}")
end

test "options" do
opts = [columns: true]
assert cc2q("foo(", opts) == s2q("foo(__cursor__())", opts)
assert cc2q("foo(123,", opts) == s2q("foo(123,__cursor__())", opts)
assert cc2q!("foo(", opts) == s2q!("foo(__cursor__())", opts)
assert cc2q!("foo(123,", opts) == s2q!("foo(123,__cursor__())", opts)

opts = [token_metadata: true]
assert cc2q("foo(", opts) == s2q("foo(__cursor__())", opts)
assert cc2q("foo(123,", opts) == s2q("foo(123,__cursor__())", opts)
assert cc2q!("foo(", opts) == s2q!("foo(__cursor__())", opts)
assert cc2q!("foo(123,", opts) == s2q!("foo(123,__cursor__())", opts)

opts = [literal_encoder: fn ast, _ -> {:ok, {:literal, ast}} end]
assert cc2q("foo(", opts) == s2q("foo(__cursor__())", opts)
assert cc2q("foo(123,", opts) == s2q("foo({:literal, 123},__cursor__())", [])
assert cc2q!("foo(", opts) == s2q!("foo(__cursor__())", opts)
assert cc2q!("foo(123,", opts) == s2q!("foo({:literal, 123},__cursor__())", [])
end
end
end

0 comments on commit 203baf3

Please sign in to comment.