`Module.delete_attribute/2` doesn't reset `accumulate: true`

Today I encountered an weird behavior when I tried to change the accumulate option for a module attribute.

I would like to know if this behavior is intended since the documentation does not mention it.

In summary the steps are:

  • Create a module attribute with the accumulate: true option
  • Delete the module attribute
  • Recreate it with the accumulate: false option
  • Try to assign values, they will still be accumulated

Here is an example :

defmodule ModuleDeleteTest do
  # 1/ Define a module attribute with accumulate: true
  Module.register_attribute(__MODULE__, :attribut, accumulate: true)
  
  # 2/ Then delete and recreate the module attribute but with accumulate: false
  Module.delete_attribute(__MODULE__, :attribut)
  Module.register_attribute(__MODULE__, :attribut, accumulate: false)

  # Unfortunately, the accumulate: true flag is still present
  @attribut :foo
  @attribut :bar
  
  # > CURRENT attribut value: [:bar, :foo]
  IO.inspect(@attribut, label: "CURRENT attribut value")
end

On this example I was expecting@attribut to be equals to :bar at the end.

After examining the Elixir source code, I noticed that delete_attribute/2 with accumulate: true does remove the attribute from the bag but not from the set. As a result, the set still contains the accumulate: true flag.

Here here a quick fix:

defmodule ModuleFix do
  def delete_attribute(module, key) do
    {set, _bag} = :elixir_module.data_tables(module)
    
    # Here bag is clean by Module.delete_attribute/2
    Module.delete_attribute(module, key)
    
    # But you also need to clean the set or the module attribute
    # still have the :accumulate flag set
    :ets.delete(set, key)
  end
end

defmodule ModuleDeleteTestFix do
  # 1/ Define a module attribute with accumulate: true
  Module.register_attribute(__MODULE__, :attribut, accumulate: true)
  
  # 2/ Then delete and recreate the module attribute but with accumulate: false
  ModuleFix.delete_attribute(__MODULE__, :attribut)
  Module.register_attribute(__MODULE__, :attribut, accumulate: false)

  # Now, module attribute is properly reset
  @attribut :foo
  @attribut :bar
  
  # > FIXED field value: :bar
  IO.inspect(@attribut, label: "FIXED attribut value")
end

As said in the introduction, I don’t know if this behavior is intended and if it is, I think it should be mentioned in the doc If it’s not, I’d be happy to open an issue on Github and propose a PR.

1 Like

It’s not a bug: https://github.com/elixir-lang/elixir/issues/11635

1 Like