Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01a1acd27b | ||
|
|
127691d4ad | ||
|
|
e848e87f52 | ||
|
|
bcadd96e31 | ||
|
|
99a0bfa074 | ||
|
|
5d00232633 | ||
|
|
c319bc450e | ||
|
|
a9ed654ae2 | ||
|
|
6dacf53268 | ||
|
|
5e22b12272 | ||
|
|
d4501893c7 | ||
|
|
744859ee42 | ||
|
|
4596baab3f | ||
|
|
c065041263 | ||
|
|
95bff23ede | ||
|
|
0bc04195f2 | ||
|
|
d195b75aa3 | ||
|
|
dc06a18287 | ||
|
|
54c0574be2 | ||
|
|
8d50e1fa95 | ||
|
|
e70d9723eb | ||
|
|
3ce029615d |
@@ -268,6 +268,37 @@ end
|
||||
|
||||
By restricting hierarchies in favor of named setups, it is straight-forward for the developer to glance at each describe block and know exactly the setup steps involved.
|
||||
|
||||
## v1.3.1 (2016-06-28)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] `Add import_file_if_available` for importing files only if they are available
|
||||
* [IEx.Helpers] `Add import_if_available` for importing modules only if they are available
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Ensure structs can be expanded in dynamic module names
|
||||
* [Kernel] Ensure aliases warnings are not accidentally discarded when the same module is imported
|
||||
* [Kernel.ParallelCompiler] Ensure two modules with cyclic struct dependencies cannot run into a deadlock when compiling
|
||||
* [Kernel.Typespec] Support module attributes in remote types
|
||||
* [Module] Do not expect stacktraces to be always present when dispatching to locals during the module compilation
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Fix `h` helper for operators
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Do not load modules for xref purposes, instead use BEAM info
|
||||
* [Mix] Ensure `deps.check` does not check archives (that's done in loadpaths)
|
||||
* [Mix] Validate application properties before traversing them
|
||||
* [Mix] Check for proper Makefile when compiling on Windows
|
||||
* [Mix] Enforce space after comma in `mix do`
|
||||
|
||||
## v1.3.0 (2016-06-21)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -997,7 +997,8 @@ defmodule Enum do
|
||||
|
||||
# TODO: Remove on 2.0
|
||||
def group_by(enumerable, dict, fun) when is_function(fun, 1) do
|
||||
IO.warn "Enum.group_by/3 with a map/dictionary as second element is deprecated, please use a map instead"
|
||||
IO.warn "Enum.group_by/3 with a map/dictionary as second element is deprecated. " <>
|
||||
"A map is used by default and it is no longer required to pass one to this function"
|
||||
reduce(reverse(enumerable), dict, fn(entry, categories) ->
|
||||
Dict.update(categories, fun.(entry), [entry], &[entry | &1])
|
||||
end)
|
||||
|
||||
@@ -746,7 +746,7 @@ defmodule GenServer do
|
||||
@spec reply(from, term) :: :ok
|
||||
def reply(client, reply)
|
||||
|
||||
def reply({to, tag}, reply) do
|
||||
def reply({to, tag}, reply) when is_pid(to) do
|
||||
try do
|
||||
send(to, {tag, reply})
|
||||
:ok
|
||||
|
||||
@@ -3473,7 +3473,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
unquote(builder)
|
||||
|
||||
Kernel.Utils.announce_struct(__MODULE__)
|
||||
fields
|
||||
end
|
||||
end
|
||||
|
||||
@@ -148,17 +148,15 @@ defmodule Kernel.LexicalTracker do
|
||||
|
||||
def handle_cast({:add_import, module, fas, line, warn}, state) when is_atom(module) do
|
||||
directives =
|
||||
for {{:import, {import_module, _, _}}, _} = directive <- state.directives,
|
||||
module != import_module,
|
||||
do: directive,
|
||||
into: %{}
|
||||
|
||||
directives = add_directive(directives, module, line, warn, :import)
|
||||
state.directives
|
||||
|> Enum.reject(&match?({{:import, {^module, _, _}}, _}, &1))
|
||||
|> :maps.from_list
|
||||
|> add_directive(module, line, warn, :import)
|
||||
|
||||
directives =
|
||||
Enum.reduce fas, directives, fn {function, arity}, directives ->
|
||||
Enum.reduce(fas, directives, fn {function, arity}, directives ->
|
||||
add_directive(directives, {module, function, arity}, line, warn, :import)
|
||||
end
|
||||
end)
|
||||
|
||||
{:noreply, %{state | directives: directives}}
|
||||
end
|
||||
|
||||
@@ -864,6 +864,19 @@ defmodule Kernel.Typespec do
|
||||
{:op, line(meta), op, {:integer, line(meta), integer}}
|
||||
end
|
||||
|
||||
# Handle remote calls in the form of @module_attribute.type.
|
||||
# These are not handled by the general remote type clause as calling
|
||||
# Macro.expand/2 on the remote does not expand module attributes (but expands
|
||||
# things like __MODULE__).
|
||||
defp typespec({{:., meta, [{:@, _, [{attr, _, _}]}, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Module.get_attribute(caller.module, attr)
|
||||
unless is_atom(remote) and remote != nil do
|
||||
message = "invalid remote in typespec: #{Macro.to_string(orig)} (@#{attr} is #{inspect remote})"
|
||||
compile_error(caller, message)
|
||||
end
|
||||
remote_type({typespec(remote, vars, caller), meta, typespec(name, vars, caller), args}, vars, caller)
|
||||
end
|
||||
|
||||
# Handle remote calls
|
||||
defp typespec({{:., meta, [remote, name]}, _, args} = orig, vars, caller) do
|
||||
remote = Macro.expand remote, caller
|
||||
|
||||
@@ -84,4 +84,11 @@ defmodule Kernel.Utils do
|
||||
List.wrap(Module.get_attribute(module, :enforce_keys)),
|
||||
Module.get_attribute(module, :derive)}
|
||||
end
|
||||
|
||||
def announce_struct(module) do
|
||||
case :erlang.get(:elixir_compiler_pid) do
|
||||
:undefined -> :ok
|
||||
pid -> send(pid, {:struct_available, module})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -369,7 +369,7 @@ defmodule Module do
|
||||
* `@compile :debug_info` - includes `:debug_info` regardless of the
|
||||
setting in `Code.compiler_options`
|
||||
|
||||
* `@compile {:debug_info, false} - disables `:debug_info` regardless
|
||||
* `@compile {:debug_info, false}` - disables `:debug_info` regardless
|
||||
of the setting in `Code.compiler_options`
|
||||
|
||||
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
|
||||
|
||||
@@ -105,8 +105,6 @@ store_definition(Meta, Line, Kind, CheckClauses, Name, Args, Guards, Body, KeepL
|
||||
store_each(CheckClauses, Kind, File, Location, Module, DefaultsLength, Function),
|
||||
[store_each(false, Kind, File, Location, Module, 0,
|
||||
default_function_for(Kind, Name, Default)) || Default <- Defaults],
|
||||
|
||||
make_struct_available(Kind, Module, Name, Args),
|
||||
{Name, Arity}.
|
||||
|
||||
%% @on_definition
|
||||
@@ -117,16 +115,6 @@ run_on_definition_callbacks(Kind, Line, Module, Name, Args, Guards, Expr, E) ->
|
||||
_ = [Mod:Fun(Env, Kind, Name, Args, Guards, Expr) || {Mod, Fun} <- Callbacks],
|
||||
ok.
|
||||
|
||||
make_struct_available(def, Module, '__struct__', []) ->
|
||||
case erlang:get(elixir_compiler_pid) of
|
||||
undefined -> ok;
|
||||
Pid ->
|
||||
Pid ! {struct_available, Module},
|
||||
ok
|
||||
end;
|
||||
make_struct_available(_, _, _, _) ->
|
||||
ok.
|
||||
|
||||
%% Retrieve location from meta file (if Key == keep)
|
||||
%% or @file, otherwise nil
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ local_for(Module, Name, Arity, Given) ->
|
||||
{Kind, Ann, [_ | _] = Clauses} when Given == nil; Kind == Given ->
|
||||
get_function(Ann, Module, Clauses);
|
||||
_ ->
|
||||
[_ | T] = erlang:get_stacktrace(),
|
||||
{current_stacktrace, [_ | T]} = erlang:process_info(self(), current_stacktrace),
|
||||
erlang:raise(error, undef, [{Module, Name, Arity, []} | T])
|
||||
end.
|
||||
|
||||
|
||||
@@ -31,7 +31,9 @@ expand_struct(Meta, Left, Right, #{context := Context} = E) ->
|
||||
end,
|
||||
|
||||
EMeta =
|
||||
case lists:member(ELeft, ?m(E, context_modules)) of
|
||||
%% We also include the current module because it won't be present
|
||||
%% in context module in case the module name is defined dynamically.
|
||||
case lists:member(ELeft, [?m(E, module) | ?m(E, context_modules)]) of
|
||||
true -> [{struct, context} | Meta];
|
||||
false -> Meta
|
||||
end,
|
||||
|
||||
@@ -45,7 +45,7 @@ expand(Meta, Args, E) ->
|
||||
|
||||
{ECases, EC} = lists:mapfoldl(fun expand/2, E, Cases),
|
||||
{EDoExpr, _} = elixir_exp:expand(DoExpr, EC),
|
||||
{EElseExpr, _} = expand_else(ElseExpr, E),
|
||||
{EElseExpr, _} = expand_else(Meta, ElseExpr, E),
|
||||
{{with, Meta, ECases ++ [[{do, EDoExpr} | EElseExpr]]}, E}.
|
||||
|
||||
expand({'<-', Meta, [Left, Right]}, E) ->
|
||||
@@ -55,11 +55,14 @@ expand({'<-', Meta, [Left, Right]}, E) ->
|
||||
expand(X, E) ->
|
||||
elixir_exp:expand(X, E).
|
||||
|
||||
expand_else(KV, E) when is_list(KV) ->
|
||||
expand_else(_Meta, KV, E) when is_list(KV) ->
|
||||
{[{do, EClauses}], EC} = elixir_exp_clauses:'case'([], [{do, KV}], E),
|
||||
{[{else, EClauses}], EC};
|
||||
expand_else(nil, E) ->
|
||||
{[], E}.
|
||||
expand_else(_Meta, nil, E) ->
|
||||
{[], E};
|
||||
expand_else(Meta, _KV, E) ->
|
||||
Message = "expected -> clauses for else in with",
|
||||
elixir_errors:compile_error(Meta, ?m(E, file), Message, []).
|
||||
|
||||
%% Translation
|
||||
|
||||
|
||||
@@ -3,30 +3,33 @@ Code.require_file "test_helper.exs", __DIR__
|
||||
defmodule BehaviourTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defmodule Sample do
|
||||
use Behaviour
|
||||
{_, _, sample_binary, _} =
|
||||
defmodule Sample do
|
||||
use Behaviour
|
||||
|
||||
defcallback first(integer) :: integer
|
||||
defcallback first(integer) :: integer
|
||||
|
||||
defcallback foo(atom(), binary) :: binary
|
||||
defcallback foo(atom(), binary) :: binary
|
||||
|
||||
defcallback bar(External.hello, my_var :: binary) :: binary
|
||||
defcallback bar(External.hello, my_var :: binary) :: binary
|
||||
|
||||
defcallback guarded(my_var) :: my_var when my_var: binary
|
||||
defcallback guarded(my_var) :: my_var when my_var: binary
|
||||
|
||||
defcallback orr(atom | integer) :: atom
|
||||
defcallback orr(atom | integer) :: atom
|
||||
|
||||
defcallback literal(123, {atom}, :atom, [integer], true) :: atom
|
||||
defcallback literal(123, {atom}, :atom, [integer], true) :: atom
|
||||
|
||||
defmacrocallback last(integer) :: Macro.t
|
||||
end
|
||||
defmacrocallback last(integer) :: Macro.t
|
||||
end
|
||||
|
||||
@sample_binary sample_binary
|
||||
|
||||
test "callbacks" do
|
||||
assert Sample.__behaviour__(:callbacks) == [first: 1, guarded: 1, "MACRO-last": 2, literal: 5, orr: 1, foo: 2, bar: 2]
|
||||
end
|
||||
|
||||
test "specs" do
|
||||
assert length(Keyword.get_values(Sample.module_info[:attributes], :callback)) == 7
|
||||
assert length(Kernel.Typespec.beam_callbacks(@sample_binary)) == 7
|
||||
end
|
||||
|
||||
test "default is not supported" do
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
defmodule Bar do
|
||||
defstruct name: ""
|
||||
def foo?(%Foo{}), do: true
|
||||
defstruct name: "", foo: %Foo{}
|
||||
end
|
||||
|
||||
@@ -48,6 +48,7 @@ defmodule Kernel.DialyzerTest do
|
||||
{:ok, [outdir: dir, dialyzer: dialyzer]}
|
||||
end
|
||||
|
||||
@tag otp19: false
|
||||
test "no warnings on valid remote calls", context do
|
||||
copy_beam! context, Dialyzer.RemoteCall
|
||||
assert_dialyze_no_warnings! context
|
||||
@@ -58,6 +59,7 @@ defmodule Kernel.DialyzerTest do
|
||||
assert_dialyze_no_warnings! context
|
||||
end
|
||||
|
||||
@tag otp19: false
|
||||
test "no warnings on raise", context do
|
||||
copy_beam! context, Dialyzer.Raise
|
||||
assert_dialyze_no_warnings! context
|
||||
|
||||
@@ -283,9 +283,10 @@ defmodule Kernel.RaiseTest do
|
||||
end
|
||||
|
||||
test "badfun error" do
|
||||
x = :example
|
||||
# Avoid "invalid function call" warning in >= OTP 19
|
||||
x = fn -> :example end
|
||||
result = try do
|
||||
x.(2)
|
||||
x.().(2)
|
||||
rescue
|
||||
x in [BadFunctionError] -> Exception.message(x)
|
||||
end
|
||||
|
||||
@@ -455,6 +455,27 @@ defmodule Kernel.TypespecTest do
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "@type with module attributes" do
|
||||
module = test_module do
|
||||
@keyword Keyword
|
||||
@type kw :: @keyword.t
|
||||
@type kw(value) :: @keyword.t(value)
|
||||
end
|
||||
|
||||
assert [type: {:kw, {:remote_type, _, [{:atom, _, Keyword}, {:atom, _, :t}, []]}, _},
|
||||
type: {:kw, {:remote_type, _, [{:atom, _, Keyword}, {:atom, _, :t}, [{:var, _, :value}]]}, [{:var, _, :value}]}] =
|
||||
types(module)
|
||||
end
|
||||
|
||||
test "invalid remote @type with module attribute that does not evaluate to a module" do
|
||||
assert_raise CompileError, ~r/\(@foo is "bar"\)/, fn ->
|
||||
test_module do
|
||||
@foo "bar"
|
||||
@type t :: @foo.t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "defines_type?" do
|
||||
test_module do
|
||||
@type mytype :: tuple
|
||||
|
||||
@@ -310,6 +310,20 @@ defmodule Kernel.WarningTest do
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test "unused alias when also import" do
|
||||
assert capture_err(fn ->
|
||||
Code.compile_string """
|
||||
defmodule Sample do
|
||||
alias :lists, as: List
|
||||
import MapSet
|
||||
new()
|
||||
end
|
||||
"""
|
||||
end) =~ "unused alias List"
|
||||
after
|
||||
purge Sample
|
||||
end
|
||||
|
||||
test "unused inside dynamic module" do
|
||||
import List, only: [flatten: 1], warn: false
|
||||
|
||||
@@ -421,8 +435,8 @@ defmodule Kernel.WarningTest do
|
||||
assert capture_err(fn ->
|
||||
Code.eval_string ~S"""
|
||||
defmodule Sample do
|
||||
def hello(arg \\ 0), do: nil
|
||||
def hello(arg), do: nil
|
||||
def hello(arg \\ 0), do: nil
|
||||
def hello(arg), do: nil
|
||||
end
|
||||
"""
|
||||
end) =~ "definitions with multiple clauses and default values require a function head"
|
||||
@@ -551,7 +565,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "undefined behaviour function foo/0 (for behaviour Sample1)"
|
||||
after
|
||||
purge [Sample1, Sample2, Sample3]
|
||||
purge [Sample1, Sample2]
|
||||
end
|
||||
|
||||
test "undefined macro for behaviour" do
|
||||
@@ -567,7 +581,7 @@ defmodule Kernel.WarningTest do
|
||||
"""
|
||||
end) =~ "undefined behaviour macro foo/0 (for behaviour Sample1)"
|
||||
after
|
||||
purge [Sample1, Sample2, Sample3]
|
||||
purge [Sample1, Sample2]
|
||||
end
|
||||
|
||||
test "undefined behavior" do
|
||||
|
||||
@@ -75,6 +75,12 @@ defmodule Kernel.WithTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "invalid else form" do
|
||||
assert_raise CompileError, "nofile:1: expected -> clauses for else in with", fn ->
|
||||
Code.eval_quoted(quote do: with(_ <- true, do: :ok, else: :error))
|
||||
end
|
||||
end
|
||||
|
||||
defp four() do
|
||||
4
|
||||
end
|
||||
|
||||
@@ -147,6 +147,16 @@ defmodule MapTest do
|
||||
%^struct{name: "john"} = user
|
||||
end
|
||||
|
||||
test "structs when using dynamic modules" do
|
||||
defmodule Module.concat(MapTest, DynamicUser) do
|
||||
defstruct [:name, :age]
|
||||
|
||||
def sample do
|
||||
%__MODULE__{}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "structs when quoted" do
|
||||
assert (quote do
|
||||
%User{foo: 1}
|
||||
|
||||
@@ -5,18 +5,24 @@ defmodule ProtocolTest do
|
||||
|
||||
doctest Protocol
|
||||
|
||||
defprotocol Sample do
|
||||
@type t :: any
|
||||
@doc "Ok"
|
||||
@spec ok(t) :: boolean
|
||||
def ok(term)
|
||||
end
|
||||
{_, _, sample_binary, _} =
|
||||
defprotocol Sample do
|
||||
@type t :: any
|
||||
@doc "Ok"
|
||||
@spec ok(t) :: boolean
|
||||
def ok(term)
|
||||
end
|
||||
|
||||
defprotocol WithAny do
|
||||
@fallback_to_any true
|
||||
@doc "Ok"
|
||||
def ok(term)
|
||||
end
|
||||
@sample_binary sample_binary
|
||||
|
||||
{_, _, with_any_binary, _} =
|
||||
defprotocol WithAny do
|
||||
@fallback_to_any true
|
||||
@doc "Ok"
|
||||
def ok(term)
|
||||
end
|
||||
|
||||
@with_any_binary with_any_binary
|
||||
|
||||
defprotocol Derivable do
|
||||
def ok(a)
|
||||
@@ -123,11 +129,11 @@ defmodule ProtocolTest do
|
||||
end
|
||||
|
||||
test "protocol defines callbacks" do
|
||||
assert get_callbacks(Sample, :ok, 1) ==
|
||||
[{:type, [11], :fun, [{:type, [11], :product, [{:user_type, [11], :t, []}]}, {:type, [11], :boolean, []}]}]
|
||||
assert get_callbacks(@sample_binary, :ok, 1) ==
|
||||
[{:type, 12, :fun, [{:type, 12, :product, [{:user_type, 12, :t, []}]}, {:type, 12, :boolean, []}]}]
|
||||
|
||||
assert get_callbacks(WithAny, :ok, 1) ==
|
||||
[{:type, [18], :fun, [{:type, [18], :product, [{:user_type, [18], :t, []}]}, {:type, [18], :term, []}]}]
|
||||
assert get_callbacks(@with_any_binary, :ok, 1) ==
|
||||
[{:type, 22, :fun, [{:type, 22, :product, [{:user_type, 22, :t, []}]}, {:type, 22, :term, []}]}]
|
||||
end
|
||||
|
||||
test "protocol defines functions and attributes" do
|
||||
@@ -182,8 +188,8 @@ defmodule ProtocolTest do
|
||||
assert Multi.test(:a) == :a
|
||||
end
|
||||
|
||||
defp get_callbacks(module, name, arity) do
|
||||
callbacks = for {:callback, info} <- module.__info__(:attributes), do: hd(info)
|
||||
defp get_callbacks(beam, name, arity) do
|
||||
callbacks = Kernel.Typespec.beam_callbacks(beam)
|
||||
List.keyfind(callbacks, {name, arity}, 0) |> elem(1)
|
||||
end
|
||||
|
||||
@@ -308,6 +314,8 @@ defmodule Protocol.ConsolidationTest do
|
||||
{:ok, binary} = Protocol.consolidate(Sample, [Any, ImplStruct])
|
||||
:code.load_binary(Sample, 'protocol_test.exs', binary)
|
||||
|
||||
@sample_binary binary
|
||||
|
||||
# Any should be moved to the end
|
||||
:code.purge(WithAny)
|
||||
:code.delete(WithAny)
|
||||
@@ -367,7 +375,7 @@ defmodule Protocol.ConsolidationTest do
|
||||
end
|
||||
|
||||
test "consolidated keeps callbacks" do
|
||||
callbacks = for {:callback, info} <- Sample.__info__(:attributes), do: hd(info)
|
||||
callbacks = Kernel.Typespec.beam_callbacks(@sample_binary)
|
||||
assert callbacks != []
|
||||
end
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
ExUnit.start [trace: "--trace" in System.argv]
|
||||
exclude =
|
||||
case :erlang.system_info(:otp_release) do
|
||||
'19' -> [otp19: false]
|
||||
_ -> []
|
||||
end
|
||||
|
||||
ExUnit.start [exclude: exclude, trace: "--trace" in System.argv]
|
||||
|
||||
# Beam files compiled on demand
|
||||
path = Path.expand("../../tmp/beams", __DIR__)
|
||||
|
||||
@@ -533,6 +533,15 @@ defmodule IEx.Helpers do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Similar to `import_file` but only imports it if it is available.
|
||||
"""
|
||||
defmacro import_file_if_available(path) do
|
||||
quote do
|
||||
import_file(unquote(path), optional: true)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Evaluates the contents of the file at `path` as if it were directly typed into
|
||||
the shell.
|
||||
@@ -585,6 +594,25 @@ defmodule IEx.Helpers do
|
||||
raise ArgumentError, "import_file/1 expects a literal binary as its argument"
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calls `import/2` with the given arguments, but only if the module is available.
|
||||
|
||||
This lets you put imports in `.iex.exs` files (including `~/.iex.exs`) without
|
||||
getting compile errors if you open a console where the module is not available.
|
||||
|
||||
## Example
|
||||
|
||||
# In ~/.iex.exs
|
||||
import_if_available Ecto.Query
|
||||
"""
|
||||
defmacro import_if_available(quoted_module, opts \\ []) do
|
||||
module = Macro.expand(quoted_module, __CALLER__)
|
||||
|
||||
if Code.ensure_loaded?(module) do
|
||||
quote do: import unquote(quoted_module), unquote(opts)
|
||||
end
|
||||
end
|
||||
|
||||
# Compiles and loads an Erlang source file, returns {module, binary}
|
||||
defp compile_erlang(source) do
|
||||
source = Path.relative_to_cwd(source) |> String.to_charlist
|
||||
|
||||
@@ -108,7 +108,7 @@ defmodule IEx.Introspection do
|
||||
if docs = Code.get_docs(mod, :docs) do
|
||||
if doc = find_doc(docs, fun, arity) do
|
||||
if callback_module = is_nil(elem(doc, 4)) and callback_module(mod, fun, arity) do
|
||||
filter = &match?({^fun, _}, elem(&1, 0))
|
||||
filter = &match?({^fun, ^arity}, elem(&1, 0))
|
||||
print_callback_docs(callback_module, filter, &print_doc/2)
|
||||
else
|
||||
print_doc(doc)
|
||||
@@ -139,6 +139,8 @@ defmodule IEx.Introspection do
|
||||
end)
|
||||
end
|
||||
|
||||
defp has_content?(nil),
|
||||
do: false
|
||||
defp has_content?({_, _, _, _, false}),
|
||||
do: false
|
||||
defp has_content?({{name, _}, _, _, _, nil}),
|
||||
@@ -147,14 +149,11 @@ defmodule IEx.Introspection do
|
||||
do: true
|
||||
|
||||
defp callback_module(mod, fun, arity) do
|
||||
filter = &match?({{^fun, ^arity}, _}, &1)
|
||||
mod.module_info(:attributes)
|
||||
|> Keyword.get_values(:behaviour)
|
||||
|> Stream.concat()
|
||||
|> Enum.find(fn module ->
|
||||
module.module_info(:attributes)
|
||||
|> Enum.filter(&match?({:callback, _}, &1))
|
||||
|> Enum.any?(&match?({_, [{{^fun, ^arity}, _} | _]}, &1))
|
||||
end)
|
||||
|> Enum.find(&Enum.any?(Typespec.beam_callbacks(&1), filter))
|
||||
end
|
||||
|
||||
defp print_doc({{fun, _}, _line, kind, args, doc}) do
|
||||
|
||||
@@ -34,9 +34,11 @@ defmodule IEx.HelpersTest do
|
||||
test "h helper function" do
|
||||
pwd_h = "* def pwd()\n\nPrints the current working directory.\n\n"
|
||||
c_h = "* def c(files, path \\\\ \".\")\n\nCompiles the given files."
|
||||
eq_h = "* def ==(left, right)\n\nReturns `true` if the two items are equal.\n\n"
|
||||
|
||||
assert capture_io(fn -> h IEx.Helpers.pwd/0 end) =~ pwd_h
|
||||
assert capture_io(fn -> h IEx.Helpers.c/2 end) =~ c_h
|
||||
assert capture_io(fn -> h ==/2 end) =~ eq_h
|
||||
|
||||
assert capture_io(fn -> h IEx.Helpers.c/1 end) =~ c_h
|
||||
assert capture_io(fn -> h pwd end) =~ pwd_h
|
||||
@@ -77,14 +79,17 @@ defmodule IEx.HelpersTest do
|
||||
@doc "Docs for MyBehaviour.first"
|
||||
@callback first(integer) :: integer
|
||||
@callback second(integer) :: integer
|
||||
@callback second(integer, integer) :: integer
|
||||
end
|
||||
"""
|
||||
impl = """
|
||||
defmodule Impl do
|
||||
@behaviour MyBehaviour
|
||||
def first(0), do: 0
|
||||
@doc "Docs for Impl.second"
|
||||
@doc "Docs for Impl.second/1"
|
||||
def second(0), do: 0
|
||||
@doc "Docs for Impl.second/2"
|
||||
def second(0, 0), do: 0
|
||||
end
|
||||
"""
|
||||
files = ["my_behaviour.ex", "impl.ex"]
|
||||
@@ -92,10 +97,11 @@ defmodule IEx.HelpersTest do
|
||||
assert c(files) |> Enum.sort == [Impl, MyBehaviour]
|
||||
|
||||
assert capture_io(fn -> h Impl.first/1 end) == "* @callback first(integer()) :: integer()\n\nDocs for MyBehaviour.first\n"
|
||||
assert capture_io(fn -> h Impl.second/1 end) == "* def second(int)\n\nDocs for Impl.second\n"
|
||||
assert capture_io(fn -> h Impl.second/1 end) == "* def second(int)\n\nDocs for Impl.second/1\n"
|
||||
assert capture_io(fn -> h Impl.second/2 end) == "* def second(int1, int2)\n\nDocs for Impl.second/2\n"
|
||||
|
||||
assert capture_io(fn -> h Impl.first end) == "* @callback first(integer()) :: integer()\n\nDocs for MyBehaviour.first\n"
|
||||
assert capture_io(fn -> h Impl.second end) == "* def second(int)\n\nDocs for Impl.second\n"
|
||||
assert capture_io(fn -> h Impl.second end) == "* def second(int)\n\nDocs for Impl.second/1\n* def second(int1, int2)\n\nDocs for Impl.second/2\n"
|
||||
end
|
||||
after
|
||||
cleanup_modules([Impl, MyBehaviour])
|
||||
@@ -255,11 +261,17 @@ defmodule IEx.HelpersTest do
|
||||
end
|
||||
|
||||
test "import_file when the file is missing" do
|
||||
assert "nil" == capture_iex("import_file \"nonexistent\", optional: true")
|
||||
|
||||
failing = capture_iex("import_file \"nonexistent\"")
|
||||
assert "** (File.Error) could not read file" <> _ = failing
|
||||
assert failing =~ "no such file or directory"
|
||||
|
||||
assert "nil" == capture_iex("import_file_if_available \"nonexistent\"")
|
||||
end
|
||||
|
||||
test "import_if_available helper" do
|
||||
assert "nil" == capture_iex("import_if_available NoSuchModule")
|
||||
assert "[1, 2, 3]" == capture_iex("import_if_available Integer; digits 123")
|
||||
assert "[1, 2, 3]" == capture_iex("import_if_available Integer, only: [digits: 1]; digits 123")
|
||||
end
|
||||
|
||||
test "c helper" do
|
||||
|
||||
@@ -70,4 +70,3 @@ defmodule IEx.Case do
|
||||
|> String.trim
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -47,7 +47,8 @@ defmodule Mix.Tasks.Clean do
|
||||
|
||||
# Loadpaths without checks because compilers may be defined in deps.
|
||||
defp loadpaths! do
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check"]
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
Mix.Task.reenable "loadpaths"
|
||||
Mix.Task.reenable "deps.check"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -90,14 +90,14 @@ defmodule Mix.Tasks.Compile.App do
|
||||
best_guess
|
||||
end
|
||||
|
||||
properties = ensure_correct_properties(app, config, properties)
|
||||
|
||||
# Ensure we always prepend the standard application dependencies
|
||||
core_apps = [:kernel, :stdlib] ++ language_app(config)
|
||||
properties = Keyword.update!(properties, :applications, fn apps ->
|
||||
core_apps ++ apps
|
||||
[:kernel, :stdlib] ++ language_app(config) ++ apps
|
||||
end)
|
||||
|
||||
properties = ensure_correct_properties(app, config, properties)
|
||||
contents = {:application, app, properties}
|
||||
contents = {:application, app, properties}
|
||||
|
||||
Mix.Project.ensure_structure()
|
||||
File.write!(target, :io_lib.format("~p.", [contents]), [:utf8])
|
||||
|
||||
@@ -93,8 +93,9 @@ defmodule Mix.Tasks.Compile do
|
||||
|
||||
# Loadpaths without checks because compilers may be defined in deps.
|
||||
defp loadpaths! do
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check"]
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
Mix.Task.reenable "loadpaths"
|
||||
Mix.Task.reenable "deps.check"
|
||||
end
|
||||
|
||||
defp consolidate_protocols? do
|
||||
|
||||
@@ -5,28 +5,20 @@ defmodule Mix.Tasks.Deps.Check do
|
||||
format_status: 1, check_lock: 1]
|
||||
|
||||
@moduledoc """
|
||||
Checks if all dependencies are valid, loading them along
|
||||
the way.
|
||||
Checks if all dependencies are valid,
|
||||
loading them along the way.
|
||||
|
||||
If there is an invalid dependency, its status is printed
|
||||
before aborting.
|
||||
|
||||
This task is not shown in `mix help` but it is part
|
||||
of the `mix` public API and can be depended on.
|
||||
|
||||
## Command line options
|
||||
|
||||
* `--no-archives-check` - do not check archives
|
||||
* `--no-deps-check` - do not check deps
|
||||
* `--no-deps-check` - do not check or compile deps, only load available ones
|
||||
* `--no-compile` - do not compile dependencies
|
||||
|
||||
"""
|
||||
@spec run(OptionParser.argv) :: :ok
|
||||
def run(args) do
|
||||
unless "--no-archives-check" in args do
|
||||
Mix.Task.run "archive.check", args
|
||||
end
|
||||
|
||||
all = Enum.map(Mix.Dep.cached(), &check_lock/1)
|
||||
|
||||
unless "--no-deps-check" in args do
|
||||
|
||||
@@ -133,7 +133,7 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
end
|
||||
|
||||
try do
|
||||
res = Mix.Task.run("compile", ["--no-deps", "--no-elixir-version-check"])
|
||||
res = Mix.Task.run("compile", ["--no-deps", "--no-archives-check", "--no-elixir-version-check"])
|
||||
:ok in List.wrap(res)
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -184,12 +184,13 @@ defmodule Mix.Tasks.Deps.Compile do
|
||||
Mix.raise "\"#{manager}\" installation failed"
|
||||
end
|
||||
|
||||
defp do_make(dep, config) do
|
||||
command = if match?({:win32, _}, :os.type) and File.regular?("Makefile.win") do
|
||||
"nmake /F Makefile.win"
|
||||
else
|
||||
"make"
|
||||
end
|
||||
defp do_make(%{opts: opts} = dep, config) do
|
||||
command =
|
||||
if match?({:win32, _}, :os.type) and File.regular?(Path.join(opts[:dest], "Makefile.win")) do
|
||||
"nmake /F Makefile.win"
|
||||
else
|
||||
"make"
|
||||
end
|
||||
do_command(dep, config, command, true)
|
||||
end
|
||||
|
||||
|
||||
+13
-17
@@ -6,6 +6,8 @@ defmodule Mix.Tasks.Do do
|
||||
@moduledoc """
|
||||
Executes the tasks separated by comma.
|
||||
|
||||
The comma should be followed by a space.
|
||||
|
||||
## Examples
|
||||
|
||||
The example below prints the available compilers and
|
||||
@@ -24,27 +26,21 @@ defmodule Mix.Tasks.Do do
|
||||
|
||||
@doc false
|
||||
def gather_commands(args) do
|
||||
gather_commands(args, [], [])
|
||||
gather_commands args, [], []
|
||||
end
|
||||
|
||||
defp gather_commands([], current, commands) do
|
||||
[current | commands]
|
||||
|> Enum.reject(&(&1 == []))
|
||||
|> Enum.map(&Enum.reverse(&1))
|
||||
|> Enum.reverse
|
||||
def gather_commands([head | rest], current, acc)
|
||||
when binary_part(head, byte_size(head), -1) == "," do
|
||||
part = binary_part(head, 0, byte_size(head) - 1)
|
||||
current = Enum.reverse([part | current])
|
||||
gather_commands rest, [], [current | acc]
|
||||
end
|
||||
|
||||
defp gather_commands([arg | rest], current, commands) do
|
||||
case String.split(arg, ",", parts: 2) do
|
||||
[arg] ->
|
||||
gather_commands(rest, [arg | current], commands)
|
||||
[left, right] ->
|
||||
rest = append_unless_empty(right, rest)
|
||||
current = append_unless_empty(left, current)
|
||||
gather_commands(rest, [], [current | commands])
|
||||
end
|
||||
def gather_commands([head | rest], current, acc) do
|
||||
gather_commands rest, [head | current], acc
|
||||
end
|
||||
|
||||
defp append_unless_empty("", list), do: list
|
||||
defp append_unless_empty(h, list), do: [h | list]
|
||||
def gather_commands([], current, acc) do
|
||||
Enum.reverse [Enum.reverse(current) | acc]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -105,8 +105,9 @@ defmodule Mix.Tasks.Help do
|
||||
|
||||
# Loadpaths without checks because tasks may be defined in deps.
|
||||
defp loadpaths! do
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check"]
|
||||
Mix.Task.run "loadpaths", ["--no-elixir-version-check", "--no-deps-check", "--no-archives-check"]
|
||||
Mix.Task.reenable "loadpaths"
|
||||
Mix.Task.reenable "deps.check"
|
||||
end
|
||||
|
||||
defp load_tasks() do
|
||||
|
||||
@@ -12,12 +12,13 @@ defmodule Mix.Tasks.Loadpaths do
|
||||
## Command line options
|
||||
|
||||
* `--no-archives-check` - do not check archive
|
||||
* `--no-deps-check` - do not check dependencies (also implies --no-archives-check)
|
||||
* `--no-deps-check` - do not check dependencies
|
||||
* `--no-elixir-version-check` - do not check Elixir version
|
||||
|
||||
"""
|
||||
|
||||
@spec run(OptionParser.argv) :: :ok
|
||||
|
||||
def run(args) do
|
||||
config = Mix.Project.config
|
||||
|
||||
@@ -25,7 +26,11 @@ defmodule Mix.Tasks.Loadpaths do
|
||||
check_elixir_version(config, args)
|
||||
end
|
||||
|
||||
# --no-deps is used only internally. It has not purpose
|
||||
unless "--no-archives-check" in args do
|
||||
Mix.Task.run "archive.check", args
|
||||
end
|
||||
|
||||
# --no-deps is used only internally. It has no purpose
|
||||
# from Mix.CLI because the CLI itself already loads deps.
|
||||
unless "--no-deps" in args do
|
||||
Mix.Task.run "deps.check", args
|
||||
|
||||
@@ -160,7 +160,6 @@ defmodule Mix.Tasks.Xref do
|
||||
|
||||
defp unreachable(pair_fun) do
|
||||
excludes = excludes()
|
||||
|
||||
each_source_entries(&source_warnings(&1, excludes), pair_fun)
|
||||
end
|
||||
|
||||
@@ -168,20 +167,38 @@ defmodule Mix.Tasks.Xref do
|
||||
source(runtime_dispatches: runtime_dispatches) = source
|
||||
|
||||
for {module, func_arity_lines} <- runtime_dispatches,
|
||||
exports = load_exports(module),
|
||||
{{func, arity}, lines} <- func_arity_lines,
|
||||
warning = unreachable_mfa(module, func, arity, lines, excludes),
|
||||
warning = unreachable_mfa(exports, module, func, arity, lines, excludes),
|
||||
do: warning
|
||||
end
|
||||
|
||||
defp unreachable_mfa(module, func, arity, lines, excludes) do
|
||||
defp load_exports(module) do
|
||||
if :code.is_loaded(module) do
|
||||
# If the module is loaded, we will use the faster function_exported?/3 check
|
||||
module
|
||||
else
|
||||
# Otherwise we get all exports from :beam_lib to avoid loading modules
|
||||
with file when is_list(file) <- :code.which(module),
|
||||
{:ok, {^module, [exports: exports]}} <- :beam_lib.chunks(file, [:exports]) do
|
||||
exports
|
||||
else
|
||||
_ -> :unknown_module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp unreachable_mfa(exports, module, func, arity, lines, excludes) do
|
||||
cond do
|
||||
excluded?(module, func, arity, excludes) ->
|
||||
nil
|
||||
skip?(module, func, arity) ->
|
||||
nil
|
||||
not Code.ensure_loaded?(module) ->
|
||||
exports == :unknown_module ->
|
||||
{Enum.sort(lines), :unknown_module, module, func, arity}
|
||||
not function_exported?(module, func, arity) ->
|
||||
is_atom(exports) and not function_exported?(module, func, arity) ->
|
||||
{Enum.sort(lines), :unknown_function, module, func, arity}
|
||||
is_list(exports) and not {func, arity} in exports ->
|
||||
{Enum.sort(lines), :unknown_function, module, func, arity}
|
||||
true ->
|
||||
nil
|
||||
@@ -276,7 +293,7 @@ defmodule Mix.Tasks.Xref do
|
||||
defp source_calls_for_filter(source, filter) do
|
||||
runtime_dispatches = source(source, :runtime_dispatches)
|
||||
compile_dispatches = source(source, :compile_dispatches)
|
||||
dispatches = Stream.concat(runtime_dispatches, compile_dispatches)
|
||||
dispatches = runtime_dispatches ++ compile_dispatches
|
||||
|
||||
calls =
|
||||
for {module, func_arity_lines} <- dispatches,
|
||||
|
||||
@@ -11,15 +11,11 @@ defmodule Mix.Tasks.DoTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "gather_command ignore spaces and trailing commas" do
|
||||
test "gather_command returns a list of commands" do
|
||||
import Mix.Tasks.Do, only: [gather_commands: 1]
|
||||
assert gather_commands(["compile", "--list,", "help"]) == [["compile", "--list"], ["help"]]
|
||||
assert gather_commands(["compile", "--list,help"]) == [["compile", "--list"], ["help"]]
|
||||
assert gather_commands(["help", ",compile", "--list"]) == [["help"], ["compile", "--list"]]
|
||||
assert gather_commands(["compile", "--list", ",", "help"]) == [["compile", "--list"], ["help"]]
|
||||
assert gather_commands(["help,", "compile", "--list"]) == [["help"], ["compile", "--list"]]
|
||||
assert gather_commands(["compile,", "run", "-e", "IO.puts :hello"]) == [["compile"], ["run", "-e", "IO.puts :hello"]]
|
||||
assert gather_commands(
|
||||
[",", "compile,", "run", "-e", "IO.puts :hello",",foo", "--bar", "--baz", ",", "baz,qux,abc", ","]) ==
|
||||
[["compile"], ["run", "-e", "IO.puts :hello"], ["foo", "--bar", "--baz"], ["baz"], ["qux"], ["abc"]]
|
||||
assert gather_commands(["compile,", "run", "-e", "[1, 2]"]) == [["compile"], ["run", "-e", "[1, 2]"]]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -129,25 +129,46 @@ defmodule Mix.Tasks.XrefTest do
|
||||
|
||||
test "warnings: handles multiple modules in one file" do
|
||||
assert_warnings """
|
||||
defmodule A do
|
||||
defmodule A1 do
|
||||
def a, do: A2.no_func
|
||||
def b, do: A2.a
|
||||
end
|
||||
|
||||
defmodule A2 do
|
||||
def a, do: A.no_func
|
||||
def b, do: A.b
|
||||
def a, do: A1.no_func
|
||||
def b, do: A1.b
|
||||
end
|
||||
""", """
|
||||
warning: function A2.no_func/0 is undefined or private
|
||||
lib/a.ex:2
|
||||
|
||||
warning: function A.no_func/0 is undefined or private
|
||||
warning: function A1.no_func/0 is undefined or private
|
||||
lib/a.ex:7
|
||||
|
||||
"""
|
||||
end
|
||||
|
||||
test "warnings: doesn't load unloaded modules" do
|
||||
assert_warnings """
|
||||
defmodule A1 do
|
||||
@compile {:autoload, false}
|
||||
@on_load :init
|
||||
def init do
|
||||
raise "oops"
|
||||
end
|
||||
end
|
||||
|
||||
defmodule A2 do
|
||||
def a, do: A1.no_func
|
||||
def b, do: A1.init
|
||||
end
|
||||
""", """
|
||||
warning: function A1.no_func/0 is undefined or private
|
||||
lib/a.ex:10
|
||||
|
||||
"""
|
||||
end
|
||||
|
||||
test "warnings: groups multiple warnings in one file" do
|
||||
assert_warnings """
|
||||
defmodule A do
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.3.0"},
|
||||
{vsn, "1.3.1"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
|
||||
Reference in New Issue
Block a user