From 617eb83bb41ed081652489dd6f4f1b733577e9ca Mon Sep 17 00:00:00 2001 From: Lukasz Samson Date: Sat, 10 Aug 2024 09:52:04 +0200 Subject: [PATCH] make line and column extraction more robust --- lib/elixir_sense/core/compiler.ex | 100 +++++-------------------- lib/elixir_sense/core/state.ex | 117 +++++++++++++++++++++--------- 2 files changed, 100 insertions(+), 117 deletions(-) diff --git a/lib/elixir_sense/core/compiler.ex b/lib/elixir_sense/core/compiler.ex index e56fdc81..c46f6399 100644 --- a/lib/elixir_sense/core/compiler.ex +++ b/lib/elixir_sense/core/compiler.ex @@ -833,8 +833,6 @@ defmodule ElixirSense.Core.Compiler do env = %{module: module} ) when module != nil do - {position, end_position} = extract_range(meta) - {opts, state, env} = expand(opts, state, env) # elixir does validation here target = Keyword.get(opts, :to, :__unknown__) @@ -861,8 +859,7 @@ defmodule ElixirSense.Core.Compiler do env, name, args, - position, - end_position, + extract_range(meta), :defdelegate, target: {target, as, length(as_args)} ) @@ -1019,9 +1016,6 @@ defmodule ElixirSense.Core.Compiler do env = %{module: module} ) when module != nil do - line = Keyword.fetch!(meta, :line) - column = Keyword.fetch!(meta, :column) - state = List.wrap(derived_protos) |> Enum.map(fn @@ -1041,7 +1035,7 @@ defmodule ElixirSense.Core.Compiler do nil -> # implementation for: Any not detected (is in other file etc.) acc - |> add_module_to_index(mod, {line, column}, nil, generated: true) + |> add_module_to_index(mod, extract_range(meta), generated: true) _any_mods_funs -> # copy implementation for: Any @@ -1091,11 +1085,9 @@ defmodule ElixirSense.Core.Compiler do spec = TypeInfo.typespec_to_string(kind, expr) - {position, end_position} = extract_range(attr_meta) - state = state - |> add_type(env, name, type_args, spec, kind, position, end_position) + |> add_type(env, name, type_args, spec, kind, extract_range(attr_meta)) |> with_typespec({name, length(type_args)}) |> add_current_env_to_line(attr_meta, env) |> with_typespec(nil) @@ -1136,7 +1128,7 @@ defmodule ElixirSense.Core.Compiler do {name, type_args} -> spec = TypeInfo.typespec_to_string(kind, expr) - {position, end_position} = extract_range(attr_meta) + range = extract_range(attr_meta) state = if kind in [:callback, :macrocallback] do @@ -1145,8 +1137,7 @@ defmodule ElixirSense.Core.Compiler do env, :behaviour_info, [{:atom, attr_meta, nil}], - position, - end_position, + range, :def, generated: true ) @@ -1156,7 +1147,7 @@ defmodule ElixirSense.Core.Compiler do state = state - |> add_spec(env, name, type_args, spec, kind, position, end_position) + |> add_spec(env, name, type_args, spec, kind, range) |> with_typespec({name, length(type_args)}) |> add_current_env_to_line(attr_meta, env) |> with_typespec(nil) @@ -1178,9 +1169,6 @@ defmodule ElixirSense.Core.Compiler do env = %{module: module} ) when is_atom(name) and module != nil do - line = Keyword.fetch!(meta, :line) - column = Keyword.get(meta, :column, 1) - {is_definition, {e_args, state, env}} = case args do arg when is_atom(arg) -> @@ -1209,7 +1197,7 @@ defmodule ElixirSense.Core.Compiler do state = state - |> add_attribute(name, inferred_type, is_definition, {line, column}) + |> add_attribute(name, inferred_type, is_definition, meta) |> add_current_env_to_line(meta, env) {{:@, meta, [{name, name_meta, e_args}]}, state, env} @@ -1274,8 +1262,6 @@ defmodule ElixirSense.Core.Compiler do [] end - {position, end_position} = extract_range(meta) - fields = fields |> Enum.filter(fn @@ -1290,7 +1276,7 @@ defmodule ElixirSense.Core.Compiler do state = state - |> add_struct_or_exception(env, type, fields, position, end_position) + |> add_struct_or_exception(env, type, fields, extract_range(meta)) {{type, meta, [fields]}, state, env} end @@ -1305,7 +1291,7 @@ defmodule ElixirSense.Core.Compiler do env = %{module: module} ) when call in [:defrecord, :defrecordp] and module != nil do - {position, end_position} = extract_range(meta) + range = extract_range(meta) type = case call do @@ -1321,8 +1307,7 @@ defmodule ElixirSense.Core.Compiler do env, name, [{:\\, [], [{:args, [], nil}, []]}], - position, - end_position, + range, type, options ) @@ -1330,8 +1315,7 @@ defmodule ElixirSense.Core.Compiler do env, name, [{:record, [], nil}, {:args, [], nil}], - position, - end_position, + range, type, options ) @@ -1349,7 +1333,6 @@ defmodule ElixirSense.Core.Compiler do state, env ) do - {position, end_position} = extract_range(meta) original_env = env # expand the macro normally {ast, state, env} = @@ -1364,8 +1347,7 @@ defmodule ElixirSense.Core.Compiler do %{env | module: module}, :behaviour_info, [:atom], - position, - end_position, + extract_range(meta), :def, generated: true ) @@ -1504,7 +1486,7 @@ defmodule ElixirSense.Core.Compiler do %{state | runtime_modules: [full | state.runtime_modules]} end - {position, end_position} = extract_range(meta) + range = extract_range(meta) module_functions = case state.protocol do @@ -1514,10 +1496,10 @@ defmodule ElixirSense.Core.Compiler do state = state - |> add_module_to_index(full, position, end_position, []) + |> add_module_to_index(full, range, []) |> add_module |> add_current_env_to_line(meta, %{env | module: full}) - |> add_module_functions(%{env | module: full}, module_functions, position, end_position) + |> add_module_functions(%{env | module: full}, module_functions, range) |> new_vars_scope |> new_attributes_scope @@ -1658,8 +1640,6 @@ defmodule ElixirSense.Core.Compiler do env_for_expand = %{env_for_expand | context: nil} - {position, end_position} = extract_range(meta) - state = state |> add_current_env_to_line(meta, env_for_expand) @@ -1667,8 +1647,7 @@ defmodule ElixirSense.Core.Compiler do env, name, args, - position, - end_position, + extract_range(meta), def_kind ) @@ -1735,7 +1714,7 @@ defmodule ElixirSense.Core.Compiler do [context, do_block | _] -> {[context], do_block} end - line = Keyword.fetch!(meta, :line) + line = __MODULE__.Utils.get_line(meta) # NOTE this name is not 100% correct - ex_unit uses counters instead of line but it's too complicated call = {:"__ex_unit_#{setup}_#{line}", meta, args} @@ -1790,42 +1769,6 @@ defmodule ElixirSense.Core.Compiler do {ast, state, env} end - defp extract_range(meta) do - line = Keyword.get(meta, :line, 0) - - if line <= 0 do - {nil, nil} - else - position = { - line, - Keyword.get(meta, :column, 1) - } - - end_position = - case meta[:end] do - nil -> - case meta[:end_of_expression] do - nil -> - nil - - end_of_expression_meta -> - { - Keyword.fetch!(end_of_expression_meta, :line), - Keyword.fetch!(end_of_expression_meta, :column) - } - end - - end_meta -> - { - Keyword.fetch!(end_meta, :line), - Keyword.fetch!(end_meta, :column) + 3 - } - end - - {position, end_position} - end - end - defp ex_unit_test_name(state, name) do case state.ex_unit_describe do nil -> "test #{name}" @@ -3046,7 +2989,7 @@ defmodule ElixirSense.Core.Compiler do # rescue expr() => rescue expanded_expr() defp expand_rescue({_, meta, _} = arg, s, e) do # TODO wut? - case Macro.expand_once(arg, %{e | line: line(meta)}) do + case Macro.expand_once(arg, %{e | line: ElixirUtils.get_line(meta)}) do ^arg -> # elixir rejects this case # try to recover from error by generating fake expression @@ -3121,13 +3064,6 @@ defmodule ElixirSense.Core.Compiler do defp origin(meta, default) do Keyword.get(meta, :origin, default) end - - defp line(opts) when is_list(opts) do - case Keyword.fetch(opts, :line) do - {:ok, line} when is_integer(line) -> line - _ -> 0 - end - end end defmodule Bitstring do diff --git a/lib/elixir_sense/core/state.ex b/lib/elixir_sense/core/state.ex index 48657c6b..f63e0f27 100644 --- a/lib/elixir_sense/core/state.ex +++ b/lib/elixir_sense/core/state.ex @@ -468,6 +468,7 @@ defmodule ElixirSense.Core.State do end defp do_add_call_to_line(%__MODULE__{} = state, {mod, func, arity}, meta) do + # extract_position is not suitable here, we need to handle invalid lines line = Keyword.get(meta, :line, 0) column = Keyword.get(meta, :column, nil) @@ -609,7 +610,7 @@ defmodule ElixirSense.Core.State do %{state | specs: updated_specs} end - def add_module_to_index(%__MODULE__{} = state, module, position, end_position, options) + def add_module_to_index(%__MODULE__{} = state, module, {position, end_position}, options) when (is_tuple(position) and is_tuple(end_position)) or is_nil(end_position) do # TODO :defprotocol, :defimpl? add_mod_fun_to_position( @@ -630,8 +631,7 @@ defmodule ElixirSense.Core.State do env, func, params, - position, - end_position, + {position, end_position}, type, options \\ [] ) @@ -806,8 +806,7 @@ defmodule ElixirSense.Core.State do type_args, spec, kind, - pos, - end_pos, + {pos, end_pos}, options \\ [] ) do arg_names = @@ -880,8 +879,7 @@ defmodule ElixirSense.Core.State do type_args, spec, kind, - pos, - end_pos, + {pos, end_pos}, options \\ [] ) do arg_names = @@ -930,14 +928,12 @@ defmodule ElixirSense.Core.State do def add_var_write(%__MODULE__{} = state, {name, meta, _}) when name != :_ do version = meta[:version] - line = meta[:line] - column = meta[:column] scope_id = hd(state.scope_ids) info = %VarInfo{ name: name, version: version, - positions: [{line, column}], + positions: [extract_position(meta)], scope_id: scope_id } @@ -954,8 +950,6 @@ defmodule ElixirSense.Core.State do def add_var_read(%__MODULE__{} = state, {name, meta, _}) when name != :_ do version = meta[:version] - line = meta[:line] - column = meta[:column] [vars_from_scope | other_vars] = state.vars_info @@ -964,7 +958,11 @@ defmodule ElixirSense.Core.State do state info -> - info = %VarInfo{info | positions: (info.positions ++ [{line, column}]) |> Enum.uniq()} + info = %VarInfo{ + info + | positions: (info.positions ++ [extract_position(meta)]) |> Enum.uniq() + } + vars_from_scope = Map.put(vars_from_scope, {name, version}, info) %__MODULE__{ @@ -978,8 +976,9 @@ defmodule ElixirSense.Core.State do @builtin_attributes ElixirSense.Core.BuiltinAttributes.all() - def add_attribute(%__MODULE__{} = state, attribute, type, is_definition, position) + def add_attribute(%__MODULE__{} = state, attribute, type, is_definition, meta) when attribute not in @builtin_attributes do + position = extract_position(meta) [attributes_from_scope | other_attributes] = state.attributes existing_attribute_index = @@ -1009,7 +1008,7 @@ defmodule ElixirSense.Core.State do %AttributeInfo{ existing - | # FIXME this is wrong for accumulating attributes + | # TODO this is wrong for accumulating attributes type: type, positions: (existing.positions ++ [position]) |> Enum.uniq() |> Enum.sort() } @@ -1021,7 +1020,7 @@ defmodule ElixirSense.Core.State do %__MODULE__{state | attributes: attributes, scope_attributes: scope_attributes} end - def add_attribute(%__MODULE__{} = state, _attribute, _type, _is_definition, _position) do + def add_attribute(%__MODULE__{} = state, _attribute, _type, _is_definition, _meta) do state end @@ -1167,20 +1166,20 @@ defmodule ElixirSense.Core.State do {:module_info, [:atom], :def} ] - def add_module_functions(state, env, functions, position, end_position) do - {line, column} = position + def add_module_functions(state, env, functions, range) do + {{line, column}, _} = range + meta = [line: line || 0] ++ if(column > 0, do: [column: column], else: []) (functions ++ @module_functions) |> Enum.reduce(state, fn {name, args, kind}, acc -> - mapped_args = for arg <- args, do: {arg, [line: line, column: column], nil} + mapped_args = for arg <- args, do: {arg, meta, nil} acc |> add_func_to_index( env, name, mapped_args, - position, - end_position, + range, kind, generated: true ) @@ -1191,7 +1190,10 @@ defmodule ElixirSense.Core.State do %{state | typespec: typespec} end - def add_struct_or_exception(state, env, type, fields, {line, column} = position, end_position) do + def add_struct_or_exception(state, env, type, fields, range) do + {{line, column}, _} = range + meta = [line: line || 0] ++ if(column > 0, do: [column: column], else: []) + fields = fields ++ if type == :defexception do @@ -1211,18 +1213,16 @@ defmodule ElixirSense.Core.State do |> add_func_to_index( env, :exception, - [{:msg, [line: line, column: column], nil}], - position, - end_position, + [{:msg, meta, nil}], + range, :def, options ) |> add_func_to_index( env, :message, - [{:exception, [line: line, column: column], nil}], - position, - end_position, + [{:exception, meta, nil}], + range, :def, options ) @@ -1232,22 +1232,20 @@ defmodule ElixirSense.Core.State do |> add_func_to_index( env, :exception, - [{:args, [line: line, column: column], nil}], - position, - end_position, + [{:args, meta, nil}], + range, :def, options ) else state end - |> add_func_to_index(env, :__struct__, [], position, end_position, :def, options) + |> add_func_to_index(env, :__struct__, [], range, :def, options) |> add_func_to_index( env, :__struct__, - [{:kv, [line: line, column: column], nil}], - position, - end_position, + [{:kv, meta, nil}], + range, :def, options ) @@ -1352,4 +1350,53 @@ defmodule ElixirSense.Core.State do %{state | vars_info: [h | t]} end + + def extract_position(meta) do + line = Keyword.get(meta, :line, 0) + + if line <= 0 do + nil + else + { + line, + Keyword.get(meta, :column) + } + end + end + + def extract_range(meta) do + line = Keyword.get(meta, :line, 0) + + if line <= 0 do + {nil, nil} + else + position = { + line, + Keyword.get(meta, :column) + } + + end_position = + case meta[:end] do + nil -> + case meta[:end_of_expression] do + nil -> + nil + + end_of_expression_meta -> + { + Keyword.fetch!(end_of_expression_meta, :line), + Keyword.fetch!(end_of_expression_meta, :column) + } + end + + end_meta -> + { + Keyword.fetch!(end_meta, :line), + Keyword.fetch!(end_meta, :column) + 3 + } + end + + {position, end_position} + end + end end