@@ -560,25 +560,26 @@ defmodule Protocol do
560560 # Ensure the types are sorted so the compiled beam is deterministic
561561 types = Enum . sort ( types )
562562
563- with { :ok , ast_info , specs , compile_info } <- beam_protocol ( protocol ) ,
564- { :ok , definitions } <- change_debug_info ( protocol , ast_info , types ) ,
565- do: compile ( definitions , specs , compile_info )
563+ with { :ok , any , definitions , signatures , compile_info } <- beam_protocol ( protocol ) ,
564+ { :ok , definitions , signatures } <-
565+ consolidate ( protocol , any , definitions , signatures , types ) ,
566+ do: compile ( definitions , signatures , compile_info )
566567 end
567568
568569 defp beam_protocol ( protocol ) do
569- chunk_ids = [ :debug_info , [ ?D , ?o , ?c , ?s ] , [ ?E , ?x , ?C , ?k ] ]
570+ chunk_ids = [ :debug_info , [ ?D , ?o , ?c , ?s ] ]
570571 opts = [ :allow_missing_chunks ]
571572
572573 case :beam_lib . chunks ( beam_file ( protocol ) , chunk_ids , opts ) do
573574 { :ok , { ^ protocol , [ { :debug_info , debug_info } | chunks ] } } ->
574- { :debug_info_v1 , _backend , { :elixir_v1 , info , specs } } = debug_info
575- % { attributes: attributes , definitions: definitions } = info
575+ { :debug_info_v1 , _backend , { :elixir_v1 , module_map , specs } } = debug_info
576+ % { attributes: attributes , definitions: definitions , signatures: signatures } = module_map
576577 chunks = :lists . filter ( fn { _name , value } -> value != :missing_chunk end , chunks )
577578 chunks = :lists . map ( fn { name , value } -> { List . to_string ( name ) , value } end , chunks )
578579
579580 case attributes [ :__protocol__ ] do
580581 [ fallback_to_any: any ] ->
581- { :ok , { any , definitions } , specs , { info , chunks } }
582+ { :ok , any , definitions , signatures , { module_map , specs , chunks } }
582583
583584 _ ->
584585 { :error , :not_a_protocol }
@@ -596,29 +597,67 @@ defmodule Protocol do
596597 end
597598 end
598599
599- # Change the debug information to the optimized
600- # impl_for/1 dispatch version.
601- defp change_debug_info ( protocol , { any , definitions } , types ) do
602- types = if any , do: types , else: List . delete ( types , Any )
603- all = [ Any ] ++ for { mod , _guard } <- built_in ( ) , do: mod
604- structs = types -- all
605-
600+ # Consolidate the protocol for faster implementations and fine-grained type information.
601+ defp consolidate ( protocol , fallback_to_any? , definitions , signatures , types ) do
606602 case List . keytake ( definitions , { :__protocol__ , 1 } , 0 ) do
607603 { protocol_def , definitions } ->
604+ types = if fallback_to_any? , do: types , else: List . delete ( types , Any )
605+ built_in_plus_any = [ Any ] ++ for { mod , _guard } <- built_in ( ) , do: mod
606+ structs = types -- built_in_plus_any
607+
608608 { impl_for , definitions } = List . keytake ( definitions , { :impl_for , 1 } , 0 )
609+ { impl_for! , definitions } = List . keytake ( definitions , { :impl_for! , 1 } , 0 )
609610 { struct_impl_for , definitions } = List . keytake ( definitions , { :struct_impl_for , 1 } , 0 )
610611
611612 protocol_def = change_protocol ( protocol_def , types )
612613 impl_for = change_impl_for ( impl_for , protocol , types )
613614 struct_impl_for = change_struct_impl_for ( struct_impl_for , protocol , types , structs )
615+ definitions = [ protocol_def , impl_for , impl_for! , struct_impl_for ] ++ definitions
614616
615- { :ok , [ protocol_def , impl_for , struct_impl_for ] ++ definitions }
617+ new_signatures = new_signatures ( definitions , protocol , types , structs )
618+ signatures = Enum . into ( new_signatures , signatures )
619+ { :ok , definitions , signatures }
616620
617621 nil ->
618622 { :error , :not_a_protocol }
619623 end
620624 end
621625
626+ defp new_signatures ( definitions , protocol , types , structs ) do
627+ alias Module.Types.Descr
628+
629+ clauses =
630+ Enum . map ( structs ++ List . delete ( types , Any ) , fn impl ->
631+ { [ Module.Types.Of . impl ( impl ) ] , Descr . atom ( [ Module . concat ( protocol , impl ) ] ) }
632+ end )
633+
634+ domain =
635+ clauses
636+ |> Enum . map ( fn { [ domain ] , _ } -> domain end )
637+ |> Enum . reduce ( & Descr . union / 2 )
638+
639+ not_domain = Descr . negation ( domain )
640+
641+ { domain , impl_for , impl_for! } =
642+ if Any in types do
643+ clauses = clauses ++ [ { [ not_domain ] , Descr . atom ( [ Module . concat ( protocol , Any ) ] ) } ]
644+ { Descr . term ( ) , clauses , clauses }
645+ else
646+ { domain , clauses ++ [ { [ not_domain ] , Descr . atom ( [ nil ] ) } ] , clauses }
647+ end
648+
649+ new_signatures =
650+ for { { fun , arity } , :def , _ , _ } <- definitions do
651+ rest = List . duplicate ( Descr . term ( ) , arity - 1 )
652+ { { fun , arity } , { :strong , nil , [ { [ domain | rest ] , Descr . dynamic ( ) } ] } }
653+ end
654+
655+ [
656+ { { :impl_for , 1 } , { :strong , [ Descr . term ( ) ] , impl_for } } ,
657+ { { :impl_for! , 1 } , { :strong , [ domain ] , impl_for! } }
658+ ] ++ new_signatures
659+ end
660+
622661 defp change_protocol ( { _name , _kind , meta , clauses } , types ) do
623662 clauses =
624663 Enum . map ( clauses , fn
@@ -682,9 +721,9 @@ defmodule Protocol do
682721 end
683722
684723 # Finally compile the module and emit its bytecode.
685- defp compile ( definitions , specs , { info , chunks } ) do
686- info = % { info | definitions: definitions }
687- { :ok , :elixir_erl . consolidate ( info , specs , chunks ) }
724+ defp compile ( definitions , signatures , { module_map , specs , docs_chunk } ) do
725+ module_map = % { module_map | definitions: definitions , signatures: signatures }
726+ { :ok , :elixir_erl . consolidate ( module_map , specs , docs_chunk ) }
688727 end
689728
690729 ## Definition callbacks
0 commit comments