Compare commits

...
47 Commits
Author SHA1 Message Date
José Valim 7533e56c1b Release v1.11.2 2020-11-03 15:23:56 +01:00
José Valim 3f083795b6 Do not trace protocol impl checks
A more complete solution is found in master but this
approach is less disruptive in the v1.11 branch.
2020-11-02 11:35:51 +01:00
José Valim 1bbc959d1a Prune tracers when fetching __ENV__ inside functions, closes #10466 2020-10-30 13:30:17 +01:00
José Valim 1603f21b0a Fix race condition when loading test helper, closes #10464 2020-10-29 19:29:06 +01:00
José Valim 02c3957667 Clarify wording about siblings in umbrella 2020-10-22 10:53:14 +02:00
José Valim eeab9f5aa1 Ensure manually set tracers are kept on compilation
Closes #10451.
2020-10-22 08:50:10 +02:00
José Valim 3f3e987a23 Handle aliases with code recursion
It addresses a regression introduced in 97e8b0242d.

Closes https://github.com/nerves-project/nerves/issues/557.
2020-10-20 20:33:58 +02:00
José Valim 53c8b27ee2 Fix bootstrap issue in Code module 2020-10-17 20:10:26 +02:00
José Valim 3550dd10af Do not crash when getting docs for missing erts appdir
Closes #10437.
2020-10-17 19:46:51 +02:00
José Valim 7e44b88122 Raise for is_record guards, closes #10436 2020-10-17 19:33:10 +02:00
José Valim 5c5259826f Release v1.11.1 2020-10-16 09:31:35 +02:00
Xavier Noria ccdefd904f Improves the docs of config/runtime.exs and Config.config_env/0 (#10414) 2020-10-16 09:23:13 +02:00
José Valim ff95a59ec0 Add :append to stream_mode, closes #10421 2020-10-16 09:22:44 +02:00
José Valim 4081590742 Add inet6 fallback to Mix usage of httpc, closes #10423 2020-10-16 09:22:16 +02:00
José Valim 693df5fe8c Properly handle tags inside typespec in Erlang docs, closes #10430 2020-10-16 09:22:11 +02:00
José Valim b4605374b5 Do not deadlock Logger if handler crashes on sync mode, closes #10420 2020-10-12 09:27:32 +02:00
Eksperimental 36ec08e0c8 Fix warning message in app requirement (#10417)
It was missing a closing square bracket.

Previously it was saying

  3. In case you don't want to add a requirement to :crypto, you may optionally skip this warning by adding [xref: [exclude: :crypto] to your "def project" in mix.exs

Now

  3. In case you don't want to add a requirement to :crypto, you may optionally skip this warning by adding [xref: [exclude: :crypto]] to your "def project" in mix.exs
2020-10-11 00:15:59 +02:00
José Valim ae0ac9b3c9 Ignore tracers if lexical tracker is dead or explicitly nil 2020-10-11 00:15:41 +02:00
Milton Mazzarri e3479831ba Do not show warning when super is in GenServer.child_spec/1 (#10418)
The original goal[1] was to deprecate the usage of `super` on every `GenServer`
callback, but `child_spec/1` is not a callback, it's a default implementation
that can be re-implemented.

This commit removes the _deprecation warning_ when someone use `super` in
`GenServer.child_spec/1`.

Fixes: #10415

[1] https://github.com/elixir-lang/elixir/commit/c024b0eeb2dc69a759fe5ceeb1c8216d58ae9386#diff-ed0344c13ed25389035f337d94902894R301-R316
2020-10-11 00:15:40 +02:00
José Valim ffbcc004ec Do not crash when :reduce is set to nil in comprehensions, closes #10411 2020-10-08 15:37:53 +02:00
José Valim e0d61c7875 Fix order dependent tests in logger suite 2020-10-08 09:43:51 +02:00
Michał Łępicki b2059031a6 Fix returning errors in Module.Types.Expr.of_expr (#10410) 2020-10-08 09:37:13 +02:00
José Valim ed2829d8ea Keep warnings when traversing clauses 2020-10-07 23:53:23 +02:00
José Valim 081f99cd37 Consider targets when filtering apps, closes #10405 2020-10-06 19:31:36 +02:00
José Valim 7837cc5c59 Release v1.11.0 2020-10-06 15:43:09 +02:00
Daniil Fedotov b96fc39422 Restrict Logger.configure on config_change callback (#10388)
The `config_change` callback is called from `application_controller` process,
which is receiving env updates on releases. `Logger.configure` does a blocking
call to the `Logger` process, which calls `Application.put_env`

This causes a crash on logger config update.

The fix is to call `Logger.configure` only for the config that have not yet
been persisted.
2020-10-03 08:33:31 +02:00
Waldemar Rachwał e1cb898198 Take --dot-iex into account after shell respawn (#10384) 2020-10-01 20:36:14 +02:00
Dave Lucia 88e2894925 Enum.max_by/3 and Enum.min_by/3 dialyzer fixes (#10383) 2020-10-01 13:23:19 +02:00
José Valim f7cb5ae053 Fix inference of open maps with non-singleton keys
Closes #10371.
2020-09-29 13:11:22 +02:00
José Valim b4845e6854 Update iso.ex 2020-09-25 13:34:38 +02:00
José Valim 0f1200f99b Add a note on async_stream+take 2020-09-25 13:33:15 +02:00
José Valim cec1a7190b Make config/runtime.exs deep merge, closes #10365 2020-09-25 13:32:54 +02:00
Gabriel Alves 53d1b98a05 Raise instead of crashing on when trying to use list operators in guards (#10363) 2020-09-23 14:30:39 +02:00
José Valim 4828f11cd2 Keep invoking loadconfig on cli boot 2020-09-22 15:47:35 +02:00
José Valim 501271cad0 Make replace and replace! in Keyword consistent, closes #10345 2020-09-18 16:13:16 +02:00
José Valim fd4a6ad5cf Handle empty map in other places, closes #10343 2020-09-18 10:53:56 +02:00
José Valim 196aab87ca Align doc chunks return with EEP 48 2020-09-18 10:41:39 +02:00
José Valim 59b266506a Append space on middle and start expr if necessary, closes #10340 2020-09-17 17:11:28 +02:00
José Valim 1c8e625d77 Speed up compilation with multiple describe 2020-09-17 14:24:54 +02:00
José Valim f9d7adbfdf Introduce register_test/6 to speed up test definition 2020-09-17 10:57:00 +02:00
José Valim f1721c8a58 Reduce amount of injected code in case 2020-09-17 10:29:40 +02:00
José Valim 57abca4c16 Properly handle reduce in types, closes #10224 2020-09-14 23:02:07 +02:00
José Valim 8e9ece0d62 More typing improvements (#10333)
1. Move push_expr_stack to unify and keep helpers typing free

2. Add get_var! when accessing variables so it helps us find
   variables that have not been properly processed

3. Properly process all variables found missing on step 2
2020-09-14 22:45:34 +02:00
José Valim 54ba096d34 Remove boolean and number as builtin types (#10329) 2020-09-14 22:45:34 +02:00
José Valim 6042fd0bcc Infer -> Unify 2020-09-14 22:45:34 +02:00
Eksperimental 475b73b46f Use proper casing in CHANGELOG (#10331) 2020-09-14 15:24:51 +02:00
José Valim 3cf6069ad9 Fix typo in CHANGELOG 2020-09-14 09:43:19 +02:00
61 changed files with 847 additions and 438 deletions
+49 -8
View File
@@ -14,7 +14,7 @@ Elixir v1.11 builds on top of the recently added compilation tracers to track ap
These two conditions may seem contradictory. After all, if a module is available, it must have come from a dependency. This is not true in two scenarios:
* Modules from Elixir and Erlang/OTP are always available - even if their applications are not explicitly listed as a dependency
* Modules from Elixir and Erlang/OTP are always available - even if their applications are not listed as a dependency
* In an umbrella project, because all child applications are compiled within the same VM, you may have a module from a sibling project available, even if you don't depend on said sibling
@@ -36,7 +36,7 @@ on :ssl. To fix this, you must do one of:
to your "def project" in mix.exs
```
This comes with extra benefits in umbrella projects, as it requires child applications to explicitly list their dependencies, completely rejecting cyclic dependencies between siblings.
This comes with extra benefits in umbrella projects, as it requires applications to depend on the siblings they depend on, which will fail if there are any cyclic dependencies.
## Compiler checks: data constructors
@@ -243,7 +243,46 @@ The Calendar module ships with a new `Calendar.strftime/3` function, which provi
Mix also includes two new tasks: `mix app.config`, for application runtime configuration, and `mix test.coverage`, which generates aggregated coverage reports for umbrella projects and for test suites partitioned across processes.
## v1.11.0-rc.0 (2020-09-14)
## v1.11.2 (2020-11-03)
### 1. Bug fixes
#### Elixir
* [Code] Do not crash when getting docs for missing `erts` appdir
* [Kernel] Raise meaningful error if `:erlang.is_record` is used in guards
* [Kernel] Prune tracers when fetching `__ENV__` inside functions
#### Mix
* [mix] Fix regression where aliases could not call themselves recursively
* [mix compile] Do not discard tracers that are set programatically
## v1.11.1 (2020-10-16)
### 1. Bug fixes
#### Elixir
* [Code] Ignore tracers if lexical tracker is dead or explicitly nil when evaling code with an environment
* [GenServer] Do not show warning when using `super` in `GenServer.child_spec/1`
* [Kernel] Do not crash when :reduce is set to `nil` in comprehensions
* [Kernel] Fix a scenario where undefined function warnings were not being emitted
#### IEx
* [IEx.Helpers] Properly handle tags inside typespec when showing Erlang docs
#### Logger
* [Logger] Do not deadlock Logger if handler crashes on sync mode
#### Mix
* [Mix] Add inet6 fallback to Mix usage of httpc
* [mix compile.app] Do not list apps that do not match the current target
## v1.11.0 (2020-10-06)
### 1. Enhancements
@@ -284,6 +323,7 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
* [ExUnit] Add `@tag :tmp_dir` support to ExUnit. The temporary directory is automatically created and pruned before each test
* [ExUnit] Add file and line to ExUnit's `--trace`
* [ExUnit.Assertion] Allow receive timeouts to be computed at runtime
* [ExUnit.Case] Add `register_test/6` to speed up compilation of custom tests
* [ExUnit.Doctest] Allow users to add tags to doctests
#### IEx
@@ -305,17 +345,17 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
* [mix] Allow requirements for a Mix task to be listed via the `@requirements` module attribute
* [mix] Allow optional dependencies to be defined in `:extra_applications` and `:applications`
* [mix app.config] Add new `mix app.config` task that compiles applications and loads runtime configuration
* [mix archive.install] Support `--repo` option on hex packages
* [mix archive.install] Support `--repo` option on Hex packages
* [mix compile] Support the `__mix_recompile__?/0` callback for custom behaviour on when Mix should recompile a given module
* [mix compile.elixir] Mark modules for path dependencies as "Export dependencies" if they changed but their public interface is the same
* [mix compile.elixir] Track application boundaries in the Elixir compiler. If you invoke code from Erlang or Elixir standard libraries and you don't depend on the proper applications, a warning will be emitted. A warning will also be emitted if you invoke code from an umbrella sibling that you don't depend on - effectively forbidding cyclic dependencies between apps
* [mix deps] Sort the dependencies alphabetically before printing
* [mix deps] Use `origin/HEAD` as the default git ref in dependencies
* [mix deps] Use `origin/HEAD` as the default Git ref in dependencies
* [mix deps] Redact Git `username`/`password` in output log
* [mix deps] Support rebar3's `git_subdir` resource type
* [mix deps.compile] Allow local deps to be skipped on `mix deps.compile`
* [mix deps.unlock] Print which dependencies get unlocked when using the `--unused` flag
* [mix escript.install] Support `--repo` option on hex packages
* [mix escript.install] Support `--repo` option on Hex packages
* [mix new] Add `@impl` to application generated by `mix new --sup`
* [mix release] Enable overriding `sys.config` location via `RELEASE_SYS_CONFIG` env var
* [mix release] Boot a release under configuration in interactive mode and then swap to embedded mode (if running on Erlang/OTP 23+)
@@ -343,6 +383,7 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
* [Kernel] Properly parse `&//2` (i.e. the capture of the division operator)
* [Kernel] Raise `CompileError` when trying to define reserved types
* [Kernel] Improve compiler error message when using `|` in a `def` signature
* [Kernel] Improve error message when trying to use invalid list operators in guards
* [Kernel.SpecialForms] Add `|/2` to the list of special forms to avoid inconsistent behaviour on overrides
* [Keyword] Enforce keys to be atoms in `Keyword.keys/1`
* [Record] Keep lexical ordering when creating records
@@ -359,7 +400,7 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
#### IEx
* [IEx] Fix tokenizer emitting repeated warnings in the REPL
* [IEx] Ensure `dot_iex_path` is preserved when restarting the evaluator
* [IEx] Ensure `--dot-iex` is preserved when restarting the evaluator and after shell respawn
* [IEx.Pry] Ensure `IEx.pry` can be triggered more than twice when invoked from the same process
#### Mix
@@ -397,7 +438,7 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
* [Supervisor] Deprecate `Supervisor.start_child/2` and `Supervisor.terminate_child/2` in favor of `DynamicSupervisor`
* [Supervisor.Spec] Deprecate `Supervisor.Spec.worker/3` and `Supervisor.Spec.supervisor/3` in favor of the new typespecs
* [System] Deprecate `System.stracktrace/0` in favor of `__STACKTRACE__`
* [System] Deprecate `System.stacktrace/0` in favor of `__STACKTRACE__`
#### Mix
+1 -1
View File
@@ -1 +1 @@
1.11.0-rc.0
1.11.2
+16 -12
View File
@@ -65,10 +65,10 @@ defmodule EEx.Tokenizer do
error
{:ok, expr, new_line, new_column, rest} ->
key =
{key, expr} =
case :elixir_tokenizer.tokenize(expr, 1, file: "eex", check_terminators: false) do
{:ok, tokens} -> token_key(tokens)
{:error, _, _, _} -> :expr
{:ok, tokens} -> token_key(tokens, expr)
{:error, _, _, _} -> {:expr, expr}
end
{rest, new_line, new_column, buffer} =
@@ -122,38 +122,42 @@ defmodule EEx.Tokenizer do
end
# Receives tokens and check if it is a start, middle or an end token.
defp token_key(tokens) do
defp token_key(tokens, expr) do
case {tokens, Enum.reverse(tokens)} do
{[{:end, _} | _], [{:do, _} | _]} ->
:middle_expr
{:middle_expr, expr}
{_, [{:do, _} | _]} ->
:start_expr
{:start_expr, maybe_append_space(expr)}
{_, [{:block_identifier, _, _} | _]} ->
:middle_expr
{:middle_expr, maybe_append_space(expr)}
{[{:end, _} | _], [{:stab_op, _, _} | _]} ->
:middle_expr
{:middle_expr, expr}
{_, [{:stab_op, _, _} | reverse_tokens]} ->
fn_index = Enum.find_index(reverse_tokens, &match?({:fn, _}, &1)) || :infinity
end_index = Enum.find_index(reverse_tokens, &match?({:end, _}, &1)) || :infinity
if end_index > fn_index do
:start_expr
{:start_expr, expr}
else
:middle_expr
{:middle_expr, expr}
end
{tokens, _} ->
case Enum.drop_while(tokens, &closing_bracket?/1) do
[{:end, _} | _] -> :end_expr
_ -> :expr
[{:end, _} | _] -> {:end_expr, expr}
_ -> {:expr, expr}
end
end
end
defp maybe_append_space([?\s]), do: [?\s]
defp maybe_append_space([h]), do: [h, ?\s]
defp maybe_append_space([h | t]), do: [h | maybe_append_space(t)]
defp closing_bracket?({closing, _}) when closing in ~w"( [ {"a, do: true
defp closing_bracket?(_), do: false
+11
View File
@@ -127,6 +127,17 @@ defmodule EExTest do
end
test "trim mode with no spaces" do
string = """
<%=if true do%>
this
<%else%>
that
<%end%>
"""
expected = "\n this\n"
assert_eval(expected, string, [], trim: true)
string = """
<%=cond do%>
<%false ->%>
+2 -2
View File
@@ -111,9 +111,9 @@ defmodule Behaviour do
end
end
defp __behaviour__doc_value(:none), do: nil
defp __behaviour__doc_value(:hidden), do: false
defp __behaviour__doc_value(%{"en" => doc}), do: doc
defp __behaviour__doc_value(:hidden), do: false
defp __behaviour__doc_value(_), do: nil
import unquote(__MODULE__)
end
+6
View File
@@ -8,6 +8,12 @@ defmodule Calendar.ISO do
applied for all time, consequently the dates give different results
before the year 1583 from when the Gregorian calendar was adopted.
Given this is the default calendar used by Elixir, it has one
difference compared to the ISO8601 specification in that it allows
a whitespace instead of `T` as a seperator between date and times
both when parsing and formatting. Strict formatting can be done
by using the `to_iso8601` found in `NaiveDateTime` and `DateTime`.
Note that while ISO 8601 allows times and datetimes to specify
24:00:00 as the zero hour of the next day, this notation is not
supported by Elixir.
+11 -3
View File
@@ -1348,7 +1348,7 @@ defmodule Code do
| {:error, :module_not_found | :chunk_not_found | {:invalid_chunk, binary}}
when annotation: :erl_anno.anno(),
beam_language: :elixir | :erlang | atom(),
doc_content: %{required(binary) => binary} | :none | :hidden,
doc_content: %{optional(binary) => binary} | :none | :hidden,
doc_element:
{{kind :: atom, function_name :: atom, arity}, annotation, signature, doc_content,
metadata},
@@ -1373,8 +1373,16 @@ defmodule Code do
:error ->
case :code.which(module) do
:preloaded ->
path = Path.join([:code.lib_dir(:erts), "doc", "chunks", "#{module}.chunk"])
fetch_docs_from_chunk(path)
# The erts directory is not necessarily included in releases
# unless it is listed as an extra application.
case :code.lib_dir(:erts) do
path when is_list(path) ->
path = Path.join([path, "doc", "chunks", "#{module}.chunk"])
fetch_docs_from_chunk(path)
{:error, _} ->
{:error, :chunk_not_found}
end
_ ->
{:error, :module_not_found}
+6 -3
View File
@@ -64,8 +64,8 @@ defmodule Config do
## config/runtime.exs
For runtime configuration, you can use the `config/runtime.exs` file.
It is executed after your Mix project is compiled and also before a
release (assembled with `mix release`) starts.
It is executed right before applications start in both Mix and releases
(assembled with `mix release`).
"""
@opts_key {__MODULE__, :opts}
@@ -160,7 +160,10 @@ defmodule Config do
end
@doc """
Returns the environemnt this configuration file is executed on.
Returns the environment this configuration file is executed on.
In Mix projects this function returns the environment this configuration
file is executed on. In releases, the environment when `mix release` ran.
This is most often used to execute conditional code:
+10 -14
View File
@@ -1593,7 +1593,11 @@ defmodule Enum do
defp max_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) != :lt)
@doc false
@spec max_by(t, (element -> any), (() -> empty_result)) :: element | empty_result
@spec max_by(
t,
(element -> any),
(() -> empty_result) | (element, element -> boolean) | module()
) :: element | empty_result
when empty_result: any
def max_by(enumerable, fun, empty_fallback)
when is_function(fun, 1) and is_function(empty_fallback, 0) do
@@ -1644,12 +1648,6 @@ defmodule Enum do
nil
"""
@spec max_by(
t,
(element -> any),
(element, element -> boolean) | module()
) :: element | empty_result
when empty_result: any
@spec max_by(
t,
(element -> any),
@@ -1767,7 +1765,11 @@ defmodule Enum do
defp min_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) != :gt)
@doc false
@spec min_by(t, (element -> any), (() -> empty_result)) :: element | empty_result
@spec min_by(
t,
(element -> any),
(() -> empty_result) | (element, element -> boolean) | module()
) :: element | empty_result
when empty_result: any
def min_by(enumerable, fun, empty_fallback)
when is_function(fun, 1) and is_function(empty_fallback, 0) do
@@ -1818,12 +1820,6 @@ defmodule Enum do
nil
"""
@spec min_by(
t,
(element -> any),
(element, element -> boolean) | module()
) :: element | empty_result
when empty_result: any
@spec min_by(
t,
(element -> any),
+1
View File
@@ -110,6 +110,7 @@ defmodule File do
@type stream_mode ::
encoding_mode()
| :append
| :trim_bom
| {:read_ahead, pos_integer | false}
| {:delayed_write, non_neg_integer, non_neg_integer}
+6 -2
View File
@@ -231,9 +231,13 @@ defmodule IO.ANSI.Docs do
types =
for {:li, _, lines} <- entries,
line <- lines,
do: ["#{indent} ", line, ?\n]
do: ["#{indent} ", traverse_erlang_html(line, indent <> " ", options), ?\n]
["#{indent}Typespecs:\n\n", types, ?\n]
if types != [] do
["#{indent}Typespecs:\n\n", types, ?\n]
else
[]
end
else
for {:li, _, lines} <- entries do
["#{indent} ", @bullet_text | handle_erlang_html_text(lines, indent <> " ", options)]
+2 -3
View File
@@ -23,9 +23,8 @@ defmodule Kernel.Typespec do
for {{:type, name, arity}, _, _, doc, _} <- docs do
case doc do
%{"en" => doc_string} -> {{name, arity}, doc_string}
:none -> {{name, arity}, nil}
# Hidden or unknown format are ignored
_ -> {{name, arity}, false}
:hidden -> {{name, arity}, false}
_ -> {{name, arity}, nil}
end
end
+14 -5
View File
@@ -668,10 +668,19 @@ defmodule Keyword do
@doc since: "1.11.0"
@spec replace(t, key, value) :: t
def replace(keywords, key, value) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{^key, _} -> [{key, value} | delete(keywords, key)]
false -> keywords
end
do_replace(keywords, key, value)
end
defp do_replace([{key, _} | keywords], key, value) do
[{key, value} | delete(keywords, key)]
end
defp do_replace([{_, _} = e | keywords], key, value) do
[e | do_replace(keywords, key, value)]
end
defp do_replace([], _key, _value) do
[]
end
@doc """
@@ -704,7 +713,7 @@ defmodule Keyword do
[e | replace!(keywords, key, value, original)]
end
defp replace!([], key, _value, original) when is_atom(key) do
defp replace!([], key, _value, original) do
raise(KeyError, key: key, term: original)
end
+6 -6
View File
@@ -6,7 +6,7 @@ defmodule Module.Types do
end
import Module.Types.Helpers
alias Module.Types.{Expr, Pattern, Infer}
alias Module.Types.{Expr, Pattern, Unify}
@doc false
def warnings(module, file, defs, no_warn_undefined, cache) do
@@ -57,7 +57,7 @@ defmodule Module.Types do
end
defp warnings_from_clause(args, guards, body, def_expr, stack, context) do
head_stack = push_expr_stack(def_expr, stack)
head_stack = Unify.push_expr_stack(def_expr, stack)
with {:ok, _types, context} <- Pattern.of_head(args, guards, head_stack, context),
{:ok, _type, context} <- Expr.of_expr(body, stack, context) do
@@ -300,9 +300,9 @@ defmodule Module.Types do
[
"incompatible types:\n\n ",
Infer.format_type(left, simplify_left?),
Unify.format_type(left, simplify_left?),
" !~ ",
Infer.format_type(right, simplify_right?),
Unify.format_type(right, simplify_right?),
"\n\n",
format_expr(expr, location),
traces,
@@ -352,7 +352,7 @@ defmodule Module.Types do
"where \"",
Macro.to_string(var),
"\" was given the type ",
Infer.format_type(type, simplify?),
Unify.format_type(type, simplify?),
hint,
" in:\n\n # ",
format_location(location),
@@ -518,8 +518,8 @@ defmodule Module.Types do
defp map_type?(_other), do: false
defp atom_type?(:atom), do: true
defp atom_type?(:boolean), do: true
defp atom_type?({:atom, _}), do: false
defp atom_type?({:union, union}), do: Enum.all?(union, &atom_type?/1)
defp atom_type?(_other), do: false
defp integer_type?(:integer), do: true
+60 -54
View File
@@ -2,7 +2,7 @@ defmodule Module.Types.Expr do
@moduledoc false
alias Module.Types.{Of, Pattern}
import Module.Types.{Helpers, Infer}
import Module.Types.{Helpers, Unify}
def of_expr(expr, %{context: stack_context} = stack, context) when stack_context != :expr do
of_expr(expr, %{stack | context: :expr}, context)
@@ -109,8 +109,7 @@ defmodule Module.Types.Expr do
# var
def of_expr(var, _stack, context) when is_var(var) do
{type, context} = new_var(var, context)
{:ok, type, context}
{:ok, get_var!(var, context), context}
end
# {left, right}
@@ -204,7 +203,7 @@ defmodule Module.Types.Expr do
stack = push_expr_stack(expr, stack)
with {:ok, _expr_type, context} <- of_expr(case_expr, stack, context),
:ok <- of_clauses(clauses, stack, context),
{:ok, context} <- of_clauses(clauses, stack, context),
do: {:ok, :dynamic, context}
end
@@ -213,7 +212,7 @@ defmodule Module.Types.Expr do
stack = push_expr_stack(expr, stack)
case of_clauses(clauses, stack, context) do
:ok -> {:ok, :dynamic, context}
{:ok, context} -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
end
end
@@ -225,29 +224,37 @@ defmodule Module.Types.Expr do
def of_expr({:try, _meta, [blocks]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
result =
each_ok(blocks, fn
{:rescue, clauses} ->
each_ok(clauses, fn
{:->, _, [[{:in, _, [var, _exceptions]}], body]} ->
{result, context} =
reduce_ok(blocks, context, fn
{:rescue, clauses}, context ->
reduce_ok(clauses, context, fn
{:->, _, [[{:in, _, [var, _exceptions]}], body]}, context = acc ->
{_type, context} = new_pattern_var(var, context)
of_expr_ok(body, stack, context)
{:->, _, [[var], body]} ->
with {:ok, context} <- of_expr_context(body, stack, context) do
{:ok, keep_warnings(acc, context)}
end
{:->, _, [[var], body]}, context = acc ->
{_type, context} = new_pattern_var(var, context)
of_expr_ok(body, stack, context)
with {:ok, context} <- of_expr_context(body, stack, context) do
{:ok, keep_warnings(acc, context)}
end
end)
{block, body} when block in @try_blocks ->
of_expr_ok(body, stack, context)
{block, body}, context = acc when block in @try_blocks ->
with {:ok, context} <- of_expr_context(body, stack, context) do
{:ok, keep_warnings(acc, context)}
end
{block, clauses} when block in @try_clause_blocks ->
{block, clauses}, context when block in @try_clause_blocks ->
of_clauses(clauses, stack, context)
end)
case result do
:ok -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
:error -> {:error, context}
end
end
@@ -255,33 +262,42 @@ defmodule Module.Types.Expr do
def of_expr({:receive, _meta, [blocks]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
result =
each_ok(blocks, fn
{:do, {:__block__, _, []}} ->
:ok
{result, context} =
reduce_ok(blocks, context, fn
{:do, {:__block__, _, []}}, context ->
{:ok, context}
{:do, clauses} ->
{:do, clauses}, context ->
of_clauses(clauses, stack, context)
{:after, [{:->, _meta, [head, body]}]} ->
{:after, [{:->, _meta, [head, body]}]}, context = acc ->
with {:ok, _type, context} <- of_expr(head, stack, context),
{:ok, _type, _context} <- of_expr(body, stack, context),
do: :ok
{:ok, _type, context} <- of_expr(body, stack, context),
do: {:ok, keep_warnings(acc, context)}
end)
case result do
:ok -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
:error -> {:error, context}
end
end
# for pat <- expr do expr end
def of_expr({:for, _meta, args} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
{clauses, [[{:do, block} | opts]]} = Enum.split(args, -1)
case reduce_ok(args, context, &for_clause(&1, stack, &2)) do
{:ok, _context} -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
with {:ok, context} <- reduce_ok(clauses, context, &for_clause(&1, stack, &2)),
{:ok, context} <- reduce_ok(opts, context, &for_option(&1, stack, &2)) do
if Keyword.has_key?(opts, :reduce) do
with {:ok, context} <- of_clauses(block, stack, context) do
{:ok, :dynamic, context}
end
else
with {:ok, _type, context} <- of_expr(block, stack, context) do
{:ok, :dynamic, context}
end
end
end
end
@@ -379,6 +395,13 @@ defmodule Module.Types.Expr do
do: {:ok, context}
end
defp for_clause({:<<>>, _, [{:<-, _, [pattern, expr]}]}, stack, context) do
# TODO: the compiler guarantees pattern is a binary but we need to check expr is a binary
with {:ok, _pattern_type, context} <- Pattern.of_pattern(pattern, stack, context),
{:ok, _expr_type, context} <- of_expr(expr, stack, context),
do: {:ok, context}
end
defp for_clause(list, stack, context) when is_list(list) do
reduce_ok(list, context, &for_option(&1, stack, &2))
end
@@ -399,17 +422,6 @@ defmodule Module.Types.Expr do
{:ok, context}
end
defp for_option({:do, [{:->, _, [pattern, body]}]}, stack, context) do
case Pattern.of_pattern(pattern, stack, context) do
{:ok, _pattern_type, context} -> of_expr_context(body, stack, context)
{:error, reason} -> {:error, reason}
end
end
defp for_option({:do, body}, stack, context) do
of_expr_context(body, stack, context)
end
defp with_clause({:<-, _, [left, expr]}, stack, context) do
{pattern, guards} = extract_head([left])
@@ -433,22 +445,23 @@ defmodule Module.Types.Expr do
end
defp with_option({:else, clauses}, stack, context) do
case of_clauses(clauses, stack, context) do
:ok -> {:ok, context}
{:error, reason} -> {:error, reason}
end
of_clauses(clauses, stack, context)
end
defp of_clauses(clauses, stack, context) do
each_ok(clauses, fn {:->, _meta, [head, body]} ->
reduce_ok(clauses, context, fn {:->, _meta, [head, body]}, context = acc ->
{patterns, guards} = extract_head(head)
with {:ok, _, context} <- Pattern.of_head(patterns, guards, stack, context),
{:ok, _expr_type, _context} <- of_expr(body, stack, context),
do: :ok
{:ok, _expr_type, context} <- of_expr(body, stack, context),
do: {:ok, keep_warnings(acc, context)}
end)
end
defp keep_warnings(context, %{warnings: warnings}) do
%{context | warnings: warnings}
end
defp extract_head([{:when, _meta, args}]) do
case Enum.split(args, -1) do
{patterns, [guards]} -> {patterns, flatten_when(guards)}
@@ -475,13 +488,6 @@ defmodule Module.Types.Expr do
end
end
defp of_expr_ok(expr, stack, context) do
case of_expr(expr, stack, context) do
{:ok, _type, _context} -> :ok
{:error, reason} -> {:error, reason}
end
end
defp new_pattern_var({:_, _meta, var_context}, context) when is_atom(var_context) do
{:dynamic, context}
end
-23
View File
@@ -25,16 +25,6 @@ defmodule Module.Types.Helpers do
def get_meta({_, meta, _}), do: meta
def get_meta(_other), do: []
@doc """
Push expression to stack.
The expression stack is used to give the context where a type variable
was refined when show a type conflict error.
"""
def push_expr_stack(expr, stack) do
%{stack | last_expr: expr}
end
@doc """
Like `Enum.reduce/3` but only continues while `fun` returns `{:ok, acc}`
and stops on `{:error, reason}`.
@@ -91,19 +81,6 @@ defmodule Module.Types.Helpers do
defp do_map_ok([], acc, _fun), do: {:ok, Enum.reverse(acc)}
@doc """
Like `Enum.each/2` but only continues while `fun` returns `:ok`
and stops on `{:error, reason}`.
"""
def each_ok([head | tail], fun) do
case fun.(head) do
:ok -> each_ok(tail, fun)
{:error, reason} -> {:error, reason}
end
end
def each_ok([], _fun), do: :ok
@doc """
Like `Enum.map_reduce/3` but only continues while `fun` returns `{:ok, elem, acc}`
and stops on `{:error, reason}`.
+21 -16
View File
@@ -6,17 +6,27 @@ defmodule Module.Types.Of do
@prefix quote(do: ...)
@suffix quote(do: ...)
alias Module.Types.Infer
alias Module.ParallelChecker
import Module.Types.Helpers
import Module.Types.Unify
@doc """
Handles open maps (with dynamic => dynamic).
"""
def open_map(args, stack, context, fun) do
with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
{:ok, {:map, pairs_to_unions(pairs, context) ++ [{:optional, :dynamic, :dynamic}]}, context}
pairs =
for {key, value} <- pairs, not has_unbound_var?(key, context) do
if singleton?(key, context) do
{key, value}
else
{key, to_union([value, :dynamic], context)}
end
end
triplets = pairs_to_unions(pairs, [], context) ++ [{:optional, :dynamic, :dynamic}]
{:ok, {:map, triplets}, context}
end
end
@@ -25,7 +35,7 @@ defmodule Module.Types.Of do
"""
def closed_map(args, stack, context, fun) do
with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
{:ok, {:map, pairs_to_unions(pairs, context)}, context}
{:ok, {:map, closed_to_unions(pairs, context)}, context}
end
end
@@ -37,10 +47,10 @@ defmodule Module.Types.Of do
end)
end
defp pairs_to_unions([{key, value}], _context), do: [{:required, key, value}]
defp closed_to_unions([{key, value}], _context), do: [{:required, key, value}]
defp pairs_to_unions(pairs, context) do
case Enum.split_with(pairs, fn {key, _value} -> Infer.has_unbound_var?(key, context) end) do
defp closed_to_unions(pairs, context) do
case Enum.split_with(pairs, fn {key, _value} -> has_unbound_var?(key, context) end) do
{[], pairs} -> pairs_to_unions(pairs, [], context)
{[_ | _], pairs} -> pairs_to_unions([{:dynamic, :dynamic} | pairs], [], context)
end
@@ -57,17 +67,17 @@ defmodule Module.Types.Of do
find_subtype_values(ahead, key, context) ++
find_subtype_values(behind, key, context)
pairs_to_unions(ahead, [{key, Infer.to_union(all_values, context)} | behind], context)
pairs_to_unions(ahead, [{key, to_union(all_values, context)} | behind], context)
end
defp pairs_to_unions([], acc, context) do
acc
|> Enum.sort(&Infer.subtype?(elem(&1, 0), elem(&2, 0), context))
|> Enum.sort(&subtype?(elem(&1, 0), elem(&2, 0), context))
|> Enum.map(fn {key, value} -> {:required, key, value} end)
end
defp find_subtype_values(pairs, key, context) do
for {pair_key, pair_value} <- pairs, Infer.subtype?(pair_key, key, context), do: pair_value
for {pair_key, pair_value} <- pairs, subtype?(pair_key, key, context), do: pair_value
end
defp find_matching_values([{key, value} | ahead], key, acc, values) do
@@ -153,16 +163,11 @@ defmodule Module.Types.Of do
true ->
with {:ok, type, context} <- fun.(expr, stack, context),
{:ok, _type, context} <- Infer.unify(type, expected_type, stack, context),
{:ok, _type, context} <- unify(type, expected_type, stack, context),
do: {:ok, context}
end
end
# TODO: Remove this clause once we properly handle comprehensions
defp binary_segment({:<-, _, _}, _stack, context, _fun) do
{:ok, context}
end
# Collect binary type specifiers,
# from `<<pattern::integer-size(10)>>` collect `integer`
defp collect_binary_specifier({:-, _meta, [left, right]}, fun) do
@@ -173,7 +178,7 @@ defmodule Module.Types.Of do
fun.(other)
end
defp binary_type(:expr, {:float, _, _}), do: :number
defp binary_type(:expr, {:float, _, _}), do: {:union, [:integer, :float]}
defp binary_type(:expr, {:utf8, _, _}), do: {:union, [:integer, :binary]}
defp binary_type(:expr, {:utf16, _, _}), do: {:union, [:integer, :binary]}
defp binary_type(:expr, {:utf32, _, _}), do: {:union, [:integer, :binary]}
+53 -56
View File
@@ -2,7 +2,7 @@ defmodule Module.Types.Pattern do
@moduledoc false
alias Module.Types.Of
import Module.Types.{Helpers, Infer}
import Module.Types.{Helpers, Unify}
@doc """
Handles patterns and guards at once.
@@ -129,8 +129,8 @@ defmodule Module.Types.Pattern do
end
# ^var
def of_pattern({:^, _meta, [var]}, stack, context) do
of_pattern(var, stack, context)
def of_pattern({:^, _meta, [var]}, _stack, context) do
{:ok, get_var!(var, context), context}
end
# var
@@ -194,16 +194,11 @@ defmodule Module.Types.Pattern do
end
end
# %^var{...}
def of_pattern({:%, meta1, [{:^, _meta2, [var]}, args]}, stack, context) do
of_pattern({:%, meta1, [var, args]}, stack, context)
end
# %var{...}
# %var{...} and %^var{...}
def of_pattern({:%, _meta1, [var, {:%{}, _meta2, args}]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
with {var_type, context} = new_var(var, context),
with {:ok, var_type, context} = of_pattern(var, stack, context),
{:ok, _, context} <- unify(var_type, :atom, stack, context),
{:ok, {:map, pairs}, context} <- Of.open_map(args, stack, context, &of_pattern/3) do
{:ok, {:map, [{:required, {:atom, :__struct__}, var_type} | pairs]}, context}
@@ -217,43 +212,45 @@ defmodule Module.Types.Pattern do
## GUARDS
# TODO: Some guards can be changed to intersection types or higher order types
@boolean {:union, [{:atom, true}, {:atom, false}]}
@number {:union, [:integer, :float]}
@guard_functions %{
{:is_atom, 1} => {[:atom], :boolean},
{:is_binary, 1} => {[:binary], :boolean},
{:is_bitstring, 1} => {[:binary], :boolean},
{:is_boolean, 1} => {[:boolean], :boolean},
{:is_float, 1} => {[:float], :boolean},
{:is_function, 1} => {[:fun], :boolean},
{:is_function, 2} => {[:fun, :integer], :boolean},
{:is_integer, 1} => {[:integer], :boolean},
{:is_list, 1} => {[{:list, :dynamic}], :boolean},
{:is_map, 1} => {[{:map, [{:optional, :dynamic, :dynamic}]}], :boolean},
{:is_atom, 1} => {[:atom], @boolean},
{:is_binary, 1} => {[:binary], @boolean},
{:is_bitstring, 1} => {[:binary], @boolean},
{:is_boolean, 1} => {[@boolean], @boolean},
{:is_float, 1} => {[:float], @boolean},
{:is_function, 1} => {[:fun], @boolean},
{:is_function, 2} => {[:fun, :integer], @boolean},
{:is_integer, 1} => {[:integer], @boolean},
{:is_list, 1} => {[{:list, :dynamic}], @boolean},
{:is_map, 1} => {[{:map, [{:optional, :dynamic, :dynamic}]}], @boolean},
{:is_map_key, 2} => {[:dynamic, {:map, [{:optional, :dynamic, :dynamic}]}], :dynamic},
{:is_number, 1} => {[:number], :boolean},
{:is_pid, 1} => {[:pid], :boolean},
{:is_port, 1} => {[:port], :boolean},
{:is_reference, 1} => {[:reference], :boolean},
{:is_tuple, 1} => {[:tuple], :boolean},
{:<, 2} => {[:dynamic, :dynamic], :boolean},
{:"=<", 2} => {[:dynamic, :dynamic], :boolean},
{:>, 2} => {[:dynamic, :dynamic], :boolean},
{:>=, 2} => {[:dynamic, :dynamic], :boolean},
{:"/=", 2} => {[:dynamic, :dynamic], :boolean},
{:"=/=", 2} => {[:dynamic, :dynamic], :boolean},
{:==, 2} => {[:dynamic, :dynamic], :boolean},
{:"=:=", 2} => {[:dynamic, :dynamic], :boolean},
{:*, 2} => {[:number, :number], :number},
{:+, 1} => {[:number], :number},
{:+, 2} => {[:number, :number], :number},
{:-, 1} => {[:number], :number},
{:-, 2} => {[:number, :number], :number},
{:/, 2} => {[:number, :number], :number},
{:abs, 1} => {[:number], :number},
{:ceil, 1} => {[:number], :integer},
{:floor, 1} => {[:number], :integer},
{:round, 1} => {[:number], :integer},
{:trunc, 1} => {[:number], :integer},
{:is_number, 1} => {[@number], @boolean},
{:is_pid, 1} => {[:pid], @boolean},
{:is_port, 1} => {[:port], @boolean},
{:is_reference, 1} => {[:reference], @boolean},
{:is_tuple, 1} => {[:tuple], @boolean},
{:<, 2} => {[:dynamic, :dynamic], @boolean},
{:"=<", 2} => {[:dynamic, :dynamic], @boolean},
{:>, 2} => {[:dynamic, :dynamic], @boolean},
{:>=, 2} => {[:dynamic, :dynamic], @boolean},
{:"/=", 2} => {[:dynamic, :dynamic], @boolean},
{:"=/=", 2} => {[:dynamic, :dynamic], @boolean},
{:==, 2} => {[:dynamic, :dynamic], @boolean},
{:"=:=", 2} => {[:dynamic, :dynamic], @boolean},
{:*, 2} => {[@number, @number], @number},
{:+, 1} => {[@number], @number},
{:+, 2} => {[@number, @number], @number},
{:-, 1} => {[@number], @number},
{:-, 2} => {[@number, @number], @number},
{:/, 2} => {[@number, @number], @number},
{:abs, 1} => {[@number], @number},
{:ceil, 1} => {[@number], :integer},
{:floor, 1} => {[@number], :integer},
{:round, 1} => {[@number], :integer},
{:trunc, 1} => {[@number], :integer},
{:element, 2} => {[:integer, :tuple], :dynamic},
{:hd, 1} => {[{:list, :dynamic}], :dynamic},
{:length, 1} => {[{:list, :dynamic}], :integer},
@@ -265,7 +262,7 @@ defmodule Module.Types.Pattern do
{:binary_part, 3} => {[:binary, :integer, :integer], :binary},
{:bit_size, 1} => {[:binary], :integer},
{:byte_size, 1} => {[:binary], :integer},
{:size, 1} => {[{:union, [:binary, :tuple]}], :boolean},
{:size, 1} => {[{:union, [:binary, :tuple]}], @boolean},
{:div, 2} => {[:integer, :integer], :integer},
{:rem, 2} => {[:integer, :integer], :integer},
{:node, 0} => {[], :atom},
@@ -276,15 +273,15 @@ defmodule Module.Types.Pattern do
{:bxor, 2} => {[:integer, :integer], :integer},
{:bsl, 2} => {[:integer, :integer], :integer},
{:bsr, 2} => {[:integer, :integer], :integer},
{:or, 2} => {[:boolean, :boolean], :boolean},
{:and, 2} => {[:boolean, :boolean], :boolean},
{:xor, 2} => {[:boolean, :boolean], :boolean},
{:not, 1} => {[:boolean], :boolean}
{:or, 2} => {[@boolean, @boolean], @boolean},
{:and, 2} => {[@boolean, @boolean], @boolean},
{:xor, 2} => {[@boolean, @boolean], @boolean},
{:not, 1} => {[@boolean], @boolean}
# Following guards are matched explicitly to handle
# type guard functions such as is_atom/1
# {:andalso, 2} => {[:boolean, :boolean], :boolean}
# {:orelse, 2} => {[:boolean, :boolean], :boolean}
# {:andalso, 2} => {[@boolean, @boolean], @boolean}
# {:orelse, 2} => {[@boolean, @boolean], @boolean}
}
@type_guards [
@@ -320,9 +317,9 @@ defmodule Module.Types.Pattern do
with {:ok, left_type, left_context} <- of_guard(left, stack, fresh_context),
{:ok, right_type, right_context} <- of_guard(right, stack, fresh_context),
{:ok, context} <- merge_context_and(context, stack, left_context, right_context),
{:ok, _, context} <- unify(left_type, :boolean, stack, context),
{:ok, _, context} <- unify(right_type, :boolean, stack, context),
do: {:ok, :boolean, context}
{:ok, _, context} <- unify(left_type, @boolean, stack, context),
{:ok, _, context} <- unify(right_type, @boolean, stack, context),
do: {:ok, @boolean, context}
end
def of_guard({{:., _, [:erlang, :orelse]}, _, [left, right]} = expr, stack, context) do
@@ -332,8 +329,8 @@ defmodule Module.Types.Pattern do
with {:ok, left_type, left_context} <- of_guard(left, stack, fresh_context),
{:ok, _right_type, right_context} <- of_guard(right, stack, fresh_context),
{:ok, context} <- merge_context_or(context, stack, left_context, right_context),
{:ok, _, context} <- unify(left_type, :boolean, stack, context),
do: {:ok, :boolean, context}
{:ok, _, context} <- unify(left_type, @boolean, stack, context),
do: {:ok, @boolean, context}
end
# The unary operators + and - are special cased to avoid common warnings until
@@ -1,4 +1,4 @@
defmodule Module.Types.Infer do
defmodule Module.Types.Unify do
@moduledoc false
import Module.Types.Helpers
@@ -21,11 +21,6 @@ defmodule Module.Types.Infer do
# {:union, [type]}
# {:map, [{:required | :optional, key_type, value_type}]}
#
# TODO: Those types should be removed:
#
# :boolean
# :number
#
# Once new types are added, they should be considered in:
#
# * unify (all)
@@ -306,6 +301,23 @@ defmodule Module.Types.Infer do
defp error(type, reason, context), do: {:error, {type, reason, context}}
@doc """
Push expression to stack.
The expression stack is used to give the context where a type variable
was refined when show a type conflict error.
"""
def push_expr_stack(expr, stack) do
%{stack | last_expr: expr}
end
@doc """
Gets a variable.
"""
def get_var!(var, context) do
Map.fetch!(context.vars, var_name(var))
end
@doc """
Adds a variable to the typing context and returns its type variable.
If the variable has already been added, return the existing type variable.
@@ -487,14 +499,33 @@ defmodule Module.Types.Infer do
def has_unbound_var?(_type, _context), do: false
@doc """
Returns true if it is a singleton type.
Only atoms are singleton types. Unbound vars are not
considered singleton types.
"""
def singleton?({:var, var}, context) do
case context.types do
%{^var => :unbound} -> false
%{^var => type} -> singleton?(type, context)
end
end
def singleton?({:atom, _}, _context), do: true
def singleton?(_type, _context), do: false
@doc """
Checks if the first argument is a subtype of the second argument.
This function assumes that:
* dynamic is not considered a subtype of all other types but the top type
* unbound variables are not subtype of anything
* dynamic is not considered a subtype of all other types but the top type.
This allows this function can be used for ordering, in other cases, you
may need to check for both sides
"""
def subtype?(type, type, _context), do: true
@@ -515,11 +546,6 @@ defmodule Module.Types.Infer do
def subtype?(_, :dynamic, _context), do: true
def subtype?({:atom, atom}, :atom, _context) when is_atom(atom), do: true
def subtype?({:atom, boolean}, :boolean, _context) when is_boolean(boolean), do: true
def subtype?(:boolean, :atom, _context), do: true
def subtype?(:float, :number, _context), do: true
def subtype?(:integer, :number, _context), do: true
# Composite
def subtype?({:tuple, _, _}, :tuple, _context), do: true
+1 -1
View File
@@ -280,7 +280,7 @@ defmodule Regex do
Returns `true` if the given `term` is a regex.
Otherwise returns `false`.
"""
# TODO: Remove this on Elixir v1.15
# TODO: deprecate permanently on Elixir v1.15
@doc deprecated: "Use Kernel.is_struct/2 or pattern match on %Regex{} instead"
def regex?(term)
def regex?(%Regex{}), do: true
+32
View File
@@ -491,6 +491,38 @@ defmodule Task do
stream = Task.async_stream(collection, Mod, :expensive_fun, [], ordered: false)
Stream.run(stream)
## Attention: async + take
Given items in an async stream are processed concurrently, doing
`async_stream` followed by `Enum.take/2` may cause more items than
requested to be processed. Let's see an example:
1..100
|> Task.async_stream(fn i ->
Process.sleep(100)
IO.puts(to_string(i))
end)
|> Enum.take(10)
For a machine with 8 cores, the above will process 16 items instead
of 10. The reason is that `async_stream/5` always have 8 elements
processing at once. So by the time `Enum` says it got all elements
it needed, there are still 6 elements left to be processed.
The solution here is to use `Stream.take/2` instead of `Enum.take/2`
to filter elements before-hand:
1..100
|> Stream.take(10)
|> Task.async_stream(fn i ->
Process.sleep(100)
IO.puts(to_string(i))
end)
|> Enum.to_list()
If for some reason you cannot take the elements before hand,
you can use `:max_concurrency` to limit how many elements
may be over processed at the cost of reducing concurrency.
"""
@doc since: "1.4.0"
@spec async_stream(Enumerable.t(), module, atom, [term], keyword) :: Enumerable.t()
+8 -6
View File
@@ -204,21 +204,23 @@ env_for_eval(Env, Opts) ->
false -> nil
end,
Tracers = case lists:keyfind(tracers, 1, Opts) of
TempTracers = case lists:keyfind(tracers, 1, Opts) of
{tracers, TracersOpt} when is_list(TracersOpt) -> TracersOpt;
false -> []
end,
LexicalTracker = case lists:keyfind(lexical_tracker, 1, Opts) of
%% If there is a dead PID or lexical tracker is nil,
%% we assume the tracers also cannot be (re)used.
{LexicalTracker, Tracers} = case lists:keyfind(lexical_tracker, 1, Opts) of
{lexical_tracker, Pid} when is_pid(Pid) ->
case is_process_alive(Pid) of
true -> Pid;
false -> nil
true -> {Pid, TempTracers};
false -> {nil, []}
end;
{lexical_tracker, nil} ->
nil;
{nil, []};
false ->
nil
{nil, TempTracers}
end,
FA = case lists:keyfind(function, 1, Opts) of
+1 -1
View File
@@ -166,8 +166,8 @@ bootstrap_files() ->
<<"lib/elixir/lib/module/locals_tracker.ex">>,
<<"lib/elixir/lib/module/parallel_checker.ex">>,
<<"lib/elixir/lib/module/types/helpers.ex">>,
<<"lib/elixir/lib/module/types/unify.ex">>,
<<"lib/elixir/lib/module/types/of.ex">>,
<<"lib/elixir/lib/module/types/infer.ex">>,
<<"lib/elixir/lib/module/types/pattern.ex">>,
<<"lib/elixir/lib/module/types/expr.ex">>,
<<"lib/elixir/lib/module/types.ex">>,
+8 -2
View File
@@ -469,7 +469,7 @@ expand(Other, E) ->
escape_env_entries(Meta, #{current_vars := {Read, Write}, unused_vars := {Unused, Version}} = Env0) ->
Env1 = case Env0 of
#{function := nil} -> Env0;
_ -> Env0#{lexical_tracker := nil}
_ -> Env0#{lexical_tracker := nil, tracers := []}
end,
Current = {maybe_escape_map(Read), maybe_escape_map(Write)},
Env2 = Env1#{current_vars := Current, unused_vars := {maybe_escape_map(Unused), Version}},
@@ -631,7 +631,13 @@ maybe_warn_underscored_var_access(Meta, Name, Kind, E) ->
maybe_warn_deprecated_super_in_gen_server_callback(Meta, Function, SuperMeta, E) ->
case lists:keyfind(context, 1, SuperMeta) of
{context, 'Elixir.GenServer'} ->
elixir_errors:form_warn(Meta, E, ?MODULE, {super_in_genserver, Function});
case Function of
{child_spec, 1} ->
ok;
_ ->
elixir_errors:form_warn(Meta, E, ?MODULE, {super_in_genserver, Function})
end;
_ ->
ok
+4
View File
@@ -327,6 +327,10 @@ guard_rewrite(Receiver, DotMeta, Right, Meta, Args) ->
_ -> {error, {invalid_guard, Receiver, Right, length(Args)}}
end.
%% erlang:is_record/2-3 are compiler guards in Erlang which we
%% need to explicitly forbid as they are allowed in erl_internal.
allowed_guard(is_record, 2) -> false;
allowed_guard(is_record, 3) -> false;
allowed_guard(Right, Arity) ->
erl_internal:guard_bif(Right, Arity) orelse elixir_utils:guard_op(Right, Arity).
+1 -1
View File
@@ -27,9 +27,9 @@ guard_op('orelse', 2) ->
guard_op(Op, Arity) ->
try erl_internal:op_type(Op, Arity) of
arith -> true;
list -> true;
comp -> true;
bool -> true;
list -> false;
send -> false
catch
_:_ -> false
+13 -3
View File
@@ -85,11 +85,21 @@ defmodule CodeTest do
assert CodeTest.Sample.eval_quoted_info() == {CodeTest.Sample, "sample.ex", 13}
end
test "eval_quoted/2 with a %Macro.Env{} struct as the second argument" do
test "eval_quoted/2 with %Macro.Env{} at runtime" do
alias :lists, as: MyList
quoted = quote(do: MyList.flatten([[1, 2, 3]]))
assert Code.eval_quoted(quote(do: MyList.flatten([[1, 2, 3]])), [], __ENV__) ==
{[1, 2, 3], []}
assert Code.eval_quoted(quoted, [], __ENV__) == {[1, 2, 3], []}
# Let's check it discards tracers since the lexical tracker is explicitly nil
assert Code.eval_quoted(quoted, [], %{__ENV__ | tracers: [:bad]}) == {[1, 2, 3], []}
end
test "eval_quoted/2 with %Macro.Env{} at compile time" do
defmodule CompileTimeEnv do
alias String.Chars
{"foo", []} = Code.eval_string("Chars.to_string(:foo)", [], __ENV__)
end
end
test "eval_file/1" do
@@ -727,6 +727,8 @@ defmodule IO.ANSI.DocsTest do
end
test "typespecs" do
assert format_erlang([{:ul, [class: "types"], [{:li, [], []}]}]) == ""
assert format_erlang([{:ul, [class: "types"], [{:li, [], ["Hello"]}, {:li, [], ["World"]}]}]) ==
"""
Typespecs:
@@ -735,6 +737,17 @@ defmodule IO.ANSI.DocsTest do
World
"""
assert format_erlang([
{:ul, [class: "types"], [{:li, [], ["Hello", {:code, [], ["World"]}]}]}
]) ==
"""
Typespecs:
Hello
\e[36mWorld\e[0m
"""
end
test "extra markup" do
@@ -240,6 +240,19 @@ defmodule Kernel.ComprehensionTest do
assert acc == %{1 => 'olleh', 3 => 'olleh'}
end
test "for comprehensions with matched reduce" do
acc =
for entry <- [1, 2, 3], reduce: {:ok, nil} do
{:ok, _} ->
{:ok, entry}
{:error, _} = error ->
error
end
assert acc == {:ok, 3}
end
## List generators (inlined by the compiler)
test "list for comprehensions" do
@@ -294,6 +294,7 @@ defmodule Kernel.ExpansionTest do
test "__ENV__" do
env = %{__ENV__ | line: 0}
assert expand_env(quote(do: __ENV__), env) == {Macro.escape(env), env}
assert %{lexical_tracker: nil, tracers: []} = __ENV__
end
test "__ENV__.accessor" do
@@ -302,6 +303,9 @@ defmodule Kernel.ExpansionTest do
assert expand_env(quote(do: __ENV__.unknown), env) ==
{quote(do: unquote(Macro.escape(env)).unknown), env}
assert __ENV__.lexical_tracker == nil
assert __ENV__.tracers == []
end
end
@@ -310,6 +310,38 @@ defmodule Kernel.GuardTest do
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.is_record/2 inside guards",
fn ->
defmodule IsRecord2Usage do
defguard foo(rec) when :erlang.is_record(rec, :tag)
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.is_record/3 inside guards",
fn ->
defmodule IsRecord3Usage do
defguard foo(rec) when :erlang.is_record(rec, :tag, 7)
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.\+\+/2 inside guards",
fn ->
defmodule ListSubtractionUsage do
defguard foo(list) when list ++ []
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.\-\-/2 inside guards",
fn ->
defmodule ListSubtractionUsage do
defguard foo(list) when list -- []
end
end
assert_raise CompileError, ~r"invalid expression in guard", fn ->
defmodule LocalCallUsage do
defguard foo(local, call) when local.(call)
+17 -1
View File
@@ -1709,7 +1709,7 @@ defmodule Kernel.WarningTest do
end
end
test "deprecated GenServer super" do
test "deprecated GenServer super on callbacks" do
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
@@ -1725,6 +1725,22 @@ defmodule Kernel.WarningTest do
purge(Sample)
end
test "super is allowed on GenServer.child_spec/1" do
refute capture_err(fn ->
Code.eval_string("""
defmodule Sample do
use GenServer
def child_spec(opts) do
super(opts)
end
end
""")
end) =~ "calling super for GenServer callback child_spec/1 is deprecated"
after
purge(Sample)
end
test "nested comparison operators" do
message =
capture_err(fn ->
@@ -250,27 +250,43 @@ defmodule Module.Types.ExprTest do
) == {:ok, {:var, 0}}
end
test "for" do
assert quoted_expr(
[list],
for(
foo <- list,
is_integer(foo),
do: foo == 123
)
) == {:ok, :dynamic}
assert quoted_expr(
[list, bar],
(
describe "for comprehension" do
test "with generators and filters" do
assert quoted_expr(
[list],
for(
foo <- list,
is_integer(bar),
is_integer(foo),
do: foo == 123
)
) == {:ok, :dynamic}
end
bar
)
) == {:ok, {:var, 0}}
test "with unused return" do
assert quoted_expr(
[list, bar],
(
for(
foo <- list,
is_integer(bar),
do: foo == 123
)
bar
)
) == {:ok, {:var, 0}}
end
test "with reduce" do
assert quoted_expr(
[],
for(i <- [1, 2, 3], do: (acc -> i + acc), reduce: 0)
) == {:ok, :dynamic}
assert quoted_expr(
[],
for(i <- [1, 2, 3], do: (_ -> i), reduce: nil)
) == {:ok, :dynamic}
end
end
end
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.IntegrationTest do
use ExUnit.Case
@@ -297,7 +297,7 @@ defmodule Module.Types.MapTest do
end
end
test "with bound var keys" do
test "map creation with bound var keys" do
assert quoted_expr(
[atom, bool, true = var],
[is_atom(atom) and is_boolean(bool)],
@@ -307,7 +307,8 @@ defmodule Module.Types.MapTest do
{:map,
[
{:required, {:atom, true}, {:atom, true}},
{:required, :boolean, {:union, [{:atom, :bool}, {:atom, true}]}},
{:required, {:union, [atom: true, atom: false]},
{:union, [{:atom, :bool}, {:atom, true}]}},
{:required, :atom, {:union, [{:atom, :atom}, {:atom, :bool}, {:atom, true}]}}
]}}
@@ -320,7 +321,8 @@ defmodule Module.Types.MapTest do
{:map,
[
{:required, {:atom, true}, {:atom, true}},
{:required, :boolean, {:union, [{:atom, :bool}, {:atom, true}]}},
{:required, {:union, [atom: true, atom: false]},
{:union, [{:atom, :bool}, {:atom, true}]}},
{:required, :atom, {:union, [{:atom, :atom}, {:atom, :bool}, {:atom, true}]}}
]}}
@@ -333,12 +335,13 @@ defmodule Module.Types.MapTest do
{:map,
[
{:required, {:atom, true}, {:atom, true}},
{:required, :boolean, {:union, [{:atom, :bool}, {:atom, true}]}},
{:required, {:union, [atom: true, atom: false]},
{:union, [{:atom, :bool}, {:atom, true}]}},
{:required, :atom, {:union, [{:atom, :atom}, {:atom, :bool}, {:atom, true}]}}
]}}
end
test "with unbound var keys" do
test "map creation with unbound var keys" do
assert quoted_expr(
[var, struct],
(
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.PatternTest do
use ExUnit.Case, async: true
@@ -118,13 +118,18 @@ defmodule Module.Types.PatternTest do
{:map, [{:required, {:atom, :a}, {:atom, :b}}, {:optional, :dynamic, :dynamic}]}}
assert quoted_pattern(%{123 => a}) ==
{:ok, {:map, [{:required, :integer, {:var, 0}}, {:optional, :dynamic, :dynamic}]}}
{:ok,
{:map,
[
{:required, :integer, {:union, [{:var, 0}, :dynamic]}},
{:optional, :dynamic, :dynamic}
]}}
assert quoted_pattern(%{123 => :foo, 456 => :bar}) ==
{:ok,
{:map,
[
{:required, :integer, {:union, [{:atom, :foo}, {:atom, :bar}]}},
{:required, :integer, :dynamic},
{:optional, :dynamic, :dynamic}
]}}
@@ -285,10 +290,10 @@ defmodule Module.Types.PatternTest do
{:ok, [{:union, [:tuple, :atom]}]}
assert quoted_head([x], [is_boolean(x) and is_atom(x)]) ==
{:ok, [:boolean]}
{:ok, [{:union, [atom: true, atom: false]}]}
assert quoted_head([x], [is_atom(x) and is_boolean(x)]) ==
{:ok, [:boolean]}
{:ok, [{:union, [atom: true, atom: false]}]}
assert quoted_head([x], [is_atom(x) > :foo]) == {:ok, [var: 0]}
@@ -307,7 +312,7 @@ defmodule Module.Types.PatternTest do
assert {:error, {:unable_unify, {:tuple, :atom, _}}} =
quoted_head([x], [is_tuple(x) and is_atom(x)])
assert {:error, {:unable_unify, {:boolean, :tuple, _}}} =
assert {:error, {:unable_unify, {{:union, [atom: true, atom: false]}, :tuple, _}}} =
quoted_head([x], [is_tuple(is_atom(x))])
end
@@ -322,13 +327,16 @@ defmodule Module.Types.PatternTest do
assert {:error, {:unable_unify, {{:atom, :foo}, {:list, :dynamic}, _}}} =
quoted_head([x], [length(:foo)])
assert {:error, {:unable_unify, {:boolean, {:list, :dynamic}, _}}} =
assert {:error,
{:unable_unify, {{:union, [atom: true, atom: false]}, {:list, :dynamic}, _}}} =
quoted_head([x], [length(is_tuple(x))])
assert {:error, {:unable_unify, {:boolean, :tuple, _}}} =
assert {:error, {:unable_unify, {{:union, [atom: true, atom: false]}, :tuple, _}}} =
quoted_head([x], [elem(is_tuple(x), 0)])
assert {:error, {:unable_unify, {:boolean, :number, _}}} =
assert {:error,
{:unable_unify,
{{:union, [atom: true, atom: false]}, {:union, [:integer, :float]}, _}}} =
quoted_head([x], [elem({}, is_tuple(x))])
assert quoted_head([x], [elem({}, 1)]) == {:ok, [var: 0]}
@@ -371,7 +379,11 @@ defmodule Module.Types.PatternTest do
assert quoted_head([%{true: bool}], [is_boolean(bool)]) ==
{:ok,
[
{:map, [{:required, {:atom, true}, :boolean}, {:optional, :dynamic, :dynamic}]}
{:map,
[
{:required, {:atom, true}, {:union, [atom: true, atom: false]}},
{:optional, :dynamic, :dynamic}
]}
]}
assert quoted_head([%{true: true} = foo, %{false: false} = foo]) ==
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.TypesTest do
use ExUnit.Case, async: true
@@ -23,7 +23,10 @@ defmodule Module.Types.TypesTest do
with {:ok, _types, context} <-
Pattern.of_head(patterns, guards, TypeHelper.new_stack(), TypeHelper.new_context()),
{:ok, type, context} <- Expr.of_expr(body, TypeHelper.new_stack(), context) do
flunk("expexted error, got: #{inspect(Types.lift_type(type, context))}")
case context.warnings do
[warning] -> {:warning, warning}
_ -> flunk("expexted error, got: #{inspect(Types.lift_type(type, context))}")
end
else
{:error, {type, reason, context}} ->
{:error, {type, reason, context}}
@@ -48,12 +51,18 @@ defmodule Module.Types.TypesTest do
min
end
defp to_warning({:warning, {module, warning, _location}}) do
warning
|> module.format_warning()
|> IO.iodata_to_binary()
end
defp to_warning({:error, {type, reason, context}}) do
{Module.Types, error, _location} = Module.Types.error_to_warning(type, reason, context)
error
|> Module.Types.format_warning()
|> List.to_string()
|> IO.iodata_to_binary()
|> String.trim_trailing("\nConflict found at")
end
@@ -69,6 +78,17 @@ defmodule Module.Types.TypesTest do
assert Types.expr_to_string(quote(do: :erlang.element(:erlang.+(a, 1), b))) == "elem(b, a)"
end
test "undefined function warnings" do
assert warning([], URI.unknown("foo")) ==
"URI.unknown/1 is undefined or private"
assert warning([], if(true, do: URI.unknown("foo"))) ==
"URI.unknown/1 is undefined or private"
assert warning([], try(do: :ok, after: URI.unknown("foo"))) ==
"URI.unknown/1 is undefined or private"
end
describe "function head warnings" do
test "warns on literals" do
string = warning([var = 123, var = "abc"], var)
@@ -1,8 +1,8 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.InferTest do
defmodule Module.Types.UnifyTest do
use ExUnit.Case, async: true
import Module.Types.Infer
import Module.Types.Unify
alias Module.Types
defp unify_lift(left, right, context \\ new_context()) do
@@ -53,27 +53,17 @@ defmodule Module.Types.InferTest do
assert unify_lift(:integer, :integer) == {:ok, :integer}
assert unify_lift(:binary, :binary) == {:ok, :binary}
assert unify_lift(:atom, :atom) == {:ok, :atom}
assert unify_lift(:boolean, :boolean) == {:ok, :boolean}
assert {:error, {:unable_unify, {:integer, :boolean, _}}} = unify_lift(:integer, :boolean)
assert {:error, {:unable_unify, {:integer, :atom, _}}} = unify_lift(:integer, :atom)
end
test "subtype undirected" do
assert unify_lift(:boolean, :atom) == {:ok, :boolean}
assert unify_lift(:atom, :boolean) == {:ok, :boolean}
assert unify_lift(:boolean, {:atom, true}) == {:ok, {:atom, true}}
assert unify_lift({:atom, true}, :boolean) == {:ok, {:atom, true}}
assert unify_lift(:atom, {:atom, true}) == {:ok, {:atom, true}}
assert unify_lift({:atom, true}, :atom) == {:ok, {:atom, true}}
end
test "subtype directed" do
assert unify_directed_lift(:boolean, :atom) == {:ok, :boolean}
assert unify_directed_lift({:atom, true}, :boolean) == {:ok, {:atom, true}}
assert unify_directed_lift({:atom, true}, :atom) == {:ok, {:atom, true}}
assert {:error, _} = unify_directed_lift(:atom, :boolean)
assert {:error, _} = unify_directed_lift(:boolean, {:atom, true})
assert {:error, _} = unify_directed_lift(:atom, {:atom, true})
end
@@ -83,8 +73,8 @@ defmodule Module.Types.InferTest do
assert unify_lift({:tuple, 1, [:integer]}, {:tuple, 1, [:integer]}) ==
{:ok, {:tuple, 1, [:integer]}}
assert unify_lift({:tuple, 1, [:boolean]}, {:tuple, 1, [:atom]}) ==
{:ok, {:tuple, 1, [:boolean]}}
assert unify_lift({:tuple, 1, [{:atom, :foo}]}, {:tuple, 1, [:atom]}) ==
{:ok, {:tuple, 1, [{:atom, :foo}]}}
assert {:error, {:unable_unify, {{:tuple, 1, [:integer]}, {:tuple, 0, []}, _}}} =
unify_lift({:tuple, 1, [:integer]}, {:tuple, 0, []})
@@ -134,10 +124,10 @@ defmodule Module.Types.InferTest do
{:ok, {:map, [{:required, :integer, :atom}]}}
assert unify_lift(
{:map, [{:required, {:atom, :foo}, :boolean}]},
{:map, [{:required, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:required, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:required, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:required, {:atom, :foo}, {:atom, :bar}}]}}
assert {:error,
{:unable_unify,
@@ -166,28 +156,28 @@ defmodule Module.Types.InferTest do
test "map required/optional key" do
assert unify_lift(
{:map, [{:required, {:atom, :foo}, :boolean}]},
{:map, [{:required, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:required, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:required, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:required, {:atom, :foo}, {:atom, :bar}}]}}
assert unify_lift(
{:map, [{:optional, {:atom, :foo}, :boolean}]},
{:map, [{:optional, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:required, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:required, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:required, {:atom, :foo}, {:atom, :bar}}]}}
assert unify_lift(
{:map, [{:required, {:atom, :foo}, :boolean}]},
{:map, [{:required, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:optional, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:required, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:required, {:atom, :foo}, {:atom, :bar}}]}}
assert unify_lift(
{:map, [{:optional, {:atom, :foo}, :boolean}]},
{:map, [{:optional, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:optional, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:optional, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:optional, {:atom, :foo}, {:atom, :bar}}]}}
end
test "map with subtyped keys" do
@@ -277,8 +267,8 @@ defmodule Module.Types.InferTest do
assert unify_lift({:union, [:integer, :atom]}, {:union, [:atom, :integer]}) ==
{:ok, {:union, [:integer, :atom]}}
assert unify_lift({:union, [:atom]}, {:union, [:boolean]}) == {:ok, {:union, [:boolean]}}
assert unify_lift({:union, [:boolean]}, {:union, [:atom]}) == {:ok, {:union, [:boolean]}}
assert unify_lift({:union, [:atom]}, {:union, [{:atom, :bar}]}) ==
{:ok, {:union, [{:atom, :bar}]}}
assert {:error, {:unable_unify, {{:union, [:integer]}, {:union, [:atom]}, _}}} =
unify_lift({:union, [:integer]}, {:union, [:atom]})
@@ -425,15 +415,11 @@ defmodule Module.Types.InferTest do
describe "subtype?/3" do
test "with simple types" do
assert subtype?({:atom, :foo}, :atom, new_context())
assert subtype?({:atom, true}, :boolean, new_context())
assert subtype?({:atom, true}, :atom, new_context())
assert subtype?(:boolean, :atom, new_context())
refute subtype?(:integer, :binary, new_context())
refute subtype?(:atom, {:atom, :foo}, new_context())
refute subtype?(:boolean, {:atom, true}, new_context())
refute subtype?(:atom, {:atom, true}, new_context())
refute subtype?(:atom, :boolean, new_context())
end
test "with composite types" do
@@ -503,8 +489,7 @@ defmodule Module.Types.InferTest do
test "to_union/2" do
assert to_union([:atom], new_context()) == :atom
assert to_union([:integer, :integer], new_context()) == :integer
assert to_union([:boolean, :atom], new_context()) == :atom
assert to_union([{:atom, :foo}, :boolean, :atom], new_context()) == :atom
assert to_union([{:atom, :foo}, {:atom, :bar}, :atom], new_context()) == :atom
assert to_union([:binary, :atom], new_context()) == {:union, [:binary, :atom]}
assert to_union([:atom, :binary, :atom], new_context()) == {:union, [:atom, :binary]}
+83 -49
View File
@@ -259,41 +259,10 @@ defmodule ExUnit.Case do
end
quote do
unless Module.has_attribute?(__MODULE__, :ex_unit_tests) do
tag_check =
[:moduletag, :describetag, :tag]
|> Enum.any?(&Module.has_attribute?(__MODULE__, &1))
if tag_check do
raise "you must set @tag, @describetag, and @moduletag after the call to \"use ExUnit.Case\""
end
attributes = [
:ex_unit_tests,
:tag,
:describetag,
:moduletag,
:ex_unit_registered_test_attributes,
:ex_unit_registered_describe_attributes,
:ex_unit_registered_module_attributes,
:ex_unit_used_describes
]
Enum.each(attributes, &Module.register_attribute(__MODULE__, &1, accumulate: true))
@before_compile ExUnit.Case
@after_compile ExUnit.Case
@ex_unit_async false
@ex_unit_describe nil
unless ExUnit.Case.__register__(__MODULE__, unquote(opts)) do
use ExUnit.Callbacks
end
async = unquote(opts)[:async]
if is_boolean(async) do
@ex_unit_async async
end
import ExUnit.Callbacks
import ExUnit.Assertions
import ExUnit.Case, only: [describe: 2, test: 1, test: 2, test: 3]
@@ -301,6 +270,49 @@ defmodule ExUnit.Case do
end
end
@doc false
def __register__(module, opts) do
registered? = Module.has_attribute?(module, :ex_unit_tests)
unless registered? do
tag_check = Enum.any?([:moduletag, :describetag, :tag], &Module.has_attribute?(module, &1))
if tag_check do
raise "you must set @tag, @describetag, and @moduletag after the call to \"use ExUnit.Case\""
end
attributes = [
:ex_unit_tests,
:tag,
:describetag,
:moduletag,
:ex_unit_registered_test_attributes,
:ex_unit_registered_describe_attributes,
:ex_unit_registered_module_attributes,
:ex_unit_used_describes
]
Enum.each(attributes, &Module.register_attribute(module, &1, accumulate: true))
attributes = [
before_compile: ExUnit.Case,
after_compile: ExUnit.Case,
ex_unit_async: false,
ex_unit_describe: nil
]
Enum.each(attributes, fn {k, v} -> Module.put_attribute(module, k, v) end)
end
async? = opts[:async]
if is_boolean(async?) do
Module.put_attribute(module, :ex_unit_async, async?)
end
registered?
end
@doc """
Defines a test with `message`.
@@ -333,9 +345,17 @@ defmodule ExUnit.Case do
var = Macro.escape(var)
contents = Macro.escape(contents, unquote: true)
%{module: mod, file: file, line: line} = __CALLER__
quote bind_quoted: [var: var, contents: contents, message: message] do
name = ExUnit.Case.register_test(__ENV__, :test, message, [])
quote bind_quoted: [
var: var,
contents: contents,
message: message,
mod: mod,
file: file,
line: line
] do
name = ExUnit.Case.register_test(mod, file, line, :test, message, [])
def unquote(name)(unquote(var)), do: unquote(contents)
end
end
@@ -354,8 +374,10 @@ defmodule ExUnit.Case do
"""
defmacro test(message) do
%{module: mod, file: file, line: line} = __CALLER__
quote bind_quoted: binding() do
name = ExUnit.Case.register_test(__ENV__, :test, message, [:not_implemented])
name = ExUnit.Case.register_test(mod, file, line, :test, message, [:not_implemented])
def unquote(name)(_), do: flunk("Not implemented")
end
end
@@ -423,23 +445,14 @@ defmodule ExUnit.Case do
"""
defmacro describe(message, do: block) do
quote do
ExUnit.Case.__describe__(__MODULE__, __ENV__.line, unquote(message))
try do
ExUnit.Case.__describe__(__MODULE__, __ENV__.line, unquote(message), fn ->
unquote(block)
after
@ex_unit_describe nil
Module.delete_attribute(__MODULE__, :describetag)
for attribute <- Module.get_attribute(__MODULE__, :ex_unit_registered_describe_attributes) do
Module.delete_attribute(__MODULE__, attribute)
end
end
end)
end
end
@doc false
def __describe__(module, line, message) do
def __describe__(module, line, message, fun) do
if Module.get_attribute(module, :ex_unit_describe) do
raise "cannot call \"describe\" inside another \"describe\". See the documentation " <>
"for ExUnit.Case.describe/2 on named setups and how to handle hierarchies"
@@ -463,7 +476,17 @@ defmodule ExUnit.Case do
Module.put_attribute(module, :ex_unit_describe, {line, message})
Module.put_attribute(module, :ex_unit_used_describes, message)
:ok
try do
fun.()
after
Module.put_attribute(module, :ex_unit_describe, nil)
Module.delete_attribute(module, :describetag)
for attribute <- Module.get_attribute(module, :ex_unit_registered_describe_attributes) do
Module.delete_attribute(module, attribute)
end
end
end
@doc false
@@ -496,7 +519,7 @@ defmodule ExUnit.Case do
display. You can use `ExUnit.plural_rule/2` to set a custom
pluralization.
"""
def register_test(%{module: mod, file: file, line: line}, test_type, name, tags) do
def register_test(mod, file, line, test_type, name, tags) do
unless Module.has_attribute?(mod, :ex_unit_tests) do
raise "cannot define #{test_type}. Please make sure you have invoked " <>
"\"use ExUnit.Case\" in the current module"
@@ -557,6 +580,17 @@ defmodule ExUnit.Case do
name
end
@doc """
Reigsters a test with the given environment.
This function is deprecated in favor of register_test/6 which performs
better under tight loops by avoiding `__ENV__`.
"""
@doc deprecated: "Use register_test/6 instead"
def register_test(%{module: mod, file: file, line: line}, test_type, name, tags) do
register_test(mod, file, line, test_type, name, tags)
end
@doc """
Registers a new attribute to be used during `ExUnit.Case` tests.
+2 -1
View File
@@ -778,9 +778,10 @@ defmodule IEx.Introspection do
doc && IO.ANSI.Docs.print(doc, format, opts)
end
defp translate_doc(%{"en" => doc}), do: doc
defp translate_doc(%{}), do: nil
defp translate_doc(:none), do: nil
defp translate_doc(:hidden), do: nil
defp translate_doc(%{"en" => doc}), do: doc
defp no_beam(module) do
case Code.ensure_loaded(module) do
+6 -6
View File
@@ -242,8 +242,8 @@ defmodule IEx.Server do
end
end
defp handle_take_over({:respawn, evaluator}, _state, evaluator, evaluator_ref, input, _callback) do
rerun([], evaluator, evaluator_ref, input)
defp handle_take_over({:respawn, evaluator}, state, evaluator, evaluator_ref, input, _callback) do
rerun(state.evaluator_options, evaluator, evaluator_ref, input)
end
defp handle_take_over({:continue, evaluator}, state, evaluator, evaluator_ref, input, _callback) do
@@ -254,18 +254,18 @@ defmodule IEx.Server do
defp handle_take_over(
{:DOWN, evaluator_ref, :process, evaluator, :normal},
_state,
state,
evaluator,
evaluator_ref,
input,
_callback
) do
rerun([], evaluator, evaluator_ref, input)
rerun(state.evaluator_options, evaluator, evaluator_ref, input)
end
defp handle_take_over(
{:DOWN, evaluator_ref, :process, evaluator, reason},
_state,
state,
evaluator,
evaluator_ref,
input,
@@ -281,7 +281,7 @@ defmodule IEx.Server do
io_error("** (IEx.Error) #{type} when printing EXIT message: #{inspect(detail)}")
end
rerun([], evaluator, evaluator_ref, input)
rerun(state.evaluator_options, evaluator, evaluator_ref, input)
end
defp handle_take_over(_, state, _evaluator, _evaluator_ref, _input, callback) do
+3 -1
View File
@@ -62,7 +62,9 @@ defmodule Logger.App do
@doc false
def config_change(changed, _new, _removed) do
Logger.configure(changed)
# All other config has already been persisted, we only need to
# update the level and reload the logger state.
Logger.configure(Keyword.take(changed, [:level]))
end
@doc """
+11 -2
View File
@@ -147,8 +147,17 @@ defmodule Logger.Handler do
end
end
defp notify(:sync, msg), do: :gen_event.sync_notify(Logger, msg)
defp notify(:async, msg), do: :gen_event.notify(Logger, msg)
defp notify(:sync, msg) do
pid = Process.whereis(Logger)
# If we are within the logger process itself,
# we cannot use sync notify as that will deadlock.
if pid == self(), do: :gen_event.notify(pid, msg), else: :gen_event.sync_notify(pid, msg)
end
defp notify(:async, msg) do
:gen_event.notify(Logger, msg)
end
defp truncate(data, n) when is_list(data) do
Logger.Utils.truncate(data, n)
+33 -9
View File
@@ -62,7 +62,7 @@ defmodule LoggerTest do
Logger.add_backend({UnknownBackend, :hello})
end
test "logs or writes to stderr on failed backends" do
test "logs or writes to stderr on failed call on async mode" do
assert {:ok, _} = Logger.add_backend({MyBackend, :hello})
assert capture_log(fn ->
@@ -86,6 +86,32 @@ defmodule LoggerTest do
Logger.add_backend(:console)
end
test "logs or writes to stderr on failed call on sync mode" do
Logger.configure(sync_threshold: 0)
assert {:ok, _} = Logger.add_backend({MyBackend, :hello})
assert capture_log(fn ->
:gen_event.call(Logger, {MyBackend, :hello}, :error)
wait_for_handler(Logger, {MyBackend, :hello})
end) =~
":gen_event handler {LoggerTest.MyBackend, :hello} installed in Logger terminating"
assert :ok = Logger.remove_backend(:console)
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
:gen_event.call(Logger, {MyBackend, :hello}, :error)
wait_for_handler(Logger, {MyBackend, :hello})
end) =~
":gen_event handler {LoggerTest.MyBackend, :hello} installed in Logger terminating"
# Flush logs before reattaching to avoid OTP reports
Logger.flush()
after
Logger.configure(sync_threshold: 20)
Logger.remove_backend({MyBackend, :hello})
Logger.add_backend(:console)
end
test "level/0" do
assert Logger.level() == :debug
@@ -644,14 +670,12 @@ defmodule LoggerTest do
end
test "updates config on config_change/3" do
:ok = Logger.configure(level: :debug)
try do
assert Logger.App.config_change([level: :error], [], []) === :ok
assert Logger.level() === :error
after
Logger.configure(level: :debug)
end
assert Application.put_env(:logger, :level, :error) == :ok
assert :application_controller.config_change(logger: [level: :debug]) == :ok
assert Logger.level() == :error
after
Application.put_env(:logger, :level, :debug)
Logger.configure(level: :debug)
end
describe "OTP integration" do
+4 -2
View File
@@ -78,7 +78,9 @@ defmodule Mix.CLI do
defp run_task(name, args) do
try do
ensure_no_slashes(name)
Mix.Tasks.Loadconfig.load_default()
# We must go through the task instead of invoking the module directly
# because projects like Nerves use this to invoke it early.
Mix.Task.run("loadconfig")
Mix.Task.run(name, args)
rescue
# We only rescue exceptions in the Mix namespace, all
@@ -144,7 +146,7 @@ defmodule Mix.CLI do
path = Path.join(Mix.Utils.mix_config(), "config.exs")
if File.regular?(path) do
Mix.Tasks.Loadconfig.load_imports(path)
Mix.Tasks.Loadconfig.load_compile(path)
end
end
@@ -25,6 +25,12 @@ defmodule Mix.Compilers.ApplicationTracer do
:ok
end
# Also skip __impl__ calls inside protocols as they are meant
# to invert dependencies.
def trace({_, _, _, :__impl__, _}, _env) do
:ok
end
def trace({type, meta, module, function, arity}, env)
when type in [:remote_function, :remote_macro, :imported_function, :imported_macro] do
# Unknown modules need to be looked up and filtered later
@@ -120,7 +126,7 @@ defmodule Mix.Compilers.ApplicationTracer do
in your mix.exs
3. In case you don't want to add a requirement to :#{app}, you may \
optionally skip this warning by adding [xref: [exclude: #{inspect(module)}] \
optionally skip this warning by adding [xref: [exclude: #{inspect(module)}]] \
to your "def project" in mix.exs
"""
end
-13
View File
@@ -75,19 +75,6 @@ defmodule Mix.Dep do
system_env: keyword
}
@doc """
Receives the project configuration and returns
a map saying if dependencies are runtime or compile time.
"""
def deps_opts(config) do
for config_dep <- Keyword.get(config, :deps, []),
do: {elem(config_dep, 0), dep_opts(config_dep)}
end
defp dep_opts({_app, opts}) when is_list(opts), do: opts
defp dep_opts({_app, _req, opts}) when is_list(opts), do: opts
defp dep_opts(_), do: []
@doc """
Returns loaded dependencies from the cache for the current environment.
+8 -7
View File
@@ -355,12 +355,12 @@ defmodule Mix.Task do
alias = Mix.Project.config()[:aliases][String.to_atom(task)]
cond do
is_nil(alias) ->
run_task(proj, task, args)
Mix.TasksServer.run({:alias, task, proj}) ->
alias && Mix.TasksServer.run({:alias, task, proj}) ->
run_alias(List.wrap(alias), args, proj, task, :ok)
!Mix.TasksServer.get({:task, task, proj}) ->
run_task(proj, task, args)
true ->
:noop
end
@@ -371,8 +371,8 @@ defmodule Mix.Task do
# 2. Otherwise we compile and load 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.loadpaths") end) ||
get_task_or_run(proj, task, fn -> Mix.Task.run("compile", []) end) ||
get_task_or_run(proj, task, fn -> run("deps.loadpaths") end) ||
get_task_or_run(proj, task, fn -> run("compile", []) end) ||
get!(task)
recursive = recursive(module)
@@ -450,7 +450,8 @@ defmodule Mix.Task do
run_alias(t, alias_args, proj, original_task, res)
end
defp run_alias([], _alias_task, _proj, _original_task, res) do
defp run_alias([], _alias_args, proj, original_task, res) do
Mix.TasksServer.put({:task, original_task, proj})
res
end
+1 -1
View File
@@ -34,7 +34,7 @@ defmodule Mix.Tasks.App.Config do
runtime = config[:config_path] |> Path.dirname() |> Path.join("runtime.exs")
if File.exists?(runtime) do
Mix.Tasks.Loadconfig.load_file(runtime)
Mix.Tasks.Loadconfig.load_runtime(runtime)
end
if opts[:preload_modules] do
+20 -3
View File
@@ -354,14 +354,15 @@ defmodule Mix.Tasks.Compile.App do
defp apps_from_runtime_prod_deps(properties, config) do
included_applications = Keyword.get(properties, :included_applications, [])
for {app, opts} <- Mix.Dep.deps_opts(config),
for {app, opts} <- deps_opts(config),
runtime_app?(opts),
app not in included_applications,
do: {app, if(Keyword.get(opts, :optional, false), do: :optional, else: :required)}
end
defp runtime_app?(opts) do
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) and matching_only?(opts)
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) and matching_only?(opts) and
matching_target?(opts)
end
defp matching_only?(opts) do
@@ -371,6 +372,22 @@ defmodule Mix.Tasks.Compile.App do
end
end
defp matching_target?(opts) do
case Keyword.fetch(opts, :targets) do
{:ok, value} -> Mix.target() in List.wrap(value)
:error -> true
end
end
defp deps_opts(config) do
for config_dep <- Keyword.get(config, :deps, []),
do: {elem(config_dep, 0), dep_opts(config_dep)}
end
defp dep_opts({_app, opts}) when is_list(opts), do: opts
defp dep_opts({_app, _req, opts}) when is_list(opts), do: opts
defp dep_opts(_), do: []
## Helpers for loading and manipulating apps
@doc false
@@ -386,7 +403,7 @@ defmodule Mix.Tasks.Compile.App do
Keyword.get(properties, :extra_applications, [])
project_apps(properties, config, extra, fn ->
config |> Mix.Dep.deps_opts() |> Keyword.keys()
config |> deps_opts() |> Keyword.keys()
end)
end
+10 -5
View File
@@ -103,12 +103,13 @@ defmodule Mix.Tasks.Compile.Elixir do
manifest = manifest()
configs = [Mix.Project.config_mtime() | Mix.Tasks.Compile.Erlang.manifests()]
force = opts[:force] || Mix.Utils.stale?(configs, [manifest])
{tracers, opts} = pop_tracers(opts)
opts =
(project[:elixirc_options] || [])
|> Keyword.merge(opts)
|> xref_exclude_opts(project)
|> tracers_opts()
|> tracers_opts(tracers)
|> profile_opts()
Mix.Compilers.Elixir.compile(manifest, srcs, dest, [:ex], force, opts)
@@ -134,17 +135,21 @@ defmodule Mix.Tasks.Compile.Elixir do
end
end
defp tracers_opts(opts) do
defp pop_tracers(opts) do
case Keyword.pop_values(opts, :tracer) do
{[], opts} ->
opts
{[], opts}
{tracers, opts} ->
tracers = Enum.map(tracers, &Module.concat([&1]))
Keyword.update(opts, :tracers, tracers, &(tracers ++ &1))
{Enum.map(tracers, &Module.concat([&1])), opts}
end
end
defp tracers_opts(opts, tracers) do
tracers = tracers ++ Code.get_compiler_option(:tracers)
Keyword.update(opts, :tracers, tracers, &(tracers ++ &1))
end
defp profile_opts(opts) do
case Keyword.fetch(opts, :profile) do
{:ok, "time"} -> Keyword.put(opts, :profile, :time)
+14 -7
View File
@@ -29,35 +29,42 @@ defmodule Mix.Tasks.Loadconfig do
case args do
[] -> load_default()
[file] -> load_imports(file)
[file] -> load_compile(file)
end
end
@doc false
def load_default do
defp load_default do
config = Mix.Project.config()
if File.regular?(config[:config_path]) or config[:config_path] != "config/config.exs" do
load_imports(config[:config_path])
load_compile(config[:config_path])
else
[]
end
end
@doc false
def load_imports(file) do
# Loads compile-time configuration, they support imports, and are not deep merged.
def load_compile(file) do
{config, files} = Config.Reader.read_imports!(file, env: Mix.env(), target: Mix.target())
Mix.ProjectStack.loaded_config(persist_apps(config, file), files)
config
end
@doc false
def load_file(file) do
# Loads runtime configuration, they do not support imports, and are deep merged.
def load_runtime(file) do
config = Config.Reader.read!(file, env: Mix.env(), target: Mix.target(), imports: :disabled)
Mix.ProjectStack.loaded_config(persist_apps(config, file), [])
Mix.ProjectStack.loaded_config(persist_apps(hydrate_apps(config), file), [])
config
end
defp hydrate_apps(config) do
for {app, pairs} <- config do
hd(Config.Reader.merge([{app, Application.get_all_env(app)}], [{app, pairs}]))
end
end
defp persist_apps(config, file) do
Application.put_all_env(config, persistent: true)
apps = Keyword.keys(config)
+7 -1
View File
@@ -154,7 +154,13 @@ defmodule Mix.Tasks.Run do
defp process_config(opts) do
for {:config, value} <- opts do
Mix.Tasks.Loadconfig.load_imports(value)
# TODO: Remove on v2.0.
IO.warn(
"the --config flag is deprecated. If you need to handle multiple configurations, " <>
"it is preferable to dynamically import them in your config files"
)
Mix.Tasks.Loadconfig.load_compile(value)
end
:ok
+4
View File
@@ -21,6 +21,10 @@ defmodule Mix.TasksServer do
Agent.update(@name, &Map.put(&1, tuple, true), @timeout)
end
def get(tuple) do
Agent.get(@name, &Map.get(&1, tuple), @timeout)
end
def delete_many(many) do
Agent.update(@name, &Map.drop(&1, many), @timeout)
end
+23 -4
View File
@@ -588,7 +588,7 @@ defmodule Mix.Utils do
# Starting an HTTP client profile allows us to scope
# the effects of using an HTTP proxy to this function
{:ok, _pid} = :inets.start(:httpc, [{:profile, :mix}])
{:ok, _pid} = :inets.start(:httpc, profile: :mix)
headers = [{'user-agent', 'Mix/#{System.version()}'}]
request = {:binary.bin_to_list(path), headers}
@@ -601,7 +601,28 @@ defmodule Mix.Utils do
# If a proxy environment variable was supplied add a proxy to httpc.
http_options = [relaxed: true] ++ proxy_config(path)
case :httpc.request(:get, request, http_options, [body_format: :binary], :mix) do
case httpc_request(request, http_options) do
{:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}
when inet in [:inet, :inet6] and reason in [:ehostunreach, :enetunreach] ->
:httpc.set_options([ipfamily: fallback(inet)], :mix)
request |> httpc_request(http_options) |> httpc_response()
response ->
httpc_response(response)
end
after
:inets.stop(:httpc, :mix)
end
defp fallback(:inet), do: :inet6
defp fallback(:inet6), do: :inet
defp httpc_request(request, http_options) do
:httpc.request(:get, request, http_options, [body_format: :binary], :mix)
end
defp httpc_response(response) do
case response do
{:ok, {{_, status, _}, _, body}} when status in 200..299 ->
{:ok, body}
@@ -611,8 +632,6 @@ defmodule Mix.Utils do
{:error, reason} ->
{:remote, "httpc request failed with: #{inspect(reason)}"}
end
after
:inets.stop(:httpc, :mix)
end
defp file?(path) do
+10 -2
View File
@@ -10,11 +10,14 @@ defmodule Mix.AliasesTest do
h: "hello",
p: &inspect/1,
compile: "hello",
cmd: &call_cmd/1,
help: ["help", "hello"],
"nested.h": [&Mix.shell().info(inspect(&1)), "h foo bar"]
]
]
end
defp call_cmd(args), do: Mix.Task.run("cmd", args)
end
setup do
@@ -50,13 +53,18 @@ defmodule Mix.AliasesTest do
assert Mix.Task.run("compile", []) == :noop
end
test "run alias override with recursion" do
test "run alias override with name-recursion" do
assert Mix.Task.rerun("help", []) == "Hello, World!"
assert_received {:mix_shell, :info, ["mix test" <> _]}
# Arguments are passed to the recursive task and not the last one.
assert ExUnit.CaptureIO.capture_io(fn ->
Mix.Task.rerun("help", ["test"]) == "Hello, World!"
Mix.Task.rerun("help", ["test"])
end) =~ "mix test"
end
test "run alias override with code-recursion" do
assert Mix.Task.rerun("cmd", ["echo", "hello"]) == :ok
assert_received {:mix_shell, :run, ["hello" <> _]}
end
end
+5 -1
View File
@@ -17,11 +17,13 @@ defmodule Mix.Tasks.App.ConfigTest do
File.write!("config/config.exs", """
import Config
config :sample, from_compile: :compile, from_runtime: :compile
config :sample, :nested, from_compile: :compile, from_runtime: :compile
""")
File.write!("config/runtime.exs", """
import Config
config :sample, from_runtime: :runtime
config :sample, :nested, from_compile: :compile, from_runtime: :compile
""")
Mix.Task.run("loadconfig")
@@ -30,10 +32,12 @@ defmodule Mix.Tasks.App.ConfigTest do
assert Application.get_all_env(:sample) |> Enum.sort() == [
from_compile: :compile,
from_env: :env,
from_runtime: :runtime
from_runtime: :runtime,
nested: [from_compile: :compile, from_runtime: :compile]
]
end)
after
Application.delete_env(:sample, :nested, persistent: true)
Application.delete_env(:sample, :from_env, persistent: true)
Application.delete_env(:sample, :from_compile, persistent: true)
Application.delete_env(:sample, :from_runtime, persistent: true)
+4 -2
View File
@@ -40,7 +40,9 @@ defmodule Mix.Tasks.Compile.AppTest do
{:ok6, path: "../ok", optional: true},
{:ok7, path: "../ok", optional: false},
{:ok8, path: "../ok", app: false},
{:ok9, path: "../ok"}
{:ok9, path: "../ok"},
{:ok10, path: "../ok", targets: [:will_never_be_listed]},
{:ok11, path: "../ok", targets: [Mix.target()]}
]
end
end
@@ -130,7 +132,7 @@ defmodule Mix.Tasks.Compile.AppTest do
properties = parse_resource_file(:custom_deps)
assert properties[:applications] ==
[:kernel, :stdlib, :elixir, :logger, :ok1, :ok3, :ok4, :ok7]
[:kernel, :stdlib, :elixir, :logger, :ok1, :ok3, :ok4, :ok7, :ok11]
end)
end
@@ -57,6 +57,19 @@ defmodule Mix.Tasks.Compile.ElixirTest do
Code.put_compiler_option(:tracers, [])
end
test "compiles a project with a previously set custom tracer" do
Process.register(self(), __MODULE__)
Code.put_compiler_option(:tracers, [__MODULE__])
in_fixture("no_mixfile", fn ->
Mix.Tasks.Compile.Elixir.run([])
assert_received {:alias_reference, _meta, A}
assert_received {:alias_reference, _meta, B}
end)
after
Code.put_compiler_option(:tracers, [])
end
test "warns when Logger is used but not depended on" do
in_fixture("no_mixfile", fn ->
File.write!("lib/a.ex", """
+13 -2
View File
@@ -359,6 +359,11 @@ defmodule Mix.Tasks.ReleaseTest do
config = [releases: [runtime_config: [reboot_system_after_config: true]]]
Mix.Project.in_project(:release_test, ".", config, fn _ ->
File.write!("config/config.exs", """
#{File.read!("config/config.exs")}
config :release_test, :runtime, keep: :static, override: :static
""")
File.write!("config/runtime.exs", """
import Config
@@ -366,7 +371,11 @@ defmodule Mix.Tasks.ReleaseTest do
raise "file should not be loaded while assembling release"
end
config :release_test, :runtime, {:was_set, config_env(), config_target()}
config :release_test, :runtime,
override: :runtime,
config_env: config_env(),
config_target: config_target()
config :release_test, :encoding, {:runtime, :time_μs, :"£", "£", '£'}
""")
@@ -401,7 +410,9 @@ defmodule Mix.Tasks.ReleaseTest do
release_mode: "embedded",
release_node: "runtime_config",
release_vsn: "0.1.0",
runtime_config: {:ok, {:was_set, :dev, :host}},
runtime_config:
{:ok,
[keep: :static, override: :runtime, config_env: :dev, config_target: :host]},
static_config: {:ok, :was_set},
sys_config_env: sys_config_env,
sys_config_init: sys_config_init
-14
View File
@@ -3,24 +3,10 @@ Code.require_file("../../test_helper.exs", __DIR__)
defmodule Mix.Tasks.RunTest do
use MixTest.Case
import ExUnit.CaptureIO
setup do
Mix.Project.push(MixTest.Case.Sample)
end
test "loads configuration", context do
in_tmp(context.test, fn ->
config = fixture_path("configs/good_config.exs")
expr = "IO.puts(Application.get_env(:my_app, :key))"
assert capture_io(fn -> Mix.Task.run("run", ["--config", config, "--eval", expr]) end) ==
"value\n"
end)
after
Application.delete_env(:my_app, :key)
end
test "run requires files before evaling commands", context do
git_repo = fixture_path("git_repo/lib/git_repo.ex")