Compare commits

..
49 Commits
Author SHA1 Message Date
José Valim 56f1037973 Release v1.3.2 2016-07-15 18:49:26 +02:00
Alex e5fcef3832 Make sure to return nil if enumerable halts
Fix issue #5004

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-15 10:55:47 +02:00
José Valim 9081a7aad2 Update VERSION 2016-07-13 00:15:40 +02:00
Eric Meadows-Jönsson 328f581ef5 Add MIX_NO_DEPS env var for disabling dep loading
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-12 17:51:37 +02:00
Eksperimental aa695875fe Standardize mix escript task's shortdoc (#4984)
Correct verb tense, and remove trailing period.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-12 17:51:37 +02:00
Aleksei Magusev aa039ce9f9 Fix edit script for maps when there are no joint elements
Closes #4977.
2016-07-11 01:19:30 +02:00
Eric Entin 2fdf96e747 The else clause in with supports guards (#4960)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-07 20:26:52 +02:00
José Valim 494bded12a deps.check -> deps.loadpaths
deps.loadpaths was public API that Nerves, in
particular, depended on. This commit brings
back the deps.loadpaths task, replacing the
incorrect deps.check that should not be required
by Nerves.
2016-07-05 21:07:11 +02:00
José Valim 2b0fc529ee Improve error messages for :only conflict
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-04 20:58:01 +02:00
James Fish aaa775a3bf Fix @macrocallback spec translation
Signed-off-by: James Fish <james@fishcakez.com>
2016-07-04 18:21:25 +01:00
Andrea Leopardi 6647b00fcf Fix the spec for OptionParser.parse_head!/2
Signed-off-by: Andrea Leopardi <an.leopardi@gmail.com>
2016-07-04 00:04:49 +02:00
Andrea Leopardi d149c4c1e0 Polish and fix docs for OptionParser (#4924)
I also

* fixed some style in just a couple of lines of code
* renamed a couple of variables for clarity

Signed-off-by: Andrea Leopardi <an.leopardi@gmail.com>
2016-07-04 00:03:45 +02:00
Justin Schneck 58810bd33c add filter option to deps.unlock (#4932)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-03 22:08:09 +02:00
sunboshan 050cb45664 Fix typespec for Kernel.pop_in/2 (#4931)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-03 22:06:52 +02:00
Eric Entin 7f59aeeb6f mix test --listen-on-stdin will no longer exit on failure (#4930)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-03 19:12:53 +02:00
José Valim e2abd35b3c Ensure missing protocol dependencies are discarded, closes #4842
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-03 00:01:52 +02:00
Aleksei Magusev cc35c8fc22 Actually hide to_char_list/1 2016-07-02 00:34:33 +03:00
Eric Entin 2718631eb6 mix test --listen-on-stdin (#4921)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-07-01 21:02:04 +02:00
José Valim 9d9aa8bff8 Disable --warnings-as-errors for deps
See #4913
2016-06-30 22:26:35 +01:00
Eric Meadows-Jönsson 65aee00809 Add IS_DEP=1 env var when compiling make deps
When IS_DEP=1 erlang.mk disables warnings as errors.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-30 22:22:58 +01:00
Eric Meadows-Jönsson 9f028834d7 Reenable dialyzer reaise test
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-30 22:18:46 +01:00
Eric Meadows-Jönsson 44ef53ec2b Allow custom fields in Exception.t typespec
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-30 22:18:39 +01:00
Eric Meadows-Jönsson d1290d62db Fix struct/0 typespec
Closes #4915.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-30 22:18:32 +01:00
Mike Stok 301aa6eddc keep tokenizer's column counts in sync for numbers with _ characters
For providing better feedback in credo and tools like it we should be
able to map a token back to its original source.  This makes sure that
`_` characters in numbers are properly accounted for so that they stay
in sync after we've encountered something like 123_456_789.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-30 10:31:13 +01:00
José Valim 098b2d7cfd Ensure Access.at/1 properly pop list elements, closes #4911
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-30 10:31:10 +01:00
José Valim d92b7a331b Traverse dependnecies when consolidate umbrella protocols, closes #4842
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-29 01:21:18 +01:00
José Valim 8930ea7598 Do not attempt to load modules that have not been required, closes #4900 2016-06-29 01:20:45 +01:00
José Valim 01a1acd27b Release v1.3.1 2016-06-28 10:52:54 +01:00
José Valim 127691d4ad Ensure structs can be expanded in dynamic module names, closes #4894
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-28 10:42:34 +01:00
José Valim e848e87f52 Do not load modules for xref purposes, instead use BEAM info
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-28 01:00:26 +02:00
Daniel Perez bcadd96e31 Revert mix do to enforce space after comma (#4893)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-27 22:05:52 +02:00
Eric Entin 99a0bfa074 Skip/fix tests on OTP 19 (#4855)
* Callback-related tests use beam AST to find callbacks

* Fix invalid function call warning on OTP 19

* Skip dialyzer-related tests on OTP 19

* Fix IEx h helper for OTP 19

* Improve IEx h helper test for multi-arity callbacks

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-27 00:02:43 +02:00
Andrea Leopardi 5d00232633 Fix an assertion in Kernel.TypespecTest
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-26 19:34:50 +02:00
Andrea Leopardi c319bc450e Support module attributes in remote types (#4891)
For example, @for.t in protocols or similar use cases.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-26 19:34:46 +02:00
José Valim a9ed654ae2 Update CHANGELOG 2016-06-25 01:09:21 +02:00
Aleksei Magusev 6dacf53268 Fix Kernel.LexicalTracker not to discard alias directives on import
Closes #4871.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-25 01:05:46 +02:00
Aleksei Magusev 5e22b12272 Validate application properties before altering them
Closes #4873.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-25 01:05:26 +02:00
José Valim d4501893c7 Add import_file_if_available, closes #4878
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-25 00:48:02 +02:00
Benjamin Falk 744859ee42 Generate error for no pid given to Genserver.reply (#4876)
Prior to this change it is possible to send any two-pair tuple to
`GenServer.reply/2` and it will happily return `:ok`, catching any
errors.  This leads to some hard to track-down bugs if you pass
it the wrong term.

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-25 00:40:56 +02:00
Henrik Nyh 4596baab3f Add IEx.Helpers.import_if_available/2 (#4877)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-25 00:40:46 +02:00
Eksperimental c065041263 Add missing backquote in Module docs (#4866)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-23 23:44:50 +02:00
Daniel Perez 95bff23ede Fix h helper for binary operators, closes #4859 (#4860)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-23 10:11:06 +02:00
Marcus Gartner 0bc04195f2 Clarify Enum.group_by/3 deprecation message (#4852)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-22 19:02:04 +02:00
José Valim d195b75aa3 deps.check does not check archives 2016-06-22 10:54:28 +02:00
José Valim dc06a18287 Reenable deps.check on loadpaths dry-run
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-22 10:48:52 +02:00
José Valim 54c0574be2 Explicitly announce structs
Before this patch, we could announce a struct too early,
only when __struct__/0 was defined (and not __struct__/1)
leading to deadlocks in the compiler. We fixed the bug and
made struct announcements to avoid future bugs.

Closes #4844

Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-22 10:05:13 +02:00
Daniel Perez 8d50e1fa95 Improve with/else error message on invalid form (#4847)
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-22 09:33:28 +02:00
José Valim e70d9723eb Check for proper Makefile when compiling on Windows, closes #4841 2016-06-21 22:17:35 +02:00
José Valim 3ce029615d Do not expect stacktraces to be always present, closes #4840
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
2016-06-21 20:27:01 +02:00
65 changed files with 868 additions and 348 deletions
+61
View File
@@ -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
+1 -1
View File
@@ -1 +1 @@
1.3.0
1.3.2
+8 -1
View File
@@ -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
+6 -5
View File
@@ -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)
+5 -1
View File
@@ -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}
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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
+6 -8
View File
@@ -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
+18 -1
View File
@@ -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
+7
View File
@@ -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
+1 -1
View File
@@ -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
+98 -73
View File
@@ -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
View File
@@ -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}
]}.
+6 -1
View File
@@ -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]).
-12
View File
@@ -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
+25 -17
View File
@@ -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
+1 -1
View File
@@ -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.
+3 -1
View File
@@ -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,
+1 -1
View File
@@ -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),
+37 -28
View File
@@ -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
+13 -5
View File
@@ -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}]}}.
+14 -11
View File
@@ -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
+2
View File
@@ -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),
+3 -2
View File
@@ -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
+18 -4
View File
@@ -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
+10
View File
@@ -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}
+26 -18
View File
@@ -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
+7 -1
View File
@@ -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__)
+5 -4
View File
@@ -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(":+"),
+11
View File
@@ -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
+2
View File
@@ -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
+28
View File
@@ -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
+5 -6
View File
@@ -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
+17 -5
View File
@@ -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
-1
View File
@@ -70,4 +70,3 @@ defmodule IEx.Case do
|> String.trim
end
end
+6 -6
View File
@@ -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
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+2 -1
View File
@@ -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
+4 -4
View File
@@ -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])
+2 -1
View File
@@ -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
+26 -10
View File
@@ -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
+10 -8
View File
@@ -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
+22 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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.
+2 -1
View File
@@ -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
+10 -5
View File
@@ -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
+13 -1
View File
@@ -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"
+23 -6
View File
@@ -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,
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+32 -17
View File
@@ -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]}
+3 -7
View File
@@ -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
+78
View File
@@ -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
+25 -4
View File
@@ -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
+48 -1
View File
@@ -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",
+9
View File
@@ -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
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.3.0"},
{vsn, "1.3.2"},
{modules, [
elixir
]},