Skip to content

Module.delete_attribute/2 doesn’t reset accumulate: true #11635

@ImNotAVirus

Description

@ImNotAVirus

I'm not sure if it's a bug but if it's I can do a PR to fix it.

Current behavior

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

Steps to reproduce

Here is a small example.

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

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

This code displays attribute value: [:bar, :foo] when compiled.

Expected behavior

I expect the code to display attribute value: :bar.

How to fix

The bug come from theses lines:

[{_, _, :accumulate}] ->
reverse_values(:ets.take(bag, {:accumulate, key}), [])

Here, when the module attribute has the flag accumulate: true, the bag is emptied but not the set which always contains {attribute, [], :accumulate}. So when register_attribute/3 is then called with accumulate: true, this line https://github.com/elixir-lang/elixir/blob/main/lib/elixir/lib/module.ex#L1592 will do nothing.

Here is a quick woking example:

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__, :attribute, accumulate: true)
  
  # 2/ Then delete and recreate the module attribute but with accumulate: false
  ModuleFix.delete_attribute(__MODULE__, :attribute)
  Module.register_attribute(__MODULE__, :attribute, accumulate: false)

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

Environment

  • Elixir & Erlang/OTP versions (elixir --version):
elixir --version
Erlang/OTP 24 [erts-12.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1]

Elixir 1.13.2 (compiled with Erlang/OTP 24)
  • Operating system: Windows & Linux Debian

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions