elixir-lsp / elixir_sense

Provides context-aware information for code completion, documentation, go/jump to definition, signature info and more
MIT License
303 stars 41 forks source link

Type info not able to properly expand var types #181

Open lukaszsamson opened 1 year ago

lukaszsamson commented 1 year ago

var params in fun, remote and local type expansion is broken in TypeInfo. Maybe we should remove it completely. It's used only in param options completions

lukaszsamson commented 1 year ago

Some examples of broken stuff

defmodule ElixirSenseExample.ModuleWithTypespecs do
  defmodule Remote do
    @typedoc "Remote type"
    @type remote_t :: atom

    @typedoc "Remote type with params"
    @type remote_t(a, b) :: {a, b}

    @typedoc "Remote list type"
    @type remote_list_t :: [remote_t]

    @type remote_option_t :: {:remote_option_1, remote_t} | {:remote_option_2, remote_list_t}
  end

  defmodule OtherRemote do
    @type other :: Remote.remote_option_t()
    @type bounded_type(t) :: {t, integer}
    @type some :: :a
  end

  defmodule Local do
    alias Remote, as: R

    @typep private_t :: atom

    @typedoc "Local opaque type"
    @opaque opaque_t :: atom

    @typedoc "Local type"
    @type local_t :: atom

    @typedoc "Local type with params"
    @type local_t(a, b) :: {a, b}

    @typedoc "Local union type"
    @type union_t :: atom | integer

    @typedoc "Local list type"
    @type list_t :: [:trace | :log]

    @typedoc "Local type with large spec"
    @type large_t :: pid | port | (registered_name :: atom) | {registered_name :: atom, node}

    @typedoc "Remote type from aliased module"
    @type remote_aliased_t :: R.remote_t() | R.remote_list_t()

    @type tuple_opt_t :: {:opt_name, :opt_value}

    @typedoc "Local keyword-value type"
    @type option_t ::
            {:local_o, local_t}
            | {:local_with_params_o, local_t(atom, integer)}
            | {:union_o, union_t}
            | {:inline_union_o, :a | :b}
            | {:list_o, list_t}
            | {:inline_list_o, [:trace | :log]}
            | {:basic_o, pid}
            | {:basic_with_params_o, nonempty_list(atom)}
            | {:builtin_o, keyword}
            | {:builtin_with_params_o, keyword(term)}
            | {:remote_o, Remote.remote_t()}
            | {:remote_with_params_o, Remote.remote_t(atom, integer)}
            | {:remote_aliased_o, remote_aliased_t}
            | {:remote_aliased_inline_o, R.remote_t()}
            | {:private_o, private_t}
            | {:opaque_o, opaque_t}
            | {:non_existent_o, Remote.non_existent()}
            | {:large_o, large_t}

    @typedoc "Extra option"
    @type extra_option_t :: {:option_1, atom} | {:option_2, integer}

    @typedoc "Options"
    @type options_t :: [option_t]

    @typedoc "Option | Extra option"
    @type option_or_extra_option_t ::
            {:option_1, boolean} | {:option_2, timeout} | Remote.remote_option_t()

    @type extra_option_1_t :: extra_option_t

    @type atom_opt_t :: :atom_opt

    @type bounded_type(t) :: {t, integer}

    @spec func_with_options(options_t) :: any
    def func_with_options(options) do
      options
    end

    @spec func_with_union_of_options([option_t | extra_option_t]) :: any
    def func_with_union_of_options(options) do
      options
    end

    @spec func_with_union_of_options_as_type([option_or_extra_option_t]) :: any
    def func_with_union_of_options_as_type(options) do
      options
    end

    @spec func_with_union_of_options_inline([{:option_1, atom} | {:option_2, integer} | option_t]) ::
            any
    def func_with_union_of_options_inline(options) do
      options
    end

    @spec func_with_named_options(options :: options_t) :: any
    def func_with_named_options(options) do
      options
    end

    @spec func_with_options_as_inline_list([{:local_o, local_t} | {:builtin_o, keyword}]) :: any
    def func_with_options_as_inline_list(options) do
      options
    end

    @spec func_with_option_var_defined_in_when([opt]) :: any when opt: option_t
    def func_with_option_var_defined_in_when(options) do
      options
    end

    @spec func_with_options_var_defined_in_when(opts) :: any when opts: [option_t]
    def func_with_options_var_defined_in_when(options) do
      options
    end

    @spec func_with_one_option([{:option_1, integer}]) :: any
    def func_with_one_option(options) do
      options
    end

    @spec fun_without_options([integer]) :: integer
    def fun_without_options(a), do: length(a)

    @spec fun_with_atom_option([:option_name]) :: any
    def fun_with_atom_option(a), do: a

    @spec fun_with_atom_option_in_when(opts) :: any when opts: [:option_name]
    def fun_with_atom_option_in_when(a), do: a

    @spec fun_with_recursive_remote_type_option([OtherRemote.other()]) :: any
    def fun_with_recursive_remote_type_option(a), do: a

    @spec fun_with_recursive_user_type_option([extra_option_1_t]) :: any
    def fun_with_recursive_user_type_option(a), do: a

    @spec fun_with_tuple_option_in_when(opt) :: any when opt: [tuple_opt_t]
    def fun_with_tuple_option_in_when(a), do: a

    @spec fun_with_tuple_option([tuple_opt_t]) :: any
    def fun_with_tuple_option(a), do: a

    @spec fun_with_atom_user_type_option_in_when(opt) :: any when opt: [atom_opt_t]
    def fun_with_atom_user_type_option_in_when(a), do: a

    @spec fun_with_atom_user_type_option([atom_opt_t]) :: any
    def fun_with_atom_user_type_option(a), do: a

    @spec fun_with_list_of_lists([opt]) :: any when opt: [tuple_opt_t]
    def fun_with_list_of_lists(a), do: a

    @spec fun_with_recursive_type(opt) :: any when opt: [term :: opt]
    def fun_with_recursive_type(a), do: a

    @spec fun_with_multiple_specs(nil) :: any
    @spec fun_with_multiple_specs([tuple_opt_t]) :: any
    def fun_with_multiple_specs(a), do: a

    @spec fun_with_multiple_specs_when(nil) :: any
    @spec fun_with_multiple_specs_when([opts]) :: any when opts: tuple_opt_t
    def fun_with_multiple_specs_when(a), do: a

    @spec fun_with_bounded_type([bounded_type(:a | :b)]) :: any
    def fun_with_bounded_type(a), do: a

    @spec fun_with_bounded_type_when([opt]) :: any when opt: bounded_type(:a | :b)
    def fun_with_bounded_type_when(a), do: a

    @spec fun_with_bounded_type_remote([OtherRemote.bounded_type(:a | :b)]) :: any
    def fun_with_bounded_type_remote(a), do: a

    @spec fun_with_bounded_type_remote_local_arg([OtherRemote.bounded_type(atom_opt_t)]) :: any
    def fun_with_bounded_type_remote_local_arg(a), do: a

    @spec fun_with_bounded_type_remote_local_arg_union([OtherRemote.bounded_type(atom_opt_t | :l)]) :: any
    def fun_with_bounded_type_remote_local_arg_union(a), do: a

    @spec fun_with_bounded_type_remote_arg([bounded_type(OtherRemote.some)]) :: any
    def fun_with_bounded_type_remote_arg(a), do: a

    @spec fun_with_keyword_list([key: integer]) :: any
    def fun_with_keyword_list(a), do: a

    @spec fun_with_bounded_type_arg_from_when([local_t(v, integer | binary)]) :: any when v: :a | :b
    def fun_with_bounded_type_arg_from_when(a), do: a

    @spec fun_with_remote_bounded_type_arg_from_when([OtherRemote.bounded_type(v)]) :: any when v: :a | :b
    def fun_with_remote_bounded_type_arg_from_when(a), do: a

    @type m_true(l, _r)::l
    @type m_false(_l, r)::r

    @type m_0(_l, r)::r
    @type m_1(l, r)::l(r)
  end
end
  test "fun_with_bounded_type" do
    assert [
      {Local, :a, {:type, _, :integer, []}},
      {Local, :b, {:type, _, :integer, []}}
      ] = TypeInfo.extract_param_options(Local, :fun_with_bounded_type, 0)
  end

  test "fun_with_bounded_type_when" do
    assert [
      {Local, :a, {:type, _, :integer, []}},
      {Local, :b, {:type, _, :integer, []}}
      ] = TypeInfo.extract_param_options(Local, :fun_with_bounded_type_when, 0)
  end

  test "fun_with_bounded_type_remote" do
    assert [
      {OtherRemote, :a, {:type, _, :integer, []}},
      {OtherRemote, :b, {:type, _, :integer, []}}
      ] = TypeInfo.extract_param_options(Local, :fun_with_bounded_type_remote, 0)
  end

  test "fun_with_bounded_type_remote_arg" do
    assert [{Local, :a, {:type, _, :integer, []}}] = TypeInfo.extract_param_options(Local, :fun_with_bounded_type_remote_arg, 0)
  end

  test "fun_with_bounded_type_remote_local_arg" do
    assert [{OtherRemote, :atom_opt, {:type, _, :integer, []}}] = TypeInfo.extract_param_options(Local, :fun_with_bounded_type_remote_local_arg, 0)
  end

  test "fun_with_bounded_type_remote_local_arg_union" do
    assert [{OtherRemote, :atom_opt, {:type, _, :integer, []}}] = TypeInfo.extract_param_options(Local, :fun_with_bounded_type_remote_local_arg_union, 0)
  end

  test "fun_with_bounded_type_arg_from_when" do
    assert [
      {Local, :a, {:type, _, :union, [{:type, _, :integer, []}, {:type, _, :binary, []}]}},
      {Local, :b, {:type, _, :union, [{:type, _, :integer, []}, {:type, _, :binary, []}]}}
      ] = TypeInfo.extract_param_options(Local, :fun_with_bounded_type_arg_from_when, 0)
  end

  test "fun_with_remote_bounded_type_arg_from_when" do
    assert [
      {OtherRemote, :a, {:type, _, :integer, []}},
      {OtherRemote, :b, {:type, _, :integer, []}}
      ] = TypeInfo.extract_param_options(Local, :fun_with_remote_bounded_type_arg_from_when, 0)
  end

  test "fun_with_keyword_list" do
    assert [{Local, :key, {:type, _, :integer, []}}] = TypeInfo.extract_param_options(Local, :fun_with_keyword_list, 0)
  end