:zip.create/3 and Dialyzer warning

I recently started using Dialyzer in my project and have been going through and fixing all the warnings. I’m down to one!

I call the :zip.create/3 function like this:

:zip.create("ceu_files.zip", [{'file1', "content1"}, {'file2',"content2"}], [:memory])

Dialyzer issues a warning saying that the function call will not succeed. However, the function has been working for months now. Is it trying to tell me that there’s an code path that results in the function not working?

The difference between the success typing and the contract is confusing and it’s hard for me to understand what it’s trying to convey. I’m guessing it has something to do with the file name (first argument), but I’m not entirely sure.

Do you have an idea of what this error message is trying to communicate?

The function call will not succeed.

:zip.create(_zip_name :: <<_::8, _::size(1)>>, _files :: [any()], [:memory])

will never return since the success typing is:
(
  atom() | [atom() | [any()] | char()],
  [
    atom()
    | [atom() | [any()] | char()]
    | {atom() | [atom() | [any()] | char()], binary()}
    | {atom() | [atom() | [any()] | char()], binary(),
       {:file_info, :undefined | non_neg_integer(),
        :device | :directory | :other | :regular | :symlink | :undefined,
        :none | :read | :read_write | :undefined | :write,
        :undefined | non_neg_integer() | {_, _}, :undefined | non_neg_integer() | {_, _},
        :undefined | non_neg_integer() | {_, _}, :undefined | non_neg_integer(),
        :undefined | non_neg_integer(), :undefined | non_neg_integer(),
        :undefined | non_neg_integer(), :undefined | non_neg_integer(),
        :undefined | non_neg_integer(), :undefined | non_neg_integer()}}
  ],
  [
    :cooked
    | :memory
    | :verbose
    | {:comment, string()}
    | {:compress, :all | [[any()]] | {:add, [any()]} | {:del, [any()]}}
    | {:cwd, string()}
    | {:uncompress, :all | [[any()]] | {:add, [any()]} | {:del, [any()]}}
  ]
) ::
  {:error, _}
  | {:ok,
     atom() | [atom() | [any()] | char()] | {atom() | [atom() | [any()] | char()], binary()}}

and the contract is
(name, fileList, options) :: retValue
when name: :file.name(),
     fileList: [:FileSpec],
     fileSpec:
       :file.name() | {:file.name(), binary()} | {:file.name(), binary(), :file.file_info()},
     options: [:Option],
     option: create_option(),
     retValue:
       {:ok, FileName :: filename()}
       | {:ok, {FileName :: filename(), binary()}}
       | {:error, Reason :: term()}

looking here in the types on documentation from :zip.create/3, looks like the file:name() type must be in the list representation, so "ceu_files.zip" is in binary representation and has to be write with single quote 'ceu_files.zip'

here the types defined in :zip function to you get a look too: Erlang -- zip
and here is the file:name() type: Erlang -- file

note that string() type is defined as [char()] i.e a list of integer

2 Likes

This was the bit that was tripping me up! I saw the file:name type and figured that was the culprit, but when I saw the string(), I thought it was binary. Oops!

I had also seen the [char()] part in the spec, but after changing it, things broke later when I called send_download/4, passing in the list represented filename that the function returns, so I assumed that was the wrong path. Oops again!

It turned out that the subsequent error was that send_download/4 expects a binary when passing in the filename value, which :zip.create/3 is now returning the list representation. A simple to_string fixed that issue!

send_download(
  conn,
  {:file, tmp_file},
  content_type: "application/zip",
  filename: filename |> to_string()

The oddest thing is that this works with a binary representation just fine; it’s only because of Dialyzer that I realized I’ve been calling it wrong.

All is good now! Thanks so much for your help, @NetonD!

1 Like

One interesting thing to note - a lot of the APIs in :file accept a file:name_all type which can be a charlist or a binary. As you noted, :zip.create works fine with a binary filename since the things it calls will accept either shape.

My guess is that the :zip.create spec wasn’t broadened to consider a binary the same as a charlist because the extraction functions treat binaries completely differently (as an in-memory ZIP file).