Skip to content

Commit

Permalink
Better type mismatch analysis
Browse files Browse the repository at this point in the history
Summary:
Changes are located in the new module `TypeMismatch.scala`, as well as in `Show.scala` and `TcDiagnostics.scala`

## Finding mismatches

The main logic is entirely contained in `TypeMismatch.scala`. This module operates in two steps: it first finds a mismatch between two types (`findMismatch`) and then formats it (`explainMismatch`).

A mismatch is essentially a path in the two types, where the final step is a basic type incompatibility. Because the two types do not necessarily have the same form (because of, e.g., unions), `findMismatch` rebuilds the two types to only keep the incompatibility, building the mismatch path along the way.

`explainMismatch` then uses the reconstructed types as well as the mismatch path to provide an explanation, inlining it in the unfolding of the expression's type.

## Scoring

`findMismatch` is also based on a basic scoring system: it assigns a matching score (0-100) to the pair of arguments it is given. This system is fairly simple for now, and used only for choosing union candidates (or concluding there is no candidate). In the future, we can build upon it to provide better info to the user, especially with maps and tuples.

## Displaying

For now, the new error message and the old error message are displayed together, while we gather user feedback.

Reviewed By: ilya-klyuchnikov

Differential Revision: D69245458

fbshipit-source-id: aa0f022791a7efd76d0c27739de62544de372de5
  • Loading branch information
VLanvin authored and facebook-github-bot committed Feb 10, 2025
1 parent bfd3f5f commit 863d44a
Show file tree
Hide file tree
Showing 70 changed files with 3,297 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types
┌─ check/src/any_fun_type.erl:46:21
46 │ to_f_any_neg2(F) -> F.
│ ^ F.
│ ^
│ │
│ F.
Expression has type: 'f0' | fun((atom()) -> pid()) | 'f1'
Context expected type: fun()

Because in the expression's type:
Here the type is: 'f0'
Context expects type: fun()

error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
┌─ check/src/any_fun_type.erl:49:23
Expand All @@ -25,6 +32,12 @@ Expression has type: fun((term()) -> term())
Context expected type: f2()

Because in the expression's type:
Here the type is: fun((term()) -> term())
Context expects type: fun((term(), term()) -> term())

------------------------------ Detailed message ------------------------------

fun((term()) -> term()) is not compatible with f2()
because
fun((term()) -> term()) is not compatible with fun((term(), term()) -> term())
Expand All @@ -40,6 +53,14 @@ Expression has type: f5('a' | 'b')
Context expected type: f4('a')

Because in the expression's type:
fun((term()) ->
Here the type is: 'b'
Context expects type: 'a'
)

------------------------------ Detailed message ------------------------------

f5('a' | 'b') is not compatible with f4('a')
because
fun((term()) -> 'a' | 'b') is not compatible with f4('a')
Expand Down Expand Up @@ -67,6 +88,14 @@ Expression has type: fun((term()) -> 'a' | 'b')
Context expected type: f4('a')

Because in the expression's type:
fun((term()) ->
Here the type is: 'b'
Context expects type: 'a'
)

------------------------------ Detailed message ------------------------------

fun((term()) -> 'a' | 'b') is not compatible with f4('a')
because
fun((term()) -> 'a' | 'b') is not compatible with fun((...) -> 'a')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ Expression has type: string() | dynamic()
Context expected type: 'anything'

Because in the expression's type:
Here the type is: string()
Context expects type: 'anything'

------------------------------ Detailed message ------------------------------

string() | dynamic() is not compatible with 'anything'
because
string() is not compatible with 'anything'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ Expression has type: 'false' | 'true'
Context expected type: 'true'

Because in the expression's type:
Here the type is: 'false'
Context expects type: 'true'

------------------------------ Detailed message ------------------------------

'false' | 'true' is not compatible with 'true'
because
'false' is not compatible with 'true'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ Expression has type: {'p', none() | pid() | reference()}
Context expected type: {'a', atom()} | {'p', pid()}

Because in the expression's type:
{ 'p',
Here the type is: reference()
Context expects type: pid()
}

------------------------------ Detailed message ------------------------------

{'p', none() | pid() | reference()} is not compatible with {'a', atom()} | {'p', pid()}
because
at tuple index 2:
Expand All @@ -37,6 +45,13 @@ Expression has type: 'undefined' | none() | 'restarting'
Context expected type: {'p', pid()} | 'undefined'

Because in the expression's type:
Here the type is: 'restarting'
Context expects type: {'p', pid()} | 'undefined'
No candidate matches in the expected union.

------------------------------ Detailed message ------------------------------

'undefined' | none() | 'restarting' is not compatible with {'p', pid()} | 'undefined'
because
'restarting' is not compatible with {'p', pid()} | 'undefined'
Expand All @@ -54,6 +69,12 @@ Expression has type: #{dynamic() => dynamic()} | none()
Context expected type: [T]

Because in the expression's type:
Here the type is: #{dynamic() => dynamic()}
Context expects type: [T]

------------------------------ Detailed message ------------------------------

#{dynamic() => dynamic()} | none() is not compatible with [T]
because
#{dynamic() => dynamic()} is not compatible with [T]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ Expression has type: #{a => 'b', c => 'd'}
Context expected type: #{a => 'b'}

Because in the expression's type:
Here the type is: #{c => ...}
Context expects type: #{...}
The expected map has no corresponding key for: c.

------------------------------ Detailed message ------------------------------

key `c` is declared in the former but not in the latter and the latter map has no default association

error: incompatible_types (See https://fb.me/eqwalizer_errors#incompatible_types)
Expand All @@ -22,6 +29,14 @@ Expression has type: #{a => 'b', c => 'd'}
Context expected type: #{a => 'b', atom() => number()}

Because in the expression's type:
#{ c =>
Here the type is: 'd'
Context expects type: number()
, ... }

------------------------------ Detailed message ------------------------------

#{a => 'b', c => 'd'} is not compatible with #{a => 'b', atom() => number()}
because
#{a => 'b', c => 'd'} is not compatible with #{a => 'b', atom() => number()}
Expand All @@ -40,6 +55,13 @@ Expression has type: #{a => 'b', atom() => atom()}
Context expected type: #{a => 'b', c => 'd'}

Because in the expression's type:
Here the type is: #{atom() => atom()}
Context expects type: #{...} (no default association)
The expected map has no default association while the type of the expression has one.

------------------------------ Detailed message ------------------------------

#{a => 'b', atom() => atom()} is not compatible with #{a => 'b', c => 'd'}
key c is not present in the former map but is incompatible with its default association
because
Expand All @@ -56,6 +78,15 @@ Expression has type: #{a => 'b', atom() => number()}
Context expected type: #{a => 'b', c => 'd', atom() => number()}

Because in the expression's type:
#{ atom() =>
Here the type is: number()
Context expects type: 'd'
, ... }
The context introduces a new association c => 'd' which is incompatible with the expression's default association.

------------------------------ Detailed message ------------------------------

#{a => 'b', atom() => number()} is not compatible with #{a => 'b', c => 'd', atom() => number()}
key c is not present in the former map but is incompatible with its default association
because
Expand All @@ -72,6 +103,13 @@ Expression has type: #{a => 'b', atom() => atom()}
Context expected type: #{a => 'b'}

Because in the expression's type:
Here the type is: #{atom() => atom()}
Context expects type: #{...} (no default association)
The expected map has no default association while the type of the expression has one.

------------------------------ Detailed message ------------------------------

#{a => 'b', atom() => atom()} is not compatible with #{a => 'b'}
because
#{a => 'b', atom() => atom()} is not compatible with #{a => 'b'}
Expand All @@ -88,6 +126,13 @@ Expression has type: #{a => 'b', dynamic(atom()) => atom()}
Context expected type: #{a => 'b'}

Because in the expression's type:
Here the type is: #{dynamic(atom()) => atom()}
Context expects type: #{...} (no default association)
The expected map has no default association while the type of the expression has one.

------------------------------ Detailed message ------------------------------

#{a => 'b', dynamic(atom()) => atom()} is not compatible with #{a => 'b'}
because
#{a => 'b', dynamic(atom()) => atom()} is not compatible with #{a => 'b'}
Expand All @@ -104,6 +149,13 @@ Expression has type: #{a => 'b', atom() => dynamic(atom())}
Context expected type: #{a => 'b'}

Because in the expression's type:
Here the type is: #{atom() => dynamic(atom())}
Context expects type: #{...} (no default association)
The expected map has no default association while the type of the expression has one.

------------------------------ Detailed message ------------------------------

#{a => 'b', atom() => dynamic(atom())} is not compatible with #{a => 'b'}
because
#{a => 'b', atom() => dynamic(atom())} is not compatible with #{a => 'b'}
Expand Down Expand Up @@ -160,6 +212,14 @@ Expression has type: #{atom() => binary()}
Context expected type: #{a => binary(), atom() => atom()}

Because in the expression's type:
#{ atom() =>
Here the type is: binary()
Context expects type: atom()
, ... }

------------------------------ Detailed message ------------------------------

#{atom() => binary()} is not compatible with #{a => binary(), atom() => atom()}
the default associations are not compatible
because
Expand All @@ -176,6 +236,14 @@ Expression has type: #{a := 'b', atom() => binary()}
Context expected type: #{a => 'b', {c, d} => atom() | binary(), term() => atom()}

Because in the expression's type:
#{ atom() =>
Here the type is: binary()
Context expects type: atom()
, ... }

------------------------------ Detailed message ------------------------------

#{a := 'b', atom() => binary()} is not compatible with #{a => 'b', {c, d} => atom() | binary(), term() => atom()}
the default associations are not compatible
because
Expand All @@ -192,6 +260,14 @@ Expression has type: #{a := 'b', atom() => binary()}
Context expected type: #{a => 'b', {c, d} => atom() | binary(), atom() => atom()}

Because in the expression's type:
#{ atom() =>
Here the type is: binary()
Context expects type: atom()
, ... }

------------------------------ Detailed message ------------------------------

#{a := 'b', atom() => binary()} is not compatible with #{a => 'b', {c, d} => atom() | binary(), atom() => atom()}
the default associations are not compatible
because
Expand All @@ -208,6 +284,14 @@ Expression has type: #{dynamic() => atom()}
Context expected type: #{dynamic() => binary()}

Because in the expression's type:
#{ dynamic() =>
Here the type is: atom()
Context expects type: binary()
, ... }

------------------------------ Detailed message ------------------------------

#{dynamic() => atom()} is not compatible with #{dynamic() => binary()}
the default associations are not compatible
because
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ Expression has type: [number()]
Context expected type: [atom()]

Because in the expression's type:
[
Here the type is: number()
Context expects type: atom()
]

------------------------------ Detailed message ------------------------------

[number()] is not compatible with [atom()]
because
number() is not compatible with atom()
Expand Down Expand Up @@ -56,6 +64,14 @@ Expression has type: [number()]
Context expected type: [binary()]

Because in the expression's type:
[
Here the type is: number()
Context expects type: binary()
]

------------------------------ Detailed message ------------------------------

[number()] is not compatible with [binary()]
because
number() is not compatible with binary()
Expand Down Expand Up @@ -152,6 +168,14 @@ Expression has type: [atom()]
Context expected type: [binary()]

Because in the expression's type:
[
Here the type is: atom()
Context expects type: binary()
]

------------------------------ Detailed message ------------------------------

[atom()] is not compatible with [binary()]
because
atom() is not compatible with binary()
Expand All @@ -167,6 +191,14 @@ Expression has type: ['true']
Context expected type: ['false']

Because in the expression's type:
[
Here the type is: 'true'
Context expects type: 'false'
]

------------------------------ Detailed message ------------------------------

['true'] is not compatible with ['false']
because
'true' is not compatible with 'false'
Expand All @@ -182,6 +214,14 @@ Expression has type: ['true']
Context expected type: ['false']

Because in the expression's type:
[
Here the type is: 'true'
Context expects type: 'false'
]

------------------------------ Detailed message ------------------------------

['true'] is not compatible with ['false']
because
'true' is not compatible with 'false'
Expand All @@ -197,6 +237,14 @@ Expression has type: [number()]
Context expected type: [atom()]

Because in the expression's type:
[
Here the type is: number()
Context expects type: atom()
]

------------------------------ Detailed message ------------------------------

[number()] is not compatible with [atom()]
because
number() is not compatible with atom()
Expand All @@ -212,6 +260,14 @@ Expression has type: [binary() | 'undefined']
Context expected type: [binary()]

Because in the expression's type:
[
Here the type is: 'undefined'
Context expects type: binary()
]

------------------------------ Detailed message ------------------------------

[binary() | 'undefined'] is not compatible with [binary()]
because
binary() | 'undefined' is not compatible with binary()
Expand Down
Loading

0 comments on commit 863d44a

Please sign in to comment.