diff --git a/lib/elixir_sense/core/compiler.ex b/lib/elixir_sense/core/compiler.ex index d928e1ef..01fd3fd9 100644 --- a/lib/elixir_sense/core/compiler.ex +++ b/lib/elixir_sense/core/compiler.ex @@ -549,7 +549,7 @@ defmodule ElixirSense.Core.Compiler do end defp do_expand({name, meta, kind}, s, e) when is_atom(name) and is_atom(kind) do - %{vars: {read, _write}, unused: version, prematch: prematch} = s + %{vars: {read, _write}, prematch: prematch} = s pair = {name, var_context(meta, kind)} result = @@ -584,34 +584,31 @@ defmodule ElixirSense.Core.Compiler do {:ok, pair_version} -> var = {name, [{:version, pair_version} | meta], kind} s = add_var_read(s, var) - {var, %{s | unused: version}, e} + {var, s, e} error -> case Keyword.fetch(meta, :if_undefined) do {:ok, :apply} -> - # TODO check if this can happen + # convert to local call expand({name, meta, []}, s, e) # elixir plans to remove this clause on v2.0 {:ok, :raise} -> - # TODO is it worth registering var access - # function_error(meta, e, __MODULE__, {:undefined_var, name, kind}) + # elixir raises here undefined_var {{name, meta, kind}, s, e} # elixir plans to remove this clause on v2.0 _ when error == :warn -> - # TODO is it worth registering var access? + # convert to local call and add if_undefined meta expand({name, [{:if_undefined, :warn} | meta], []}, s, e) _ when error == :pin -> - # TODO is it worth registering var access - # function_error(meta, e, __MODULE__, {:undefined_var_pin, name, kind}) + # elixir raises here undefined_var_pin {{name, meta, kind}, s, e} _ -> - # TODO is it worth registering var access + # elixir raises here undefined_var span_meta = __MODULE__.Env.calculate_span(meta, name) - # function_error(span_meta, e, __MODULE__, {:undefined_var, name, kind}) {{name, span_meta, kind}, s, e} end end diff --git a/lib/elixir_sense/core/metadata_builder.ex b/lib/elixir_sense/core/metadata_builder.ex index b65af884..bccadd07 100644 --- a/lib/elixir_sense/core/metadata_builder.ex +++ b/lib/elixir_sense/core/metadata_builder.ex @@ -14,7 +14,15 @@ defmodule ElixirSense.Core.MetadataBuilder do """ @spec build(Macro.t()) :: State.t() def build(ast) do - {_ast, state, _env} = Compiler.expand(ast, %State{}, Compiler.env()) + {_ast, state, _env} = + Compiler.expand( + ast, + %State{ + # TODO remove default when we require elixir 1.15 + prematch: Code.get_compiler_option(:on_undefined_variable) || :warn + }, + Compiler.env() + ) state |> remove_attributes_scope diff --git a/test/elixir_sense/core/compiler_test.exs b/test/elixir_sense/core/compiler_test.exs index 854962e7..717c74d3 100644 --- a/test/elixir_sense/core/compiler_test.exs +++ b/test/elixir_sense/core/compiler_test.exs @@ -5,7 +5,6 @@ if true or Version.match?(System.version(), ">= 1.17.0-dev") do alias ElixirSense.Core.State require Record - defp to_quoted!(string_or_ast, ast \\ false) defp to_quoted!(ast, true), do: ast defp to_quoted!(string, false), @@ -568,7 +567,7 @@ if true or Version.match?(System.version(), ">= 1.17.0-dev") do end defmodule Overridable do - defmacro __using__(args) do + defmacro __using__(_args) do quote do def foo(a) do a @@ -625,6 +624,44 @@ if true or Version.match?(System.version(), ">= 1.17.0-dev") do assert_expansion("a = 5; ^a = 6") end + test "expands nullary call if_undefined: :apply" do + ast = {:self, [if_undefined: :apply], nil} + {expanded, state, env} = Compiler.expand(ast, %State{}, Compiler.env()) + elixir_env = :elixir_env.new() + + {elixir_expanded, elixir_state, elixir_env} = + :elixir_expand.expand(ast, :elixir_env.env_to_ex(elixir_env), elixir_env) + + assert expanded == elixir_expanded + assert env == elixir_env + assert state_to_map(state) == elixir_ex_to_map(elixir_state) + end + + test "expands nullary call if_undefined: :warn" do + Code.put_compiler_option(:on_undefined_variable, :warn) + ast = {:self, [], nil} + + {expanded, state, env} = + Compiler.expand( + ast, + %State{ + prematch: Code.get_compiler_option(:on_undefined_variable) || :warn + }, + Compiler.env() + ) + + elixir_env = :elixir_env.new() + + {elixir_expanded, elixir_state, elixir_env} = + :elixir_expand.expand(ast, :elixir_env.env_to_ex(elixir_env), elixir_env) + + assert expanded == elixir_expanded + assert env == elixir_env + assert state_to_map(state) == elixir_ex_to_map(elixir_state) + after + Code.put_compiler_option(:on_undefined_variable, :raise) + end + test "expands local call" do assert_expansion("get_in(%{}, [:bar])") assert_expansion("length([])") @@ -788,7 +825,9 @@ if true or Version.match?(System.version(), ">= 1.17.0-dev") do token_metadata: true ) - {expanded, state, env} = Compiler.expand(ast, %State{}, %{Compiler.env() | module: Foo}) + {_expanded, _state, _env} = + Compiler.expand(ast, %State{}, %{Compiler.env() | module: Foo}) + # elixir_env = %{:elixir_env.new() | module: Foo} # {elixir_expanded, _elixir_state, elixir_env} = :elixir_expand.expand(ast, :elixir_env.env_to_ex(elixir_env), elixir_env) @@ -850,7 +889,7 @@ if true or Version.match?(System.version(), ">= 1.17.0-dev") do defp clean_capture_arg_elixir(ast) do {ast, _} = Macro.prewalk(ast, nil, fn - {:capture, meta, nil} = node, state -> + {:capture, meta, nil} = _node, state -> # elixir changes the name to capture and does different counter tracking meta = Keyword.delete(meta, :counter) {{:capture, meta, nil}, state} diff --git a/test/elixir_sense/core/metadata_builder_test.exs b/test/elixir_sense/core/metadata_builder_test.exs index 725d48b0..9ee43003 100644 --- a/test/elixir_sense/core/metadata_builder_test.exs +++ b/test/elixir_sense/core/metadata_builder_test.exs @@ -135,6 +135,19 @@ defmodule ElixirSense.Core.MetadataBuilderTest do ] = state |> get_line_vars(3) end + test "pin undefined" do + state = + """ + ^abc = foo() + record_env() + """ + |> string_to_state + + refute Map.has_key?(state.lines_to_env[2].versioned_vars, {:abc, nil}) + + assert [] = state |> get_line_vars(3) + end + test "rebinding" do state = """