Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compilation error when using conforms? #146

Open
lbueso opened this issue Sep 27, 2022 · 3 comments
Open

Compilation error when using conforms? #146

lbueso opened this issue Sep 27, 2022 · 3 comments
Labels
bug Something isn't working

Comments

@lbueso
Copy link

lbueso commented Sep 27, 2022

When a type is defined as follows:

defmodule A do
  use TypeCheck
  @type! t() :: %{v: integer()}

  @spec! f(t()) :: integer()
  def f(x), do: x.v
end

TypeCheck works correctly, but if I try to use TypeCheck.conforms? in the same module where a type is defined, I get a complation error:

defmodule A do
  use TypeCheck
  @type! t() :: %{v: integer()}


  def main() do
    x = 1
    TypeCheck.conforms?(x, t())
  end
end
== Compilation error in file lib/examples.ex ==
** (UndefinedFunctionError) function A.t/0 is undefined (function not available)
    A.t()
    (stdlib 3.17) erl_eval.erl:685: :erl_eval.do_apply/6
    (elixir 1.13.4) lib/code.ex:797: Code.eval_quoted/3
    (type_check 0.12.1) lib/type_check/type.ex:96: TypeCheck.Type.build_unescaped/4
    (type_check 0.12.1) lib/type_check.ex:202: TypeCheck."MACRO-conforms?"/4
    (type_check 0.12.1) expanding macro: TypeCheck.conforms?/2
@Qqwy Qqwy added the bug Something isn't working label Sep 27, 2022
@Qqwy
Copy link
Owner

Qqwy commented Sep 27, 2022

Thank you for your bug report. 😊
This indeed should not happen, and I'll do my best to fix it shortly (which will probably be some time later this week).

@Qqwy
Copy link
Owner

Qqwy commented Oct 14, 2022

I started working on this.
Turns out that there is a bit of a conondrum with how the library currently is implemented that makes fixing this very difficult.

To be able to support recursive types and the likes, we currently:

  • First accumulate all type definitions
  • Then in a before_compile callback, define a separate module that only contains the "type functions", and import this in the body of the normal module.
  • The types can now be found from within @spec! and the likes (any other macro in the module body).
  • The type-functions themselves are added to the (end of the) module body.
  • Then we compile the module itself.
  • If types are ever referred to inside a function directly, the definition in the main module is used.

However, it seems that when you use a macro inside a function and refer to a type from within, then this macro's expansion happens before the before_compile callback step.

At this point we do have some of the type information available inside a module attribute (Module.get_attribute(__MODULE__, TypeCheck.TypeDefs)), for all the types that were already seen.
But importing type definitions from there requires manually altering how Elixir does its function lookups in this very specific instance. This feels like a brittle solution.

However, I do not have an alternative idea as of right now.

@Qqwy
Copy link
Owner

Qqwy commented Oct 15, 2022

Essentially, the problem is the same as in this minimal example:

defmodule MacroModule do
  defmacro precompute(x) do
    result = Code.eval_quoted(x)
    quote do
      unquote(result)
    end
  end
end

defmodule Example do
  require MacroModule
  def foo() do
    42
  end

  def bar(y) do
    MacroModule.precompute(foo()) * 3
  end
end
** (UndefinedFunctionError) function Example.foo/0 is undefined (function not available)
    Example.foo()
    nofile:23: (file)
    iex:16: (file)

I do not think there is a way to fix this problem in the general sense.
But maybe we can lookup local types as mentioned before in some restricted cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants