Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
8 changes: 4 additions & 4 deletions lib/iex/lib/iex/helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ defmodule IEx.Helpers do
"""
defmacro open(term) do
quote do
IEx.Introspection.open(unquote(IEx.Introspection.decompose(term, __CALLER__)))
IEx.Introspection.open(unquote(Macro.escape(IEx.Introspection.decompose(term, __CALLER__))))
end
end

Expand Down Expand Up @@ -362,7 +362,7 @@ defmodule IEx.Helpers do
"""
defmacro h(term) do
quote do
IEx.Introspection.h(unquote(IEx.Introspection.decompose(term, __CALLER__)))
IEx.Introspection.h(unquote(Macro.escape(IEx.Introspection.decompose(term, __CALLER__))))
end
end

Expand All @@ -381,7 +381,7 @@ defmodule IEx.Helpers do
"""
defmacro b(term) do
quote do
IEx.Introspection.b(unquote(IEx.Introspection.decompose(term, __CALLER__)))
IEx.Introspection.b(unquote(Macro.escape(IEx.Introspection.decompose(term, __CALLER__))))
end
end

Expand All @@ -406,7 +406,7 @@ defmodule IEx.Helpers do
"""
defmacro t(term) do
quote do
IEx.Introspection.t(unquote(IEx.Introspection.decompose(term, __CALLER__)))
IEx.Introspection.t(unquote(Macro.escape(IEx.Introspection.decompose(term, __CALLER__))))
end
end

Expand Down
22 changes: 14 additions & 8 deletions lib/iex/lib/iex/introspection.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,22 @@ defmodule IEx.Introspection do
Decomposes an introspection call into `{mod, fun, arity}`,
`{mod, fun}` or `mod`.
"""
def decompose(atom, _context) when is_atom(atom), do: atom

def decompose({:__aliases__, _, _} = module, context) do
Macro.expand(module, context)
end

def decompose({:/, _, [call, arity]} = term, context) do
case Macro.decompose_call(call) do
{_mod, :__info__, []} when arity == 1 ->
{:{}, [], [Module, :__info__, 1]}
{Module, :__info__, 1}

{mod, fun, []} ->
{:{}, [], [mod, fun, arity]}
{Macro.expand(mod, context), fun, arity}

{fun, []} ->
{:{}, [], [find_decompose_fun_arity(fun, arity, context), fun, arity]}
{find_decompose_fun_arity(fun, arity, context), fun, arity}

_ ->
term
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error you are getting is because of this line here. We were previously returning the term as is, without escaping, and now we are escaping it always on IEx.Helpers. We also do it in the decompose clause below. My suggestion is to make it return :error instead.

Then, inside IEx.Helpers, you can write this helper:

defp decompose(term, context) do
  case IEx.Introspection.decompose(term, context) do
    :error -> term
    m_mf_mfa -> Macro.escape(m_mf_mfa)
  end
end

And then, in mix help, you raise if decompose returns :error.

Copy link
Contributor Author

@renanroberto renanroberto Feb 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great idea! I don't really know what to use on the raise message. Came up with Invalid expression: #{module} for now. Feel free to suggest something else!

Expand All @@ -30,20 +36,20 @@ defmodule IEx.Introspection do
def decompose(call, context) do
case Macro.decompose_call(call) do
{_mod, :__info__, []} ->
Macro.escape({Module, :__info__, 1})

{mod, fun, []} ->
{mod, fun}
{Module, :__info__, 1}

{maybe_sigil, [_, _]} ->
case Atom.to_string(maybe_sigil) do
"sigil_" <> _ ->
{:{}, [], [find_decompose_fun_arity(maybe_sigil, 2, context), maybe_sigil, 2]}
{find_decompose_fun_arity(maybe_sigil, 2, context), maybe_sigil, 2}

_ ->
call
end

{mod, fun, []} ->
{Macro.expand(mod, context), fun}

{fun, []} ->
{find_decompose_fun(fun, context), fun}

Expand Down
18 changes: 18 additions & 0 deletions lib/mix/lib/mix/tasks/help.ex
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ defmodule Mix.Tasks.Help do
Mix.raise("Unexpected arguments, expected \"mix help --search PATTERN\"")
end

def run([module = <<first, _::binary>>]) when first in ?A..?Z or first == ?: do
loadpaths!()

iex_colors = Application.get_env(:iex, :colors, [])
mix_colors = Application.get_env(:mix, :colors, [])

try do
Application.put_env(:iex, :colors, mix_colors)

module
|> Code.string_to_quoted!()
|> IEx.Introspection.decompose(__ENV__)
|> IEx.Introspection.h()
after
Application.put_env(:iex, :colors, iex_colors)
end
end

def run([task]) do
loadpaths!()
opts = Application.get_env(:mix, :colors)
Expand Down
74 changes: 74 additions & 0 deletions lib/mix/test/mix/tasks/help_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,80 @@ defmodule Mix.Tasks.HelpTest do
end)
end

test "help Elixir MODULE", context do
in_tmp(context.test, fn ->
output =
capture_io(fn ->
Mix.Tasks.Help.run(["Mix"])
end)

assert output =~
"Mix is a build tool that provides tasks for creating, compiling, and testing\nElixir projects, managing its dependencies, and more."
end)
end

test "help Elixir NESTED MODULE", context do
in_tmp(context.test, fn ->
output =
capture_io(fn ->
Mix.Tasks.Help.run(["IO.ANSI"])
end)

assert output =~ "Functionality to render ANSI escape sequences."
end)
end

test "help Erlang MODULE", context do
otp_docs? = match?({:docs_v1, _, _, _, _, _, _}, Code.fetch_docs(:math))

in_tmp(context.test, fn ->
output =
capture_io(fn ->
Mix.Tasks.Help.run([":math"])
end)

if otp_docs? do
assert output =~
"This module provides an interface to a number of mathematical functions."
else
assert output =~ ":math was not compiled with docs"
end
end)
end

test "help FUNCTION/0", context do
in_tmp(context.test, fn ->
output =
capture_io(fn ->
Mix.Tasks.Help.run(["DateTime.utc_now()"])
end)

assert output =~ "Returns the current datetime in UTC"
end)
end

test "help FUNCTION/1", context do
in_tmp(context.test, fn ->
output =
capture_io(fn ->
Mix.Tasks.Help.run(["Enum.all?/1"])
end)

assert output =~ "Returns `true` if all elements in `enumerable` are truthy."
end)
end

test "help NESTED MODULE FUNCTION/3", context do
in_tmp(context.test, fn ->
output =
capture_io(fn ->
Mix.Tasks.Help.run(["IO.ANSI.color/3"])
end)

assert output =~ "Sets the foreground color from individual RGB values"
end)
end

test "help --search PATTERN", context do
in_tmp(context.test, fn ->
Mix.Tasks.Help.run(["--search", "deps"])
Expand Down
Loading