Compare commits
24
Commits
v1.12.0-rc.0
...
v1.11.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7837cc5c59 | ||
|
|
b96fc39422 | ||
|
|
e1cb898198 | ||
|
|
88e2894925 | ||
|
|
f7cb5ae053 | ||
|
|
b4845e6854 | ||
|
|
0f1200f99b | ||
|
|
cec1a7190b | ||
|
|
53d1b98a05 | ||
|
|
4828f11cd2 | ||
|
|
501271cad0 | ||
|
|
fd4a6ad5cf | ||
|
|
196aab87ca | ||
|
|
59b266506a | ||
|
|
1c8e625d77 | ||
|
|
f9d7adbfdf | ||
|
|
f1721c8a58 | ||
|
|
57abca4c16 | ||
|
|
8e9ece0d62 | ||
|
|
54ba096d34 | ||
|
|
6042fd0bcc | ||
|
|
475b73b46f | ||
|
|
3cf6069ad9 | ||
|
|
48cee5aaa5 |
+11
-7
@@ -243,7 +243,7 @@ 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-dev
|
||||
## v1.11.0 (2020-10-06)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -284,6 +284,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 +306,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,9 +344,12 @@ 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`
|
||||
* [URI] `URI.decode_query/2` emits an empty string for parameters without values, according to https://url.spec.whatwg.org/#application/x-www-form-urlencoded
|
||||
* [Record] Keep lexical ordering when creating records
|
||||
* [Registry] Do not crash when a process with key-value has been registered using `:via` and it fails to start on `init`
|
||||
* [URI] `URI.decode_query/2` emits an empty string for parameters without values, according to [URL's living standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded) - note this behaviour is not specified in the spec implemented by the URI module, so the living standard was chosen
|
||||
* [Version] Add defaults and enforce keys in `Version` struct
|
||||
|
||||
#### ExUnit
|
||||
@@ -357,7 +361,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
|
||||
@@ -395,7 +399,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
|
||||
|
||||
|
||||
@@ -2,8 +2,7 @@ PREFIX ?= /usr/local
|
||||
TEST_FILES ?= "*_test.exs"
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
MAN_PREFIX ?= $(SHARE_PREFIX)/man
|
||||
#CANONICAL := vMAJOR.MINOR/
|
||||
CANONICAL ?= master/
|
||||
CANONICAL := v1.11/
|
||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL_MAKE := if [ -n "$(ERLC_OPTS)" ]; then ERL_COMPILER_OPTIONS=$(ERLC_OPTS) erl -make; else erl -make; fi
|
||||
|
||||
+2
-3
@@ -6,12 +6,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
||||
|
||||
| Elixir version | Support
|
||||
| -------------- | ------------------------------
|
||||
| 1.11 | Development
|
||||
| 1.10 | Bug fixes and security patches
|
||||
| 1.11 | Bug fixes and security patches
|
||||
| 1.10 | Security patches only
|
||||
| 1.9 | Security patches only
|
||||
| 1.8 | Security patches only
|
||||
| 1.7 | Security patches only
|
||||
| 1.6 | Security patches only
|
||||
|
||||
## Announcements
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 ->%>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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},
|
||||
|
||||
+10
-14
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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}
|
||||
@@ -278,10 +277,19 @@ defmodule Module.Types.Expr do
|
||||
# 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 opts[:reduce] do
|
||||
with :ok <- 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 +387,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 +414,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])
|
||||
|
||||
|
||||
@@ -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}`.
|
||||
|
||||
@@ -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]}
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -174,4 +174,4 @@ Version | Deprecated feature | Replaced by (ava
|
||||
[v1.8]: https://github.com/elixir-lang/elixir/blob/v1.8/CHANGELOG.md#4-hard-deprecations
|
||||
[v1.9]: https://github.com/elixir-lang/elixir/blob/v1.9/CHANGELOG.md#4-hard-deprecations
|
||||
[v1.10]: https://github.com/elixir-lang/elixir/blob/v1.10/CHANGELOG.md#4-hard-deprecations
|
||||
[v1.11]: https://github.com/elixir-lang/elixir/blob/master/CHANGELOG.md#4-hard-deprecations
|
||||
[v1.11]: https://github.com/elixir-lang/elixir/blob/v1.11/CHANGELOG.md#4-hard-deprecations
|
||||
|
||||
@@ -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">>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -310,6 +310,22 @@ defmodule Kernel.GuardTest do
|
||||
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)
|
||||
|
||||
@@ -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],
|
||||
(
|
||||
|
||||
@@ -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]) ==
|
||||
|
||||
+18
-33
@@ -1,8 +1,8 @@
|
||||
Code.require_file("../../test_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]}
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 """
|
||||
|
||||
@@ -647,7 +647,8 @@ defmodule LoggerTest do
|
||||
:ok = Logger.configure(level: :debug)
|
||||
|
||||
try do
|
||||
assert Logger.App.config_change([level: :error], [], []) === :ok
|
||||
assert Application.put_env(:logger, :level, :error) === :ok
|
||||
assert :application_controller.config_change(logger: [level: :debug]) === :ok
|
||||
assert Logger.level() === :error
|
||||
after
|
||||
Logger.configure(level: :debug)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user