@@ -6,6 +6,33 @@ defmodule Module.Types.IntegrationTest do
66 import ExUnit.CaptureIO
77 import Module.Types.Descr
88
9+ defp builtin_protocols do
10+ [
11+ Collectable ,
12+ Enumerable ,
13+ IEx.Info ,
14+ Inspect ,
15+ JSON.Encoder ,
16+ List.Chars ,
17+ String.Chars
18+ ]
19+ end
20+
21+ test "built-in protocols" do
22+ builtin_protocols =
23+ for app <- ~w[ eex elixir ex_unit iex logger mix] a ,
24+ Application . ensure_loaded ( app ) ,
25+ module <- Application . spec ( app , :modules ) ,
26+ Code . ensure_loaded ( module ) ,
27+ function_exported? ( module , :__protocol__ , 1 ) ,
28+ do: module
29+
30+ # If this test fails, update:
31+ # * lib/elixir/lib/module/types/apply.ex
32+ # * lib/elixir/scripts/elixir_docs.ex
33+ assert Enum . sort ( builtin_protocols ) == builtin_protocols ( )
34+ end
35+
936 setup_all do
1037 previous = Application . get_env ( :elixir , :ansi_enabled , false )
1138 Application . put_env ( :elixir , :ansi_enabled , false )
@@ -347,6 +374,66 @@ defmodule Module.Types.IntegrationTest do
347374 assert_warnings ( files , warnings )
348375 end
349376
377+ test "protocol dispatch" do
378+ files = % {
379+ "a.ex" => """
380+ defmodule FooBar do
381+ def example1(_.._//_ = data), do: to_string(data)
382+ def example2(_.._//_ = data), do: "hello \# {data} world"
383+ end
384+ """
385+ }
386+
387+ warnings = [
388+ """
389+ warning: incompatible value given to string interpolation:
390+
391+ data
392+
393+ it has type:
394+
395+ -dynamic(%Range{first: term(), last: term(), step: term()})-
396+
397+ but expected one of:
398+
399+ %Date{} or %DateTime{} or %NaiveDateTime{} or %Time{} or %URI{} or %Version{} or
400+ %Version.Requirement{} or atom() or binary() or float() or integer() or list(term())
401+
402+ where "data" was given the type:
403+
404+ # type: dynamic(%Range{})
405+ # from: a.ex:3:24
406+ _.._//_ = data
407+
408+ hint: string interpolation in Elixir 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
409+ """ ,
410+ """
411+ warning: incompatible types given to String.Chars.to_string/1:
412+
413+ to_string(data)
414+
415+ given types:
416+
417+ -dynamic(%Range{first: term(), last: term(), step: term()})-
418+
419+ but expected one of:
420+
421+ %Date{} or %DateTime{} or %NaiveDateTime{} or %Time{} or %URI{} or %Version{} or
422+ %Version.Requirement{} or atom() or binary() or float() or integer() or list(term())
423+
424+ where "data" was given the type:
425+
426+ # type: dynamic(%Range{})
427+ # from: a.ex:2:24
428+ _.._//_ = data
429+
430+ hint: String.Chars is a protocol in Elixir. Either make sure you give valid data types as arguments or implement the protocol accordingly
431+ """
432+ ]
433+
434+ assert_warnings ( files , warnings , consolidate_protocols: true )
435+ end
436+
350437 test "returns diagnostics with source and file" do
351438 files = % {
352439 "a.ex" => """
@@ -374,8 +461,7 @@ defmodule Module.Types.IntegrationTest do
374461 assert String . ends_with? ( file , "generated.ex" )
375462 assert Path . type ( file ) == :absolute
376463 after
377- :code . delete ( A )
378- :code . purge ( A )
464+ purge ( A )
379465 end
380466 end
381467
@@ -1095,40 +1181,42 @@ defmodule Module.Types.IntegrationTest do
10951181 end
10961182 end
10971183
1098- defp assert_warnings ( files , expected ) when is_binary ( expected ) do
1099- assert capture_compile_warnings ( files ) == expected
1184+ defp assert_warnings ( files , expected , opts \\ [ ] )
1185+
1186+ defp assert_warnings ( files , expected , opts ) when is_binary ( expected ) do
1187+ assert capture_compile_warnings ( files , opts ) == expected
11001188 end
11011189
1102- defp assert_warnings ( files , expecteds ) when is_list ( expecteds ) do
1103- output = capture_compile_warnings ( files )
1190+ defp assert_warnings ( files , expecteds , opts ) when is_list ( expecteds ) do
1191+ output = capture_compile_warnings ( files , opts )
11041192
11051193 Enum . each ( expecteds , fn expected ->
11061194 assert output =~ expected
11071195 end )
11081196 end
11091197
11101198 defp assert_no_warnings ( files ) do
1111- assert capture_compile_warnings ( files ) == ""
1199+ assert capture_compile_warnings ( files , [ ] ) == ""
11121200 end
11131201
1114- defp capture_compile_warnings ( files ) do
1202+ defp capture_compile_warnings ( files , opts ) do
11151203 in_tmp ( fn ->
11161204 paths = generate_files ( files )
1117- capture_io ( :stderr , fn -> compile_to_path ( paths ) end )
1205+ capture_io ( :stderr , fn -> compile_to_path ( paths , opts ) end )
11181206 end )
11191207 end
11201208
11211209 defp with_compile_warnings ( files ) do
11221210 in_tmp ( fn ->
11231211 paths = generate_files ( files )
1124- with_io ( :stderr , fn -> compile_to_path ( paths ) end ) |> elem ( 0 )
1212+ with_io ( :stderr , fn -> compile_to_path ( paths , [ ] ) end ) |> elem ( 0 )
11251213 end )
11261214 end
11271215
11281216 defp compile_modules ( files ) do
11291217 in_tmp ( fn ->
11301218 paths = generate_files ( files )
1131- { modules , _warnings } = compile_to_path ( paths )
1219+ { modules , _warnings } = compile_to_path ( paths , [ ] )
11321220
11331221 Map . new ( modules , fn module ->
11341222 { ^ module , binary , _filename } = :code . get_object_code ( module )
@@ -1137,13 +1225,43 @@ defmodule Module.Types.IntegrationTest do
11371225 end )
11381226 end
11391227
1140- defp compile_to_path ( paths ) do
1228+ defp compile_to_path ( paths , opts ) do
1229+ if opts [ :consolidate_protocols ] do
1230+ Code . prepend_path ( "." )
1231+
1232+ result =
1233+ compile_to_path_with_after_compile ( paths , fn ->
1234+ if Keyword . get ( opts , :consolidate_protocols , false ) do
1235+ paths = [ "." , Application . app_dir ( :elixir , "ebin" ) ]
1236+ protocols = Protocol . extract_protocols ( paths )
1237+
1238+ for protocol <- protocols do
1239+ impls = Protocol . extract_impls ( protocol , paths )
1240+ { :ok , binary } = Protocol . consolidate ( protocol , impls )
1241+ File . write! ( Atom . to_string ( protocol ) <> ".beam" , binary )
1242+ purge ( protocol )
1243+ end
1244+ end
1245+ end )
1246+
1247+ Code . delete_path ( "." )
1248+ Enum . each ( builtin_protocols ( ) , & purge / 1 )
1249+
1250+ result
1251+ else
1252+ compile_to_path_with_after_compile ( paths , fn -> :ok end )
1253+ end
1254+ end
1255+
1256+ defp compile_to_path_with_after_compile ( paths , callback ) do
11411257 { :ok , modules , warnings } =
1142- Kernel.ParallelCompiler . compile_to_path ( paths , "." , return_diagnostics: true )
1258+ Kernel.ParallelCompiler . compile_to_path ( paths , "." ,
1259+ return_diagnostics: true ,
1260+ after_compile: callback
1261+ )
11431262
11441263 for module <- modules do
1145- :code . delete ( module )
1146- :code . purge ( module )
1264+ purge ( module )
11471265 end
11481266
11491267 { modules , warnings }
@@ -1162,6 +1280,11 @@ defmodule Module.Types.IntegrationTest do
11621280 map
11631281 end
11641282
1283+ defp purge ( mod ) do
1284+ :code . delete ( mod )
1285+ :code . purge ( mod )
1286+ end
1287+
11651288 defp in_tmp ( fun ) do
11661289 path = PathHelpers . tmp_path ( "checker" )
11671290
0 commit comments