elixir icon indicating copy to clipboard operation
elixir copied to clipboard

Dialyzer errors after upgrading to 1.19.0

Open joshk opened this issue 2 months ago • 17 comments

Elixir and Erlang/OTP versions

Erlang/OTP 28 [erts-16.1] [source] [64-bit] [smp:14:14] [ds:14:14:10] [async-threads:1] [jit]

Elixir 1.19.0 (compiled with Erlang/OTP 28)

Operating system

Mac and Linux

Current behavior

I've upgraded tested upgrading https://github.com/nerves-hub/nerves_hub_web to v1.19.0 and ran into a lot of new Dialyzer errors.

You can find the full output I get locally here: https://gist.github.com/joshk/1ca4371d7d6ae3cc4fa9895f0258bc3a

It looks like its also happening to others: https://github.com/jeremyjh/dialyxir/issues/574

You can also see the CI run here: https://github.com/nerves-hub/nerves_hub_web/actions/runs/18585936679/job/52989726839

Sorry if this isn't the right place to report the issue.

Expected behavior

Ideally, no new Dialyzer issues.

joshk avatar Oct 17 '25 07:10 joshk

We updated to 1.19.0 with erlang 28 and on our project there are tons of errors too

rockneurotiko avatar Oct 17 '25 08:10 rockneurotiko

The protocol error is caused by an incorrect assumption in a library, it is fixed here: https://github.com/asummers/erlex/pull/60

The additional warnings are related to opaqueness in Erlang/OTP 28. @sabiwara, we discussed those for MapSet already, do you think there is something we can do here? It seems an issue with escaping MapSets when nested inside struct fields. :(

josevalim avatar Oct 17 '25 08:10 josevalim

Wow, thank you for the quick reply and creating the erlex PR!

joshk avatar Oct 17 '25 08:10 joshk

Hey, we are getting similar opaqueness warnings in our project after upgrading to elixir 1.19 with OTP 28.1.

I've pushed a small reproducible build in this repo here. I hope that helps.

dushyantss avatar Oct 17 '25 12:10 dushyantss

for the opaque errors in dialyzer, for now I'm adding this entry in the dialyzer_ignore file:

[
  ~r/lib\/your_project_name.*call_without_opaque/
]

Otherwise, the CI on some of my projects would block the merge. It seems to be caused by using MapSet.t() inside structs as @josevalim mentioned.

phcurado avatar Oct 18 '25 08:10 phcurado

It seems an issue with escaping MapSets when nested inside struct fields. :(

@josevalim let me check, if that's the case we might be able to re-use Macro.escape(..., generated: true) introduced to fix the module attribute issue https://github.com/elixir-lang/elixir/issues/14750.

Here is a minimal reproduction:

  @type t :: %__MODULE__{set: MapSet.t()}
  defstruct set: MapSet.new()

  @spec new :: t
  def new, do: %__MODULE__{}
lib/repro.ex:22: The specification for 'Elixir.Repro':new/0 has an opaque subtype #{'__struct__':='Elixir.Repro', 'set':=#{'__struct__':='Elixir.MapSet', 'map':='Elixir.MapSet':internal(_)}} which is violated by the success typing () -> #{'__struct__':='Elixir.Repro', 'set':=#{'__struct__':='Elixir.MapSet', 'map':=#{}}}

sabiwara avatar Oct 19 '25 04:10 sabiwara

Hum might be unrelated to structs, since the previous fix (marking it as generated) doesn't seem to work with return types 😢

  @spec returned :: MapSet.t()
  def returned, do: MapSet.new([])
 opaqueness.ex:14: The specification for 'Elixir.Dialyzer.Opaqueness':returned/0 has an opaque subtype
           #{'__struct__' := 'Elixir.MapSet',
             'map' := 'Elixir.MapSet':internal(_)} which is violated by the success typing
           () -> #{'__struct__' := 'Elixir.MapSet', 'map' := #{}}

This feels like an upstream issue in OTP, since it ignores the generated?

sabiwara avatar Oct 19 '25 04:10 sabiwara

@sabiwara I looked at the source code, and this warning comes when checking the contracts of functions, there is no longer AST information attached with it (and, even if it had, I am not sure if it would work when nested).

So I think we have two options:

  1. Disable contract_with_opaque warnings by default (is this something we can do in Dialyxir? cc @jeremyjh @christhekeele
  2. Don't use opaque anymore, since structs get inlined anyway

josevalim avatar Oct 19 '25 08:10 josevalim

Disable contract_with_opaque warnings by default (is this something we can do in Dialyxir? cc @jeremyjh @christhekeele

I think this sounds reasonable, it looks like we can set :no_opaque to do this. @sabiwara do you mind trying to put this in your project settings to confirm it suppresses this warning?

dialyzer: [flags: [:no_opaque]]

https://github.com/jeremyjh/dialyxir?tab=readme-ov-file#flags

If this works we can make it a default to limit the number of people tripping over this rake.

jeremyjh avatar Oct 19 '25 13:10 jeremyjh

no_opaque may be a bit too broad but it seems we have no other option.

josevalim avatar Oct 19 '25 13:10 josevalim

think this sounds reasonable, it looks like we can set :no_opaque to do this. @sabiwara do you mind trying to put this in your project settings to confirm it suppresses this warning?

dialyzer: [flags: [:no_opaque]]

This indeed disables the error.

no_opaque may be a bit too broad but it seems we have no other option.

Maybe we could just remove the opaque type in that case, if we're considering disabling the check for it entirely?

Also, even if it is probably low-priority for now, just wondering out of curiosity: is opaqueness checks sth that set-theoretic types could be able to support in the future?

sabiwara avatar Oct 19 '25 13:10 sabiwara

Hi there, I also wanted to share that I just updated to 1.19.1 (from 1.18.4) and I am also getting Dialyzer warnings (using ElixirLS). I had one mentioning :no_opaque and related to ecto which has being correctly ignored using the no_opaque option of ElixirLS dialyzer option. But I'm also having warning everytime I'm using IO.inspect with a :label (e.g. IO.inspect(slug, label: "# Slug") Here is the output in this particular case:

The call 'Elixir.IO':inspect
 (any(),
  [{label, <<83,108,117,103>>}]) will never return since it differs in the 2nd argument from the success typing arguments: 
 (any(),
  [{'base' | 'binaries' | 'charlists' | 'custom_options' |
    'inspect_fun' | 'limit' | 'pretty' | 'printable_limit' |
    'safe' | 'structs' | 'syntax_colors' | 'width',
    'as_binaries' | 'as_charlists' | 'as_lists' | 'as_strings' |
    'binary' | 'decimal' | 'false' | 'hex' | 'infer' |
    'infinity' | 'octal' | 'true' |
    fun((_, map()) ->
            'doc_line' |
            binary() |
            maybe_improper_list() |
            {_, _} |
            {_, _, _} |
            {_, _, _, _}) |
    [{_, _}] |
    non_neg_integer()}])

The <<83,108,117,103>> just corresponds to "# Slug".. But it's like it's not accepting the :label option as being part of IO.inspect second argument options.

But it seems it's still here in the doc https://hexdocs.pm/elixir/1.19.1/IO.html#t:inspect_opts/0 and the source https://github.com/elixir-lang/elixir/blob/v1.19.1/lib/elixir/lib/io.ex#L131

How can I fix this one using Dialyzer options in the meantime it's fixed (if it's truly a bug)..

arsltan avatar Oct 27 '25 18:10 arsltan

The inspect one seems to be an Elixir bug. The opaqueness ones require this: https://github.com/elixir-lang/elixir/issues/14837#issuecomment-3419685325

josevalim avatar Oct 27 '25 19:10 josevalim

The contract_with_opaque category of warnings was added for https://github.com/erlang/otp/issues/9140 but it is not possible to disable it with a separate option, they get disabled with either no_contract or no_opaque - code

michallepicki avatar Oct 29 '25 11:10 michallepicki

no_opaque may be a bit too broad but it seems we have no other option.

I misunderstood this issue a little bit and didn't have a chance to dig in until this past weekend. Jose's fix for Erlex resolves the most pressing issue (which showed up in other warning types as well, as noted). With that in place people should be able to use existing ignore features which are fairly flexible and targeted, or :no_opaque at their discretion. So my thoughts now are just adding some more info to the message with the opaque contract warning explaining the issue and the options.

jeremyjh avatar Nov 05 '25 12:11 jeremyjh

Not entirely sure if that's related, but I was about to post an issue regarding a dialyzer error with EEx's custom engines that doesn't seem to match. I prefer to ask it here first, let me know if you prefer I open a new issue.

My impression is that Dialyzer is now way more strict than it used to with unexpected Keyword values (at least from what I saw in my sample app).

I push this sample repository to demonstrate the problem with a readme that outlines what's going on.

nicklayb avatar Nov 13 '25 15:11 nicklayb

@nicklayb EEx.eval_string ~and EEx.eval_file~ probably needs a similar fix to https://github.com/elixir-lang/elixir/pull/14865 or https://github.com/elixir-lang/elixir/pull/14928

edit: see https://github.com/elixir-lang/elixir/pull/14971

michallepicki avatar Nov 20 '25 11:11 michallepicki