elixir_sense icon indicating copy to clipboard operation
elixir_sense copied to clipboard

Type info not able to properly expand var types

Open lukaszsamson opened this issue 2 years ago • 1 comments

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 avatar Mar 04 '23 09:03 lukaszsamson

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

lukaszsamson avatar Mar 04 '23 09:03 lukaszsamson