Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c5259826f | ||
|
|
ccdefd904f | ||
|
|
ff95a59ec0 | ||
|
|
4081590742 | ||
|
|
693df5fe8c | ||
|
|
b4605374b5 | ||
|
|
36ec08e0c8 | ||
|
|
ae0ac9b3c9 | ||
|
|
e3479831ba | ||
|
|
ffbcc004ec | ||
|
|
e0d61c7875 | ||
|
|
b2059031a6 | ||
|
|
ed2829d8ea | ||
|
|
081f99cd37 | ||
|
|
7837cc5c59 | ||
|
|
b96fc39422 | ||
|
|
e1cb898198 | ||
|
|
88e2894925 | ||
|
|
f7cb5ae053 | ||
|
|
b4845e6854 | ||
|
|
0f1200f99b | ||
|
|
cec1a7190b | ||
|
|
53d1b98a05 | ||
|
|
4828f11cd2 | ||
|
|
501271cad0 | ||
|
|
fd4a6ad5cf | ||
|
|
196aab87ca | ||
|
|
59b266506a | ||
|
|
1c8e625d77 | ||
|
|
f9d7adbfdf | ||
|
|
f1721c8a58 | ||
|
|
57abca4c16 | ||
|
|
8e9ece0d62 | ||
|
|
54ba096d34 | ||
|
|
6042fd0bcc | ||
|
|
475b73b46f | ||
|
|
3cf6069ad9 | ||
|
|
48cee5aaa5 |
+35
-7
@@ -243,7 +243,31 @@ 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.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 +308,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 +330,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 +368,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 +385,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 +423,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},
|
||||
|
||||
@@ -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
@@ -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),
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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}
|
||||
@@ -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
|
||||
|
||||
@@ -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}`.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">>,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]) ==
|
||||
|
||||
@@ -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)
|
||||
|
||||
+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 """
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -120,7 +120,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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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