Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 0 additions & 58 deletions lib/elixir/lib/exception.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2547,61 +2547,3 @@ defmodule ErlangError do
defp nth(3), do: "3rd"
defp nth(n), do: "#{n}th"
end

defmodule Inspect.Error do
@moduledoc """
Raised when a struct cannot be inspected.
"""
@enforce_keys [:exception_module, :exception_message, :stacktrace, :inspected_struct]
defexception @enforce_keys

@impl true
def exception(arguments) when is_list(arguments) do
exception = Keyword.fetch!(arguments, :exception)
exception_module = exception.__struct__
exception_message = Exception.message(exception) |> String.trim_trailing("\n")
stacktrace = Keyword.fetch!(arguments, :stacktrace)
inspected_struct = Keyword.fetch!(arguments, :inspected_struct)

%Inspect.Error{
exception_module: exception_module,
exception_message: exception_message,
stacktrace: stacktrace,
inspected_struct: inspected_struct
}
end

@impl true
def message(%__MODULE__{
exception_module: exception_module,
exception_message: exception_message,
inspected_struct: inspected_struct
}) do
~s'''
got #{inspect(exception_module)} with message:

"""
#{pad(exception_message, 4)}
"""

while inspecting:

#{pad(inspected_struct, 4)}
'''
end

@doc false
def pad(message, padding_length)
when is_binary(message) and is_integer(padding_length) and padding_length >= 0 do
padding = String.duplicate(" ", padding_length)

message
|> String.split("\n")
|> Enum.map(fn
"" -> "\n"
line -> [padding, line, ?\n]
end)
|> IO.iodata_to_binary()
|> String.trim_trailing("\n")
end
end
30 changes: 0 additions & 30 deletions lib/elixir/lib/inspect.ex
Original file line number Diff line number Diff line change
Expand Up @@ -577,36 +577,6 @@ defimpl Inspect, for: Function do
end
end

defimpl Inspect, for: Inspect.Error do
@impl true
def inspect(%{stacktrace: stacktrace} = inspect_error, _opts) do
message = Exception.message(inspect_error)
format_output(message, stacktrace)
end

defp format_output(message, [_ | _] = stacktrace) do
stacktrace = Exception.format_stacktrace(stacktrace)

"""
#Inspect.Error<
#{Inspect.Error.pad(message, 2)}

Stacktrace:

#{stacktrace}
>\
"""
end

defp format_output(message, []) do
"""
#Inspect.Error<
#{Inspect.Error.pad(message, 2)}
>\
"""
end
end

defimpl Inspect, for: PID do
def inspect(pid, _opts) do
"#PID" <> IO.iodata_to_binary(:erlang.pid_to_list(pid))
Expand Down
87 changes: 87 additions & 0 deletions lib/elixir/lib/inspect/error.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
defmodule Inspect.Error do
@moduledoc """
Raised when a struct cannot be inspected.
"""
@enforce_keys [:exception_module, :exception_message, :stacktrace, :inspected_struct]
defexception @enforce_keys

@impl true
def exception(arguments) when is_list(arguments) do
exception = Keyword.fetch!(arguments, :exception)
exception_module = exception.__struct__
exception_message = Exception.message(exception) |> String.trim_trailing("\n")
stacktrace = Keyword.fetch!(arguments, :stacktrace)
inspected_struct = Keyword.fetch!(arguments, :inspected_struct)

%Inspect.Error{
exception_module: exception_module,
exception_message: exception_message,
stacktrace: stacktrace,
inspected_struct: inspected_struct
}
end

@impl true
def message(%__MODULE__{
exception_module: exception_module,
exception_message: exception_message,
inspected_struct: inspected_struct
}) do
~s'''
got #{inspect(exception_module)} with message:

"""
#{pad(exception_message, 4)}
"""

while inspecting:

#{pad(inspected_struct, 4)}
'''
end

@doc false
def pad(message, padding_length)
when is_binary(message) and is_integer(padding_length) and padding_length >= 0 do
padding = String.duplicate(" ", padding_length)

message
|> String.split("\n")
|> Enum.map(fn
"" -> "\n"
line -> [padding, line, ?\n]
end)
|> IO.iodata_to_binary()
|> String.trim_trailing("\n")
end
end

defimpl Inspect, for: Inspect.Error do
@impl true
def inspect(%{stacktrace: stacktrace} = inspect_error, _opts) do
message = Exception.message(inspect_error)
format_output(message, stacktrace)
end

defp format_output(message, [_ | _] = stacktrace) do
stacktrace = Exception.format_stacktrace(stacktrace)

"""
#Inspect.Error<
#{Inspect.Error.pad(message, 2)}

Stacktrace:

#{stacktrace}
>\
"""
end

defp format_output(message, []) do
"""
#Inspect.Error<
#{Inspect.Error.pad(message, 2)}
>\
"""
end
end
22 changes: 17 additions & 5 deletions lib/elixir/lib/module/parallel_checker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,17 @@ defmodule Module.ParallelChecker do
@doc """
Spawns a process that runs the parallel checker.
"""
def spawn({pid, {checker, table}}, module, module_map, log?) do
inner_spawn(pid, checker, table, module, cache_from_module_map(table, module_map), log?)
def spawn({pid, {checker, table}}, module, module_map, beam_location, log?) do
# Protocols may have been consolidated. So if we know their beam location,
# we discard their module map on purpose and start from file.
info =
if beam_location != [] and List.keymember?(module_map.attributes, :__protocol__, 0) do
List.to_string(beam_location)
else
cache_from_module_map(table, module_map)
end

inner_spawn(pid, checker, table, module, info, log?)
end

defp inner_spawn(pid, checker, table, module, info, log?) do
Expand Down Expand Up @@ -221,7 +230,7 @@ defmodule Module.ParallelChecker do
## Module checking

defp check_module(module_tuple, cache, log?) do
{module, file, line, definitions, no_warn_undefined, behaviours, impls, after_verify} =
{module, file, line, definitions, no_warn_undefined, behaviours, impls, attrs, after_verify} =
module_tuple

behaviour_warnings =
Expand All @@ -236,7 +245,7 @@ defmodule Module.ParallelChecker do

diagnostics =
module
|> Module.Types.warnings(file, definitions, no_warn_undefined, cache)
|> Module.Types.warnings(file, attrs, definitions, no_warn_undefined, cache)
|> Kernel.++(behaviour_warnings)
|> group_warnings()
|> emit_warnings(file, log?)
Expand Down Expand Up @@ -273,7 +282,10 @@ defmodule Module.ParallelChecker do
|> extract_no_warn_undefined()
|> merge_compiler_no_warn_undefined()

{module, file, line, definitions, no_warn_undefined, behaviours, impls, after_verify}
attributes = Keyword.take(attributes, [:__protocol__, :__impl__])

{module, file, line, definitions, no_warn_undefined, behaviours, impls, attributes,
after_verify}
end

defp extract_no_warn_undefined(compile_opts) do
Expand Down
69 changes: 56 additions & 13 deletions lib/elixir/lib/module/types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,26 @@ defmodule Module.Types do
@modes [:static, :dynamic, :infer, :traversal]

# These functions are not inferred because they are added/managed by the compiler
@no_infer [__protocol__: 1, behaviour_info: 1]
@no_infer [behaviour_info: 1]

@doc false
def infer(module, file, defs, private, used_private, env, {_, cache}) do
infer_signatures? = :elixir_config.get(:infer_signatures) and cache != nil
def infer(module, file, attrs, defs, private, used_private, env, {_, cache}) do
# We don't care about inferring signatures for protocols,
# those will be replaced anyway. There is also nothing to
# infer if there is no cache system, we only do traversals.
infer_signatures? =
:elixir_config.get(:infer_signatures) and cache != nil and not protocol?(attrs)

impl = impl_for(attrs)

finder =
fn fun_arity ->
case :lists.keyfind(fun_arity, 1, defs) do
{_, kind, _, _} = clause -> {infer_mode(kind, infer_signatures?), clause}
false -> false
{_, kind, _, _} = clause ->
{infer_mode(kind, infer_signatures?), clause, default_domain(fun_arity, impl)}

false ->
false
end
end

Expand All @@ -67,7 +76,11 @@ defmodule Module.Types do
kind in [:def, :defmacro],
reduce: {[], context()} do
{types, context} ->
finder = fn _ -> {infer_mode(kind, infer_signatures?), def} end
# Optimized version of finder, since we already the definition
finder = fn _ ->
{infer_mode(kind, infer_signatures?), def, default_domain(fun_arity, impl)}
end

{_kind, inferred, context} = local_handler(meta, fun_arity, stack, context, finder)

if infer_signatures? and kind == :def and fun_arity not in @no_infer do
Expand Down Expand Up @@ -111,6 +124,33 @@ defmodule Module.Types do
if infer_signatures? and kind in [:def, :defp], do: :infer, else: :traversal
end

defp protocol?(attrs) do
List.keymember?(attrs, :__protocol__, 0)
end

defp impl_for(attrs) do
case List.keyfind(attrs, :__impl__, 0) do
{:__impl__, [protocol: protocol, for: for]} ->
if Code.ensure_loaded?(protocol) and function_exported?(protocol, :behaviour_info, 1) do
{for, protocol.behaviour_info(:callbacks)}
else
nil
end

_ ->
nil
end
end

defp default_domain({_, arity} = fun_arity, impl) do
with {for, callbacks} <- impl,
true <- fun_arity in callbacks do
[Module.Types.Of.impl(for) | List.duplicate(Descr.dynamic(), arity - 1)]
else
_ -> List.duplicate(Descr.dynamic(), arity)
end
end

defp undefined_function!(reason, meta, {fun, arity}, stack, env) do
env = %{env | function: stack.function, file: stack.file}
tuple = {reason, {fun, arity}, stack.module}
Expand Down Expand Up @@ -159,10 +199,12 @@ defmodule Module.Types do
end

@doc false
def warnings(module, file, defs, no_warn_undefined, cache) do
def warnings(module, file, attrs, defs, no_warn_undefined, cache) do
impl = impl_for(attrs)

finder = fn fun_arity ->
case :lists.keyfind(fun_arity, 1, defs) do
{_, _, _, _} = clause -> {:dynamic, clause}
{_, _, _, _} = clause -> {:dynamic, clause, default_domain(fun_arity, impl)}
false -> false
end
end
Expand All @@ -172,7 +214,8 @@ defmodule Module.Types do

context =
Enum.reduce(defs, context(), fn {fun_arity, _kind, meta, _clauses} = def, context ->
finder = fn _ -> {:dynamic, def} end
# Optimized version of finder, since we already the definition
finder = fn _ -> {:dynamic, def, default_domain(fun_arity, impl)} end
{_kind, _inferred, context} = local_handler(meta, fun_arity, stack, context, finder)
context
end)
Expand Down Expand Up @@ -211,11 +254,11 @@ defmodule Module.Types do

local_sigs ->
case finder.(fun_arity) do
{mode, {fun_arity, kind, meta, clauses}} ->
{mode, {fun_arity, kind, meta, clauses}, expected} ->
context = put_in(context.local_sigs, Map.put(local_sigs, fun_arity, kind))

{inferred, mapping, context} =
local_handler(fun_arity, kind, meta, clauses, mode, stack, context)
local_handler(fun_arity, kind, meta, clauses, expected, mode, stack, context)

context =
update_in(context.local_sigs, &Map.put(&1, fun_arity, {kind, inferred, mapping}))
Expand All @@ -228,8 +271,8 @@ defmodule Module.Types do
end
end

defp local_handler({fun, arity} = fun_arity, kind, meta, clauses, mode, stack, context) do
expected = List.duplicate(Descr.dynamic(), arity)
defp local_handler(fun_arity, kind, meta, clauses, expected, mode, stack, context) do
{fun, _arity} = fun_arity
stack = stack |> fresh_stack(mode, fun_arity) |> with_file_meta(meta)

{_, _, mapping, clauses_types, clauses_context} =
Expand Down
Loading
Loading