janp

janp

Keyword.put in macro results in AST instead of keyword list

I am currently stuck in implementing a macro :thinking: Here’s the code I currently have (without unnecessary code):

defmacro request(opts \\ [], do: expr) do
  quote
    typedstruct unquote(opts) do
      unquote(expr)
    end
  end
end

That macro works fine, but I would like to add some options that cannot be overwritten, so I tried this:

defmacro request(opts \\ [], do: expr) do
  quote
    typedstruct Keyword.put(unquote(opts), :module, Req) do
      unquote(expr)
    end
  end
end

Sadly, this fails with a compilation error:

** (FunctionClauseError) no function clause matching in Access.get/3    
    
    The following arguments were given to Access.get/3:
    
        # 1
        {{:., [line: 6], [{:__aliases__, [line: 6, counter: {ChangeUser, 8}, alias: false], [:Keyword]}, :put]}, [line: 6], [[], :module, {:__aliases__, [line: 6, counter: {ChangeUser, 8}, alias: false], [:Req]}]}
    
        # 2
        :enforce
    
        # 3
        nil
    
    Attempted function clauses (showing 6 out of 6):
    
        def get(%module{} = container, key, default)
        def get(map, key, default) when is_map(map)
        def get(list, key, default) when is_list(list) and is_atom(key)
        def get(list, key, _default) when is_list(list) and is_integer(key)
        def get(list, key, _default) when is_list(list)
        def get(nil, _key, default)

That error occurs, because an AST is passed to the typedstruct-macro instead of a keyword list and typed_struct then tries to call opts[:enforce] (tuples do not implement the Access-behaviour).

Is there anyone who can help me figure out what is happening and how to solve this issue? :slight_smile:

Marked As Solved

sodapopcan

sodapopcan

Ah yes, that changes things!

So first thing, the way you’re doing use TypedStruct isn’t going to work. You actually want ChangeUser (or whatever module is useing Operation) to be using it. Perhaps you’ve left out some code, but basically you want something like this:

defmodule Operation do
  defmacro __using__(_) do
    quote do
      use TypedStruct
      import Operation
    end
  end

  defmacro request(opts \\ [], do: expr) do
    opts = Keyword.put(opts, :module, Module.concat([__CALLER__.module, Req]))
  
    quote do
      typedstruct unquote(opts) do
        unquote(expr)
      end
    end
  end
end

Also Liked

sodapopcan

sodapopcan

Move it outside of the quote:

defmacro request(opts \\ [], do: expr) do
  opts = Keyword.put(opts, :module, Req)

  quote do
    typedstruct unquote(opts) do
      unquote(expr)
    end
  end
end

Otherwise, as you pointed out, the code you end up with will literally be the same as typing out:

typedstruct Keyword.put([some: :user, provided: :opts], :module, Req) do
  # unquoted_expr
end

The reason you don’t have to do anything special with opts in the macro body is that lists are one of the constructs that return themselves when quoted.

EDIT: Just re-reading my answer after getting pinged about it:

For good measure you should call Macro.escape on the value you put in the keyword list:

opts = Keyword.put(opts, :module, Macro.escape(Req))

It won’t make any difference in this case (Macro.escape(Req) #=> Req) but if you put other values (like maps, for example) as value, you’ll need to do this so that you get the proper ast representation to be later unquoted.

janp

janp

Great :partying_face: It works!

A few minutes ago, I found some code using __CALLER__, but I was unsure about it. Now I know it better.

Thank you!

sodapopcan

sodapopcan

No prob!

You often don’t need to use __CALLER__ but in this case typedstruct is a macro itself, so of course its arguments get quoted so you need to grab the caller’s module name outside the quote block. But most of the time you can just use __MODULE__ inside the quote block:

defmodule Bar do
  defmarco __using__(_) do
    quote do
      dbg(__MODULE__)
    end
  end
end

defmodule Foo do
  use Bar
end

dbg here will be Foo.

Where Next?

Popular in Questions Top

New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
sabri
Can someone explain the settings of pool_size of Ecto in config file? and what is the recommend size? Thanks
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

We're in Beta

About us Mission Statement