@@ -209,7 +209,8 @@ defmodule Module.ParallelChecker do
|
||||
"""
|
||||
@spec fetch_export(cache(), module(), atom(), arity()) ::
|
||||
{:ok, mode(), binary() | nil, {:infer, [term()]} | :none}
|
||||
| {:error, :function | :module}
|
||||
| :badmodule
|
||||
| {:badfunction, mode()}
|
||||
def fetch_export({checker, table}, module, fun, arity) do
|
||||
case :ets.lookup(table, module) do
|
||||
[] ->
|
||||
@@ -217,12 +218,12 @@ defmodule Module.ParallelChecker do
|
||||
fetch_export({checker, table}, module, fun, arity)
|
||||
|
||||
[{_key, false}] ->
|
||||
{:error, :module}
|
||||
:badmodule
|
||||
|
||||
[{_key, mode}] ->
|
||||
case :ets.lookup(table, {module, {fun, arity}}) do
|
||||
[{_key, reason, signature}] -> {:ok, mode, reason, signature}
|
||||
[] -> {:error, :function}
|
||||
[] -> {:badfunction, mode}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -419,7 +420,7 @@ defmodule Module.ParallelChecker do
|
||||
end
|
||||
|
||||
defp info_exports(module) do
|
||||
{:elixir, behaviour_exports(module) ++ [{:__info__, 1} | module.__info__(:functions)]}
|
||||
{:elixir, behaviour_exports(module) ++ module.__info__(:functions)}
|
||||
rescue
|
||||
_ -> {:erlang, module.module_info(:exports)}
|
||||
end
|
||||
@@ -432,8 +433,7 @@ defmodule Module.ParallelChecker do
|
||||
|
||||
defp cache_from_module_map(table, map) do
|
||||
exports =
|
||||
[{:__info__, 1}] ++
|
||||
behaviour_exports(map) ++
|
||||
behaviour_exports(map) ++
|
||||
for({function, :def, _meta, _clauses} <- map.definitions, do: function)
|
||||
|
||||
cache_info(table, map.module, exports, Map.new(map.deprecated), map.signatures, :elixir)
|
||||
@@ -458,7 +458,6 @@ defmodule Module.ParallelChecker do
|
||||
)
|
||||
end)
|
||||
|
||||
:ets.insert(table, {{module, {:__info__, 1}}, nil, :none})
|
||||
:ets.insert(table, {module, :elixir})
|
||||
end
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ defmodule Module.Types.Apply do
|
||||
end
|
||||
|
||||
fas = list(tuple([atom(), integer()]))
|
||||
struct_info = list(closed_map(default: if_set(term()), field: atom()))
|
||||
|
||||
shared_info = [
|
||||
attributes: list(tuple([atom(), list(term())])),
|
||||
@@ -40,31 +41,32 @@ defmodule Module.Types.Apply do
|
||||
module: atom()
|
||||
]
|
||||
|
||||
module_info = [functions: fas, nifs: fas] ++ shared_info
|
||||
|
||||
elixir_info =
|
||||
[
|
||||
deprecated: list(tuple([tuple([atom(), integer()]), binary()])),
|
||||
exports_md5: binary(),
|
||||
functions: fas,
|
||||
macros: fas,
|
||||
struct: struct_info |> union(atom([nil]))
|
||||
] ++ shared_info
|
||||
|
||||
infos =
|
||||
%{
|
||||
behaviour_info: [
|
||||
callbacks: fas,
|
||||
optional_callbacks: fas
|
||||
],
|
||||
module_info: [functions: fas, nifs: fas] ++ shared_info,
|
||||
__info__:
|
||||
[
|
||||
deprecated: list(tuple([tuple([atom(), integer()]), binary()])),
|
||||
exports_md5: binary(),
|
||||
functions: fas,
|
||||
macros: fas,
|
||||
struct:
|
||||
list(closed_map(default: if_set(term()), field: atom()))
|
||||
|> union(atom([nil]))
|
||||
] ++ shared_info,
|
||||
# TODO: Move this to a type signature in the long term
|
||||
__protocol__: [
|
||||
module: atom(),
|
||||
functions: fas,
|
||||
consolidated?: boolean(),
|
||||
impls: union(atom([:not_consolidated]), tuple([atom([:consolidated]), list(atom())]))
|
||||
]
|
||||
}
|
||||
[
|
||||
# We have a special key that tracks if something is a struct or not
|
||||
{{:__info__, true}, Keyword.put(elixir_info, :struct, struct_info)},
|
||||
{{:__info__, false}, Keyword.put(elixir_info, :struct, atom([nil]))},
|
||||
{:__info__, elixir_info},
|
||||
{:behaviour_info, callbacks: fas, optional_callbacks: fas},
|
||||
{:module_info, module_info},
|
||||
# TODO: Move this to a type signature declared by `defprotocol` (or perhaps part of the behaviour)
|
||||
{:__protocol__,
|
||||
module: atom(),
|
||||
functions: fas,
|
||||
consolidated?: boolean(),
|
||||
impls: union(atom([:not_consolidated]), tuple([atom([:consolidated]), list(atom())]))}
|
||||
]
|
||||
|
||||
for {name, clauses} <- infos do
|
||||
domain = atom(Keyword.keys(clauses))
|
||||
@@ -76,7 +78,7 @@ defmodule Module.Types.Apply do
|
||||
end
|
||||
|
||||
defp signature(:module_info, 0) do
|
||||
{:strong, nil, [{[], unquote(Macro.escape(kw.(infos.module_info)))}]}
|
||||
{:strong, nil, [{[], unquote(Macro.escape(kw.(module_info)))}]}
|
||||
end
|
||||
|
||||
defp signature(_, _), do: :none
|
||||
@@ -512,10 +514,20 @@ defmodule Module.Types.Apply do
|
||||
info = if info == :none, do: signature(fun, arity), else: info
|
||||
{info, check_deprecated(mode, module, fun, arity, reason, meta, stack, context)}
|
||||
|
||||
{:error, type} ->
|
||||
{:badfunction, :elixir} when fun == :__info__ and arity == 1 ->
|
||||
key =
|
||||
cond do
|
||||
not Code.ensure_loaded?(module) -> :__info__
|
||||
module.__info__(:struct) != nil -> {:__info__, true}
|
||||
true -> {:__info__, false}
|
||||
end
|
||||
|
||||
{signature(key, arity), context}
|
||||
|
||||
error ->
|
||||
context =
|
||||
if warn_undefined?(module, fun, arity, stack) do
|
||||
warn(__MODULE__, {:undefined, type, module, fun, arity}, meta, stack, context)
|
||||
warn(__MODULE__, {:undefined, error, module, fun, arity}, meta, stack, context)
|
||||
else
|
||||
context
|
||||
end
|
||||
@@ -525,14 +537,6 @@ defmodule Module.Types.Apply do
|
||||
end
|
||||
end
|
||||
|
||||
defp check_deprecated(:elixir, module, fun, arity, reason, meta, stack, context) do
|
||||
if reason do
|
||||
warn(__MODULE__, {:deprecated, module, fun, arity, reason}, meta, stack, context)
|
||||
else
|
||||
context
|
||||
end
|
||||
end
|
||||
|
||||
defp check_deprecated(:erlang, module, fun, arity, _reason, meta, stack, context) do
|
||||
case :otp_internal.obsolete(module, fun, arity) do
|
||||
{:deprecated, string} when is_list(string) ->
|
||||
@@ -549,6 +553,14 @@ defmodule Module.Types.Apply do
|
||||
end
|
||||
end
|
||||
|
||||
defp check_deprecated(_, module, fun, arity, reason, meta, stack, context) do
|
||||
if reason do
|
||||
warn(__MODULE__, {:deprecated, module, fun, arity, reason}, meta, stack, context)
|
||||
else
|
||||
context
|
||||
end
|
||||
end
|
||||
|
||||
defp builtin_module?(module) do
|
||||
is_map_key(builtin_modules(), module)
|
||||
end
|
||||
@@ -952,7 +964,7 @@ defmodule Module.Types.Apply do
|
||||
}
|
||||
end
|
||||
|
||||
def format_diagnostic({:undefined, :module, module, fun, arity}) do
|
||||
def format_diagnostic({:undefined, :badmodule, module, fun, arity}) do
|
||||
top =
|
||||
if fun == :__struct__ and arity == 0 do
|
||||
"struct #{inspect(module)}"
|
||||
@@ -973,7 +985,7 @@ defmodule Module.Types.Apply do
|
||||
}
|
||||
end
|
||||
|
||||
def format_diagnostic({:undefined, :function, module, :__struct__, 0}) do
|
||||
def format_diagnostic({:undefined, {:badfunction, _}, module, :__struct__, 0}) do
|
||||
%{
|
||||
message:
|
||||
"struct #{inspect(module)} is undefined (there is such module but it does not define a struct)",
|
||||
@@ -981,7 +993,7 @@ defmodule Module.Types.Apply do
|
||||
}
|
||||
end
|
||||
|
||||
def format_diagnostic({:undefined, :function, module, fun, arity}) do
|
||||
def format_diagnostic({:undefined, {:badfunction, _}, module, fun, arity}) do
|
||||
%{
|
||||
message:
|
||||
IO.iodata_to_binary([
|
||||
|
||||
@@ -1436,13 +1436,13 @@ defmodule Module.Types.ExprTest do
|
||||
|
||||
describe "info" do
|
||||
test "__info__/1" do
|
||||
assert typecheck!([x], x.__info__(:functions)) == list(tuple([atom(), integer()]))
|
||||
|
||||
assert typecheck!(GenServer.__info__(:functions)) == list(tuple([atom(), integer()]))
|
||||
|
||||
assert typewarn!(:string.__info__(:functions)) ==
|
||||
{dynamic(), ":string.__info__/1 is undefined or private"}
|
||||
|
||||
assert typecheck!([x], x.__info__(:functions)) == list(tuple([atom(), integer()]))
|
||||
|
||||
assert typeerror!([x], x.__info__(:whatever)) |> strip_ansi() =~ """
|
||||
incompatible types given to __info__/1:
|
||||
|
||||
@@ -1454,6 +1454,16 @@ defmodule Module.Types.ExprTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "__info__/1 for struct information" do
|
||||
assert typecheck!(GenServer.__info__(:struct)) == atom([nil])
|
||||
|
||||
assert typecheck!(URI.__info__(:struct)) ==
|
||||
list(closed_map(default: if_set(term()), field: atom()))
|
||||
|
||||
assert typecheck!([x], x.__info__(:struct)) ==
|
||||
list(closed_map(default: if_set(term()), field: atom())) |> union(atom([nil]))
|
||||
end
|
||||
|
||||
test "behaviour_info/1" do
|
||||
assert typecheck!([x], x.behaviour_info(:callbacks)) == list(tuple([atom(), integer()]))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user