Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stale Struct Types Lead to Dialyzer Warnings #699

Closed
aj-foster opened this issue Jun 2, 2022 · 3 comments · Fixed by #1081
Closed

Stale Struct Types Lead to Dialyzer Warnings #699

aj-foster opened this issue Jun 2, 2022 · 3 comments · Fixed by #1081

Comments

@aj-foster
Copy link
Contributor

Hello 👋🏼 This could be ElixirLS or dialyzer related. Any help is appreciated. 🙂

Environment

  • Elixir & Erlang versions (elixir --version):
    • Project: 1.12.3 (compiled with Erlang/OTP 24)
    • ElixirLS compiled with Elixir 1.12.3 and erlang 24
  • Elixir Language Server version: v0.9.0
  • Operating system: macOS 12.4
  • Editor or IDE name (e.g. Emacs/VSCode): VSCode
  • Editor Plugin/LSP Client name and version: ElixirLS for VSCode, v0.9.0 (with custom compiled ElixirLS)

Current behavior

Dialyzer occasionally ignores changes to a struct type %__MODULE__{...} when dialyzing other modules. For example, if I add a new field to struct type %A{}, and then access that field in another module B, dialyzer warns that the new field "cannot exist in a map of type...".

ElixirLS logs when saving the new field to struct %A{}
MIX_ENV: test
MIX_TARGET: host
Compiling 39 files (.ex)
[Info  - 10:29:59 AM] Compile took 3196 milliseconds
[Info  - 10:29:59 AM] [ElixirLS WorkspaceSymbols] Updating index...
MIX_ENV: test
MIX_TARGET: host
[Info  - 10:30:00 AM] Compile took 895 milliseconds
[Info  - 10:30:00 AM] [ElixirLS Dialyzer] Checking for stale beam files
[Info  - 10:30:00 AM] [ElixirLS WorkspaceSymbols] 5 modules need reindexing
[Info  - 10:30:00 AM] [ElixirLS WorkspaceSymbols] 0 callbacks added to index
[Info  - 10:30:00 AM] [ElixirLS WorkspaceSymbols] 5 modules added to index
[Info  - 10:30:00 AM] [ElixirLS WorkspaceSymbols] 6 types added to index
[Info  - 10:30:00 AM] [ElixirLS WorkspaceSymbols] 91 functions added to index
[Info  - 10:30:00 AM] [ElixirLS Dialyzer] Found 72 changed files in 248 milliseconds
[Info  - 10:30:01 AM] [ElixirLS Dialyzer] Analyzing 21 modules: [...unrelated modules omitted..., B, A]
[Info  - 10:30:02 AM] [ElixirLS Dialyzer] Analysis finished in 1439 milliseconds
[Info  - 10:30:02 AM] Dialyzer analysis is up to date
[Info  - 10:30:03 AM] [ElixirLS Dialyzer] Writing manifest...
[Info  - 10:30:04 AM] [ElixirLS Dialyzer] Done writing manifest in 2058 milliseconds.
ElixirLS logs when saving module B, which accesses the new field of type %A{}
MIX_ENV: test
MIX_TARGET: host
Compiling 38 files (.ex)
[Info  - 10:30:19 AM] Compile took 3210 milliseconds
[Info  - 10:30:19 AM] [ElixirLS Dialyzer] Checking for stale beam files
[Info  - 10:30:19 AM] [ElixirLS WorkspaceSymbols] Updating index...
[Info  - 10:30:20 AM] [ElixirLS WorkspaceSymbols] 1 modules need reindexing
[Info  - 10:30:20 AM] [ElixirLS WorkspaceSymbols] 0 callbacks added to index
[Info  - 10:30:20 AM] [ElixirLS WorkspaceSymbols] 0 types added to index
[Info  - 10:30:20 AM] [ElixirLS WorkspaceSymbols] 1 modules added to index
[Info  - 10:30:20 AM] [ElixirLS WorkspaceSymbols] 57 functions added to index
[Info  - 10:30:20 AM] [ElixirLS Dialyzer] Found 41 changed files in 345 milliseconds
[Info  - 10:30:20 AM] [ElixirLS Dialyzer] Analyzing 20 modules: [...unrelated modules omitted..., B]
[Info  - 10:30:21 AM] [ElixirLS Dialyzer] Analysis finished in 1461 milliseconds
[Info  - 10:30:22 AM] Dialyzer analysis is up to date
[Info  - 10:30:23 AM] [ElixirLS Dialyzer] Writing manifest...
[Info  - 10:30:24 AM] [ElixirLS Dialyzer] Done writing manifest in 1986 milliseconds.
Dialyzer warning
A key of type 
          'test' cannot exist in a map of type 
          #{'__meta__' := _,
            '__struct__' := 'A',
            ...all fields except "test"...
          }

(No seemingly relevant console messages in the VSCode devtools. Only the usual "starting client" message.)

Heavily abbreviated LSP trace when saving module A
[Trace - 10:42:56 AM] Sending request 'textDocument/formatting - (268)'.
Params: {
    "textDocument": {
        "uri": "file:///path/to/a.ex"
    },
    "options": {
        "tabSize": 2,
        "insertSpaces": true
    }
}

[Trace - 10:42:56 AM] Received response 'textDocument/formatting - (268)' in 10ms.
Result: []

[Trace - 10:42:56 AM] Sending notification 'textDocument/didSave'.
Params: {
"textDocument": {
"uri": "file:///path/to/a.ex",
"version": 92
},
"text": ...
}

[omitting traces for emitting log messages shown in the copies above]

[Trace - 10:42:56 AM] Sending notification 'workspace/didChangeWatchedFiles'.
Params: {
"changes": [
{
"uri": "file:///path/to/a.ex",
"type": 2
},
{
"uri": "file:///path/to/a.ex",
"type": 2
}
]
}

[Trace - 10:42:59 AM] Sending notification 'workspace/didChangeWatchedFiles'.
Params: {
"changes": [
{
"uri": "file:///path/to/b.ex",
"type": 2
},
...other files that reference A...
Note: every file appears twice.
]
}

[Trace - 10:43:00 AM] Received notification 'textDocument/publishDiagnostics'.
...for every file in the project that currently has a dialyzer warning, including module B...

[Info - 10:43:01 AM] [ElixirLS Dialyzer] Checking for stale beam files

[Trace - 10:43:01 AM] Received notification 'textDocument/publishDiagnostics'.
...for every file in the project that currently has a dialyzer warning, including module B...

[Info - 10:43:02 AM] [ElixirLS Dialyzer] Analysis finished in 1385 milliseconds

[Trace - 10:43:02 AM] Received notification 'textDocument/publishDiagnostics'.
...for every file in the project that currently has a dialyzer warning, including module B...

...
[Info - 10:43:03 AM] Dialyzer analysis is up to date
[Info - 10:43:04 AM] [ElixirLS Dialyzer] Writing manifest...
[Info - 10:43:05 AM] [ElixirLS Dialyzer] Done writing manifest in 1961 milliseconds.

Heavily abbreviated LSP trace when saving module B
[Trace - 10:59:54 AM] Sending request 'textDocument/formatting - (315)'.
Params: {
    "textDocument": {
        "uri": "file:///path/to/b.ex"
    },
    "options": {
        "tabSize": 2,
        "insertSpaces": true
    }
}

[Trace - 10:59:54 AM] Received response 'textDocument/formatting - (315)' in 71ms.
Result: []

[Trace - 10:59:54 AM] Sending notification 'textDocument/didSave'.
Params: {
"textDocument": {
"uri": "file:///path/to/b.ex",
"version": 43
},
"text": ...
}

[omitting traces for emitting log messages shown in the copies above]

MIX_TARGET: host
[Trace - 10:59:54 AM] Sending notification 'workspace/didChangeWatchedFiles'.
Params: {
"changes": [
{
"uri": "file:///path/to/b.ex",
"type": 2
},
{
"uri": "file:///path/to/b.ex",
"type": 2
}
]
}

[Trace - 10:59:57 AM] Received notification 'textDocument/publishDiagnostics'.
...for every file in the project that currently has a dialyzer warning, including module B...
Note: no new warning, yet.

[Info - 10:59:58 AM] [ElixirLS Dialyzer] Found 41 changed files in 366 milliseconds
...
[Info - 11:00:00 AM] [ElixirLS Dialyzer] Analysis finished in 1470 milliseconds

[Trace - 11:00:00 AM] Received notification 'textDocument/publishDiagnostics'.
...for every file in the project that currently has a dialyzer warning, including module B...
Note: includes new warning.

[Info - 11:00:00 AM] Dialyzer analysis is up to date
[Info - 11:00:01 AM] [ElixirLS Dialyzer] Writing manifest...
[Info - 11:00:02 AM] [ElixirLS Dialyzer] Done writing manifest in 2093 milliseconds.

Steps to reproduce:

Anecdotally, I only encounter this issue in larger projects, at least the size of a Phoenix app with a few schemas (which unfortunately I cannot make public).

  • Create a module A with use Ecto.Schema and some number of fields defined.
  • Create a module B with a function that references %A{}, perhaps pattern matching a value.
  • Allow dialyzer to write out a manifest.
  • In A add a new field to the schema.
  • In B, attempt to use the new field, e.g. %A{a | new_field: "value"}
  • Observe dialyzer warning for the usage of the new field.

Other Notes

  • I usually have a canonical type t :: %__MODULE__{} defined when this occurs.
  • Manually deleting the BEAM file for module A in .elixir_ls/build and .elixir_ls/dialyzer_tmp, and then reloading the window, does nothing. The BEAM file is not regenerated until another change is made to A.
  • Deleting .elixir_ls and reloading, as you might imagine, fixes the issue.
  • I've experienced this issue over a wide range of Elixir/OTP versions (both the versions running the project, and the versions used to compile ElixirLS).

Expected behavior

Dialyzer should recognize the change to type %A{}.

@lukaszsamson
Copy link
Collaborator

OTP 26 is going to improve incremental dialyzer

@lukaszsamson
Copy link
Collaborator

This should help #742. Otherwise we can't really fix that

@lukaszsamson lukaszsamson added the wontfix This will not be worked on label Oct 6, 2022
@lukaszsamson
Copy link
Collaborator

This issue will be resolved on OTP 26+ in the upcoming 0.21 release. This release switches dialyzer backend to builtin OTP incremental mode. Incremental dialyzer mode is much better at tracking dependencies. I tested it and the scenario no longer results in persisting warnings. Unfortunately it is much slower

@lukaszsamson lukaszsamson removed the wontfix This will not be worked on label Mar 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants