From fae7195cd40e4972eca1a23912c380bb08dea57e Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 10 Dec 2024 20:48:52 +0300 Subject: [PATCH 1/2] Deprecate single quites in quoted atoms and calls, refs #13958 --- lib/elixir/src/elixir_tokenizer.erl | 28 +++++++++++---- .../test/elixir/kernel/warning_test.exs | 36 +++++++++++++++++++ 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index 0290803970f..e87ff84f02b 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -489,7 +489,15 @@ tokenize([T | Rest], Line, Column, Scope, Tokens) when ?pipe_op(T) -> % Non-operator Atoms tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H) -> - case elixir_interpolation:extract(Line, Column + 2, Scope, true, T, H) of + OuterScope = case H == $' of + true -> + prepend_warning(Line, Column, "single quotes around atoms are deprecated. Use double quotes instead.", Scope); + + false -> + Scope + end, + + case elixir_interpolation:extract(Line, Column + 2, OuterScope, true, T, H) of {NewLine, NewColumn, Parts, Rest, InterScope} -> NewScope = case is_unnecessary_quote(Parts, InterScope) of true -> @@ -508,7 +516,7 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H) case unescape_tokens(Parts, Line, Column, NewScope) of {ok, [Part]} when is_binary(Part) -> - case unsafe_to_atom(Part, Line, Column, Scope) of + case unsafe_to_atom(Part, Line, Column, OuterScope) of {ok, Atom} -> Token = {atom_quoted, {Line, Column, H}, Atom}, tokenize(Rest, NewLine, NewColumn, NewScope, [Token | Tokens]); @@ -518,7 +526,7 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H) end; {ok, Unescaped} -> - Key = case Scope#elixir_tokenizer.existing_atoms_only of + Key = case OuterScope#elixir_tokenizer.existing_atoms_only of true -> atom_safe; false -> atom_unsafe end, @@ -531,7 +539,7 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H) {error, Reason} -> Message = " (for atom starting at line ~B)", - interpolation_error(Reason, Original, Scope, Tokens, Message, [Line], Line, Column + 1, [H], [H]) + interpolation_error(Reason, Original, OuterScope, Tokens, Message, [Line], Line, Column + 1, [H], [H]) end; tokenize([$: | String] = Original, Line, Column, Scope, Tokens) -> @@ -897,7 +905,15 @@ handle_dot([$., $( | Rest], Line, Column, DotInfo, Scope, Tokens) -> tokenize([$( | Rest], Line, Column, Scope, TokensSoFar); handle_dot([$., H | T] = Original, Line, Column, DotInfo, Scope, Tokens) when ?is_quote(H) -> - case elixir_interpolation:extract(Line, Column + 1, Scope, true, T, H) of + OuterScope = case H == $' of + true -> + prepend_warning(Line, Column, "single quotes around calls are deprecated. Use double quotes instead.", Scope); + + false -> + Scope + end, + + case elixir_interpolation:extract(Line, Column + 1, OuterScope, true, T, H) of {NewLine, NewColumn, [Part], Rest, InterScope} when is_list(Part) -> NewScope = case is_unnecessary_quote([Part], InterScope) of true -> @@ -928,7 +944,7 @@ handle_dot([$., H | T] = Original, Line, Column, DotInfo, Scope, Tokens) when ?i Message = "interpolation is not allowed when calling function/macro. Found interpolation in a call starting with: ", error({?LOC(Line, Column), Message, [H]}, Rest, NewScope, Tokens); {error, Reason} -> - interpolation_error(Reason, Original, Scope, Tokens, " (for function name starting at line ~B)", [Line], Line, Column, [H], [H]) + interpolation_error(Reason, Original, OuterScope, Tokens, " (for function name starting at line ~B)", [Line], Line, Column, [H], [H]) end; handle_dot([$. | Rest], Line, Column, DotInfo, Scope, Tokens) -> diff --git a/lib/elixir/test/elixir/kernel/warning_test.exs b/lib/elixir/test/elixir/kernel/warning_test.exs index 1f0f975adbc..db7999c2cbd 100644 --- a/lib/elixir/test/elixir/kernel/warning_test.exs +++ b/lib/elixir/test/elixir/kernel/warning_test.exs @@ -201,6 +201,42 @@ defmodule Kernel.WarningTest do end end + describe "deprecated single quotes in atoms" do + test "warns for single quotes in atoms" do + assert_warn_eval( + [ + "nofile:1:1", + "single quotes around atoms are deprecated. Use double quotes instead" + ], + ~s/:'a+b'/ + ) + end + + test "warns twice for single and unnecessary atom quotes" do + assert_warn_eval( + [ + "nofile:1:1", + "single quotes around atoms are deprecated. Use double quotes instead", + "nofile:1:1", + "found quoted atom \"ab\" but the quotes are not required" + ], + ~s/:'ab'/ + ) + end + + test "warns twice for single and unnecessary call quotes" do + assert_warn_eval( + [ + "nofile:1:9", + "single quotes around calls are deprecated. Use double quotes instead", + "nofile:1:9", + "found quoted call \"length\" but the quotes are not required" + ], + ~s/[Kernel.'length'([])]/ + ) + end + end + test "warns on :: as atom" do assert_warn_eval( [ From 824b56ecfa4e1619800965351e17bb3b00e32e52 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 10 Dec 2024 21:14:09 +0300 Subject: [PATCH 2/2] Address review --- lib/elixir/src/elixir_tokenizer.erl | 28 ++++++++++++++-------------- lib/elixir/test/erlang/atom_test.erl | 3 +-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/lib/elixir/src/elixir_tokenizer.erl b/lib/elixir/src/elixir_tokenizer.erl index e87ff84f02b..33601dbbd2f 100644 --- a/lib/elixir/src/elixir_tokenizer.erl +++ b/lib/elixir/src/elixir_tokenizer.erl @@ -488,16 +488,16 @@ tokenize([T | Rest], Line, Column, Scope, Tokens) when ?pipe_op(T) -> % Non-operator Atoms -tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H) -> - OuterScope = case H == $' of +tokenize([$:, H | T] = Original, Line, Column, BaseScope, Tokens) when ?is_quote(H) -> + Scope = case H == $' of true -> - prepend_warning(Line, Column, "single quotes around atoms are deprecated. Use double quotes instead.", Scope); + prepend_warning(Line, Column, "single quotes around atoms are deprecated. Use double quotes instead", BaseScope); false -> - Scope + BaseScope end, - case elixir_interpolation:extract(Line, Column + 2, OuterScope, true, T, H) of + case elixir_interpolation:extract(Line, Column + 2, Scope, true, T, H) of {NewLine, NewColumn, Parts, Rest, InterScope} -> NewScope = case is_unnecessary_quote(Parts, InterScope) of true -> @@ -516,7 +516,7 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H) case unescape_tokens(Parts, Line, Column, NewScope) of {ok, [Part]} when is_binary(Part) -> - case unsafe_to_atom(Part, Line, Column, OuterScope) of + case unsafe_to_atom(Part, Line, Column, Scope) of {ok, Atom} -> Token = {atom_quoted, {Line, Column, H}, Atom}, tokenize(Rest, NewLine, NewColumn, NewScope, [Token | Tokens]); @@ -526,7 +526,7 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H) end; {ok, Unescaped} -> - Key = case OuterScope#elixir_tokenizer.existing_atoms_only of + Key = case Scope#elixir_tokenizer.existing_atoms_only of true -> atom_safe; false -> atom_unsafe end, @@ -539,7 +539,7 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H) {error, Reason} -> Message = " (for atom starting at line ~B)", - interpolation_error(Reason, Original, OuterScope, Tokens, Message, [Line], Line, Column + 1, [H], [H]) + interpolation_error(Reason, Original, Scope, Tokens, Message, [Line], Line, Column + 1, [H], [H]) end; tokenize([$: | String] = Original, Line, Column, Scope, Tokens) -> @@ -904,16 +904,16 @@ handle_dot([$., $( | Rest], Line, Column, DotInfo, Scope, Tokens) -> TokensSoFar = add_token_with_eol({dot_call_op, DotInfo, '.'}, Tokens), tokenize([$( | Rest], Line, Column, Scope, TokensSoFar); -handle_dot([$., H | T] = Original, Line, Column, DotInfo, Scope, Tokens) when ?is_quote(H) -> - OuterScope = case H == $' of +handle_dot([$., H | T] = Original, Line, Column, DotInfo, BaseScope, Tokens) when ?is_quote(H) -> + Scope = case H == $' of true -> - prepend_warning(Line, Column, "single quotes around calls are deprecated. Use double quotes instead.", Scope); + prepend_warning(Line, Column, "single quotes around calls are deprecated. Use double quotes instead", BaseScope); false -> - Scope + BaseScope end, - case elixir_interpolation:extract(Line, Column + 1, OuterScope, true, T, H) of + case elixir_interpolation:extract(Line, Column + 1, Scope, true, T, H) of {NewLine, NewColumn, [Part], Rest, InterScope} when is_list(Part) -> NewScope = case is_unnecessary_quote([Part], InterScope) of true -> @@ -944,7 +944,7 @@ handle_dot([$., H | T] = Original, Line, Column, DotInfo, Scope, Tokens) when ?i Message = "interpolation is not allowed when calling function/macro. Found interpolation in a call starting with: ", error({?LOC(Line, Column), Message, [H]}, Rest, NewScope, Tokens); {error, Reason} -> - interpolation_error(Reason, Original, OuterScope, Tokens, " (for function name starting at line ~B)", [Line], Line, Column, [H], [H]) + interpolation_error(Reason, Original, Scope, Tokens, " (for function name starting at line ~B)", [Line], Line, Column, [H], [H]) end; handle_dot([$. | Rest], Line, Column, DotInfo, Scope, Tokens) -> diff --git a/lib/elixir/test/erlang/atom_test.erl b/lib/elixir/test/erlang/atom_test.erl index 2f8b42ded7f..812ac321668 100644 --- a/lib/elixir/test/erlang/atom_test.erl +++ b/lib/elixir/test/erlang/atom_test.erl @@ -17,7 +17,7 @@ atom_with_punctuation_test() -> {'...', []} = eval(":..."). atom_quoted_call_test() -> - {3, []} = eval("Kernel.'+'(1, 2)"). + {3, []} = eval("Kernel.\"+\"(1, 2)"). kv_with_quotes_test() -> {'foo bar', []} = eval(":atom_test.kv(\"foo bar\": nil)"). @@ -29,7 +29,6 @@ kv_with_interpolation_test() -> quoted_atom_test() -> {'+', []} = eval(":\"+\""), - {'+', []} = eval(":'+'"), {'foo bar', []} = eval(":\"foo bar\""). atom_with_interpolation_test() ->