Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56f1037973 | ||
|
|
e5fcef3832 | ||
|
|
9081a7aad2 | ||
|
|
328f581ef5 | ||
|
|
aa695875fe | ||
|
|
aa039ce9f9 | ||
|
|
2fdf96e747 | ||
|
|
494bded12a | ||
|
|
2b0fc529ee | ||
|
|
aaa775a3bf | ||
|
|
6647b00fcf | ||
|
|
d149c4c1e0 | ||
|
|
58810bd33c | ||
|
|
050cb45664 | ||
|
|
7f59aeeb6f | ||
|
|
e2abd35b3c | ||
|
|
cc35c8fc22 | ||
|
|
2718631eb6 | ||
|
|
9d9aa8bff8 | ||
|
|
65aee00809 | ||
|
|
9f028834d7 | ||
|
|
44ef53ec2b | ||
|
|
d1290d62db | ||
|
|
301aa6eddc | ||
|
|
098b2d7cfd | ||
|
|
d92b7a331b | ||
|
|
8930ea7598 | ||
|
|
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,67 @@ 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.2 (2016-07-15)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Support guards in `else` clauses in `with`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Add `MIX_NO_DEPS` env var for disabling dep loading. Used for third-party scripts and tools like Nix package manager
|
||||
* [Mix] Add `mix test --listen-on-stdin` that automatically reruns tests on stdin
|
||||
* [Mix] Disable `--warnings-as-errors` when compiling dependencies
|
||||
* [Mix] Add `--filter` option to `mix deps.unlock` to unlock only matching dependencies
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Enum] Return `nil` if enumerable halts in `Enum.find_index/3`
|
||||
* [Kernel] Do not attempt to load modules that have not been required when invoking regular functions, otherwise this invalidates the `@compile {:autoload, false}` directive.
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Ensure missing protocol dependencies are discarded in umbrella projects with shared build
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Diff] Ensure no leading or trailing when diffing some maps
|
||||
|
||||
## 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
|
||||
|
||||
@@ -435,6 +435,13 @@ defmodule Access do
|
||||
...> prev -> {prev, String.upcase(prev)}
|
||||
...> end)
|
||||
{"john", [%{name: "JOHN"}, %{name: "mary"}]}
|
||||
|
||||
`at/1` can also be used to pop elements out of a list or
|
||||
a key inside of a list:
|
||||
|
||||
iex> list = [%{name: "john"}, %{name: "mary"}]
|
||||
iex> pop_in(list, [Access.at(0)])
|
||||
{%{name: "john"}, [%{name: "mary"}]}
|
||||
iex> pop_in(list, [Access.at(0), :name])
|
||||
{"john", [%{}, %{name: "mary"}]}
|
||||
|
||||
@@ -477,7 +484,7 @@ defmodule Access do
|
||||
defp get_and_update_at([head | rest], 0, next, updates) do
|
||||
case next.(head) do
|
||||
{get, update} -> {get, :lists.reverse([update | updates], rest)}
|
||||
:pop -> {head, :lists.reverse([head | updates], rest)}
|
||||
:pop -> {head, :lists.reverse(updates, rest)}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -861,13 +861,13 @@ defmodule Enum do
|
||||
|
||||
def find_index(enumerable, fun) do
|
||||
res =
|
||||
Enumerable.reduce(enumerable, {:cont, 0}, fn(entry, acc) ->
|
||||
if fun.(entry), do: {:halt, acc}, else: {:cont, acc + 1}
|
||||
Enumerable.reduce(enumerable, {:cont, {:not_found, 0}}, fn(entry, {status, index}) ->
|
||||
if fun.(entry), do: {:halt, {:found, index}}, else: {:cont, {:not_found, index + 1}}
|
||||
end)
|
||||
|
||||
case res do
|
||||
{:halted, entry} -> entry
|
||||
{:done, _} -> nil
|
||||
{_, {:found, index}} -> index
|
||||
{_, {:not_found, _}} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -15,7 +15,11 @@ defmodule Exception do
|
||||
"""
|
||||
|
||||
@typedoc "The exception type"
|
||||
@type t :: %{__struct__: module, __exception__: true}
|
||||
@type t :: %{
|
||||
required(:__struct__) => module,
|
||||
required(:__exception__) => true,
|
||||
atom => any
|
||||
}
|
||||
|
||||
@typedoc "The kind handled by formatting functions"
|
||||
@type kind :: :error | :exit | :throw | {:EXIT, pid}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1862,7 +1862,7 @@ defmodule Kernel do
|
||||
In case any entry returns `nil`, its key will be removed
|
||||
and the deletion will be considered a success.
|
||||
"""
|
||||
@spec pop_in(Access.t, nonempty_list(term)) :: Access.t
|
||||
@spec pop_in(Access.t, nonempty_list(term)) :: {term, Access.t}
|
||||
def pop_in(data, keys)
|
||||
def pop_in(nil, [h | _]), do: Access.pop(nil, h)
|
||||
def pop_in(data, keys), do: do_pop_in(data, keys)
|
||||
@@ -3473,7 +3473,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
unquote(builder)
|
||||
|
||||
Kernel.Utils.announce_struct(__MODULE__)
|
||||
fields
|
||||
end
|
||||
end
|
||||
@@ -4314,7 +4314,7 @@ defmodule Kernel do
|
||||
end
|
||||
|
||||
# TODO: Deprecate by v1.5
|
||||
# @doc false
|
||||
@doc false
|
||||
defmacro to_char_list(arg) do
|
||||
quote do: Kernel.to_charlist(unquote(arg))
|
||||
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
|
||||
|
||||
@@ -448,7 +448,7 @@ defmodule Kernel.Typespec do
|
||||
defp translate_spec(kind, meta, name, args, return, guard, caller) when is_atom(args),
|
||||
do: translate_spec(kind, meta, name, [], return, guard, caller)
|
||||
defp translate_spec(:macrocallback, meta, name, args, return, guard, caller),
|
||||
do: translate_spec(:callback, meta, :"MACRO-#{name}", [quote(do: env :: Macro.Env.t) | args], return, guard, caller)
|
||||
do: translate_spec(:callback, meta, :"MACRO-#{name}", macro_args(args), return, guard, caller)
|
||||
defp translate_spec(kind, meta, name, args, return, guard, caller) do
|
||||
ensure_no_defaults!(args)
|
||||
|
||||
@@ -470,6 +470,10 @@ defmodule Kernel.Typespec do
|
||||
{{kind, {name, arity}, spec}, caller.line}
|
||||
end
|
||||
|
||||
defp macro_args(args) do
|
||||
[quote(do: {line :: Macro.Env.line, env :: Macro.Env.t}) | args]
|
||||
end
|
||||
|
||||
defp ensure_no_defaults!(args) do
|
||||
:lists.foreach fn
|
||||
{:::, _, [left, right]} ->
|
||||
@@ -864,6 +868,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
|
||||
|
||||
@@ -15,13 +15,24 @@ defmodule OptionParser do
|
||||
@doc """
|
||||
Parses `argv` into a keywords list.
|
||||
|
||||
It returns a three-element tuple as follows:
|
||||
It returns a three-element tuple with the form `{parsed, args, invalid}`, where:
|
||||
|
||||
1. parsed switches,
|
||||
2. remaining arguments,
|
||||
3. invalid options.
|
||||
* `parsed` is a keyword list of parsed switches with `{switch_name, value}`
|
||||
tuples in it; `switch_name` is the atom representing the switch name while
|
||||
`value` is the value for that switch parsed according to `opts` (see the
|
||||
"Examples" section for more information)
|
||||
* `args` is a list of the remaining arguments in `argv` as strings
|
||||
* `invalid` is a list of invalid options as `{option_name, value}` where
|
||||
`option_name` is the raw option and `value` is `nil` if the option wasn't
|
||||
expected or the string value if the value didn't have the expected type for
|
||||
the corresponding option
|
||||
|
||||
## Examples
|
||||
Elixir converts switches to underscored atoms, so `--source-path` becomes
|
||||
`:source_path`. This is done to better suit Elixir conventions. However, this
|
||||
means that switches can't contain underscores and switches that do contain
|
||||
underscores are always returned in the list of invalid options.
|
||||
|
||||
Without any options, this function will try to parse all switches in the `argv`.
|
||||
|
||||
iex> OptionParser.parse(["--debug"])
|
||||
{[debug: true], [], []}
|
||||
@@ -32,65 +43,85 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--source-path", "lib", "test/enum_test.exs", "--verbose"])
|
||||
{[source_path: "lib", verbose: true], ["test/enum_test.exs"], []}
|
||||
|
||||
By default, Elixir will try to automatically parse all switches.
|
||||
Switches followed by a value will be assigned the value, as a string.
|
||||
Switches without an argument, like `--debug` will automatically
|
||||
be set to `true`.
|
||||
Switches without an argument, like `--debug` in the examples above, will
|
||||
automatically be set to `true`.
|
||||
|
||||
Note: Elixir also converts the switches to underscore atoms, so
|
||||
`--source-path` becomes `:source_path`, to better suit Elixir
|
||||
conventions. This means that option names on the command line cannot
|
||||
contain underscores; such options will be put in the invalid options
|
||||
list.
|
||||
## Options
|
||||
|
||||
## Switch Definitions
|
||||
The following options are supported:
|
||||
|
||||
* `:switches` or `:strict` - see the "Switch definitions" section below
|
||||
* `:aliases` - see the "Aliases" section below
|
||||
|
||||
## Switch definitions
|
||||
|
||||
Often it is better to explicitly list the known
|
||||
switches and their formats. The switches can be specified via two
|
||||
alternative options:
|
||||
switches and their formats. The switches can be specified via one of two
|
||||
options:
|
||||
|
||||
* `:switches` - defines some switches. An attempt is still made to parse
|
||||
switches that do not appear in the list.
|
||||
* `:switches` - defines some switches and their types. This function
|
||||
still attempts to parse switches that are not in this list.
|
||||
* `:strict` - defines strict switches. Any switch in `argv` that is not
|
||||
specified in the list is returned in the invalid options list.
|
||||
|
||||
* `:strict` - the switches are strict. Any switch that is not specified
|
||||
in the list is returned in the invalid options list.
|
||||
Both these options accept a keyword list of `{name, type}` tuples where `name`
|
||||
is an atom defining the name of the switch and `type` is an atom that
|
||||
specifies the type for the value of this switch (see the "Types" section below
|
||||
for the possible types and more information about type casting).
|
||||
|
||||
Note that you should only supply the `:switches` or `:strict` option.
|
||||
If you supply both, an error will be raised.
|
||||
If you supply both, an `ArgumentError` exception will be raised.
|
||||
|
||||
### Types
|
||||
|
||||
Option parser switches may take 0 or 1 argument.
|
||||
Switches parsed by `OptionParser` may take zero or one arguments.
|
||||
|
||||
The following switches take no argument:
|
||||
The following switches types take no arguments:
|
||||
|
||||
* `:boolean` - sets the value to true when given
|
||||
* `:count` - counts the number of times the switch is given
|
||||
* `:boolean` - sets the value to `true` when given (see also the
|
||||
"Negation switches" section below)
|
||||
* `:count` - counts the number of times the switch is given
|
||||
|
||||
The following switches take 1 argument:
|
||||
The following switches take one argument:
|
||||
|
||||
* `:integer` - parses the upcoming value as an integer.
|
||||
* `:float` - parses the upcoming value as a float.
|
||||
* `:string` - parses the upcoming value as a string.
|
||||
* `:integer` - parses the value as an integer
|
||||
* `:float` - parses the value as a float
|
||||
* `:string` - parses the value as a string
|
||||
|
||||
If a switch can't be parsed, it is returned in the invalid
|
||||
options list.
|
||||
If a switch can't be parsed according to the given type, it is returned
|
||||
in the invalid options list.
|
||||
|
||||
### Modifiers
|
||||
|
||||
Switches can be specified with modifiers, which change how
|
||||
they behave. The following modifiers are supported:
|
||||
|
||||
* `:keep` - keeps duplicated items instead of overriding them.
|
||||
Works with all types except `:count`.
|
||||
* `:keep` - keeps duplicated items instead of overriding them; works with
|
||||
all types except `:count`. Specifying `switch_name: :keep` assumes the
|
||||
type of `:switch_name` will be `:string`.
|
||||
|
||||
Note: if you want to use `:keep` with a non-string type, use a list, e.g.
|
||||
`[foo: [:integer, :keep]]`.
|
||||
Note that if you want to use `:keep` with a type other than `:string`, use a list
|
||||
as the type for the switch. For example: `[foo: [:integer, :keep]]`.
|
||||
|
||||
### Examples
|
||||
### Negation switches
|
||||
|
||||
Here are some examples of option parser working with different types
|
||||
and modifiers:
|
||||
In case a switch `SWITCH` is specified to have type `:boolean`, it may be
|
||||
passed as `--no-SWITCH` as well which will set the option to `false`:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be specified in the `:aliases` option:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{[debug: true], [], []}
|
||||
|
||||
## Examples
|
||||
|
||||
Here are some examples of working with different types and modifiers:
|
||||
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file"], strict: [unlock: :boolean])
|
||||
{[unlock: true], ["path/to/file"], []}
|
||||
@@ -121,21 +152,6 @@ defmodule OptionParser do
|
||||
iex> OptionParser.parse(["--unlock", "path/to/file", "--unlock", "path/to/another/file"], strict: [unlock: :keep])
|
||||
{[unlock: "path/to/file", unlock: "path/to/another/file"], [], []}
|
||||
|
||||
### Negation switches
|
||||
|
||||
In case a switch is declared as boolean, it may be passed as `--no-SWITCH`
|
||||
which will set the option to `false`:
|
||||
|
||||
iex> OptionParser.parse(["--no-op", "path/to/file"], switches: [op: :boolean])
|
||||
{[op: false], ["path/to/file"], []}
|
||||
|
||||
## Aliases
|
||||
|
||||
A set of aliases can be given as options too:
|
||||
|
||||
iex> OptionParser.parse(["-d"], aliases: [d: :debug])
|
||||
{[debug: true], [], []}
|
||||
|
||||
"""
|
||||
@spec parse(argv, options) :: {parsed, argv, errors}
|
||||
def parse(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
@@ -146,14 +162,16 @@ defmodule OptionParser do
|
||||
The same as `parse/2` but raises an `OptionParser.ParseError`
|
||||
exception if any invalid options are given.
|
||||
|
||||
If there weren't any errors, returns a three-element tuple as follows:
|
||||
If there are no errors, returns a `{parsed, rest}` tuple where:
|
||||
|
||||
1. parsed options,
|
||||
2. remaining arguments,
|
||||
3. empty list.
|
||||
* `parsed` is the list of parsed switches (same as in `parse/2`)
|
||||
* `rest` is the list of arguments (same as in `parse/2`)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse!(["--debug", "path/to/file"], strict: [debug: :boolean])
|
||||
{[debug: true], ["path/to/file"]}
|
||||
|
||||
iex> OptionParser.parse!(["--limit", "xyz"], strict: [limit: :integer])
|
||||
** (OptionParser.ParseError) 1 error found!
|
||||
--limit : Expected type integer, got "xyz"
|
||||
@@ -172,7 +190,7 @@ defmodule OptionParser do
|
||||
@spec parse!(argv, options) :: {parsed, argv} | no_return
|
||||
def parse!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
case parse(argv, opts) do
|
||||
{parsed, argv, []} -> {parsed, argv}
|
||||
{parsed, args, []} -> {parsed, args}
|
||||
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
||||
end
|
||||
end
|
||||
@@ -201,14 +219,16 @@ defmodule OptionParser do
|
||||
The same as `parse_head/2` but raises an `OptionParser.ParseError`
|
||||
exception if any invalid options are given.
|
||||
|
||||
If there weren't any errors, returns a three-element tuple as follows:
|
||||
If there are no errors, returns a `{parsed, rest}` tuple where:
|
||||
|
||||
1. parsed options,
|
||||
2. remaining arguments,
|
||||
3. empty list.
|
||||
* `parsed` is the list of parsed switches (same as in `parse_head/2`)
|
||||
* `rest` is the list of arguments (same as in `parse_head/2`)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.parse_head!(["--source", "lib", "path/to/file", "--verbose"])
|
||||
{[source: "lib"], ["path/to/file", "--verbose"]}
|
||||
|
||||
iex> OptionParser.parse_head!(["--number", "lib", "test/enum_test.exs", "--verbose"], strict: [number: :integer])
|
||||
** (OptionParser.ParseError) 1 error found!
|
||||
--number : Expected type integer, got "lib"
|
||||
@@ -219,10 +239,10 @@ defmodule OptionParser do
|
||||
--verbose : Missing argument of type integer
|
||||
--source : Expected type integer, got "lib"
|
||||
"""
|
||||
@spec parse_head!(argv, options) :: {parsed, argv, errors} | no_return
|
||||
@spec parse_head!(argv, options) :: {parsed, argv} | no_return
|
||||
def parse_head!(argv, opts \\ []) when is_list(argv) and is_list(opts) do
|
||||
case parse_head(argv, opts) do
|
||||
{parsed, argv, []} -> {parsed, argv}
|
||||
{parsed, args, []} -> {parsed, args}
|
||||
{_, _, errors} -> raise ParseError, format_errors(errors, opts)
|
||||
end
|
||||
end
|
||||
@@ -250,7 +270,7 @@ defmodule OptionParser do
|
||||
{:error, ["--" | rest]} ->
|
||||
{Enum.reverse(opts), Enum.reverse(args, rest), Enum.reverse(invalid)}
|
||||
|
||||
{:error, [arg | rest]=remaining_args} ->
|
||||
{:error, [arg | rest] = remaining_args} ->
|
||||
# there is no option
|
||||
if all? do
|
||||
do_parse(rest, config, opts, [arg | args], invalid, all?)
|
||||
@@ -264,20 +284,20 @@ defmodule OptionParser do
|
||||
Low-level function that parses one option.
|
||||
|
||||
It accepts the same options as `parse/2` and `parse_head/2`
|
||||
as both functions are built on top of next. This function
|
||||
as both functions are built on top of this function. This function
|
||||
may return:
|
||||
|
||||
* `{:ok, key, value, rest}` - the option `key` with `value` was
|
||||
successfully parsed
|
||||
|
||||
* `{:invalid, key, value, rest}` - the option `key` is invalid with `value`
|
||||
(returned when the switch type does not match the one given via the
|
||||
command line)
|
||||
(returned when the value cannot be parsed according to the switch type)
|
||||
|
||||
* `{:undefined, key, value, rest}` - the option `key` is undefined
|
||||
(returned in strict mode when the switch is unknown)
|
||||
|
||||
* `{:error, rest}` - there are no switches at the top of the given argv
|
||||
* `{:error, rest}` - there are no switches at the head of the given `argv`
|
||||
|
||||
"""
|
||||
|
||||
@spec next(argv, options) ::
|
||||
@@ -295,15 +315,15 @@ defmodule OptionParser do
|
||||
{:error, []}
|
||||
end
|
||||
|
||||
defp next(["--" | _]=argv, _aliases, _switches, _strict) do
|
||||
defp next(["--" | _] = argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["-" | _]=argv, _aliases, _switches, _strict) do
|
||||
defp next(["-" | _] = argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
defp next(["- " <> _ | _]=argv, _aliases, _switches, _strict) do
|
||||
defp next(["- " <> _ | _] = argv, _aliases, _switches, _strict) do
|
||||
{:error, argv}
|
||||
end
|
||||
|
||||
@@ -334,8 +354,9 @@ defmodule OptionParser do
|
||||
@doc """
|
||||
Receives a key-value enumerable and converts it to argv.
|
||||
|
||||
Keys must be atoms. Keys with nil value are discarded,
|
||||
Keys must be atoms. Keys with `nil` value are discarded,
|
||||
boolean values are converted to `--key` or `--no-key`
|
||||
(if the value is `true` or `false`, respectively),
|
||||
and all other values are converted using `to_string/1`.
|
||||
|
||||
## Examples
|
||||
@@ -364,6 +385,9 @@ defmodule OptionParser do
|
||||
@doc ~S"""
|
||||
Splits a string into argv chunks.
|
||||
|
||||
This function splits the given `string` into a list of strings in a similar
|
||||
way to many shells.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> OptionParser.split("foo bar")
|
||||
@@ -371,6 +395,7 @@ defmodule OptionParser do
|
||||
|
||||
iex> OptionParser.split("foo \"bar baz\"")
|
||||
["foo", "bar baz"]
|
||||
|
||||
"""
|
||||
@spec split(String.t) :: argv
|
||||
def split(string) do
|
||||
|
||||
+21
-20
@@ -1,23 +1,24 @@
|
||||
{erl_opts, [
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
warn_shadow_vars,
|
||||
warn_unused_import,
|
||||
warn_unused_function,
|
||||
warn_bif_clash,
|
||||
warn_unused_record,
|
||||
warn_deprecated_function,
|
||||
warn_obsolete_guard,
|
||||
strict_validation,
|
||||
warn_exported_vars,
|
||||
%% warn_export_vars,
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
%% warnings_as_errors,
|
||||
debug_info
|
||||
]}.
|
||||
warn_unused_vars,
|
||||
warn_export_all,
|
||||
warn_shadow_vars,
|
||||
warn_unused_import,
|
||||
warn_unused_function,
|
||||
warn_bif_clash,
|
||||
warn_unused_record,
|
||||
warn_deprecated_function,
|
||||
warn_obsolete_guard,
|
||||
strict_validation,
|
||||
warn_exported_vars,
|
||||
%% warn_export_vars,
|
||||
%% warn_missing_spec,
|
||||
%% warn_untyped_record,
|
||||
%% warnings_as_errors,
|
||||
debug_info,
|
||||
{platform_define, "^18.*", old_map_specs}
|
||||
]}.
|
||||
|
||||
{yrl_opts, [
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
]}.
|
||||
{report, true},
|
||||
{verbose, false}
|
||||
]}.
|
||||
|
||||
@@ -14,11 +14,16 @@
|
||||
-export_type([charlist/0, char_list/0, struct/0, as_boolean/1, keyword/0, keyword/1]).
|
||||
-type charlist() :: string().
|
||||
-type char_list() :: string().
|
||||
-type struct() :: #{'__struct__' => atom()}.
|
||||
-type as_boolean(T) :: T.
|
||||
-type keyword() :: [{atom(), any()}].
|
||||
-type keyword(T) :: [{atom(), T}].
|
||||
|
||||
-ifdef(old_map_specs).
|
||||
-type struct() :: #{'__struct__' => atom(), atom() => any()}.
|
||||
-else.
|
||||
-type struct() :: #{'__struct__' := atom(), atom() => any()}.
|
||||
-endif.
|
||||
|
||||
%% OTP Application API
|
||||
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -60,7 +60,8 @@ import_function(Meta, Name, Arity, E) ->
|
||||
end.
|
||||
|
||||
require_function(Meta, Receiver, Name, Arity, E) ->
|
||||
case is_element({Name, Arity}, get_optional_macros(Receiver)) of
|
||||
Required = is_element(Receiver, ?m(E, requires)),
|
||||
case is_element({Name, Arity}, get_macros(Receiver, Required)) of
|
||||
true -> false;
|
||||
false ->
|
||||
elixir_lexical:record_remote(Receiver, ?m(E, function), ?m(E, lexical_tracker)),
|
||||
@@ -146,7 +147,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
elixir_locals:record_import(Tuple, Receiver, Module, ?m(E, function)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
{import, Receiver} ->
|
||||
case expand_require([{require, false} | Meta], Receiver, Tuple, Args, E) of
|
||||
case expand_require([{required, true} | Meta], Receiver, Tuple, Args, E) of
|
||||
{ok, _, _} = Response -> Response;
|
||||
error -> {ok, Receiver, Name, Args}
|
||||
end;
|
||||
@@ -161,19 +162,15 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
|
||||
|
||||
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
|
||||
check_deprecation(Meta, Receiver, Name, Arity, E),
|
||||
Module = ?m(E, module),
|
||||
Required = (Receiver == ?m(E, module)) orelse is_element(Receiver, ?m(E, requires)) orelse required(Meta),
|
||||
|
||||
case is_element(Tuple, get_optional_macros(Receiver)) of
|
||||
case is_element(Tuple, get_macros(Receiver, Required)) of
|
||||
true when Required ->
|
||||
elixir_lexical:record_remote(Receiver, Name, Arity, nil, ?line(Meta), ?m(E, lexical_tracker)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
true ->
|
||||
Requires = ?m(E, requires),
|
||||
case (Receiver == Module) orelse is_element(Receiver, Requires) orelse skip_require(Meta) of
|
||||
true ->
|
||||
elixir_lexical:record_remote(Receiver, Name, Arity, nil, ?line(Meta), ?m(E, lexical_tracker)),
|
||||
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
|
||||
false ->
|
||||
Info = {unrequired_module, {Receiver, Name, length(Args), Requires}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info)
|
||||
end;
|
||||
Info = {unrequired_module, {Receiver, Name, length(Args), ?m(E, requires)}},
|
||||
elixir_errors:form_error(Meta, ?m(E, file), ?MODULE, Info);
|
||||
false ->
|
||||
error
|
||||
end.
|
||||
@@ -220,8 +217,8 @@ caller(Line, E) ->
|
||||
|
||||
%% Helpers
|
||||
|
||||
skip_require(Meta) ->
|
||||
lists:keyfind(require, 1, Meta) == {require, false}.
|
||||
required(Meta) ->
|
||||
lists:keyfind(required, 1, Meta) == {required, true}.
|
||||
|
||||
find_dispatch(Meta, Tuple, Extra, E) ->
|
||||
case is_import(Meta) of
|
||||
@@ -289,9 +286,20 @@ format_error({ambiguous_call, {Mod1, Mod2, Name, Arity}}) ->
|
||||
%% INTROSPECTION
|
||||
|
||||
%% Do not try to get macros from Erlang. Speeds up compilation a bit.
|
||||
get_optional_macros(erlang) -> [];
|
||||
get_macros(erlang, _) -> [];
|
||||
|
||||
get_optional_macros(Receiver) ->
|
||||
get_macros(Receiver, false) ->
|
||||
case code:is_loaded(Receiver) of
|
||||
{file, _} ->
|
||||
try
|
||||
Receiver:'__info__'(macros)
|
||||
catch
|
||||
error:undef -> []
|
||||
end;
|
||||
false -> []
|
||||
end;
|
||||
|
||||
get_macros(Receiver, true) ->
|
||||
case code:ensure_loaded(Receiver) of
|
||||
{module, Receiver} ->
|
||||
try
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -525,7 +525,7 @@ add_beam_chunk(Bin, Id, ChunkData)
|
||||
%% the callback can't be expanded, invokes the given
|
||||
%% fun passing a possibly expanded AM:AF(Args).
|
||||
expand_callback(Line, M, F, Args, E, Fun) ->
|
||||
Meta = [{line, Line}, {require, false}],
|
||||
Meta = [{line, Line}, {required, true}],
|
||||
|
||||
{EE, ET} = elixir_dispatch:dispatch_require(Meta, M, F, Args, E, fun(AM, AF, AA) ->
|
||||
Fun(AM, AF, AA),
|
||||
|
||||
@@ -135,15 +135,15 @@ tokenize([], EndLine, _Column, #elixir_tokenizer{terminators=[{Start, {StartLine
|
||||
% Base integers
|
||||
|
||||
tokenize([$0, $x, H | T], Line, Column, Scope, Tokens) when ?is_hex(H) ->
|
||||
{Rest, Number, Length} = tokenize_hex([H | T], []),
|
||||
{Rest, Number, Length} = tokenize_hex(T, [H], 1),
|
||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||
|
||||
tokenize([$0, $b, H | T], Line, Column, Scope, Tokens) when ?is_bin(H) ->
|
||||
{Rest, Number, Length} = tokenize_bin([H | T], []),
|
||||
{Rest, Number, Length} = tokenize_bin(T, [H], 1),
|
||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||
|
||||
tokenize([$0, $o, H | T], Line, Column, Scope, Tokens) when ?is_octal(H) ->
|
||||
{Rest, Number, Length} = tokenize_octal([H | T], []),
|
||||
{Rest, Number, Length} = tokenize_octal(T, [H], 1),
|
||||
tokenize(Rest, Line, Column + 2 + Length, Scope, [{number, {Line, Column, Column + 2 + Length}, Number} | Tokens]);
|
||||
|
||||
% Comments
|
||||
@@ -413,8 +413,8 @@ tokenize([$. | T], Line, Column, Scope, Tokens) ->
|
||||
|
||||
% Integers and floats
|
||||
|
||||
tokenize([H | _] = String, Line, Column, Scope, Tokens) when ?is_digit(H) ->
|
||||
{Rest, Number, Length} = tokenize_number(String, [], false),
|
||||
tokenize([H | T], Line, Column, Scope, Tokens) when ?is_digit(H) ->
|
||||
{Rest, Number, Length} = tokenize_number(T, [H], 1, false),
|
||||
tokenize(Rest, Line, Column + Length, Scope, [{number, {Line, Column, Column + Length}, Number} | Tokens]);
|
||||
|
||||
% Identifiers (including aliases)
|
||||
@@ -733,46 +733,55 @@ extract_heredoc_line(Marker, Rest, Buffer, _Counter) ->
|
||||
%% At this point, we are at least sure the first digit is a number.
|
||||
|
||||
%% Check if we have a point followed by a number;
|
||||
tokenize_number([$., H | T], Acc, false) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H, $. | Acc], true);
|
||||
tokenize_number([$., H | T], Acc, Length, false) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H, $. | Acc], Length + 2, true);
|
||||
|
||||
%% Check if we have an underscore followed by a number;
|
||||
tokenize_number([$_, H | T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Bool);
|
||||
tokenize_number([$_, H | T], Acc, Length, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Length + 2, Bool);
|
||||
|
||||
%% Check if we have e- followed by numbers (valid only for floats);
|
||||
tokenize_number([E, S, H | T], Acc, true)
|
||||
tokenize_number([E, S, H | T], Acc, Length, true)
|
||||
when (E == $E) or (E == $e), ?is_digit(H), S == $+ orelse S == $- ->
|
||||
tokenize_number(T, [H, S, $e | Acc], true);
|
||||
tokenize_number(T, [H, S, $e | Acc], Length + 3, true);
|
||||
|
||||
%% Check if we have e followed by numbers (valid only for floats);
|
||||
tokenize_number([E, H | T], Acc, true)
|
||||
tokenize_number([E, H | T], Acc, Length, true)
|
||||
when (E == $E) or (E == $e), ?is_digit(H) ->
|
||||
tokenize_number(T, [H, $e | Acc], true);
|
||||
tokenize_number(T, [H, $e | Acc], Length + 2, true);
|
||||
|
||||
%% Finally just numbers.
|
||||
tokenize_number([H | T], Acc, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Bool);
|
||||
tokenize_number([H | T], Acc, Length, Bool) when ?is_digit(H) ->
|
||||
tokenize_number(T, [H | Acc], Length + 1, Bool);
|
||||
|
||||
%% Cast to float...
|
||||
tokenize_number(Rest, Acc, true) ->
|
||||
{Rest, list_to_float(lists:reverse(Acc)), length(Acc)};
|
||||
tokenize_number(Rest, Acc, Length, true) ->
|
||||
{Rest, list_to_float(lists:reverse(Acc)), Length};
|
||||
|
||||
%% Or integer.
|
||||
tokenize_number(Rest, Acc, false) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc)), length(Acc)}.
|
||||
tokenize_number(Rest, Acc, Length, false) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc)), Length}.
|
||||
|
||||
tokenize_hex([H | T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H | Acc]);
|
||||
tokenize_hex([$_, H | T], Acc) when ?is_hex(H) -> tokenize_hex(T, [H | Acc]);
|
||||
tokenize_hex(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 16), length(Acc)}.
|
||||
tokenize_hex([H | T], Acc, Length) when ?is_hex(H) ->
|
||||
tokenize_hex(T, [H | Acc], Length + 1);
|
||||
tokenize_hex([$_, H | T], Acc, Length) when ?is_hex(H) ->
|
||||
tokenize_hex(T, [H | Acc], Length + 2);
|
||||
tokenize_hex(Rest, Acc, Length) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc), 16), Length}.
|
||||
|
||||
tokenize_octal([H | T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H | Acc]);
|
||||
tokenize_octal([$_, H | T], Acc) when ?is_octal(H) -> tokenize_octal(T, [H | Acc]);
|
||||
tokenize_octal(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 8), length(Acc)}.
|
||||
tokenize_octal([H | T], Acc, Length) when ?is_octal(H) ->
|
||||
tokenize_octal(T, [H | Acc], Length + 1);
|
||||
tokenize_octal([$_, H | T], Acc, Length) when ?is_octal(H) ->
|
||||
tokenize_octal(T, [H | Acc], Length + 2);
|
||||
tokenize_octal(Rest, Acc, Length) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc), 8), Length}.
|
||||
|
||||
tokenize_bin([H | T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H | Acc]);
|
||||
tokenize_bin([$_, H | T], Acc) when ?is_bin(H) -> tokenize_bin(T, [H | Acc]);
|
||||
tokenize_bin(Rest, Acc) -> {Rest, list_to_integer(lists:reverse(Acc), 2), length(Acc)}.
|
||||
tokenize_bin([H | T], Acc, Length) when ?is_bin(H) ->
|
||||
tokenize_bin(T, [H | Acc], Length + 1);
|
||||
tokenize_bin([$_, H | T], Acc, Length) when ?is_bin(H) ->
|
||||
tokenize_bin(T, [H | Acc], Length + 2);
|
||||
tokenize_bin(Rest, Acc, Length) ->
|
||||
{Rest, list_to_integer(lists:reverse(Acc), 2), Length}.
|
||||
|
||||
%% Comments
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -104,9 +107,14 @@ build_else(Meta, ElseClauses) ->
|
||||
each_clause_to_error_match(ElseClauses)] ++ [build_raise(Meta)].
|
||||
|
||||
each_clause_to_error_match(Clauses) ->
|
||||
[{match, Meta, [{error, Match}], Expr} ||
|
||||
[{match, Meta, [error_match_for_match(Match)], Expr} ||
|
||||
{'->', Meta, [[Match], Expr]} <- Clauses].
|
||||
|
||||
error_match_for_match({'when', Meta, [Left, Right]}) ->
|
||||
{'when', Meta, [{error, Left}, Right]};
|
||||
error_match_for_match(Match) ->
|
||||
{error, Match}.
|
||||
|
||||
build_raise(Meta) ->
|
||||
Other = {other, Meta, ?MODULE},
|
||||
{match, ?generated, [{error, Other}], {{'.', Meta, [erlang, error]}, Meta, [{with_clause, Other}]}}.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -195,6 +195,8 @@ defmodule EnumTest do
|
||||
test "find_index/2" do
|
||||
assert Enum.find_index([2, 4, 6], fn(x) -> rem(x, 2) == 1 end) == nil
|
||||
assert Enum.find_index([2, 3, 4], fn(x) -> rem(x, 2) == 1 end) == 1
|
||||
assert Stream.take(1..3, 3) |> Enum.find_index(fn _ -> false end) == nil
|
||||
assert Stream.take(1..6, 6) |> Enum.find_index(fn x -> x == 5 end) == 4
|
||||
end
|
||||
|
||||
test "find_value/2" do
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
defmodule Dialyzer.Macrocallback do
|
||||
@macrocallback required(atom) :: Macro.t
|
||||
@macrocallback optional(atom) :: Macro.t
|
||||
@optional_callbacks [optional: 1]
|
||||
end
|
||||
|
||||
defmodule Dialyzer.Macrocallback.Impl do
|
||||
@behaviour Dialyzer.Macrocallback
|
||||
defmacro required(var), do: Macro.expand(var, __CALLER__)
|
||||
defmacro optional(var), do: Macro.expand(var, __CALLER__)
|
||||
end
|
||||
@@ -1,4 +1,3 @@
|
||||
defmodule Bar do
|
||||
defstruct name: ""
|
||||
def foo?(%Foo{}), do: true
|
||||
defstruct name: "", foo: %Foo{}
|
||||
end
|
||||
|
||||
@@ -16,8 +16,14 @@ defmodule Kernel.DialyzerTest do
|
||||
|> Path.join("base_plt")
|
||||
|> String.to_charlist()
|
||||
|
||||
# Add a few key elixir modules for types
|
||||
files = Enum.map([Kernel, String, Keyword, Exception], &:code.which/1)
|
||||
# Some OSs (like Windows) do not provide the HOME environment variable.
|
||||
unless System.get_env("HOME") do
|
||||
System.put_env("HOME", System.user_home())
|
||||
end
|
||||
|
||||
# Add a few key elixir modules for types and macro functions
|
||||
mods = [Kernel, String, Keyword, Exception, Macro, Macro.Env, :elixir_env]
|
||||
files = Enum.map(mods, &:code.which/1)
|
||||
:dialyzer.run([analysis_type: :plt_build, output_plt: plt,
|
||||
apps: [:erts], files: files])
|
||||
|
||||
@@ -48,6 +54,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
|
||||
@@ -63,6 +70,12 @@ defmodule Kernel.DialyzerTest do
|
||||
assert_dialyze_no_warnings! context
|
||||
end
|
||||
|
||||
test "no warnings on macrocallback", context do
|
||||
copy_beam! context, Dialyzer.Macrocallback
|
||||
copy_beam! context, Dialyzer.Macrocallback.Impl
|
||||
assert_dialyze_no_warnings! context
|
||||
end
|
||||
|
||||
defp copy_beam!(context, module) do
|
||||
name = "#{module}.beam"
|
||||
File.cp! Path.join(context[:base_dir], name),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -66,6 +66,7 @@ defmodule Kernel.WithTest do
|
||||
|
||||
test "else conditions" do
|
||||
assert with({:ok, res} <- 41, do: res, else: ({:error, error} -> error; res -> res + 1)) == 42
|
||||
assert with({:ok, res} <- 41, do: res, else: (res when res == 41 -> res + 1; res -> res)) == 42
|
||||
assert with({:ok, res} <- 41, do: res, else: (_ -> :error)) == :error
|
||||
end
|
||||
|
||||
@@ -75,6 +76,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__)
|
||||
|
||||
@@ -25,15 +25,16 @@ op_kw_test() ->
|
||||
[{atom, {1, 1, 5}, foo}, {dual_op, {1, 5, 6}, '+'}, {atom, {1, 6, 10}, bar}] = tokenize(":foo+:bar").
|
||||
|
||||
scientific_test() ->
|
||||
[{number, {1, 1, 7}, 0.1}] = tokenize("1.0e-1").
|
||||
[{number, {1, 1, 7}, 0.1}] = tokenize("1.0e-1"),
|
||||
[{number, {1, 1, 16}, 1.2345678e-7}] = tokenize("1_234.567_8e-10").
|
||||
|
||||
hex_bin_octal_test() ->
|
||||
[{number, {1, 1, 5}, 255}] = tokenize("0xFF"),
|
||||
[{number, {1, 1, 5}, 255}] = tokenize("0xF_F"),
|
||||
[{number, {1, 1, 6}, 255}] = tokenize("0xF_F"),
|
||||
[{number, {1, 1, 5}, 63}] = tokenize("0o77"),
|
||||
[{number, {1, 1, 5}, 63}] = tokenize("0o7_7"),
|
||||
[{number, {1, 1, 6}, 63}] = tokenize("0o7_7"),
|
||||
[{number, {1, 1, 5}, 3}] = tokenize("0b11"),
|
||||
[{number, {1, 1, 5}, 3}] = tokenize("0b1_1").
|
||||
[{number, {1, 1, 6}, 3}] = tokenize("0b1_1").
|
||||
|
||||
unquoted_atom_test() ->
|
||||
[{atom, {1, 1, 3}, '+'}] = tokenize(":+"),
|
||||
|
||||
@@ -367,18 +367,29 @@ defmodule ExUnit.Diff do
|
||||
map_pair = format_key_value(key, val, keyword?)
|
||||
[[ins: ", ", ins: map_pair] | acc]
|
||||
end)
|
||||
result =
|
||||
if same == [] and altered == [] and missing != [] and surplus != [] do
|
||||
[[_ | elem_diff] | rest] = result
|
||||
[elem_diff | rest]
|
||||
else
|
||||
result
|
||||
end
|
||||
|
||||
result = Enum.reduce(surplus, result, fn({key, val}, acc) ->
|
||||
map_pair = format_key_value(key, val, keyword?)
|
||||
[[del: ", ", del: map_pair] | acc]
|
||||
end)
|
||||
|
||||
result = Enum.reduce(altered, result, fn({key, {val1, val2}}, acc) ->
|
||||
value_diff = script_inner(val1, val2)
|
||||
[[{:eq, ", "}, {:eq, format_key(key, keyword?)}, value_diff] | acc]
|
||||
end)
|
||||
|
||||
result = Enum.reduce(same, result, fn({key, val}, acc) ->
|
||||
map_pair = format_key_value(key, val, keyword?)
|
||||
[[eq: ", ", eq: map_pair] | acc]
|
||||
end)
|
||||
|
||||
[[_ | elem_diff] | rest] = result
|
||||
[{:eq, "%" <> name <> "{"}, [elem_diff | rest], {:eq, "}"}]
|
||||
end
|
||||
|
||||
@@ -242,6 +242,8 @@ defmodule ExUnit.DiffTest do
|
||||
assert script(map2, map1) == expected
|
||||
assert script(map1, %{}) == [{:eq, "%{"}, [[del: "baz: 12"]], {:eq, "}"}]
|
||||
assert script(%{}, map1) == [{:eq, "%{"}, [[ins: "baz: 12"]], {:eq, "}"}]
|
||||
expected = [{:eq, "%{"}, [[del: "baz: 12"], [ins: "foo: 12"]], {:eq, "}"}]
|
||||
assert script(map1, %{foo: 12}) == expected
|
||||
|
||||
assert script(%{}, %{}) == [eq: "%{}"]
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ defmodule Mix.Dep do
|
||||
@doc """
|
||||
Returns loaded dependencies from the cache for the current environment.
|
||||
|
||||
Because the dependencies are cached during deps.check, their
|
||||
Because the dependencies are cached during deps.loadpaths, their
|
||||
status may be outdated (for example, `:compile` did not
|
||||
yet become `:ok`). Therefore it is recommended to not rely
|
||||
on their status, also given they haven't been checked
|
||||
@@ -225,16 +225,16 @@ defmodule Mix.Dep do
|
||||
def format_status(%Mix.Dep{app: app, status: {:divergedonly, other}} = dep) do
|
||||
recommendation =
|
||||
if Keyword.has_key?(other.opts, :only) do
|
||||
"Ensure the parent dependency specifies a superset of the child one in"
|
||||
"Ensure you specify at least the same environments in :only in your dep"
|
||||
else
|
||||
"Remove the :only restriction from"
|
||||
"Remove the :only restriction from your dep"
|
||||
end
|
||||
|
||||
"the dependency #{app}\n" <>
|
||||
"the :only option for dependency #{app}\n" <>
|
||||
"#{dep_status(dep)}" <>
|
||||
"\n does not match the environments calculated for\n" <>
|
||||
"\n does not match the :only option calculated for\n" <>
|
||||
"#{dep_status(other)}" <>
|
||||
"\n #{recommendation} your dep"
|
||||
"\n #{recommendation}"
|
||||
end
|
||||
|
||||
def format_status(%Mix.Dep{app: app, status: {:diverged, other}} = dep) do
|
||||
|
||||
@@ -84,7 +84,7 @@ defmodule Mix.Dep.Converger do
|
||||
if not diverged? && remote do
|
||||
# If there is a lock, it means we are doing a get/update
|
||||
# and we need to hit the remote converger which do external
|
||||
# requests and what not. In case of deps.check, deps and so
|
||||
# requests and what not. In case of deps.loadpaths, deps and so
|
||||
# on, there is no lock, so we won't hit this branch.
|
||||
lock = if lock_given?, do: remote.converge(deps, lock), else: lock
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ defmodule Mix.Task do
|
||||
# 2. Otherwise we look for it in dependencies.
|
||||
# 3. Finally, we compile the current project in hope it is available.
|
||||
module =
|
||||
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.check") end) ||
|
||||
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.loadpaths") end) ||
|
||||
get_task_or_run(proj, task, fn -> Mix.Project.compile([]) end) ||
|
||||
get!(task)
|
||||
|
||||
|
||||
@@ -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.loadpaths"
|
||||
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.loadpaths"
|
||||
end
|
||||
|
||||
defp consolidate_protocols? do
|
||||
|
||||
@@ -44,10 +44,7 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
output = Mix.Project.consolidation_path(config)
|
||||
manifest = Path.join(output, @manifest)
|
||||
|
||||
protocols_and_impls =
|
||||
unless Mix.Project.umbrella?(config) do
|
||||
protocols_and_impls(config)
|
||||
end
|
||||
protocols_and_impls = protocols_and_impls(config)
|
||||
|
||||
cond do
|
||||
opts[:force] || Mix.Utils.stale?(Mix.Project.config_files(), [manifest]) ->
|
||||
@@ -79,10 +76,15 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
not scm.fetchable?,
|
||||
do: opts[:build])
|
||||
|
||||
app = Mix.Project.app_path(config)
|
||||
app =
|
||||
if Mix.Project.umbrella?(config) do
|
||||
[]
|
||||
else
|
||||
[Mix.Project.app_path(config)]
|
||||
end
|
||||
|
||||
protocols_and_impls =
|
||||
for path <- [app | deps] do
|
||||
for path <- app ++ deps do
|
||||
manifest_path = Path.join(path, ".compile.elixir")
|
||||
compile_path = Path.join(path, "ebin")
|
||||
Mix.Compilers.Elixir.protocols_and_impls(manifest_path, compile_path)
|
||||
@@ -120,10 +122,24 @@ defmodule Mix.Tasks.Compile.Protocols do
|
||||
defp consolidate(protocol, paths, output, opts) do
|
||||
impls = Protocol.extract_impls(protocol, paths)
|
||||
reload(protocol)
|
||||
{:ok, binary} = Protocol.consolidate(protocol, impls)
|
||||
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
||||
if opts[:verbose] do
|
||||
Mix.shell.info "Consolidated #{inspect protocol}"
|
||||
case Protocol.consolidate(protocol, impls) do
|
||||
{:ok, binary} ->
|
||||
File.write!(Path.join(output, "#{protocol}.beam"), binary)
|
||||
if opts[:verbose] do
|
||||
Mix.shell.info "Consolidated #{inspect protocol}"
|
||||
end
|
||||
|
||||
# If we remove a dependency and we have implemented one of its
|
||||
# protocols locally, we will mark the protocol as needing to be
|
||||
# reconsolidated when the implementation is removed even though
|
||||
# the protocol no longer exists. Although most times removing a
|
||||
# dependency will trigger a full recompilation, such won't happen
|
||||
# in umbrella apps with shared build.
|
||||
{:error, :no_beam_info} ->
|
||||
remove_consolidated(protocol, output)
|
||||
if opts[:verbose] do
|
||||
Mix.shell.info "Unavailable #{inspect protocol}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -133,7 +133,8 @@ 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", "--no-warnings-as-errors"])
|
||||
:ok in List.wrap(res)
|
||||
catch
|
||||
kind, reason ->
|
||||
@@ -184,13 +185,14 @@ 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
|
||||
do_command(dep, config, command, true)
|
||||
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, [{"IS_DEP", "1"}])
|
||||
end
|
||||
|
||||
defp do_compile(%Mix.Dep{opts: opts} = dep, config) do
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
defmodule Mix.Tasks.Deps.Check do
|
||||
defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
use Mix.Task
|
||||
|
||||
import Mix.Dep, only: [loaded_by_name: 2, format_dep: 1, ok?: 1,
|
||||
format_status: 1, check_lock: 1]
|
||||
|
||||
@moduledoc """
|
||||
Checks if all dependencies are valid, loading them along
|
||||
the way.
|
||||
Checks and loads all dependencies 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.
|
||||
Although this task does not show up in `mix help`, it is
|
||||
part of 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
|
||||
@@ -16,7 +16,7 @@ defmodule Mix.Tasks.Deps.Unlock do
|
||||
|
||||
"""
|
||||
|
||||
@switches [all: :boolean, unused: :boolean]
|
||||
@switches [all: :boolean, unused: :boolean, filter: :string]
|
||||
|
||||
@spec run(OptionParser.argv) :: :ok
|
||||
def run(args) do
|
||||
@@ -29,6 +29,27 @@ defmodule Mix.Tasks.Deps.Unlock do
|
||||
opts[:unused] ->
|
||||
apps = Mix.Dep.loaded([]) |> Enum.map(& &1.app)
|
||||
Mix.Dep.Lock.read() |> Map.take(apps) |> Mix.Dep.Lock.write()
|
||||
filter = opts[:filter] ->
|
||||
lock = Mix.Dep.Lock.read
|
||||
apps = Map.keys(lock)
|
||||
|
||||
unlock =
|
||||
apps
|
||||
|> Enum.filter(&String.contains?("#{&1}", filter))
|
||||
|
||||
if unlock == [] do
|
||||
Mix.shell.error "warning: no dependencies were matched"
|
||||
else
|
||||
lock =
|
||||
Enum.reject(lock, fn({app, _}) ->
|
||||
app in unlock
|
||||
end)
|
||||
Mix.Dep.Lock.write(lock)
|
||||
Mix.shell.info """
|
||||
Unlocked deps:
|
||||
* #{Enum.join(unlock, "\n* ")}
|
||||
"""
|
||||
end
|
||||
|
||||
apps != [] ->
|
||||
lock =
|
||||
|
||||
+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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
defmodule Mix.Tasks.Escript do
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "List installed escripts."
|
||||
@shortdoc "Lists installed escripts"
|
||||
|
||||
@moduledoc ~S"""
|
||||
Lists all installed escripts.
|
||||
|
||||
@@ -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.loadpaths"
|
||||
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,10 +26,14 @@ defmodule Mix.Tasks.Loadpaths do
|
||||
check_elixir_version(config, args)
|
||||
end
|
||||
|
||||
# --no-deps is used only internally. It has not purpose
|
||||
# from Mix.CLI because the CLI itself already loads deps.
|
||||
unless "--no-deps" in args do
|
||||
Mix.Task.run "deps.check", args
|
||||
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 running a task may load deps.
|
||||
unless "--no-deps" in args or System.get_env("MIX_NO_DEPS") in ~w(1 true) do
|
||||
Mix.Task.run "deps.loadpaths", args
|
||||
end
|
||||
|
||||
if config[:app] do
|
||||
|
||||
@@ -67,6 +67,9 @@ defmodule Mix.Tasks.Test do
|
||||
* `--no-elixir-version-check` - do not check the Elixir version from mix.exs
|
||||
* `--stale` - run only tests which reference modules that changed since the
|
||||
last `test --stale`. You can read more about this option in the "Stale" section below.
|
||||
* `--listen-on-stdin` - run tests, and then listen on stdin. Receiving a newline will
|
||||
result in the tests being run again. Very useful when combined with `--stale` and
|
||||
external commands which produce output on stdout upon file system modification.
|
||||
|
||||
## Filters
|
||||
|
||||
@@ -164,7 +167,7 @@ defmodule Mix.Tasks.Test do
|
||||
exclude: :keep, seed: :integer, only: :keep, compile: :boolean,
|
||||
start: :boolean, timeout: :integer, raise: :boolean,
|
||||
deps_check: :boolean, archives_check: :boolean, elixir_version_check: :boolean,
|
||||
stale: :boolean]
|
||||
stale: :boolean, listen_on_stdin: :boolean]
|
||||
|
||||
@cover [output: "cover", tool: Cover]
|
||||
|
||||
@@ -172,6 +175,15 @@ defmodule Mix.Tasks.Test do
|
||||
def run(args) do
|
||||
{opts, files} = OptionParser.parse!(args, strict: @switches)
|
||||
|
||||
if opts[:listen_on_stdin] do
|
||||
System.at_exit fn _ ->
|
||||
IO.gets(:stdio, "")
|
||||
Mix.shell.info "Restarting..."
|
||||
:init.restart()
|
||||
:timer.sleep(:infinity)
|
||||
end
|
||||
end
|
||||
|
||||
unless System.get_env("MIX_ENV") || Mix.env == :test do
|
||||
Mix.raise "\"mix test\" is running on environment \"#{Mix.env}\". If you are " <>
|
||||
"running tests along another task, please set MIX_ENV explicitly"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -346,12 +346,12 @@ defmodule Mix.DepTest do
|
||||
refute_received {:mix_shell, :info, ["* Getting" <> _]}
|
||||
|
||||
assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn ->
|
||||
Mix.Tasks.Deps.Check.run([])
|
||||
Mix.Tasks.Deps.Loadpaths.run([])
|
||||
end
|
||||
|
||||
Mix.ProjectStack.clear_cache()
|
||||
Mix.env(:prod)
|
||||
Mix.Tasks.Deps.Check.run([])
|
||||
Mix.Tasks.Deps.Loadpaths.run([])
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -496,7 +496,7 @@ defmodule Mix.DepTest do
|
||||
Mix.Tasks.Deps.run([])
|
||||
assert_received {:mix_shell, :info, ["* git_repo" <> _]}
|
||||
assert_received {:mix_shell, :info, [msg]}
|
||||
assert msg =~ "Ensure the parent dependency specifies a superset of the child one"
|
||||
assert msg =~ "Ensure you specify at least the same environments in :only in your dep"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -90,7 +90,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
File.rm_rf!("deps/git_repo/.git")
|
||||
|
||||
assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn ->
|
||||
Mix.Tasks.Deps.Check.run ["git_repo"]
|
||||
Mix.Tasks.Deps.Loadpaths.run ["git_repo"]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -126,7 +126,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
Code.delete_path("_build/dev/lib/git_repo/ebin")
|
||||
|
||||
# Deps on Git repo loads it automatically on compile
|
||||
Mix.Task.reenable "deps.check"
|
||||
Mix.Task.reenable "deps.loadpaths"
|
||||
Mix.Tasks.Deps.Compile.run ["deps_on_git_repo"]
|
||||
assert File.exists?("_build/dev/lib/deps_on_git_repo/ebin")
|
||||
end
|
||||
@@ -243,7 +243,7 @@ defmodule Mix.Tasks.DepsGitTest do
|
||||
# Update the lock and now we should get an error
|
||||
Mix.Dep.Lock.write %{git_repo: {:git, fixture_path("git_repo"), last, []}}
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
# Flush the errors we got, move to a clean slate
|
||||
|
||||
@@ -121,13 +121,13 @@ defmodule Mix.Tasks.DepsTest do
|
||||
end
|
||||
end
|
||||
|
||||
## deps.check
|
||||
## deps.loadpaths
|
||||
|
||||
test "checks list of dependencies and their status with success" do
|
||||
Mix.Project.push SuccessfulDepsApp
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -136,7 +136,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, ["* ok (https://github.com/elixir-lang/ok.git)"]}
|
||||
@@ -160,7 +160,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
File.rm_rf("_build")
|
||||
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/ok/priv/sample")
|
||||
|
||||
@@ -174,7 +174,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Project.pop
|
||||
Mix.Project.push SuccessfulDepsApp
|
||||
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
refute to_charlist(Path.expand("_build/dev/lib/ok/ebin/")) in :code.get_path
|
||||
assert File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
@@ -185,7 +185,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Project.pop
|
||||
Mix.Project.push SuccessfulDepsApp
|
||||
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
refute File.exists?("_build/dev/lib/ok/ebin/ok.app")
|
||||
assert File.exists?("_build/dev/lib/sample/ebin/sample.app")
|
||||
end
|
||||
@@ -238,6 +238,21 @@ defmodule Mix.Tasks.DepsTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "unlocks filtered deps", context do
|
||||
Mix.Project.push DepsApp
|
||||
in_tmp context.test, fn ->
|
||||
Mix.Dep.Lock.write %{git_repo: "abcdef", another: "hash", another_one: "hash"}
|
||||
Mix.Tasks.Deps.Unlock.run ["--filter", "another"]
|
||||
assert Mix.Dep.Lock.read == %{git_repo: "abcdef"}
|
||||
output = """
|
||||
Unlocked deps:
|
||||
* another
|
||||
* another_one
|
||||
"""
|
||||
assert_received {:mix_shell, :info, [^output]}
|
||||
end
|
||||
end
|
||||
|
||||
## Deps environment
|
||||
|
||||
defmodule DepsEnvApp do
|
||||
@@ -364,7 +379,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
assert_received {:mix_shell, :error, [" the dependency git_repo in mix.exs is overriding a child dependency" <> _]}
|
||||
|
||||
@@ -385,7 +400,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, [" different specs were given for the git_repo app:" <> _ = msg]}
|
||||
@@ -416,7 +431,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, [" the dependency git_repo 0.1.0" <> _ = msg]}
|
||||
@@ -447,7 +462,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Get.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
assert_received {:mix_shell, :error, [" the dependency git_repo in mix.exs is overriding" <> _]}
|
||||
@@ -503,7 +518,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
assert_raise Mix.Error, fn ->
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
end
|
||||
|
||||
receive do
|
||||
@@ -523,7 +538,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
File.mkdir_p!("_build/dev/lib/ok/ebin")
|
||||
manifest_data = :erlang.term_to_binary({:v1, "the_future", :scm})
|
||||
@@ -535,8 +550,8 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Tasks.Deps.run []
|
||||
assert_received {:mix_shell, :info, [^msg]}
|
||||
|
||||
# deps.check will automatically recompile it
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
# deps.loadpaths will automatically recompile it
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
Mix.Tasks.Deps.run []
|
||||
refute_received {:mix_shell, :info, [^msg]}
|
||||
@@ -548,7 +563,7 @@ defmodule Mix.Tasks.DepsTest do
|
||||
|
||||
in_fixture "deps_status", fn ->
|
||||
Mix.Tasks.Deps.Compile.run []
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
File.mkdir_p!("_build/dev/lib/ok/ebin")
|
||||
manifest_data = :erlang.term_to_binary({:v1, System.version, :scm})
|
||||
@@ -560,8 +575,8 @@ defmodule Mix.Tasks.DepsTest do
|
||||
Mix.Tasks.Deps.run []
|
||||
assert_received {:mix_shell, :info, [^msg]}
|
||||
|
||||
# deps.check will automatically recompile it
|
||||
Mix.Tasks.Deps.Check.run []
|
||||
# deps.loadpaths will automatically recompile it
|
||||
Mix.Tasks.Deps.Loadpaths.run []
|
||||
|
||||
Mix.Tasks.Deps.run []
|
||||
refute_received {:mix_shell, :info, [^msg]}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -104,6 +104,84 @@ defmodule Mix.Tasks.TestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "--listen-on-stdin: runs tests after input" do
|
||||
in_fixture "test_stale", fn ->
|
||||
port = mix_port(~w[test --stale --listen-on-stdin])
|
||||
|
||||
assert receive_until_match(port, "seed", []) =~ "2 tests"
|
||||
|
||||
:erlang.port_command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "No stale tests.", []) =~ "Restarting..."
|
||||
end
|
||||
end
|
||||
|
||||
test "--listen-on-stdin: does not exit on compilation failure" do
|
||||
in_fixture "test_stale", fn ->
|
||||
File.write!("lib/b.ex", """
|
||||
defmodule B do
|
||||
def f, do: error_not_a_var
|
||||
end
|
||||
""")
|
||||
|
||||
port = mix_port(~w[test --listen-on-stdin])
|
||||
|
||||
assert receive_until_match(port, "error", []) =~ "lib/b.ex"
|
||||
|
||||
File.write!("lib/b.ex", """
|
||||
defmodule B do
|
||||
def f, do: A.f
|
||||
end
|
||||
""")
|
||||
|
||||
Port.command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "seed", []) =~ "2 tests"
|
||||
|
||||
File.write!("test/b_test_stale.exs", """
|
||||
defmodule BTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "f" do
|
||||
assert B.f() == error_not_a_var
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
Port.command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "undefined function error_not_a_var", []) =~ "test/b_test_stale.exs"
|
||||
|
||||
File.write!("test/b_test_stale.exs", """
|
||||
defmodule BTest do
|
||||
use ExUnit.Case
|
||||
|
||||
test "f" do
|
||||
assert B.f() == :ok
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
Port.command(port, "\n")
|
||||
|
||||
assert receive_until_match(port, "seed", []) =~ "2 tests"
|
||||
end
|
||||
end
|
||||
|
||||
defp receive_until_match(port, expected, acc) do
|
||||
receive do
|
||||
{^port, {:data, charlist}} ->
|
||||
string = to_string(charlist)
|
||||
acc = [acc | string]
|
||||
|
||||
if string =~ expected do
|
||||
IO.iodata_to_binary(acc)
|
||||
else
|
||||
receive_until_match(port, expected, acc)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp set_all_mtimes(time \\ {{2010, 1, 1}, {0, 0, 0}}) do
|
||||
Enum.each(Path.wildcard("**", match_dot: true), &File.touch!(&1, time))
|
||||
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
|
||||
|
||||
@@ -14,7 +14,7 @@ defmodule Mix.UmbrellaTest do
|
||||
|
||||
# Ensure we can compile and run checks
|
||||
Mix.Task.run "deps.compile"
|
||||
Mix.Task.run "deps.check"
|
||||
Mix.Task.run "deps.loadpaths"
|
||||
Mix.Task.run "compile", ["--verbose"]
|
||||
|
||||
assert_received {:mix_shell, :info, ["==> bar"]}
|
||||
@@ -364,6 +364,53 @@ defmodule Mix.UmbrellaTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "reconsolidates using umbrella parent information on shared _build" do
|
||||
in_fixture("umbrella_dep/deps/umbrella", fn ->
|
||||
File.write!("apps/bar/lib/bar.ex", """
|
||||
defprotocol Bar do
|
||||
def bar(arg)
|
||||
end
|
||||
defimpl Bar, for: List do
|
||||
def bar(list), do: list
|
||||
end
|
||||
""")
|
||||
|
||||
Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build"], fn _ ->
|
||||
Mix.Task.run("compile.protocols")
|
||||
refute Code.ensure_loaded?(Bar)
|
||||
end)
|
||||
|
||||
Mix.Project.in_project(:umbrella, ".", fn _ ->
|
||||
Mix.Task.run("compile.protocols")
|
||||
Mix.Task.run("app.start")
|
||||
assert Protocol.consolidated?(Bar)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
test "reconsolidates using umbrella child information on shared _build" do
|
||||
in_fixture("umbrella_dep/deps/umbrella", fn ->
|
||||
File.write!("apps/bar/lib/bar.ex", """
|
||||
defprotocol Bar do
|
||||
def foo(arg)
|
||||
end
|
||||
defimpl Bar, for: List do
|
||||
def foo(list), do: list
|
||||
end
|
||||
""")
|
||||
|
||||
Mix.Project.in_project(:umbrella, ".", fn _ ->
|
||||
Mix.Task.run("compile.protocols")
|
||||
end)
|
||||
|
||||
# Emulate the dependency being removed
|
||||
Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build", deps: []], fn _ ->
|
||||
File.rm_rf "../../_build/dev/lib/bar"
|
||||
Mix.Task.run("compile.protocols")
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defmodule Selective do
|
||||
def project do
|
||||
[apps_path: "apps",
|
||||
|
||||
@@ -136,6 +136,15 @@ defmodule MixTest.Case do
|
||||
env: envs) |> elem(0)
|
||||
end
|
||||
|
||||
def mix_port(args, envs \\ []) when is_list(args) do
|
||||
:erlang.open_port({:spawn_executable, elixir_executable()}, [
|
||||
{:args, ["-r", mix_executable(), "--" | args]},
|
||||
{:env, envs},
|
||||
:use_stdio,
|
||||
:stderr_to_stdout
|
||||
])
|
||||
end
|
||||
|
||||
defp mix_executable do
|
||||
Path.expand("../../../bin/mix", __DIR__)
|
||||
end
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{application, elixir,
|
||||
[{description, "elixir"},
|
||||
{vsn, "1.3.0"},
|
||||
{vsn, "1.3.2"},
|
||||
{modules, [
|
||||
elixir
|
||||
]},
|
||||
|
||||
Reference in New Issue
Block a user