Skip to content

Inconsistency between %Mod{} and Mod.__struct__() when delegating #8800

@mguimas

Description

@mguimas

Environment

$ elixir --version
Erlang/OTP 21 [erts-10.2.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]

Elixir 1.8.1 (compiled with Erlang/OTP 21)

Current behavior

Given the file xx.ex

defmodule XX do
  defstruct [:field]
end

the following holds for XX

iex(2)> %XX{}
%XX{field: nil}
iex(3)> XX.__struct__()
%XX{field: nil}
iex(4)> struct(XX)
%XX{field: nil}
iex(5)> %XX{} == XX.__struct__()
true

but given the file yy.ex

defmodule YY do
  defdelegate __struct__, to: XX
  defdelegate __struct__(x), to: XX
end

the follwing happens for YY

iex(6)> %YY{}
%YY{field: nil}
iex(7)> YY.__struct__()
%XX{field: nil}
iex(8)> struct(YY)
%XX{field: nil}
iex(9)> %YY{} == YY.__struct__()
false

in which different idioms that are expected to be equivalent return inconsistent results.

Expected behavior

The expected behavior is for %Mod{} to be equivalent to Mod.__struct__(), consequently the function Kernel.SpecialForms.%/2 must be fixed to return the value of Mod.__struct__/0 (and of Mod.__struct__/1 as applicable).

The defdelegate construct is working properly because it returns the result of XX.__struct__/{0,1} as the result of YY.__struct__/{0,1}, as expected for delegation.

This means that somehow Kernel.SpecialForms.%/2 is overwritting the result returned by YY.__struct__/{0,1}, and it must not, and instead pass it "as is".

Note that fixing Kernel.SpecialForms.%/2 additionally solves the case when XX is an Ecto schema. For example, given

defmodule XX do
  use Ecto.Schema
  schema "table" do
    field :field
  end
end

the following is happening now

iex(11)> %YY{}
%YY{__meta__: #Ecto.Schema.Metadata<:built, "table">, field: nil, id: nil}
iex(12)> %YY{} |> IO.inspect(structs: false, pretty: true)
%{
  __meta__: %{
    __struct__: Ecto.Schema.Metadata,
    context: nil,
    prefix: nil,
    schema: XX,
    source: "table",
    state: :built
  },
  __struct__: YY,
  field: nil,
  id: nil
}
%YY{__meta__: #Ecto.Schema.Metadata<:built, "table">, field: nil, id: nil}

where the __meta__ refers to XX, and not YY.

The solution should be to not fix in Ecto also, but to fix in Kernel.SpecialForms.%/2 instead, as doing the latter solves all the reported cases.

Thanks

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions