Skip to content

Commit ec87c6b

Browse files
committed
Improve protocol violation warnings
1 parent 0c59548 commit ec87c6b

File tree

3 files changed

+104
-88
lines changed

3 files changed

+104
-88
lines changed

lib/elixir/lib/module/types/apply.ex

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -979,7 +979,7 @@ defmodule Module.Types.Apply do
979979
{mod, fun, arity, converter} = mfac
980980
meta = elem(expr, 1)
981981

982-
{banner, hints, traces} =
982+
{banner, traces} =
983983
case Keyword.get(meta, :type_check) do
984984
:interpolation ->
985985
{_, _, [arg]} = expr
@@ -990,7 +990,7 @@ defmodule Module.Types.Apply do
990990
#{expr_to_string(arg) |> indent(4)}
991991
992992
it has type:
993-
""", [:interpolation], collect_traces(expr, context)}
993+
""", collect_traces(expr, context)}
994994

995995
:generator ->
996996
{:<-, _, [_, arg]} = expr
@@ -1001,7 +1001,7 @@ defmodule Module.Types.Apply do
10011001
#{expr_to_string(expr) |> indent(4)}
10021002
10031003
it has type:
1004-
""", [:generator], collect_traces(arg, context)}
1004+
""", collect_traces(arg, context)}
10051005

10061006
:into ->
10071007
{"""
@@ -1010,7 +1010,7 @@ defmodule Module.Types.Apply do
10101010
into: #{expr_to_string(expr) |> indent(4)}
10111011
10121012
it has type:
1013-
""", [:into], collect_traces(expr, context)}
1013+
""", collect_traces(expr, context)}
10141014

10151015
_ ->
10161016
mfa_or_fa = if mod, do: Exception.format_mfa(mod, fun, arity), else: "#{fun}/#{arity}"
@@ -1021,40 +1021,68 @@ defmodule Module.Types.Apply do
10211021
#{expr_to_string(expr) |> indent(4)}
10221022
10231023
given types:
1024-
""", [], collect_traces(expr, context)}
1024+
""", collect_traces(expr, context)}
10251025
end
10261026

1027-
explanation =
1027+
{explanation, impls} =
10281028
cond do
10291029
reason = empty_arg_reason(converter.(args_types)) ->
1030-
reason
1030+
{reason, ""}
10311031

10321032
Code.ensure_loaded?(mod) and
10331033
Keyword.has_key?(mod.module_info(:attributes), :__protocol__) ->
10341034
if function_exported?(mod, :__protocol__, 1) and
10351035
mod.__protocol__(:impls) == {:consolidated, []} do
1036-
"""
1037-
but the protocol was not yet implemented for any type and therefore will always fail. \
1038-
This error typically happens within libraries that define protocols and will disappear as \
1039-
soon as there is one implementation. If you expect the protocol to be implemented later on, \
1040-
you can define an implementation specific for development/test.
1041-
"""
1036+
{"""
1037+
but the #{inspect(mod)} protocol was not yet implemented \
1038+
for any type and therefore will always fail.
1039+
1040+
This warning will disappear once you define a implementation. \
1041+
If the protocol is part of a library, you may define a dummy \
1042+
implementation for development/test.
1043+
""", ""}
10421044
else
1043-
# Protocol errors can be very verbose, so we collapse structs
1044-
"""
1045-
but expected a type that implements the #{inspect(mod)} protocol. You either passed the wrong \
1046-
value or you forgot to implement the protocol.
1047-
1048-
The #{inspect(mod)} protocol is implemented for the following types:
1049-
#{clauses_args_to_quoted_string(clauses, converter, collapse_structs: true)}
1050-
"""
1045+
fix =
1046+
case mod do
1047+
String.Chars ->
1048+
"""
1049+
You either passed the wrong value or you must:
1050+
1051+
1. convert the given value to a string explicitly
1052+
(use inspect/1 if you want to convert any data structure to a string)
1053+
2. implement the String.Chars protocol
1054+
"""
1055+
1056+
Enumerable ->
1057+
"""
1058+
You either passed the wrong value or you must:
1059+
1060+
1. convert the given value to an Enumerable explicitly
1061+
2. implement the Enumerable protocol
1062+
"""
1063+
1064+
_ ->
1065+
"""
1066+
You either passed the wrong value or you forgot to implement the protocol.
1067+
"""
1068+
end
1069+
1070+
{"""
1071+
but expected a type that implements the #{inspect(mod)} protocol.
1072+
#{fix}\
1073+
""",
1074+
"""
1075+
1076+
#{hint()} the #{inspect(mod)} protocol is implemented for the following types:
1077+
#{clauses_args_to_quoted_string(clauses, converter, collapse_structs: true)}
1078+
"""}
10511079
end
10521080

10531081
true ->
1054-
"""
1055-
but expected one of:
1056-
#{clauses_args_to_quoted_string(clauses, converter, [])}
1057-
"""
1082+
{"""
1083+
but expected one of:
1084+
#{clauses_args_to_quoted_string(clauses, converter, [])}
1085+
""", ""}
10581086
end
10591087

10601088
%{
@@ -1069,7 +1097,7 @@ defmodule Module.Types.Apply do
10691097
""",
10701098
explanation,
10711099
format_traces(traces),
1072-
format_hints(hints)
1100+
impls
10731101
])
10741102
}
10751103
end

lib/elixir/lib/module/types/helpers.ex

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -91,29 +91,6 @@ defmodule Module.Types.Helpers do
9191
"var.fun()" (with parentheses) means "var" is an atom()
9292
"""
9393

94-
:interpolation ->
95-
"""
96-
97-
#{hint()} string interpolation uses the String.Chars protocol to \
98-
convert a data structure into a string. Either convert the data type into a \
99-
string upfront or implement the protocol accordingly
100-
"""
101-
102-
:generator ->
103-
"""
104-
105-
#{hint()} for-comprehensions use the Enumerable protocol to traverse \
106-
data structures. Either convert the data type into a list (or another Enumerable) \
107-
or implement the protocol accordingly
108-
"""
109-
110-
:into ->
111-
"""
112-
113-
#{hint()} the :into option in for-comprehensions use the Collectable protocol to \
114-
build its result. Either pass a valid data type or implement the protocol accordingly
115-
"""
116-
11794
:anonymous_rescue ->
11895
"""
11996

lib/elixir/test/elixir/module/types/integration_test.exs

Lines changed: 50 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -449,10 +449,9 @@ defmodule Module.Types.IntegrationTest do
449449
450450
-:hello-
451451
452-
but the protocol was not yet implemented for any type and therefore will always fail. \
453-
This error typically happens within libraries that define protocols and will disappear as \
454-
soon as there is one implementation. If you expect the protocol to be implemented later on, \
455-
you can define an implementation specific for development/test.
452+
but the NoImplProtocol protocol was not yet implemented for any type and therefore will always fail.
453+
454+
This warning will disappear once you define a implementation. If the protocol is part of a library, you may define a dummy implementation for development/test.
456455
457456
typing violation found at:
458457
@@ -479,53 +478,54 @@ defmodule Module.Types.IntegrationTest do
479478

480479
warnings = [
481480
"""
482-
warning: incompatible value given to string interpolation:
481+
warning: incompatible types given to String.Chars.to_string/1:
483482
484-
data
483+
to_string(data)
485484
486-
it has type:
485+
given types:
487486
488487
-dynamic(%Range{})-
489488
490-
but expected a type that implements the String.Chars protocol. You either passed the wrong value or you forgot to implement the protocol.
489+
but expected a type that implements the String.Chars protocol.
490+
You either passed the wrong value or you must:
491491
492-
The String.Chars protocol is implemented for the following types:
493-
494-
dynamic(
495-
%Date{} or %DateTime{} or %NaiveDateTime{} or %Time{} or %URI{} or %Version{} or
496-
%Version.Requirement{}
497-
) or atom() or binary() or empty_list() or float() or integer() or non_empty_list(term(), term())
492+
1. convert the given value to a string explicitly
493+
(use inspect/1 if you want to convert any data structure to a string)
494+
2. implement the String.Chars protocol
498495
499496
where "data" was given the type:
500497
501498
# type: dynamic(%Range{})
502-
# from: a.ex:3:24
499+
# from: a.ex:2:24
503500
_.._//_ = data
504501
505-
hint: string interpolation uses the String.Chars protocol to convert a data structure into a string. Either convert the data type into a string upfront or implement the protocol accordingly
502+
hint: the String.Chars protocol is implemented for the following types:
503+
504+
dynamic(
505+
%Date{} or %DateTime{} or %NaiveDateTime{} or %Time{} or %URI{} or %Version{} or
506+
%Version.Requirement{}
507+
) or atom() or binary() or empty_list() or float() or integer() or non_empty_list(term(), term())
506508
""",
507509
"""
508-
warning: incompatible types given to String.Chars.to_string/1:
510+
warning: incompatible value given to string interpolation:
509511
510-
to_string(data)
512+
data
511513
512-
given types:
514+
it has type:
513515
514516
-dynamic(%Range{})-
515517
516-
but expected a type that implements the String.Chars protocol. You either passed the wrong value or you forgot to implement the protocol.
518+
but expected a type that implements the String.Chars protocol.
519+
You either passed the wrong value or you must:
517520
518-
The String.Chars protocol is implemented for the following types:
519-
520-
dynamic(
521-
%Date{} or %DateTime{} or %NaiveDateTime{} or %Time{} or %URI{} or %Version{} or
522-
%Version.Requirement{}
523-
) or atom() or binary() or empty_list() or float() or integer() or non_empty_list(term(), term())
521+
1. convert the given value to a string explicitly
522+
(use inspect/1 if you want to convert any data structure to a string)
523+
2. implement the String.Chars protocol
524524
525525
where "data" was given the type:
526526
527527
# type: dynamic(%Range{})
528-
# from: a.ex:2:24
528+
# from: a.ex:3:24
529529
_.._//_ = data
530530
"""
531531
]
@@ -555,29 +555,41 @@ defmodule Module.Types.IntegrationTest do
555555
556556
-dynamic(%Date{})-
557557
558-
but expected a type that implements the Enumerable protocol. You either passed the wrong value or you forgot to implement the protocol.
558+
but expected a type that implements the Enumerable protocol.
559+
You either passed the wrong value or you must:
559560
560-
The Enumerable protocol is implemented for the following types:
561-
562-
dynamic(
563-
%Date.Range{} or %File.Stream{} or %GenEvent.Stream{} or %HashDict{} or %HashSet{} or
564-
%IO.Stream{} or %MapSet{} or %Range{} or %Stream{}
565-
) or empty_list() or fun() or non_empty_list(term(), term()) or non_struct_map()
561+
1. convert the given value to an Enumerable explicitly
562+
2. implement the Enumerable protocol
566563
567564
where "date" was given the type:
568565
569566
# type: dynamic(%Date{})
570567
# from: a.ex:2:24
571568
%Date{} = date
572569
573-
hint: for-comprehensions use the Enumerable protocol to traverse data structures. Either convert the data type into a list (or another Enumerable) or implement the protocol accordingly
570+
hint: the Enumerable protocol is implemented for the following types:
571+
572+
dynamic(
573+
%Date.Range{} or %File.Stream{} or %GenEvent.Stream{} or %HashDict{} or %HashSet{} or
574+
%IO.Stream{} or %MapSet{} or %Range{} or %Stream{}
575+
) or empty_list() or fun() or non_empty_list(term(), term()) or non_struct_map()
574576
""",
575577
"""
576578
warning: incompatible value given to :into option in for-comprehension:
577579
578580
into: Date.utc_today()
579581
580582
it has type:
583+
584+
-dynamic(%Date{})-
585+
586+
but expected a type that implements the Collectable protocol.
587+
You either passed the wrong value or you forgot to implement the protocol.
588+
589+
hint: the Collectable protocol is implemented for the following types:
590+
591+
dynamic(%File.Stream{} or %HashDict{} or %HashSet{} or %IO.Stream{} or %MapSet{}) or binary() or
592+
empty_list() or non_empty_list(term(), term()) or non_struct_map()
581593
""",
582594
"""
583595
warning: incompatible value given to :into option in for-comprehension:
@@ -588,14 +600,13 @@ defmodule Module.Types.IntegrationTest do
588600
589601
-integer()-
590602
591-
but expected a type that implements the Collectable protocol. You either passed the wrong value or you forgot to implement the protocol.
603+
but expected a type that implements the Collectable protocol.
604+
You either passed the wrong value or you forgot to implement the protocol.
592605
593-
The Collectable protocol is implemented for the following types:
606+
hint: the Collectable protocol is implemented for the following types:
594607
595608
dynamic(%File.Stream{} or %HashDict{} or %HashSet{} or %IO.Stream{} or %MapSet{}) or binary() or
596609
empty_list() or non_empty_list(term(), term()) or non_struct_map()
597-
598-
hint: the :into option in for-comprehensions use the Collectable protocol to build its result. Either pass a valid data type or implement the protocol accordingly
599610
"""
600611
]
601612

0 commit comments

Comments
 (0)