Dialyzer errors after upgrading to 1.19.0
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.
We updated to 1.19.0 with erlang 28 and on our project there are tons of errors too
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. :(
Wow, thank you for the quick reply and creating the erlex PR!
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.
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.
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':=#{}}}
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 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:
- Disable
contract_with_opaquewarnings by default (is this something we can do in Dialyxir? cc @jeremyjh @christhekeele - Don't use opaque anymore, since structs get inlined anyway
Disable
contract_with_opaquewarnings 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.
no_opaque may be a bit too broad but it seems we have no other option.
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?
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)..
The inspect one seems to be an Elixir bug. The opaqueness ones require this: https://github.com/elixir-lang/elixir/issues/14837#issuecomment-3419685325
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
no_opaquemay 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.
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 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