Dialyzer doesn't seem to understand File.stat output

Consider the following pair of functions (which is somewhat arbitrarily constructed in a vain attempt to satisfy dialyzer):

  defp last_modified_time(path) when is_binary(path) do
    path
    |> File.stat!(time: :posix)
    |> extract_mtime()
  end

  defp extract_mtime(%{mtime: mtime}) when is_integer(mtime), do: mtime

I can’t seem to work around the following dialyzer warning:

lib/xgit/util/file_snapshot.ex:131:call
The function call will not succeed.

Xgit.Util.FileSnapshot.extract_last_modified_time(%File.Stat{
  :access => :none | :read | :read_write | :write,
  :atime =>
    {{non_neg_integer(), 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12, 1..255},
     {byte(), byte(), byte()}},
  :ctime =>
    {{non_neg_integer(), 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12, 1..255},
     {byte(), byte(), byte()}},
  :gid => non_neg_integer(),
  :inode => non_neg_integer(),
  :links => non_neg_integer(),
  :major_device => non_neg_integer(),
  :minor_device => non_neg_integer(),
  :mode => non_neg_integer(),
  :mtime =>
    {{non_neg_integer(), 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12, 1..255},
     {byte(), byte(), byte()}},
  :size => non_neg_integer(),
  :type => :device | :directory | :other | :regular | :symlink,
  :uid => non_neg_integer()
})

will never return since it differs in arguments with
positions 1st from the success typing arguments:

(%{:mtime => integer(), _ => _})

It appears to be thinking that the mtime member of the File.Stat structure will always be of type :calendar.datetime, which is not correct when using time: :posix.

Any advice on how to make dialyzer see what is actually happening?

The typespec seems to be wrong. You can ignore this warning using @dialyzer attribute.

Could you also please file a bug/create a PR at the elixir repository?

2 Likes

Which Elixir version are you running?

Here’s the type definition of File.Stat.t in Elixir 1.9.1:

iex(8)> t File.Stat
@type t() :: %File.Stat{
        access: :read | :write | :read_write | :none,
        atime: :calendar.datetime() | integer(),
        ctime: :calendar.datetime() | integer(),
        gid: non_neg_integer(),
        inode: non_neg_integer(),
        links: non_neg_integer(),
        major_device: non_neg_integer(),
        minor_device: non_neg_integer(),
        mode: non_neg_integer(),
        mtime: :calendar.datetime() | integer(),
        size: non_neg_integer(),
        type: :device | :directory | :regular | :other | :symlink,
        uid: non_neg_integer()
      }
3 Likes

This has been added with elixir 1.9, so a bug report/PR isn’t necessary anymore, just updating the elixir version :slight_smile:

3 Likes

Confirmed. Upgraded to 1.9.1 locally and the problem is fixed.

Thank you!

1 Like