elixir-lang / elixir

Elixir is a dynamic, functional language for building scalable and maintainable applications
https://elixir-lang.org/
Apache License 2.0
24.52k stars 3.38k forks source link

Types defined in a module are not visible in other module after import. #2567

Closed wkhere closed 10 years ago

wkhere commented 10 years ago

Simplest example:

defmodule Foo do
  @type t :: String.t
end

defmodule Bar do
  import Foo

  @spec quux(t) :: any
  def quux(x), do: x
end 
% mix
lib/foo.ex:6: warning: unused import Foo

== Compilation error on file lib/foo.ex ==
** (CompileError) lib/foo.ex:8: type t() undefined
    (stdlib) lists.erl:1336: :lists.foreach/2
    (stdlib) erl_eval.erl:657: :erl_eval.do_apply/6
    (elixir) src/elixir.erl:170: :elixir.erl_eval/3
    (elixir) src/elixir.erl:158: :elixir.eval_forms/4
    (elixir) src/elixir_lexical.erl:17: :elixir_lexical.run/2
    (elixir) src/elixir.erl:170: :elixir.erl_eval/3

Elixir 0.14.3

(of course in the real world equivalent of Foo is not only used for defining types).

ericmj commented 10 years ago

We can't import types because they are not available to us at that point. import only works for functions and macros. You have to reference the remote type with it's module name (which can be aliased).

wkhere commented 10 years ago

Thx for the explanation. Is there any remote possibility it will change in the future?

ericmj commented 10 years ago

It's a technical limitation that I don't think we can work around (I could of course be wrong). Unfortunately types are not available to us for modules loaded during compilation.

josevalim commented 10 years ago

It could potentially be fixed, if we wanted, by storing the type information but I am not really sure if we should. Maybe by an implicit import type directive, I wouldn't mix it with the regular import though, since it increases the chances of conflicts and so on.

mguimas commented 5 years ago

Any chance this might get implemented after all these years? or it is a topic to be closed?

umuro commented 5 years ago

Example: @type html_tree :: Floki.html_tree Simply redeclare types you would like to use without a module name. The topic is closed but I am writing this as a tip for who would still search the topic.

kyleVsteger commented 2 years ago

Posting a (probably hacky) solution because this was driving me nuts too.

defmodule Types do
  @moduledoc """
  Common type helpers for typespecs.
  """
  @type_strings %{
    user_id: """
    @typedoc "The primary key for a `user` record."
    @type user_id :: Ecto.UUID.t()
    """,
    user_surrogate_id: """
    @typedoc "An additional identifier for a `user` typically created by the client."
    @type user_surrogate_id :: Ecto.UUID.t() | String.t()
    """
  }

  @doc """
  This macro is to be called with a list of atoms. Each atom refers to a string representation of a type definition present in 
  the `@type_strings` module attribute. The types will be defined in the calling module.
  """
  defmacro import_types(types) do
    ast =
      types
      |> Enum.reduce([], fn type, strings ->
        [Map.fetch!(@type_string, type) | strings]
      end)
      |> Enum.join("\n")
      |> Code.string_to_quoted!()

    quote do
      unquote(ast)
    end
  end
end
defmodule Foo do 
  import Types, only: [import_types: 1]
  import_types [:user_id, user_surrogate_id]

  @spec foo(user_id(), surrogate_id()) :: String.t()
  def foo(user_id, surrogate_id), do: "#{user_id}-#{surrogate_id}"
end