Skip to content

Commit 268cf85

Browse files
committed
Type behaviour_info, module_info, and __info__
1 parent 8ed104d commit 268cf85

File tree

6 files changed

+160
-71
lines changed

6 files changed

+160
-71
lines changed

lib/elixir/lib/module/behaviour.ex

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ defmodule Module.Behaviour do
44

55
@doc false
66
def callbacks(behaviour) do
7-
for callback <- behaviour_info(behaviour, :callbacks) do
7+
for callback <- behaviour.behaviour_info(:callbacks) do
88
{pair, _kind} = normalize_macro_or_function_callback(callback)
99
pair
1010
end
@@ -55,8 +55,8 @@ defmodule Module.Behaviour do
5555
warn(context, {:module_does_not_define_behaviour, context.module, behaviour})
5656

5757
true ->
58-
optional_callbacks = behaviour_info(behaviour, :optional_callbacks)
59-
callbacks = behaviour_info(behaviour, :callbacks)
58+
optional_callbacks = behaviour.behaviour_info(:optional_callbacks)
59+
callbacks = behaviour.behaviour_info(:callbacks)
6060
Enum.reduce(callbacks, context, &add_callback(&2, &1, behaviour, optional_callbacks))
6161
end
6262
end)
@@ -264,13 +264,6 @@ defmodule Module.Behaviour do
264264
". The known callbacks are:\n#{formatted_callbacks}\n"
265265
end
266266

267-
defp behaviour_info(module, key) do
268-
case module.behaviour_info(key) do
269-
list when is_list(list) -> list
270-
:undefined -> []
271-
end
272-
end
273-
274267
defp normalize_macro_or_function_callback({function_name, arity}) do
275268
case :erlang.atom_to_list(function_name) do
276269
# Macros are always provided one extra argument in behaviour_info/1

lib/elixir/lib/module/types/expr.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,8 +520,8 @@ defmodule Module.Types.Expr do
520520

521521
## General helpers
522522

523-
defp apply_many([], _function, _args_types, _expr, _stack, context) do
524-
{dynamic(), context}
523+
defp apply_many([], function, args_types, expr, stack, context) do
524+
Of.apply(function, args_types, expr, stack, context)
525525
end
526526

527527
defp apply_many([mod], function, args_types, expr, stack, context) do

lib/elixir/lib/module/types/of.ex

Lines changed: 123 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,63 @@ defmodule Module.Types.Of do
347347
# ensuring that domains for the same function have
348348
# no overlaps.
349349

350+
# Remote for callback info functions
351+
352+
kw = fn kw ->
353+
kw
354+
|> Enum.map(fn {key, type} when is_atom(key) ->
355+
tuple([atom([key]), type])
356+
end)
357+
|> Enum.reduce(&union/2)
358+
|> list()
359+
end
360+
361+
fas = list(tuple([atom(), integer()]))
362+
363+
shared_info = [
364+
attributes: list(tuple([atom(), list(term())])),
365+
compile: kw.(version: list(integer()), source: list(integer()), options: list(term())),
366+
exports: fas,
367+
md5: binary(),
368+
module: atom()
369+
]
370+
371+
infos =
372+
%{
373+
behaviour_info: [
374+
callbacks: fas,
375+
optional_callbacks: fas
376+
],
377+
module_info: [functions: fas, nifs: fas] ++ shared_info,
378+
__info__:
379+
[
380+
deprecated: list(tuple([tuple([atom(), integer()]), binary()])),
381+
exports_md5: binary(),
382+
functions: fas,
383+
macros: fas,
384+
struct:
385+
list(closed_map(default: term(), field: atom(), required: boolean()))
386+
|> union(atom([nil]))
387+
] ++ shared_info
388+
}
389+
390+
for {name, clauses} <- infos do
391+
domain = atom(Keyword.keys(clauses))
392+
clauses = Enum.map(clauses, fn {key, return} -> {[atom([key])], return} end)
393+
394+
defp remote(unquote(name), 1) do
395+
{:strong, [unquote(Macro.escape(domain))], unquote(Macro.escape(clauses))}
396+
end
397+
end
398+
399+
defp remote(:module_info, 0) do
400+
{:strong, nil, [{[], unquote(Macro.escape(kw.(infos.module_info)))}]}
401+
end
402+
403+
defp remote(_, _), do: :none
404+
405+
# Remote for compiler functions
406+
350407
mfargs = [atom(), atom(), list(term())]
351408

352409
send_destination =
@@ -367,16 +424,6 @@ defmodule Module.Types.Of do
367424

368425
args_or_arity = union(list(term()), integer())
369426
args_or_none = union(list(term()), atom([:none]))
370-
371-
kw = fn kw ->
372-
kw
373-
|> Enum.map(fn {key, type} when is_atom(key) ->
374-
tuple([atom([key]), type])
375-
end)
376-
|> Enum.reduce(&union/2)
377-
|> list()
378-
end
379-
380427
extra_info = kw.(file: list(integer()), line: integer(), error_info: open_map())
381428

382429
raise_stacktrace =
@@ -387,6 +434,16 @@ defmodule Module.Types.Of do
387434
|> union(tuple([fun(), args_or_arity]))
388435
)
389436

437+
and_signature =
438+
for left <- [true, false], right <- [true, false] do
439+
{[atom([left]), atom([right])], atom([left and right])}
440+
end
441+
442+
or_signature =
443+
for left <- [true, false], right <- [true, false] do
444+
{[atom([left]), atom([right])], atom([left or right])}
445+
end
446+
390447
for {mod, fun, clauses} <- [
391448
# :binary
392449
{:binary, :copy, [{[binary(), integer()], binary()}]},
@@ -407,6 +464,7 @@ defmodule Module.Types.Of do
407464
{:erlang, :>, [{[term(), term()], boolean()}]},
408465
{:erlang, :>=, [{[term(), term()], boolean()}]},
409466
{:erlang, :abs, [{[integer()], integer()}, {[float()], float()}]},
467+
{:erlang, :and, and_signature},
410468
{:erlang, :atom_to_binary, [{[atom()], binary()}]},
411469
{:erlang, :atom_to_list, [{[atom()], list(integer())}]},
412470
{:erlang, :band, [{[integer(), integer()], integer()}]},
@@ -462,6 +520,7 @@ defmodule Module.Types.Of do
462520
{:erlang, :node, [{[], atom()}]},
463521
{:erlang, :node, [{[pid() |> union(reference()) |> union(port())], atom()}]},
464522
{:erlang, :not, [{[atom([false])], atom([true])}, {[atom([true])], atom([false])}]},
523+
{:erlang, :or, or_signature},
465524
{:erlang, :raise, [{[atom([:error, :exit, :throw]), term(), raise_stacktrace], none()}]},
466525
{:erlang, :rem, [{[integer(), integer()], integer()}]},
467526
{:erlang, :round, [{[union(integer(), float())], integer()}]},
@@ -478,20 +537,25 @@ defmodule Module.Types.Of do
478537
# TODO: Replace term()/dynamic() by parametric types
479538
{:erlang, :++, [{[list(term()), term()], dynamic(list(term(), term()))}]},
480539
{:erlang, :--, [{[list(term()), list(term())], dynamic(list(term()))}]},
540+
{:erlang, :andalso, [{[boolean(), term()], dynamic()}]},
481541
{:erlang, :delete_element, [{[integer(), open_tuple([])], dynamic(open_tuple([]))}]},
482542
{:erlang, :hd, [{[non_empty_list(term(), term())], dynamic()}]},
483543
{:erlang, :element, [{[integer(), open_tuple([])], dynamic()}]},
484544
{:erlang, :insert_element,
485545
[{[integer(), open_tuple([]), term()], dynamic(open_tuple([]))}]},
486546
{:erlang, :max, [{[term(), term()], dynamic()}]},
487547
{:erlang, :min, [{[term(), term()], dynamic()}]},
548+
{:erlang, :orelse, [{[boolean(), term()], dynamic()}]},
488549
{:erlang, :send, [{[send_destination, term()], dynamic()}]},
489550
{:erlang, :setelement, [{[integer(), open_tuple([]), term()], dynamic(open_tuple([]))}]},
490551
{:erlang, :tl, [{[non_empty_list(term(), term())], dynamic()}]},
491552
{:erlang, :tuple_to_list, [{[open_tuple([])], dynamic(list(term()))}]}
492553
] do
493554
[arity] = Enum.map(clauses, fn {args, _return} -> length(args) end) |> Enum.uniq()
494-
true = Code.ensure_loaded?(mod) and function_exported?(mod, fun, arity)
555+
556+
true =
557+
Code.ensure_loaded?(mod) and
558+
(function_exported?(mod, fun, arity) or fun in [:orelse, :andalso])
495559

496560
domain_clauses =
497561
case clauses do
@@ -535,14 +599,29 @@ defmodule Module.Types.Of do
535599
{:none, context}
536600
else
537601
case remote(module, fun, arity) do
538-
:none -> {:none, check_export(module, fun, arity, meta, stack, context)}
602+
:none -> export(module, fun, arity, meta, stack, context)
539603
clauses -> {clauses, context}
540604
end
541605
end
542606
end
543607

544-
# TODO: Fix ordering of tuple operations
608+
@doc """
609+
Applies a function in unknown modules.
545610
611+
Used only by info functions.
612+
"""
613+
def apply(name, args_types, expr, stack, context) do
614+
arity = length(args_types)
615+
616+
case remote(name, arity) do
617+
:none -> {dynamic(), context}
618+
info -> apply_remote(info, args_types, expr, stack, context)
619+
end
620+
end
621+
622+
@doc """
623+
Applies a function in a given module.
624+
"""
546625
def apply(:erlang, :element, [_, tuple], {_, meta, [index, _]} = expr, stack, context)
547626
when is_integer(index) do
548627
case tuple_fetch(tuple, index - 1) do
@@ -677,15 +756,7 @@ defmodule Module.Types.Of do
677756

678757
false ->
679758
{info, context} = remote(mod, name, arity, elem(expr, 1), stack, context)
680-
681-
case apply_remote(info, args_types, stack) do
682-
{:ok, type} ->
683-
{type, context}
684-
685-
{:error, domain, clauses} ->
686-
error = {:badapply, expr, args_types, domain, clauses, context}
687-
{error_type(), error(error, elem(expr, 1), stack, context)}
688-
end
759+
apply_remote(info, args_types, expr, stack, context)
689760
end
690761
end
691762

@@ -697,6 +768,17 @@ defmodule Module.Types.Of do
697768
end
698769
end
699770

771+
defp apply_remote(info, args_types, expr, stack, context) do
772+
case apply_remote(info, args_types, stack) do
773+
{:ok, type} ->
774+
{type, context}
775+
776+
{:error, domain, clauses} ->
777+
error = {:badapply, expr, args_types, domain, clauses, context}
778+
{error_type(), error(error, elem(expr, 1), stack, context)}
779+
end
780+
end
781+
700782
defp apply_remote(:none, _args_types, _stack) do
701783
{:ok, dynamic()}
702784
end
@@ -740,25 +822,25 @@ defmodule Module.Types.Of do
740822

741823
defp zip_not_disjoint?([], []), do: true
742824

743-
defp check_export(module, fun, arity, meta, stack, context) do
825+
defp export(_module, :module_info, arity, _meta, _stack, context) when arity in [0, 1] do
826+
{remote(:module_info, arity), context}
827+
end
828+
829+
defp export(module, fun, arity, meta, stack, context) do
744830
case ParallelChecker.fetch_export(stack.cache, module, fun, arity) do
745831
{:ok, mode, reason} ->
746-
check_deprecated(mode, module, fun, arity, reason, meta, stack, context)
747-
748-
{:error, :module} ->
749-
if warn_undefined?(module, fun, arity, stack) do
750-
warn(__MODULE__, {:undefined_module, module, fun, arity}, meta, stack, context)
751-
else
752-
context
753-
end
832+
{remote(fun, arity),
833+
check_deprecated(mode, module, fun, arity, reason, meta, stack, context)}
834+
835+
{:error, type} ->
836+
context =
837+
if warn_undefined?(module, fun, arity, stack) do
838+
warn(__MODULE__, {:undefined, type, module, fun, arity}, meta, stack, context)
839+
else
840+
context
841+
end
754842

755-
{:error, :function} ->
756-
if warn_undefined?(module, fun, arity, stack) do
757-
payload = {:undefined_function, module, fun, arity}
758-
warn(__MODULE__, payload, meta, stack, context)
759-
else
760-
context
761-
end
843+
{:none, context}
762844
end
763845
end
764846

@@ -786,22 +868,6 @@ defmodule Module.Types.Of do
786868
end
787869
end
788870

789-
# The protocol code dispatches to unknown modules, so we ignore them here.
790-
#
791-
# try do
792-
# SomeProtocol.Atom.__impl__
793-
# rescue
794-
# ...
795-
# end
796-
#
797-
# But for protocols we don't want to traverse the protocol code anyway.
798-
# TODO: remove this clause once we no longer traverse the protocol code.
799-
defp warn_undefined?(_module, :__impl__, 1, _stack), do: false
800-
defp warn_undefined?(_module, :module_info, 0, _stack), do: false
801-
defp warn_undefined?(_module, :module_info, 1, _stack), do: false
802-
defp warn_undefined?(:erlang, :orelse, 2, _stack), do: false
803-
defp warn_undefined?(:erlang, :andalso, 2, _stack), do: false
804-
805871
defp warn_undefined?(_, _, _, %{no_warn_undefined: :all}) do
806872
false
807873
end
@@ -1053,7 +1119,7 @@ defmodule Module.Types.Of do
10531119
}
10541120
end
10551121

1056-
def format_diagnostic({:undefined_module, module, fun, arity}) do
1122+
def format_diagnostic({:undefined, :module, module, fun, arity}) do
10571123
top =
10581124
if fun == :__struct__ and arity == 0 do
10591125
"struct #{inspect(module)}"
@@ -1074,15 +1140,15 @@ defmodule Module.Types.Of do
10741140
}
10751141
end
10761142

1077-
def format_diagnostic({:undefined_function, module, :__struct__, 0}) do
1143+
def format_diagnostic({:undefined, :function, module, :__struct__, 0}) do
10781144
%{
10791145
message:
10801146
"struct #{inspect(module)} is undefined (there is such module but it does not define a struct)",
10811147
group: true
10821148
}
10831149
end
10841150

1085-
def format_diagnostic({:undefined_function, module, fun, arity}) do
1151+
def format_diagnostic({:undefined, :function, module, fun, arity}) do
10861152
%{
10871153
message:
10881154
IO.iodata_to_binary([

lib/elixir/src/elixir_def.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,4 +513,4 @@ format_error({module_info, Kind, Arity}) ->
513513

514514
format_error({is_record, Kind}) ->
515515
io_lib:format("cannot define ~ts is_record/2 due to compatibility "
516-
"issues with the Erlang compiler (it is a known limitation)", [Kind]).
516+
"with the Erlang compiler (it is a known limitation)", [Kind]).

lib/elixir/test/elixir/kernel_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -797,7 +797,7 @@ defmodule KernelTest do
797797

798798
test ":struct" do
799799
assert Kernel.__info__(:struct) == nil
800-
assert hd(URI.__info__(:struct)) == %{field: :scheme, required: false, default: nil}
800+
assert [%{field: :scheme, required: false, default: nil} | _] = URI.__info__(:struct)
801801
end
802802

803803
test "others" do

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1355,4 +1355,34 @@ defmodule Module.Types.ExprTest do
13551355
"""
13561356
end
13571357
end
1358+
1359+
describe "info" do
1360+
test "__info__/1" do
1361+
assert typecheck!([x], x.__info__(:functions)) == list(tuple([atom(), integer()]))
1362+
1363+
assert typecheck!(GenServer.__info__(:functions)) == list(tuple([atom(), integer()]))
1364+
1365+
assert typewarn!(:string.__info__(:functions)) ==
1366+
{dynamic(), ":string.__info__/1 is undefined or private"}
1367+
end
1368+
1369+
test "behaviour_info/1" do
1370+
assert typecheck!([x], x.behaviour_info(:callbacks)) == list(tuple([atom(), integer()]))
1371+
1372+
assert typecheck!(GenServer.behaviour_info(:callbacks)) == list(tuple([atom(), integer()]))
1373+
1374+
assert typewarn!(String.behaviour_info(:callbacks)) ==
1375+
{dynamic(), "String.behaviour_info/1 is undefined or private"}
1376+
end
1377+
1378+
test "module_info/1" do
1379+
assert typecheck!([x], x.module_info(:exports)) == list(tuple([atom(), integer()]))
1380+
assert typecheck!(GenServer.module_info(:exports)) == list(tuple([atom(), integer()]))
1381+
end
1382+
1383+
test "module_info/0" do
1384+
assert typecheck!([x], x.module_info()) |> subtype?(list(tuple([atom(), term()])))
1385+
assert typecheck!(GenServer.module_info()) |> subtype?(list(tuple([atom(), term()])))
1386+
end
1387+
end
13581388
end

0 commit comments

Comments
 (0)