Compare commits

...
48 Commits
Author SHA1 Message Date
José Valim 63b8d0ba34 Release v1.6.0 2018-01-17 19:20:24 +01:00
Jean-Philippe Cugnet 12102d8015 Don’t add parens for defrecord/3 and defrecordp/3 (#7204) 2018-01-13 19:56:28 +01:00
José Valim 67e575eca7 Release v1.6.0-rc.1 2018-01-11 10:21:31 +01:00
José Valim f3960a2f34 Use flex_glue for bitstrings and tuples in the formatter (#7183)
Closes #7152
2018-01-11 10:09:16 +01:00
José Valim 824fcea04a Introduce a group for collections (#7185) 2018-01-11 10:09:14 +01:00
José Valim a6928f774c Add discard_threshold to Logger (#7193) 2018-01-11 10:09:11 +01:00
José Valim 3d08d243d8 Fetch new dependencies on mix deps.update, closes #7194 2018-01-10 11:55:17 +01:00
José Valim 301e0f6b63 Remove leftover inspect 2018-01-10 09:41:40 +01:00
José Valim 7c56a02b0a Allow :macros and :includes to be given to Record.extract, closes #7195 2018-01-10 09:33:56 +01:00
Devon Estes e442bc630b Add additional documentation for assert_in_delta and refute_in_delta (#7191) 2018-01-09 15:08:25 +01:00
José Valim 3dbefd7704 Fix precedence of & in regards to =, closes #7188 2018-01-09 14:14:04 +01:00
José Valim fd1d15a8c5 Consider case ignorable characters on Greek downcasing, closes #7149
Note this does not impact the runtime cost of other downcasing
operations. The final beam file grew only in 8kb.
2018-01-09 13:59:55 +01:00
Devon Estes 56fcd2a45d Change assert_in_delta better handle a delta of 0 (#7189)
Previously if the two values that we were checking were within a given
delta were equal, they would fail if the expected delta was `0`.
2018-01-09 13:13:16 +01:00
Andrea Leopardi 900043c7f2 Fix a typo in a heredoc-related warning
[ci skip]
2018-01-09 13:13:13 +01:00
José Valim 14f0625911 Remove : from mix path helpers to avoid issues on Windows 2018-01-07 22:13:23 +01:00
José Valim d76c35818e Warn if heredoc is outdented, closes #7174 2018-01-07 17:47:52 +01:00
José Valim 6a581275aa Do not autogenerate docs for defdelegate
This would cause code with multiple clauses where the last
one is a delegate to contain the wrong documentation.
2018-01-06 23:27:00 +01:00
José Valim bcbe91a25c Hint the node@host format in --remsh error, closes #7170 2018-01-06 14:23:40 +01:00
José Valim 949ac2b517 Tolerate modules that do not export :deprecated info, closes #7182 2018-01-06 14:06:18 +01:00
Frank Hunleth adb1fa1374 Fix inspect for non-decimal negative integers (#7181)
Before:

iex> inspect(-1, base: :hex)
"0x-1"

After:

iex> inspect(-1, base: :hex)
"-0x1"

This change also applies to the octal and binary base options.
2018-01-05 18:44:50 +01:00
José Valim bd275ea51c Raise if imported dependencies have not been checked out on mix format 2018-01-05 17:49:18 +01:00
José Valim d02755530a Revert "Attempt to soft_purge modules, closes #7047"
Soft purging modules was badfun/badarg errors when
invoking left over anonymous functions.
2018-01-05 17:23:07 +01:00
José Valim 2ab2bffc00 Properly handle :erlang.element in Exception blame 2018-01-05 17:23:04 +01:00
Aaron Tinio 701c0b433d Fix typo (#7178) 2018-01-05 17:23:00 +01:00
José Valim 895adcb001 Rearrange equals and inserts for shorter diff scripts
Closes #7169.
2018-01-05 17:22:57 +01:00
Tobias Pfeiffer e6e6f8d484 Changelog: mix test reports doctests separately now (#7176)
Couldn't find it in the Changelog, maybe I'm a bit daft :) But the feature is there and I love it. Thanks 💚

```
tobi@comfy ~/github/benchee $ mix test
.............................................................................................................................................................................................................................................................................................

Finished in 2.3 seconds
102 doctests, 183 tests, 0 failures
```
2018-01-03 20:21:39 +00:00
Wojtek Mach 09b0ee3f12 Add @impl true to StringIO & IEx.Pry (#7167) 2018-01-01 20:26:19 +01:00
Wojtek Mach 5c6f7b9ce5 Don't mention deprecated {Map,Keyword}.replace/3 in docs (#7166) 2018-01-01 20:25:59 +01:00
José Valim adbbba118d Do not leak variables during optimizations, closes #7161 2018-01-01 12:04:52 +01:00
Xavier Noria fab66ad834 s/when/after/ in Application.stop/1 docs [ci skip] (#7164) 2018-01-01 12:04:45 +01:00
José Valim 31ef3abff0 Improve docs for matching macro arguments on break!
See #7155.
2017-12-30 18:37:22 +01:00
Donald Little 021a84aafe Return right side expression value on struct matching (#7160) 2017-12-30 18:37:17 +01:00
Daniel Kempkens d8fd6ef6ff Fix Supervisor.child_spec() type (#7157) 2017-12-30 18:37:13 +01:00
José Valim f8f4e1dd79 Clear up deprecations page 2017-12-30 18:37:10 +01:00
Tomasz Marek Sulima d3af8ea8aa Make formatting options and task options clearer (#7150) 2017-12-27 09:38:17 +01:00
José Valim d587434be2 Improvements to greek handling of downcase, closes #7149 2017-12-26 23:18:51 +01:00
José Valim 6aadfba586 Store expanded expressions in defguard, closes #7147 2017-12-26 00:06:36 +01:00
José Valim 625f45930a Fixes to readme and deprecation pages 2017-12-25 10:12:57 +01:00
José Valim cb47d08c13 Update CHANGELOG 2017-12-25 10:12:46 +01:00
José Valim 9a5d1a39d1 Fix clean command for erlang-based compiler tasks 2017-12-24 23:22:42 +01:00
José Valim 27102e276d Release v1.6.0-rc.0 2017-12-24 23:22:23 +01:00
José Valim 0b646c6188 Improve wording in CHANGELOG 2017-12-24 15:23:28 +01:00
José Valim 3879f0d8b3 Update deprecations table, closes #7137 2017-12-24 12:15:51 +01:00
José Valim 24369d5724 Ensure we can still receive [term()] in start_child spec, closes #7138 2017-12-24 12:00:04 +01:00
José Valim 3d20f049e6 Update CHANGELOG 2017-12-23 23:02:55 +01:00
José Valim 8d07fe6f59 Update CHANGELOG and release info 2017-12-23 20:26:39 +01:00
José Valim d27b285687 Revert "Add support for supervisor fun to Task.Supervisor.async_stream/* (#6552)"
This reverts commit 91061bf8f9.
2017-12-23 20:01:15 +01:00
José Valim 1684c8067e Add advice regards deprecations 2017-12-23 19:48:52 +01:00
76 changed files with 1195 additions and 870 deletions
+100 -13
View File
@@ -1,16 +1,69 @@
# Changelog for Elixir v1.6
## Compiler diagnostics
TODO.
## Code formatter
TODO.
The big feature in Elixir v1.6 is the addition of a code formatter and an accompanying `mix format` task that adds automatic formatting to your projects.
## Stream data and property testing
The goal of the formatter is to automate the styling of codebases into a unique and consistent layout used across teams and the whole community. Code is now easier to write, as you no longer need to concern yourself with formatting rules. Code is also easier to read, as you no longer need to convert the styles of other developers in your mind.
TODO.
The formatter also helps new developers to learn the language, by giving immediate feedback on code structure, and eases code reviews by allowing teams to focus on business rules and code quality, rather than code style.
To automatically format your codebase, you can run the new `mix format` task. A `.formatter.exs` file may be added to your project root for rudimentary formatter configuration. The mix task also supports flags for CI integration. For instance, you can make your build or a Pull Request fail if the code is not formatted accordingly. We also recommend developers to check their favorite editor and see if they already provide key bindings for `mix format`, allowing a file or a code snippet to be formatted without ceremony.
The Elixir codebase itself has been already fully formatted and all further contributions are expected to contain formatted code. We recommend existing codebases to be formatted in steps. While the formatter will correctly handle long lines and complex expressions, refactoring the code by breaking those into variables or smaller functions as you format them will lead to overall cleaner and more readable codebases.
## Dynamic Supervisor
Supervisors in Elixir are responsible for starting, shutting down and restarting child process when things go wrong. Most of the interaction with supervisors happen with the Supervisor module and it contains three main strategies: `:one_for_one`, `:rest_for_one` and `:one_for_all`.
However, sometimes the children of a supervisor are not known upfront and are rather started dynamically. For example, if you are building a web server, you have each request beind handled by a separate supervised process. Those cases were handled in the Supervisor module under a special strategy called `:simple_one_for_one`.
Unfortunately, this special strategy changed the semantics of the supervisor in regards to initialization and shutdown. Plus some APIs expected different inputs or would be completely unavailable depending on the supervision strategy.
Elixir v1.6 addresses this issue by introducing a new `DynamicSupervisor` module, which encapsulates the old `:simple_one_for_one` strategy and APIs in a proper module while allowing the documentation and API of the `Supervisor` module to focus on its main use cases. Having a separate `DynamicSupervisor` module also makes it simpler to add new features to the dynamic supervisor, such as the new `:max_children` option that limits the maximum number of children supervised dynamically.
## `@deprecated` and `@since` attributes
This release also introduces two new attributes associated to function definitions: `@deprecated` and `@since`. The former marks if a function or macro is deprecated, the latter annotates the version the API was introduced:
@doc "Breaks a collection into chunks"
@since "1.0.0"
@deprecated "Use chunk_every/2 instead"
def chunk_every(collection, chunk_size) do
...
end
The `mix xref` task was also updated to warn if your project calls deprecated code. So if a definition is marked as `@deprecated` and a module invokes it, a warning will be emitted during compilation. This effectively provides libraries and frameworks a mechanism to deprecate code without causing multiple warnings to be printed in runtime and without impacting performance.
Note those attributes are not yet available to tools that generate documentation. Such functionality will be added in future releases as it requires changes to how Elixir stores documentation in BEAM files. We still recommend developers to properly annotate their APIs, as the information will then be already available when the tooling is updated.
## defguard and defguardp
Elixir provides the concepts of guards: expressions used alongside pattern matching to select a matching clause. Let's see an example straight from Elixir's home page:
def serve_drinks(%User{age: age}) when age >= 21 do
# Code that serves drinks!
end
`%User{age: age}` is matching on a `User` struct with an age field and `when age >= 21` is the guard.
Since only a handful of constructs are [allowed in guards](https://hexdocs.pm/elixir/guards.html#content), if you were in a situation where you had to check the age to be more than or equal to 21 in multiple times, extracting the guard to a separate function would be [less than obvious and error prone](https://github.com/elixir-lang/elixir/issues/2469). To address those issues, this release introduces `defguard/1` and `defguardp/1`:
defguard is_drinking_age(age) when age >= 21
def serve_drinks(%User{age: age}) when is_drinking_age(age) do
# Code that serves drinks!
end
## IEx improvements
IEx also got its share of improvements. The new code formatter allows us to pretty print code snippets, types and specifications, improving the overall experience when exploring code through the terminal.
The autocomplete mechanism also got smarter, being able to provide context autocompletion. For example, typing `t Enum.` and hitting TAB will autocomplete only the types in Enum (in contrast to all functions). Typing `b GenServer.` and hitting TAB will autocomplete only the behaviour callbacks.
Finally, the breakpoint functionality added in Elixir v1.5 has been improved to support pattern matching and guards. For example, to pattern match on a function call when the first argument is the atom `:foo`, you may do:
break! SomeFunction.call(:foo, _, _)
## mix xref
@@ -20,7 +73,7 @@ One of such additions is the `--include-siblings` option that can be given to al
$ mix xref callers SomeModule --include-siblings
The `graph` command in `mix xref` can also output general statistics about the graph. In the hexpm project, you would get:
The `graph` command in `mix xref` now can also output general statistics about the graph. In [the hexpm project](https://github.com/hexpm/hexpm), you would get:
$ mix xref graph --format stats
Tracked files: 129 (nodes)
@@ -52,7 +105,7 @@ The `graph` command in `mix xref` can also output general statistics about the g
* lib/hexpm/accounts/user.ex (19)
* lib/hexpm/repo.ex (19)
`mix xref graph` also get the `--only-nodes` and `--label` options. The former asks Mix to only output file names (nodes) without the edges. The latter allows you to focus on certain relationships:
`mix xref graph` also got the `--only-nodes` and `--label` options. The former asks Mix to only output file names (nodes) without the edges. The latter allows you to focus on certain relationships:
# To get all files that depend on lib/foo.ex
mix xref graph --sink lib/foo.ex --only-nodes
@@ -68,7 +121,9 @@ The `graph` command in `mix xref` can also output general statistics about the g
Those improvements will help developers better understand the relationship between files and reveal potentially complex parts of their systems.
## v1.6.0-dev
Other improvements in Mix include better compiler diagnostics for editor integration, support for the `--slowest N` flag in `mix test` that shows the slowest tests in your suite, and a new `mix profile.eprof` task that provides time based profiling, complementing the existing `mix profile.cprof` (count based) and `mix profile.fprof` (flame based).
## v1.6.0 (2018-01-17)
### 1. Enhancements
@@ -78,10 +133,15 @@ Those improvements will help developers better understand the relationship betwe
#### Elixir
* [Calendar] Add truncate to `Time`, `DateTime` and `NaiveDateTime` to facilitate microsecond precision pruning
* [Code] Add `format_string!/2` and `format_file!/2` for automatic code formatting
* [Code] Support column annotations in quoted expressions with `columns: true` in `Code.string_to_quoted/2`
* [DynamicSupervisor] Add `DynamicSupervisor` designed to manage children that are added and removed dynamically
* [Exception] Make `Exception.blame/3` extensible by adding an optional `blame/2` callback to exceptions
* [Exception] Improve the printing of guards on blamed exceptions
* [Enumerable] Add `Enumerable.slice/1` and optimize many `Enum` operations with the new protocol. This allows data-structures with index-based random access to provide a non-linear implementation
* [Inspect.Algebra] Add `:strict` and `:flex` breaks
* [Inspect] Show UTF-8 BOM on inspected strings
* [Inspect.Algebra] Add `:strict` and `:flex` breaks - this gives more control over the document fitting
* [Inspect.Algebra] Allow a group to inherit the parent group break
* [Inspect.Algebra] Add `force_unfit/1` and `next_break_fits/2` which give more control over document fitting
* [Inspect.Algebra] Add `collapse_lines/1` for collapsing multiple lines to a maximum value
@@ -89,25 +149,41 @@ Those improvements will help developers better understand the relationship betwe
* [Kernel] Prefix variables with V when emitting Erlang code. This improves the integration with tools such as Erlang code formatters and the GUI debugger
* [Kernel] Warn on the use of `length(x) == 0` in guards
* [Kernel] Warn if `catch` comes before `rescue` in try
* [Kernel] Warn if heredoc is outdented compared to its closing quotes
* [Kernel] Add `defguard/1` and `defguardp/1` to make it easier to build guard-safe macros
* [Kernel.ParallelCompiler] Add `compile/2`, `compile_to_path/3` and `require/2` which provide detailed information about warnings and errors
* [Kernel.SpecialForms] Support the `uniq: true` flag in `for` comprehensions
* [Module] Introduce `@deprecated` and `@since` attributes
* [List] Rearrange equals and inserts for shorter diff scripts in `List.myers_difference/2`
* [Record] Allow `:macros` and `:includes` to be given to `Record.extract/2`
* [Stream] Add `Stream.intersperse/2`
* [String] Update to Unicode 10
* [String] Allow passing empty string `match` to `String.replace/4`
* [Task] Allow a custom supervisor to be given to `Task.Supervisor.async_stream/3`
* [String] Support context and language sensitive operations in `String.upcase/2` and `String.downcase/2`. Currently only the `:greek` context is supported
* [String] Support `:ascii` conversion in `String.upcase/2` and `String.downcase/2`
* [Time] Add `Time.add/3`
#### ExUnit
* [ExUnit.Assertions] Perform inclusive checks in `assert_in_delta`
* [ExUnit.Callbacks] Add `ExUnit.Callbacks.start_supervised!/2`
* [ExUnit.Case] Generate a random seed per test based on the test suite seed
#### IEx
* [IEx.Autocomplete] Provide contextual autocompletion: `t Enum.` will autocomplete types, `b Enum` will autocomplete callbacks
* [IEx.CLI] Provide hints for developers when a bad host name is given to `--remsh`
* [IEx.Helpers] Automatically include specs when showing documentation for functions/macros
* [IEx.Helpers] Improve formatting of behaviours and typespecs by using the formatter
* [IEx.Helpers] Allow pattern matching and guard expressions when on `IEx.break!`
#### Logger
* [Logger] Add `:discard_threshold` to Logger to help with message queue overflow
#### Mix
* [mix app.start] Add `--preload-modules` to `mix app.start`
* [mix archive.build] Allow `mix archive.build` to bundle dot files via an option
* [mix compile] Define a behavior for Mix compiler tasks and return diagnostics from compiler tasks
* [mix compile] Track struct dependencies between files and recompile them only if the struct changes
@@ -116,26 +192,35 @@ Those improvements will help developers better understand the relationship betwe
* [mix profile.eprof] Add a new task for time-based profiling with eprof
* [mix test] Run all functions in a describe block by giving the `file:line` the describe block starts
* [mix test] Report the top N slowest tests with the `--slowest N` flag
* [mix test] Report the number of doctests and tests separately
* [mix xref] Support `--include-siblings` in reports for umbrella support
* [mix xref] Add `mix xref graph --format stats`
* [mix xref] Add `--only-nodes` and `--label` filters to mix xref graph
* [mix xref] Add `mix xref deprecated` that shows the callsite of deprecated functions
### 2. Bug fixes
#### Elixir
* [CLI] Support path with spaces as argument to elixir.bat
* [Inspect] Properly handle minus signal for non-decimal negative integers
* [Integer] Do not raise on non-integer values in `is_odd`/`is_even`
* [Kernel] Solve a precedence issue between `&` and `|`, such as `[&Foo.bar/1 | &Baz.bat/2]`
* [Kernel] Do not load dynamic Elixir modules as `:in_memory` as this value is not officially supported by the code server. Instead, use an empty list, which is the same value used by Erlang.
* [Kernel] Validate variable struct name is atom when used in pattern matching
* [Macro] Fix `Macro.to_string/2` for tuple calls, such as `alias Foo.{Bar, Baz}`
* [MapSet] Return valid MapSet when unioning a legacy MapSet
* [String] Properly downcase the greek sigma letter in `String.downcase/1`
* [Regex] Return a leading empty space when splitting on empty pattern. This makes the `split` operation consistent with the other operations in the `Regex` module
* [Stream] Ensure `Stream.chunk_while/4` does not emit more elements than necessary when halted
* [String] Return a leading empty space when splitting on empty string. This makes the `split` operation consistent with the other operations in the `String` module
* [URI] Preserve empty fragments in `URI.parse/1`
#### Mix
* [mix app.start] Improve the quality of reports if app fails to boot
* [mix cmd] Allow `mix cmd` to be invoked multiple times without marking it as executed
* [mix deps] Ensure optional dependencies in umbrella applications are loaded
* [mix deps.update] Ensure transitive new non-Hex dependencies are also fetched when a repo is updated
* [mix xref] Take compile dependencies with higher priority than runtime ones when building a graph
* [mix xref] Handle external files for xref callers and warnings
@@ -146,6 +231,7 @@ Those improvements will help developers better understand the relationship betwe
* [Inspect.Algebra] `surround/3` and `surround_many/6` are deprecated in favor of `container_doc/6`
* [Kernel.ParallelCompiler] `files/2` and `files_to_path/3` are deprecated in favor of `compile/2` and `compile_to_path/3`
* [Kernel.ParallelRequire] `files/2` is deprecated in favor of `Kernel.ParallelCompiler.require/2`
* [GenServer] Warn if `init/1` is not defined in `GenServer`. This brings GenServer closer to the implementation in OTP and aligns all behaviours to require the `init/1` callback
#### ExUnit
@@ -158,6 +244,7 @@ Those improvements will help developers better understand the relationship betwe
* [Enum] `Enum.partition/2` is deprecated in favor of `Enum.split_with/2`
* [Keyword] `Keyword.replace/3` is deprecated in favor of `Keyword.fetch/2` and `Keyword.put/3`
* [Map] `Map.replace/3` is deprecated in favor of `Map.fetch/2` and `Map.put/3`
* [Macro] `Macro.unescape_tokens/1` and `Macro.unescape_tokens/2` are deprecated in favor of `Enum.map/2`
* [Range] Deprecate `Range.range?/1` in favor of pattern matching on `_ .. _`
## v1.5
+1 -1
View File
@@ -1,7 +1,7 @@
REBAR ?= "$(CURDIR)/rebar"
PREFIX ?= /usr/local
SHARE_PREFIX ?= $(PREFIX)/share
CANONICAL := master/
CANONICAL := v1.6/
ELIXIRC := bin/elixirc --verbose --ignore-module-conflict
ERLC := erlc -I lib/elixir/include
ERL := erl -I lib/elixir/include -noshell -pa lib/elixir/ebin
+2 -2
View File
@@ -32,10 +32,10 @@ If tests pass, you are ready to move on to the [Getting Started guide][1]
or to try Interactive Elixir by running `bin/iex` in your terminal.
However, if tests fail, it is likely you have an outdated Erlang version
(Elixir requires Erlang 18.0 or later). You can check your Erlang version
(Elixir requires Erlang 19.0 or later). You can check your Erlang version
by calling `erl` in the command line. You will see some information as follows:
Erlang/OTP 18 [erts-7.0] [source] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]
Erlang/OTP 19 [erts-8.0] [smp:2:2] [async-threads:10] [kernel-poll:false]
If you have properly set up your dependencies and tests still fail,
you may want to open up a bug report, as explained next.
+1 -1
View File
@@ -10,7 +10,7 @@ This document simply outlines the release process:
3. Ensure CHANGELOG is updated and add current date
4. If a new `vMAJOR.MINOR`, replace "master" with "vVERSION" in the "Compatibility and Deprecations" page and commit
4. Update "Compatibility and Deprecations" if a new OTP version is supported. If a new `vMAJOR.MINOR`, replace "master" with "vVERSION" in the "Deprecations" section
5. If a new `vMAJOR.MINOR`, create a new branch "vMAJOR.MINOR" and set `CANONICAL=` in Makefile
+1 -1
View File
@@ -1 +1 @@
1.6.0-dev
1.6.0
+13 -10
View File
@@ -4,29 +4,20 @@
groups_for_modules: [
# [Kernel, Kernel.SpecialForms],
"Data & Behaviours": [
Access,
"Basic Types": [
Atom,
Base,
Bitwise,
Calendar,
Calendar.ISO,
Date,
Date.Range,
DateTime,
Enum,
Exception,
Float,
Integer,
Keyword,
List,
Map,
MapSet,
NaiveDateTime,
Range,
Record,
Regex,
Stream,
String,
Time,
Tuple,
@@ -34,6 +25,18 @@
Version,
],
"Collections & Enumerables": [
Access,
Date.Range,
Enum,
Keyword,
List,
Map,
MapSet,
Range,
Stream,
],
"IO & System": [
File,
File.Stat,
+2 -2
View File
@@ -186,9 +186,9 @@ defmodule Application do
| {:error, reason :: term}
@doc """
Called when an application is stopped.
Called after an application has been stopped.
This function is called when an application has stopped, i.e., when its
This function is called after an application has been stopped, i.e., after its
supervision tree has been stopped. It should do the opposite of what the
`start/2` callback did, and should perform any necessary cleanup. The return
value of this callback is ignored.
+9 -10
View File
@@ -74,16 +74,15 @@ defmodule Calendar.ISO do
{2000, 1, 1, 12, 0, 0, {0, 6}}
"""
@spec naive_datetime_from_iso_days(Calendar.iso_days()) ::
{
Calendar.year(),
Calendar.month(),
Calendar.day(),
Calendar.hour(),
Calendar.minute(),
Calendar.second(),
Calendar.microsecond()
}
@spec naive_datetime_from_iso_days(Calendar.iso_days()) :: {
Calendar.year(),
Calendar.month(),
Calendar.day(),
Calendar.hour(),
Calendar.minute(),
Calendar.second(),
Calendar.microsecond()
}
@impl true
def naive_datetime_from_iso_days({days, day_fraction}) do
{year, month, day} = date_from_iso_days(days)
+4 -5
View File
@@ -303,7 +303,7 @@ defmodule Code do
The string concatenation makes the code fit on a single line and also
gives more options to the formatter.
A similar example is when the formatter breaks a fuction definition
A similar example is when the formatter breaks a function definition
over multiple clauses:
def my_function(
@@ -379,10 +379,9 @@ defmodule Code do
arg2,
arg3
If the last argument is a data structure of variable length, such as
maps and lists, and the beginning of the data structure fits on the
same line as the function call, then no indentation happens, this
allows code like this:
If the last argument is a data structure, such as maps and lists, and
the beginning of the data structure fits on the same line as the function
call, then no indentation happens, this allows code like this:
Enum.reduce(some_collection, initial_value, fn element, acc ->
# code
+85 -68
View File
@@ -92,7 +92,9 @@ defmodule Code.Formatter do
# Stdlib,
defrecord: 2,
defrecord: 3,
defrecordp: 2,
defrecordp: 3,
# Testing
all: :*,
@@ -415,11 +417,11 @@ defmodule Code.Formatter do
# {}
# {1, 2}
defp quoted_to_algebra({:{}, meta, args}, _context, state) do
tuple_to_algebra(meta, args, state)
tuple_to_algebra(meta, args, :flex_glue, state)
end
defp quoted_to_algebra({:__block__, meta, [{left, right}]}, _context, state) do
tuple_to_algebra(meta, [left, right], state)
tuple_to_algebra(meta, [left, right], :flex_glue, state)
end
defp quoted_to_algebra({:__block__, meta, [list]}, _context, state) when is_list(list) do
@@ -558,7 +560,7 @@ defmodule Code.Formatter do
end
doc =
with_next_break_fits(next_break_fits?(right_arg), right, fn right ->
with_next_break_fits(next_break_fits?(right_arg, state), right, fn right ->
concat(group(left), group(nest(glue(op, group(right)), 2, :break)))
end)
@@ -727,7 +729,7 @@ defmodule Code.Formatter do
true ->
next_break_fits? =
op in @next_break_fits_operators and next_break_fits?(right_arg) and
op in @next_break_fits_operators and next_break_fits?(right_arg, state) and
not Keyword.get(meta, :eol, false)
with_next_break_fits(next_break_fits?, right, fn right ->
@@ -887,7 +889,7 @@ defmodule Code.Formatter do
# expression.{arguments}
defp remote_to_algebra({{:., _, [target, :{}]}, meta, args}, _context, state) do
{target_doc, state} = remote_target_to_algebra(target, state)
{call_doc, state} = tuple_to_algebra(meta, args, state)
{call_doc, state} = tuple_to_algebra(meta, args, :glue, state)
{concat(concat(target_doc, "."), call_doc), state}
end
@@ -1008,8 +1010,8 @@ defmodule Code.Formatter do
# * :required - never skip parens
#
defp call_args_to_algebra([], meta, _context, _parens, _list_to_keyword?, state) do
{args_doc, state} =
args_to_algebra_with_comments([], meta, false, false, false, state, &{&1, &2})
{args_doc, _join, state} =
args_to_algebra_with_comments([], meta, false, false, :glue, state, &{&1, &2})
{{surround("(", args_doc, ")"), state}, false}
end
@@ -1056,19 +1058,19 @@ defmodule Code.Formatter do
if left != [] and keyword? and skip_parens? and generators_count == 0 do
call_args_to_algebra_with_no_parens_keywords(meta, left, right, context, extra, state)
else
next_break_fits? = next_break_fits?(right)
next_break_fits? = next_break_fits?(right, state)
force_keyword? = keyword? and force_keyword?(right)
non_empty_eol? = left != [] and not next_break_fits? and Keyword.get(meta, :eol, false)
force_unfit? = generators_count > 1 or force_keyword? or non_empty_eol?
join = if generators_count > 1 or force_keyword? or non_empty_eol?, do: :line, else: :glue
args = if keyword?, do: left ++ right, else: left ++ [right]
{args_doc, state} =
{args_doc, _join, state} =
args_to_algebra_with_comments(
args,
meta,
skip_parens?,
next_break_fits?,
force_unfit?,
join,
state,
&quoted_to_algebra(&1, context, &2)
)
@@ -1098,11 +1100,11 @@ defmodule Code.Formatter do
defp call_args_to_algebra_with_no_parens_keywords(meta, left, right, context, extra, state) do
to_algebra_fun = &quoted_to_algebra(&1, context, &2)
{left_doc, state} =
args_to_algebra_with_comments(left, meta, true, false, false, state, to_algebra_fun)
{left_doc, _join, state} =
args_to_algebra_with_comments(left, meta, true, false, :glue, state, to_algebra_fun)
{right_doc, state} =
args_to_algebra_with_comments(right, meta, false, false, false, state, to_algebra_fun)
{right_doc, _join, state} =
args_to_algebra_with_comments(right, meta, false, false, :glue, state, to_algebra_fun)
right_doc = "," |> glue(right_doc) |> force_keyword(right) |> group(:inherit)
@@ -1241,15 +1243,19 @@ defmodule Code.Formatter do
defp bitstring_to_algebra(meta, args, state) do
last = length(args) - 1
join = if Keyword.get(meta, :eol, false), do: :line, else: :flex_glue
to_algebra_fun = &bitstring_segment_to_algebra(&1, &2, last)
force_unfit? = Keyword.get(meta, :eol, false)
{args_doc, state} =
{args_doc, join, state} =
args
|> Enum.with_index()
|> args_to_algebra_with_comments(meta, false, false, force_unfit?, state, to_algebra_fun)
|> args_to_algebra_with_comments(meta, false, false, join, state, to_algebra_fun)
{surround("<<", args_doc, ">>"), state}
if join == :flex_glue do
{"<<" |> concat(args_doc) |> nest(2) |> concat(">>") |> group(), state}
else
{surround("<<", args_doc, ">>"), state}
end
end
defp bitstring_segment_to_algebra({{:<-, meta, [left, right]}, i}, state, last) do
@@ -1298,22 +1304,22 @@ defmodule Code.Formatter do
## Literals
defp list_to_algebra(meta, args, state) do
to_algebra_fun = &quoted_to_algebra(&1, :parens_arg, &2)
force_unfit? = Keyword.get(meta, :eol, false)
join = if Keyword.get(meta, :eol, false), do: :line, else: :glue
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, state} =
args_to_algebra_with_comments(args, meta, false, false, force_unfit?, state, to_algebra_fun)
{args_doc, _join, state} =
args_to_algebra_with_comments(args, meta, false, false, join, state, fun)
{surround("[", args_doc, "]"), state}
end
defp map_to_algebra(meta, name_doc, [{:|, _, [left, right]}], state) do
join = if Keyword.get(meta, :eol, false), do: :line, else: :glue
fun = &quoted_to_algebra(&1, :parens_arg, &2)
force_unfit? = Keyword.get(meta, :eol, false)
{left_doc, state} = fun.(left, state)
{right_doc, state} =
args_to_algebra_with_comments(right, meta, false, false, force_unfit?, state, fun)
{right_doc, _join, state} =
args_to_algebra_with_comments(right, meta, false, false, join, state, fun)
args_doc =
left_doc
@@ -1325,33 +1331,27 @@ defmodule Code.Formatter do
end
defp map_to_algebra(meta, name_doc, args, state) do
force_unfit? = Keyword.get(meta, :eol, false)
join = if Keyword.get(meta, :eol, false), do: :line, else: :glue
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, state} =
args_to_algebra_with_comments(args, meta, false, false, force_unfit?, state, fun)
{args_doc, _join, state} =
args_to_algebra_with_comments(args, meta, false, false, join, state, fun)
name_doc = "%" |> concat(name_doc) |> concat("{")
{surround(name_doc, args_doc, "}"), state}
end
defp tuple_to_algebra(meta, args, state) do
force_unfit? = Keyword.get(meta, :eol, false)
defp tuple_to_algebra(meta, args, join, state) do
join = if Keyword.get(meta, :eol, false), do: :line, else: join
fun = &quoted_to_algebra(&1, :parens_arg, &2)
next_break_fits? =
args != [] and next_break_fits?(Enum.fetch!(args, -1)) and
not Keyword.get(meta, :eol, false)
{args_doc, join, state} =
args_to_algebra_with_comments(args, meta, false, false, join, state, fun)
{args_doc, state} =
args_to_algebra_with_comments(args, meta, false, next_break_fits?, force_unfit?, state, fun)
doc = surround("{", args_doc, "}")
if next_break_fits? do
{next_break_fits(doc, :disabled), state}
if join == :flex_glue do
{"{" |> concat(args_doc) |> nest(1) |> concat("}") |> group(), state}
else
{doc, state}
{surround("{", args_doc, "}"), state}
end
end
@@ -1456,15 +1456,7 @@ defmodule Code.Formatter do
defp heredoc_line(["", _ | _]), do: nest(line(), :reset)
defp heredoc_line(_), do: line()
defp args_to_algebra_with_comments(
args,
meta,
skip_parens?,
next_break_fits?,
force_unfit?,
state,
fun
) do
defp args_to_algebra_with_comments(args, meta, skip_parens?, next_break_fits?, join, state, fun) do
min_line = line(meta)
max_line = end_line(meta)
@@ -1498,13 +1490,16 @@ defmodule Code.Formatter do
cond do
args_docs == [] ->
{@empty, state}
{@empty, :empty, state}
force_unfit? or comments? ->
{args_docs |> Enum.reduce(&line(&2, &1)) |> force_unfit(), state}
join == :line or comments? ->
{args_docs |> Enum.reduce(&line(&2, &1)) |> force_unfit(), :line, state}
true ->
{args_docs |> Enum.reduce(&glue(&2, &1)), state}
join == :glue ->
{args_docs |> Enum.reduce(&glue(&2, &1)), :glue, state}
join == :flex_glue ->
{args_docs |> Enum.reduce(&flex_glue(&2, &1)), :flex_glue, state}
end
end
@@ -1674,7 +1669,11 @@ defmodule Code.Formatter do
defp clause_args_to_algebra(args, min_line, state) do
meta = [line: min_line]
fun = &clause_args_to_algebra/2
args_to_algebra_with_comments([args], meta, false, false, false, state, fun)
{args_docs, _join, state} =
args_to_algebra_with_comments([args], meta, false, false, :glue, state, fun)
{args_docs, state}
end
# fn a, b, c when d -> e end
@@ -1916,43 +1915,61 @@ defmodule Code.Formatter do
end
end
defp next_break_fits?({:<<>>, meta, [_ | _] = entries}) do
meta[:format] == :bin_heredoc or not interpolated?(entries)
defp next_break_fits?({:{}, meta, _args}, state) do
eol_or_comments?(meta, state)
end
defp next_break_fits?({{:., _, [String, :to_charlist]}, _, [{:<<>>, meta, [_ | _]}]}) do
defp next_break_fits?({:__block__, meta, [{_, _}]}, state) do
eol_or_comments?(meta, state)
end
defp next_break_fits?({:<<>>, meta, [_ | _] = entries}, state) do
meta[:format] == :bin_heredoc or
(not interpolated?(entries) and eol_or_comments?(meta, state))
end
defp next_break_fits?({{:., _, [String, :to_charlist]}, _, [{:<<>>, meta, [_ | _]}]}, _state) do
meta[:format] == :list_heredoc
end
defp next_break_fits?({{:., _, [_left, :{}]}, _, _}) do
defp next_break_fits?({{:., _, [_left, :{}]}, _, _}, _state) do
true
end
defp next_break_fits?({:__block__, meta, [string]}) when is_binary(string) do
defp next_break_fits?({:__block__, meta, [string]}, _state) when is_binary(string) do
meta[:format] == :bin_heredoc
end
defp next_break_fits?({:__block__, meta, [list]}) when is_list(list) do
defp next_break_fits?({:__block__, meta, [list]}, _state) when is_list(list) do
meta[:format] != :charlist
end
defp next_break_fits?({form, _, [_ | _]}) when form in [:fn, :%{}, :%] do
defp next_break_fits?({form, _, [_ | _]}, _state) when form in [:fn, :%{}, :%] do
true
end
defp next_break_fits?({fun, meta, args}) when is_atom(fun) and is_list(args) do
defp next_break_fits?({fun, meta, args}, _state) when is_atom(fun) and is_list(args) do
meta[:terminator] in [@double_heredoc, @single_heredoc] and
fun |> Atom.to_string() |> String.starts_with?("sigil_")
end
defp next_break_fits?({{:__block__, _, [atom]}, expr}) when is_atom(atom) do
next_break_fits?(expr)
defp next_break_fits?({{:__block__, _, [atom]}, expr}, state) when is_atom(atom) do
next_break_fits?(expr, state)
end
defp next_break_fits?(_) do
defp next_break_fits?(_, _state) do
false
end
defp eol_or_comments?(meta, %{comments: comments}) do
Keyword.get(meta, :eol, false) or
(
min_line = line(meta)
max_line = end_line(meta)
Enum.any?(comments, fn {line, _, _} -> line > min_line and line < max_line end)
)
end
defp last_arg_to_keyword([_ | _] = arg, _list_to_keyword?) do
{keyword?(arg), arg}
end
+2 -2
View File
@@ -13,7 +13,7 @@ defmodule Code.Identifier do
@spec unary_op(atom) :: {:non_associative, precedence :: pos_integer} | :error
def unary_op(op) do
cond do
op in [:&] -> {:non_associative, 100}
op in [:&] -> {:non_associative, 90}
op in [:!, :^, :not, :+, :-, :~~~] -> {:non_associative, 300}
op in [:@] -> {:non_associative, 320}
true -> :error
@@ -36,7 +36,7 @@ defmodule Code.Identifier do
op in [:when] -> {:right, 50}
op in [:::] -> {:right, 60}
op in [:|] -> {:right, 70}
op in [:=] -> {:right, 90}
op in [:=] -> {:right, 100}
op in [:||, :|||, :or] -> {:left, 130}
op in [:&&, :&&&, :and] -> {:left, 140}
op in [:==, :!=, :=~, :===, :!==] -> {:left, 150}
+9 -8
View File
@@ -440,14 +440,15 @@ defmodule DynamicSupervisor do
:ok <- validate_seconds(max_seconds),
:ok <- validate_dynamic(max_children),
:ok <- validate_extra_arguments(extra_arguments) do
{:ok, %{
state
| extra_arguments: extra_arguments,
max_children: max_children,
max_restarts: max_restarts,
max_seconds: max_seconds,
strategy: strategy
}}
{:ok,
%{
state
| extra_arguments: extra_arguments,
max_children: max_children,
max_restarts: max_restarts,
max_seconds: max_seconds,
strategy: strategy
}}
end
end
+11 -2
View File
@@ -301,8 +301,17 @@ defmodule Exception do
defp rewrite_guard(guard) do
Macro.prewalk(guard, fn
{:., _, [:erlang, call]} -> rewrite_guard_call(call)
other -> other
{{:., _, [:erlang, :element]}, _, [{{:., _, [:erlang, :+]}, _, [int, 1]}, arg]} ->
{:elem, [], [arg, int]}
{{:., _, [:erlang, :element]}, _, [int, arg]} when is_integer(int) ->
{:elem, [], [arg, int - 1]}
{:., _, [:erlang, call]} ->
rewrite_guard_call(call)
other ->
other
end)
end
+4
View File
@@ -262,6 +262,10 @@ defimpl Inspect, for: Integer do
defp prepend_prefix(value, :decimal), do: value
defp prepend_prefix(<<?-, value::binary>>, base) do
"-" <> prepend_prefix(value, base)
end
defp prepend_prefix(value, base) do
prefix =
case base do
+10 -13
View File
@@ -1555,7 +1555,7 @@ defmodule Kernel do
optimize_boolean(
quote do
case unquote(value) do
x when x in [false, nil] -> false
x when :"Elixir.Kernel".in(x, [false, nil]) -> false
_ -> true
end
end
@@ -1566,7 +1566,7 @@ defmodule Kernel do
optimize_boolean(
quote do
case unquote(value) do
x when x in [false, nil] -> true
x when :"Elixir.Kernel".in(x, [false, nil]) -> true
_ -> false
end
end
@@ -2838,7 +2838,7 @@ defmodule Kernel do
optimize_boolean(
quote do
case unquote(condition) do
x when x in [false, nil] -> unquote(else_clause)
x when :"Elixir.Kernel".in(x, [false, nil]) -> unquote(else_clause)
_ -> unquote(do_clause)
end
end
@@ -3006,7 +3006,7 @@ defmodule Kernel do
defmacro left && right do
quote do
case unquote(left) do
x when x in [false, nil] ->
x when :"Elixir.Kernel".in(x, [false, nil]) ->
x
_ ->
@@ -3042,7 +3042,7 @@ defmodule Kernel do
defmacro left || right do
quote do
case unquote(left) do
x when x in [false, nil] ->
x when :"Elixir.Kernel".in(x, [false, nil]) ->
unquote(right)
x ->
@@ -3134,10 +3134,11 @@ defmodule Kernel do
fun = fn {x, pos}, acc ->
case x do
{op, _, [_]} when op == :+ or op == :- ->
:elixir_errors.warn(__CALLER__.line, __CALLER__.file, <<
"piping into a unary operator is deprecated, please use the ",
"qualified name. For example, Kernel.+(5), instead of +5"
>>)
message =
<<"piping into a unary operator is deprecated, please use the ",
"qualified name. For example, Kernel.+(5), instead of +5">>
:elixir_errors.warn(__CALLER__.line, __CALLER__.file, message)
_ ->
:ok
@@ -4795,10 +4796,6 @@ defmodule Kernel do
for fun <- List.wrap(funs) do
{name, args, as, as_args} = Kernel.Utils.defdelegate(fun, opts)
unless Module.get_attribute(__MODULE__, :doc) do
@doc "See `#{inspect(target)}.#{as}/#{:erlang.length(args)}`."
end
def unquote(name)(unquote_splicing(args)) do
unquote(target).unquote(as)(unquote_splicing(as_args))
end
+5 -7
View File
@@ -751,13 +751,11 @@ defmodule Kernel.Typespec do
typespec_to_ast({:type, line, :charlist, []})
end
defp typespec_to_ast(
{
:remote_type,
line,
[{:atom, _, :elixir}, {:atom, _, :nonempty_charlist}, []]
}
) do
defp typespec_to_ast({
:remote_type,
line,
[{:atom, _, :elixir}, {:atom, _, :nonempty_charlist}, []]
}) do
typespec_to_ast({:type, line, :nonempty_charlist, []})
end
+2 -1
View File
@@ -179,6 +179,7 @@ defmodule Kernel.Utils do
is_integer(value) and rem(value, 2) == 0
end
end
"""
defmacro defguard(args, expr) do
defguard(args, expr, __CALLER__)
@@ -187,7 +188,7 @@ defmodule Kernel.Utils do
@spec defguard([Macro.t()], Macro.t(), Macro.Env.t()) :: Macro.t()
def defguard(args, expr, env) do
{^args, vars} = extract_refs_from_args(args)
_valid? = :elixir_expand.expand(expr, %{env | context: :guard, vars: vars})
{expr, _scope} = :elixir_expand.expand(expr, %{env | context: :guard, vars: vars})
quote do
case Macro.Env.in_guard?(__CALLER__) do
+4 -2
View File
@@ -606,8 +606,10 @@ defmodule Keyword do
end
@doc """
Similar to `replace/3`, but will raise a `KeyError`
if the entry `key` does not exist.
Alters the value stored under `key` to `value`, but only
if the entry `key` already exists in `keywords`.
If `key` is not present in `keywords`, a `KeyError` exception is raised.
## Examples
+8 -3
View File
@@ -885,19 +885,24 @@ defmodule List do
compact_reverse(rest, [{kind, [elem | result]} | acc])
end
defp compact_reverse(rest, [{:eq, elem}, {:ins, elem}, {:eq, other} | acc]) do
compact_reverse(rest, [{:ins, elem}, {:eq, elem ++ other} | acc])
end
defp compact_reverse([{kind, elem} | rest], acc) do
compact_reverse(rest, [{kind, [elem]} | acc])
end
defp each_diagonal(diag, limit, _paths, next_paths) when diag > limit do
{:next, Enum.reverse(next_paths)}
{:next, :lists.reverse(next_paths)}
end
defp each_diagonal(diag, limit, paths, next_paths) do
{path, rest} = proceed_path(diag, limit, paths)
with {:cont, path} <- follow_snake(path) do
each_diagonal(diag + 2, limit, rest, [path | next_paths])
case follow_snake(path) do
{:cont, path} -> each_diagonal(diag + 2, limit, rest, [path | next_paths])
{:done, edits} -> {:done, edits}
end
end
+4 -2
View File
@@ -297,8 +297,10 @@ defmodule Map do
end
@doc """
Similar to `replace/3`, but will raise a `KeyError`
if the key does not exist in the map.
Alters the value stored under `key` to `value`, but only
if the entry `key` already exists in `map`.
If `key` is not present in `map`, a `KeyError` exception is raised.
## Examples
+7
View File
@@ -118,6 +118,13 @@ defmodule Module do
The mix compiler automatically looks for calls to deprecated modules
and emit warnings during compilation, computed via `mix xref warnings`.
We recommend using this feature with care, especially library authors.
Deprecating code always pushes the burden towards library users. We
also recommend for deprecated functionality to be maintained for long
periods of time, even after deprecation, giving developers plenty of
time to update (except for cases where keeping the deprecated API is
undesired, such as in the presence of security issues).
### `@doc` (and `@since`)
Provides documentation for the function or macro that follows the
+9
View File
@@ -54,11 +54,20 @@ defmodule Record do
that contains the record definition to extract; with this option, this
function uses the same path lookup used by the `-include` attribute used in
Erlang modules.
* `:from_lib` - (binary representing a path to a file) path to the Erlang
file that contains the record definition to extract; with this option,
this function uses the same path lookup used by the `-include_lib`
attribute used in Erlang modules.
* `:includes` - (a list of directories as binaries) if the record being
extracted depends on relative includes, this option allows developers
to specify the directory those relative includes exist
* `:macros` - (keyword list of macro names and values) if the record
being extract depends on the values of macros, this option allows
the value of those macros to be set
These options are expected to be literals (including the binary values) at
compile time.
+23 -24
View File
@@ -1,28 +1,25 @@
defmodule Record.Extractor do
@moduledoc false
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def extract(name, from: file) when is_binary(file) do
extract_record(name, from_file(file))
def extract(name, opts) do
extract_record(name, from_or_from_lib_file(opts))
end
# Retrieve a record definition from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def extract(name, from_lib: file) when is_binary(file) do
extract_record(name, from_lib_file(file))
def extract_all(opts) do
extract_all_records(from_or_from_lib_file(opts))
end
# Retrieve all records definitions from an Erlang file using
# the same lookup as the *include* attribute from Erlang modules.
def extract_all(from: file) when is_binary(file) do
extract_all_records(from_file(file))
end
defp from_or_from_lib_file(opts) do
cond do
file = opts[:from] ->
{from_file(file), Keyword.delete(opts, :from)}
# Retrieve all records definitions from an Erlang file using
# the same lookup as the *include_lib* attribute from Erlang modules.
def extract_all(from_lib: file) when is_binary(file) do
extract_all_records(from_lib_file(file))
file = opts[:from_lib] ->
{from_lib_file(file), Keyword.delete(opts, :from_lib)}
true ->
raise ArgumentError, "expected :from or :from_lib to be given as option"
end
end
# Find file using the same lookup as the *include* attribute from Erlang modules.
@@ -49,20 +46,22 @@ defmodule Record.Extractor do
end
# Retrieve the record with the given name from the given file
defp extract_record(name, file) do
form = read_file(file)
defp extract_record(name, {file, opts}) do
form = read_file(file, opts)
records = extract_records(form)
if record = List.keyfind(records, name, 0) do
parse_record(record, form)
else
raise ArgumentError, "no record #{name} found at #{file}"
raise ArgumentError,
"no record #{name} found at #{file}. Or the record does not exist or " <>
"its entry is malformed or depends on other include files"
end
end
# Retrieve all records from the given file
defp extract_all_records(file) do
form = read_file(file)
defp extract_all_records({file, opts}) do
form = read_file(file, opts)
records = extract_records(form)
for rec = {name, _fields} <- records, do: {name, parse_record(rec, form)}
end
@@ -76,8 +75,8 @@ defmodule Record.Extractor do
# includes record but with macros and other attributes expanded,
# such as "-include(...)" and "-include_lib(...)". This is done
# by using Erlang's epp.
defp read_file(file) do
case :epp.parse_file(file, []) do
defp read_file(file, opts) do
case :epp.parse_file(file, opts) do
{:ok, form} ->
form
+3 -3
View File
@@ -42,9 +42,9 @@ defmodule Regex do
For such reasons, we always recommend precompiling Elixir projects using
the OTP version meant to run in production. In case cross-compilation is
really necessary, you can manually invoke `Regex.recompile/1` or `Regex.
recompile!/1` to perform a runtime version check and recompile the regex
if necessary.
really necessary, you can manually invoke `Regex.recompile/1` or
`Regex.recompile!/1` to perform a runtime version check and recompile the
regex if necessary.
## Modifiers
+13 -5
View File
@@ -635,8 +635,12 @@ defmodule String do
@spec upcase(t, :default | :ascii | :greek) :: t
def upcase(string, mode \\ :default)
def upcase("", _mode) do
""
end
def upcase(string, :default) when is_binary(string) do
String.Casing.upcase(string, "", :default)
String.Casing.upcase(string, [], :default)
end
def upcase(string, :ascii) when is_binary(string) do
@@ -646,7 +650,7 @@ defmodule String do
end
def upcase(string, mode) when mode in @conditional_mappings do
String.Casing.upcase(string, "", mode)
String.Casing.upcase(string, [], mode)
end
@doc """
@@ -678,7 +682,7 @@ defmodule String do
And `:greek` properly handles the context sensitive sigma in Greek:
iex> String.downcase("ΣΣ")
"ςς"
"σσ"
iex> String.downcase("ΣΣ", :greek)
"σς"
@@ -687,8 +691,12 @@ defmodule String do
@spec downcase(t, :default | :ascii | :greek) :: t
def downcase(string, mode \\ :default)
def downcase("", _mode) do
""
end
def downcase(string, :default) when is_binary(string) do
String.Casing.downcase(string, "", :default)
String.Casing.downcase(string, [], :default)
end
def downcase(string, :ascii) when is_binary(string) do
@@ -698,7 +706,7 @@ defmodule String do
end
def downcase(string, mode) when mode in @conditional_mappings do
String.Casing.downcase(string, "", mode)
String.Casing.downcase(string, [], mode)
end
@doc """
+3
View File
@@ -99,11 +99,13 @@ defmodule StringIO do
## callbacks
@impl true
def init({string, options}) do
capture_prompt = options[:capture_prompt] || false
{:ok, %{input: string, output: "", capture_prompt: capture_prompt}}
end
@impl true
def handle_info({:io_request, from, reply_as, req}, state) do
state = io_request(from, reply_as, req, state)
{:noreply, state}
@@ -113,6 +115,7 @@ defmodule StringIO do
super(message, state)
end
@impl true
def handle_call(:contents, _from, %{input: input, output: output} = state) do
{:reply, {input, output}, state}
end
+4 -12
View File
@@ -528,7 +528,7 @@ defmodule Supervisor do
@typedoc "The supervisor specification"
@type child_spec :: %{
required(:id) => term(),
required(:start) => {module(), function(), [term()]},
required(:start) => {module(), atom(), [term()]},
optional(:restart) => :permanent | :transient | :temporary,
optional(:shutdown) => :brutal_kill | non_neg_integer() | :infinity,
optional(:type) => :worker | :supervisor,
@@ -592,7 +592,7 @@ defmodule Supervisor do
## Options
* `:strategy` - the restart strategy option. It can be either
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or
`:one_for_one`, `:rest_for_one`, `:one_for_all`, or the deprecated
`:simple_one_for_one`.
* `:max_restarts` - the maximum number of restarts allowed in
@@ -722,14 +722,6 @@ defmodule Supervisor do
#=> %{id: {Agent, 1},
#=> start: {Agent, :start_link, [fn -> :ok end]}}
It may also be used when there is a need to change the number
of arguments when starting a module under a `:simple_one_for_one`
strategy, since most args may be given dynamically:
Supervisor.child_spec(Agent, start: {Agent, :start_link, []})
#=> %{id: Agent,
#=> start: {Agent, :start_link, []}}
"""
@spec child_spec(child_spec() | {module, arg :: term} | module, keyword) :: child_spec()
def child_spec(module_or_map, overrides)
@@ -821,13 +813,13 @@ defmodule Supervisor do
returns `{:error, error}` where `error` is a term containing information about
the error and child specification.
"""
@spec start_child(supervisor, :supervisor.child_spec() | {module, term} | module) ::
@spec start_child(supervisor, :supervisor.child_spec() | {module, term} | module | [term]) ::
on_start_child
def start_child(supervisor, {_, _, _, _, _, _} = child_spec) do
call(supervisor, {:start_child, child_spec})
end
# TODO: Deprecate this on Elixir v1.8
# TODO: Deprecate this on Elixir v1.8. Remove and update typespec on v2.0.
def start_child(supervisor, args) when is_list(args) do
call(supervisor, {:start_child, args})
end
+5 -34
View File
@@ -20,11 +20,6 @@ defmodule Task.Supervisor do
| {:restart, :supervisor.restart()}
| {:shutdown, :supervisor.shutdown()}
@typedoc "Supervisor spec used by `async_stream`"
@type async_stream_supervisor ::
Supervisor.supervisor()
| (term -> Supervisor.supervisor())
@doc false
def child_spec(arg) do
%{
@@ -158,15 +153,6 @@ defmodule Task.Supervisor do
own task. The tasks will be spawned under the given `supervisor` and
linked to the current process, similarly to `async/4`.
You may also provide a function as the `supervisor`. Before each task is
started, the function will be invoked (in a new process which is linked to
the current process) with the stream entry that the to-be-spawned task will
process as its argument. The function should return a supervisor pid or name,
which will be used to spawn the task. This allows one to dynamically start
tasks in different locations in the supervision tree(s) on the local (or
another) node. Notably, this enables the distribution of concurrent stream
tasks over multiple nodes.
When streamed, each task will emit `{:ok, value}` upon successful
completion or `{:exit, reason}` if the caller is trapping exits.
Results are emitted in the same order as the original `enumerable`.
@@ -207,7 +193,7 @@ defmodule Task.Supervisor do
Enum.to_list(stream)
"""
@spec async_stream(async_stream_supervisor, Enumerable.t(), module, atom, [term], keyword) ::
@spec async_stream(Supervisor.supervisor(), Enumerable.t(), module, atom, [term], keyword) ::
Enumerable.t()
def async_stream(supervisor, enumerable, module, function, args, options \\ [])
when is_atom(module) and is_atom(function) and is_list(args) do
@@ -224,7 +210,7 @@ defmodule Task.Supervisor do
See `async_stream/6` for discussion, options, and examples.
"""
@spec async_stream(async_stream_supervisor, Enumerable.t(), (term -> term), keyword) ::
@spec async_stream(Supervisor.supervisor(), Enumerable.t(), (term -> term), keyword) ::
Enumerable.t()
def async_stream(supervisor, enumerable, fun, options \\ []) when is_function(fun, 1) do
build_stream(supervisor, :link, enumerable, fun, options)
@@ -241,7 +227,7 @@ defmodule Task.Supervisor do
See `async_stream/6` for discussion, options, and examples.
"""
@spec async_stream_nolink(
async_stream_supervisor,
Supervisor.supervisor(),
Enumerable.t(),
module,
atom,
@@ -263,7 +249,7 @@ defmodule Task.Supervisor do
See `async_stream/6` for discussion and examples.
"""
@spec async_stream_nolink(async_stream_supervisor, Enumerable.t(), (term -> term), keyword) ::
@spec async_stream_nolink(Supervisor.supervisor(), Enumerable.t(), (term -> term), keyword) ::
Enumerable.t()
def async_stream_nolink(supervisor, enumerable, fun, options \\ []) when is_function(fun, 1) do
build_stream(supervisor, :nolink, enumerable, fun, options)
@@ -354,27 +340,12 @@ defmodule Task.Supervisor do
%Task{pid: pid, ref: ref, owner: owner}
end
defp supervisor_fun(supervisor, {_module, _fun, _args})
when is_function(supervisor, 1) do
fn {_module, _fun, [entry | _rest_args]} -> supervisor.(entry) end
end
defp supervisor_fun(supervisor, fun)
when is_function(supervisor, 1) and is_function(fun, 1) do
fn {_erlang, _apply, [_fun, [entry]]} -> supervisor.(entry) end
end
defp supervisor_fun(supervisor, _fun) do
fn _mfa -> supervisor end
end
defp build_stream(supervisor, link_type, enumerable, fun, options) do
supervisor_fun = supervisor_fun(supervisor, fun)
shutdown = options[:shutdown]
&Task.Supervised.stream(enumerable, &1, &2, fun, options, fn owner, mfa ->
args = [owner, :monitor, get_info(owner), mfa]
{:ok, pid} = start_child_with_spec(supervisor_fun.(mfa), args, :temporary, shutdown)
{:ok, pid} = start_child_with_spec(supervisor, args, :temporary, shutdown)
if link_type == :link, do: Process.link(pid)
{link_type, pid}
end)
@@ -6,35 +6,15 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Each version
Elixir version | Supported Erlang/OTP versions
:------------- | :----------------------------
v1.0.0 | 17
v1.0.1 | 17
v1.0.2 | 17
v1.0.3 | 17
v1.0.4 | 17
v1.0.5 | 17-18
v1.1.0 | 17-18
v1.1.1 | 17-18
v1.2.0 | 18
v1.2.1 | 18
v1.2.2 | 18
v1.2.3 | 18
v1.2.4 | 18
v1.2.5 | 18
v1.2.6 | 18-19
v1.3.0 | 18-19
v1.3.1 | 18-19
v1.3.2 | 18-19
v1.3.3 | 18-19
v1.3.4 | 18-19
v1.4.0 | 18-19
v1.4.1 | 18-19
v1.4.2 | 18-19
v1.4.3 | 18-19
v1.4.4 | 18-19
v1.4.5 | 18-20
v1.5.0 | 18-20
v1.5.1 | 18-20
v1.5.2 | 18-20
1.0 | 17 - 17 (and OTP 18 from v1.0.5)
1.1 | 17 - 18
1.2 | 18 - 18 (and OTP 19 from v1.2.6)
1.3 | 18 - 19
1.4 | 18 - 19 (and OTP 20 from v1.4.5)
1.5 | 18 - 20
1.6 | 19 - 20
While Elixir often adds compatibility to new Erlang versions on released branches, such as support for OTP 20 in v1.4.5, those releases usually contain the minimum changes for Elixir to run without errors. Only the next minor release, in this case v1.5.0, does effectively leverage the new features provided by the latest Erlang release.
## Deprecations
@@ -54,9 +34,9 @@ Deprecated feature | Deprecated in | Replaced by (
:----------------------------------------------- | :------------ | :----------------------------
`Enum.partition/2` | [v1.6] | `Enum.split_with/2` (v1.4)
`Keyword.replace/3` | [v1.6] | Use `Keyword.fetch/2` + `Keyword.put/3` (v1.0)
`Map.replace/3` | [v1.6] | Use `Map.fetch/2` + `Map.put/3` (v1.0)
`Macro.unescape_tokens/1` and `Macro.unescape_tokens/2` | [v1.6] | Use `Enum.map/2` to traverse over the arguments (v1.0)
`Range.range?/1` | [v1.6] | Pattern match on `left..right` instead (v1.0)
`Map.replace/3` | [v1.6] | Use `Map.fetch/2` + `Map.put/3` (v1.0)
`Range.range?/1` | [v1.6] | Pattern match on `_.._` instead (v1.0)
`Atom.to_char_list/1` | [v1.5] | `Atom.to_charlist/1` (v1.3)
`Enum.filter_map/3` | [v1.5] | `Enum.filter/2` + `Enum.map/2` or for comprehensions (v1.0)
`Float.to_char_list/1` | [v1.5] | `Float.to_charlist/1` (v1.3)
@@ -85,12 +65,12 @@ EEx: `<%=` in middle and end expressions | [v1.5] | Use `<%` (= i
`HashSet` module | [v1.4] | `MapSet` (v1.1)
`Set` module | [v1.4] | `MapSet` (v1.1)
`Stream.uniq/2` | [v1.4] | `Stream.uniq_by/2` (v1.2)
`IEx.Helpers.import_file/2` | [v1.4] | [`IEx.Helpers.import_file_if_available/1`](https://hexdocs.pm/iex/IEx.Helpers.html#import_file_if_available/1) (v1.3)
`IEx.Helpers.import_file/2` | [v1.4] | `IEx.Helpers.import_file_if_available/1` (v1.3)
`Mix.Utils.camelize/1` | [v1.4] | `Macro.camelize/1` (v1.2)
`Mix.Utils.underscore/1` | [v1.4] | `Macro.underscore/1` (v1.2)
Variable used as function call | [v1.4] | Use parentheses (v1.0)
Anonymous functions with no expression after `->` | [v1.4] | Use an expression or explicitly return `nil` (v1.0)
`Dict` module | [v1.3] | `Keyword` (v1.0);<br/>`Map` (v1.2)
`Dict` module | [v1.3] | `Keyword` (v1.0) or `Map` (v1.2)
`Keyword.size/1` | [v1.3] | `Kernel.length/1` (v1.0)
`Map.size/1` | [v1.3] | `Kernel.map_size/1` (v1.0)
`Set` behaviour | [v1.3] | `MapSet` data structure (v1.1)
@@ -103,13 +83,12 @@ Map or dictionary as second argument in `Enum.group_by/3` | [v1.3] | Use `Enum.r
Non-map as second argument in `URI.decode_query/2` | [v1.3] | Use a map (v1.0)
`Dict` behaviour | [v1.2] | `MapSet` data structure (v1.1)
`Access` protocol | [v1.1] | `Access` behaviour (v1.1)
`as: true \| false` in `alias/2` and `require/2` | [v1.1] | *None*
`as: true \| false` in `alias/2` and `require/2` | [v1.1] | *None*
`?\xHEX` | [v1.1] | `0xHEX` (v1.0)
Empty string in `String.starts_with?/2`, `String.ends_with?/2`, `String.contains?/2`.<br/>*__NOTE__: Feature made back available in v1.3* | [v1.1] to [v1.2] | Explicitly check for `""` beforehand (v1.0)
[v1.1]: https://github.com/elixir-lang/elixir/blob/v1.1/CHANGELOG.md#4-deprecations
[v1.2]: https://github.com/elixir-lang/elixir/blob/v1.2/CHANGELOG.md#changelog-for-elixir-v12
[v1.3]: https://github.com/elixir-lang/elixir/blob/v1.3/CHANGELOG.md#4-deprecations
[v1.4]: https://github.com/elixir-lang/elixir/blob/v1.4/CHANGELOG.md#4-deprecations
[v1.5]: https://github.com/elixir-lang/elixir/blob/v1.5/CHANGELOG.md#4-deprecations
[v1.6]: https://github.com/elixir-lang/elixir/blob/master/CHANGELOG.md#4-deprecations
[v1.6]: https://github.com/elixir-lang/elixir/blob/v1.6/CHANGELOG.md#4-deprecations
+1 -1
View File
@@ -1,6 +1,6 @@
{application, elixir,
[{description, "elixir"},
{vsn, "1.5.2"},
{vsn, "1.6.0"},
{modules, [
elixir
]},
+10 -5
View File
@@ -76,14 +76,19 @@ compile(Forms, Vars, #{line := Line, file := File} = E) ->
{Module, Binary} = elixir_erl_compiler:noenv_forms(Form, File, [nowarn_nomatch]),
code:load_binary(Module, "", Binary),
dispatch(Module, Fun, Args, I, EE).
dispatch(Module, Fun, Args, I, E) ->
Purgeable = beam_lib:chunks(Binary, [labeled_locals]) ==
{ok, {Module, [{labeled_locals, []}]}},
dispatch(Module, Fun, Args, Purgeable, I, EE).
dispatch(Module, Fun, Args, Purgeable, I, E) ->
Res = Module:Fun(Args),
code:delete(Module),
case code:soft_purge(Module) of
true -> return_compiler_module(I);
false -> ok
if Purgeable ->
code:purge(Module),
return_compiler_module(I);
true ->
ok
end,
{Res, E}.
+1 -1
View File
@@ -25,7 +25,7 @@ translate({'=', Meta, [Left, Right]}, S) ->
Reason = {tuple, Generated, [{atom, Generated, badmatch}, ResultVar]},
RaiseExpr = elixir_erl:remote(Generated, erlang, error, [Reason]),
GuardsExp = {'if', Generated, [
{clause, Generated, [], [ExtraGuards], [True]},
{clause, Generated, [], [ExtraGuards], [ResultVar]},
{clause, Generated, [], [[True]], [RaiseExpr]}
]},
{{block, Generated, [ResultMatch, GuardsExp]}, SL2};
+6 -9
View File
@@ -560,31 +560,28 @@ var_context(Meta, Kind) ->
expand_case(true, Meta, Expr, Opts, E) ->
{EExpr, EE} = expand(Expr, E),
{EOpts, EO} = elixir_clauses:'case'(Meta, Opts, EE),
ROpts =
case proplists:get_value(optimize_boolean, Meta, false) of
true ->
case elixir_utils:returns_boolean(EExpr) of
true -> rewrite_case_clauses(EOpts);
false -> generated_case_clauses(EOpts)
true -> rewrite_case_clauses(Opts);
false -> generated_case_clauses(Opts)
end;
false ->
EOpts
Opts
end,
{{'case', Meta, [EExpr, ROpts]}, EO};
{EOpts, EO} = elixir_clauses:'case'(Meta, ROpts, EE),
{{'case', Meta, [EExpr, EOpts]}, EO};
expand_case(false, Meta, Expr, Opts, E) ->
{Case, _} = expand_case(true, Meta, Expr, Opts, E),
{Case, E}.
rewrite_case_clauses([{do, [
{'->', FalseMeta, [
[{'when', _, [Var, {{'.', _, [erlang, 'orelse']}, _, [
{{'.', _, [erlang, '=:=']}, _, [Var, nil]},
{{'.', _, [erlang, '=:=']}, _, [Var, false]}
]}]}],
[{'when', _, [Var, {{'.', _, ['Elixir.Kernel', 'in']}, _, [Var, [false, nil]]}]}],
FalseExpr
]},
{'->', TrueMeta, [
+2 -2
View File
@@ -59,8 +59,8 @@ Right 50 when_op_eol. %% when
Right 60 type_op_eol. %% ::
Right 70 pipe_op_eol. %% |
Right 80 assoc_op_eol. %% =>
Right 90 match_op_eol. %% =
Nonassoc 100 capture_op_eol. %% &
Nonassoc 90 capture_op_eol. %% &
Right 100 match_op_eol. %% =
Left 130 or_op_eol. %% ||, |||, or
Left 140 and_op_eol. %% &&, &&&, and
Left 150 comp_op_eol. %% ==, !=, =~, ===, !==
+49 -23
View File
@@ -711,7 +711,7 @@ collect_modifiers(Rest, Buffer) ->
%% Heredocs
extract_heredoc_with_interpolation(Line, Column, Scope, Interpol, T, H) ->
case extract_heredoc(Line, Column, T, H) of
case extract_heredoc(Line, Column, T, H, Scope) of
{ok, NewLine, NewColumn, Body, Rest} ->
case elixir_interpolation:extract(Line + 1, 1, Scope, Interpol, Body, 0) of
{error, Reason} ->
@@ -723,7 +723,7 @@ extract_heredoc_with_interpolation(Line, Column, Scope, Interpol, T, H) ->
Error
end.
extract_heredoc(Line0, Column0, Rest0, Marker) ->
extract_heredoc(Line0, Column0, Rest0, Marker, Scope) ->
case extract_heredoc_header(Rest0) of
{ok, Rest1} ->
%% We prepend a new line so we can transparently remove
@@ -731,7 +731,7 @@ extract_heredoc(Line0, Column0, Rest0, Marker) ->
%% in the final heredoc body three lines below.
case extract_heredoc_body(Line0, Column0, Marker, [$\n | Rest1], []) of
{ok, Line1, Body, Rest2, Spaces} ->
{ok, Line1, 1, tl(remove_heredoc_spaces(Body, Spaces)), Rest2};
{ok, Line1, 1, tl(remove_heredoc_spaces(Body, Spaces, Marker, Scope)), Rest2};
{error, Reason, ErrorLine} ->
Terminator = [Marker, Marker, Marker],
{Message, Token} = heredoc_error_message(Reason, Line0, Terminator),
@@ -746,23 +746,49 @@ heredoc_error_message(eof, Line, Terminator) ->
{io_lib:format("missing terminator: ~ts (for heredoc starting at line ~B)",
[Terminator, Line]),
[]};
heredoc_error_message(misplacedterminator, _Line, Terminator) ->
heredoc_error_message(badterminator, _Line, Terminator) ->
{"invalid location for heredoc terminator, please escape token or move it to its own line: ",
Terminator}.
%% Remove spaces from heredoc based on the position of the final quotes.
remove_heredoc_spaces(Body, 0) ->
lists:reverse([0 | Body]);
remove_heredoc_spaces(Body, Spaces) ->
remove_heredoc_spaces([0 | Body], [], Spaces, Spaces).
remove_heredoc_spaces([H, $\n | T], [Backtrack | Buffer], Spaces, Original) when Spaces > 0, ?is_horizontal_space(H) ->
remove_heredoc_spaces([Backtrack, $\n | T], Buffer, Spaces - 1, Original);
remove_heredoc_spaces([$\n=H | T], Buffer, _Spaces, Original) ->
remove_heredoc_spaces(T, [H | Buffer], Original, Original);
remove_heredoc_spaces([H | T], Buffer, Spaces, Original) ->
remove_heredoc_spaces(T, [H | Buffer], Spaces, Original);
remove_heredoc_spaces([], Buffer, _Spaces, _Original) ->
Buffer.
remove_heredoc_spaces(Body, Spaces, Marker, Scope) ->
case trim_spaces(Body, [0], Spaces, false) of
{Acc, false} ->
Acc;
{Acc, Line} ->
Msg = io_lib:format("outdented heredoc line. The contents inside the heredoc should be indented "
"at the same level as the closing ~ts. The following is forbidden:~n~n"
" def text do~n"
" \"\"\"~n"
" contents~n"
" \"\"\"~n"
" end~n~n"
"Instead make sure the contents are indented as much as the heredoc closing:~n~n"
" def text do~n"
" \"\"\"~n"
" contents~n"
" \"\"\"~n"
" end~n~n"
"The current heredoc line is indented too little", [[Marker, Marker, Marker]]),
elixir_errors:warn(Line, Scope#elixir_tokenizer.file, Msg),
Acc
end.
trim_spaces([{Line, Entry} | Rest], Acc, Spaces, Warned) ->
case trim_space(lists:reverse(Entry), Spaces) of
{Trimmed, true} when Warned == false ->
trim_spaces(Rest, Trimmed ++ Acc, Spaces, Line);
{Trimmed, _} ->
trim_spaces(Rest, Trimmed ++ Acc, Spaces, Warned)
end;
trim_spaces([], Acc, _Spaces, Warned) ->
{Acc, Warned}.
trim_space(Rest, 0) -> {Rest, false};
trim_space([$\n], _) -> {[$\n], false};
trim_space([H | T], Spaces) when ?is_horizontal_space(H) -> trim_space(T, Spaces - 1);
trim_space(Rest, _Spaces) -> {Rest, true}.
%% Extract the heredoc header.
@@ -780,11 +806,11 @@ extract_heredoc_header(_) ->
%% is aligned.
extract_heredoc_body(Line, _Column, Marker, Rest, Buffer) ->
case extract_heredoc_line(Marker, Rest, Buffer, 0) of
{ok, NewBuffer, NewRest} ->
extract_heredoc_body(Line + 1, 1, Marker, NewRest, NewBuffer);
{ok, NewBuffer, NewRest, Spaces} ->
{ok, Line, NewBuffer, NewRest, Spaces};
case extract_heredoc_line(Marker, Rest, [], 0) of
{ok, Entry, NewRest} ->
extract_heredoc_body(Line + 1, 1, Marker, NewRest, [{Line, Entry} | Buffer]);
{done, Entry, NewRest, Spaces} ->
{ok, Line, [{Line, Entry} | Buffer], NewRest, Spaces};
{error, Reason} ->
{error, Reason, Line}
end.
@@ -797,7 +823,7 @@ extract_heredoc_line(Marker, [$\\, $\\ | T], Buffer) ->
extract_heredoc_line(Marker, [$\\, Marker | T], Buffer) ->
extract_heredoc_line(Marker, T, [Marker, $\\ | Buffer]);
extract_heredoc_line(Marker, [Marker, Marker, Marker | _], _) ->
{error, misplacedterminator};
{error, badterminator};
extract_heredoc_line(_, "\r\n" ++ Rest, Buffer) ->
{ok, [$\n | Buffer], Rest};
extract_heredoc_line(_, "\n" ++ Rest, Buffer) ->
@@ -812,7 +838,7 @@ extract_heredoc_line(_, _, _) ->
extract_heredoc_line(Marker, [H | T], Buffer, Counter) when ?is_horizontal_space(H) ->
extract_heredoc_line(Marker, T, [H | Buffer], Counter + 1);
extract_heredoc_line(Marker, [Marker, Marker, Marker | T], Buffer, Counter) ->
{ok, Buffer, T, Counter};
{done, Buffer, T, Counter};
extract_heredoc_line(Marker, Rest, Buffer, _Counter) ->
extract_heredoc_line(Marker, Rest, Buffer).
@@ -100,21 +100,6 @@ defmodule Code.Formatter.CallsTest do
@short_length
end
test "for binaries" do
bad = "foo(<<1, 2, 3, 4>>)"
good = """
foo(<<
1,
2,
3,
4
>>)
"""
assert_format bad, good, @short_length
end
test "for lists" do
bad = "foo([1, 2, 3, 4])"
@@ -146,6 +131,40 @@ defmodule Code.Formatter.CallsTest do
assert_format bad, good, @medium_length
end
test "for binaries only on eol" do
bad = "foo(<<1, 2, 3, 4>>)"
good = """
foo(
<<1, 2,
3, 4>>
)
"""
assert_format bad, good, @short_length
bad = """
foo(<<
# foo
1,
2,
3,
4>>)
"""
good = """
foo(<<
# foo
1,
2,
3,
4
>>)
"""
assert_format bad, good, @short_length
end
end
describe "local calls" do
@@ -18,16 +18,12 @@ defmodule Code.Formatter.ContainersTest do
assert_format "{1,2,3}", "{1, 2, 3}"
end
test "is strict on line limits" do
test "is flex on line limits" do
bad = "{1, 2, 3, 4}"
good = """
{
1,
2,
3,
4
}
{1, 2, 3,
4}
"""
assert_format bad, good, @short_length
@@ -261,16 +257,12 @@ defmodule Code.Formatter.ContainersTest do
assert_same "<<(<<y>> <- x)>>"
end
test "is strict on line limits" do
test "is flex on line limits" do
bad = "<<1, 2, 3, 4>>"
good = """
<<
1,
2,
3,
4
>>
<<1, 2, 3,
4>>
"""
assert_format bad, good, @short_length
@@ -399,17 +399,20 @@ defmodule Code.Formatter.IntegrationTest do
assert_format bad, """
@document Parser.parse(
{"html", [], [
{"head", [], []},
{"body", [], [
{"div", [], [
{"p", [], ["1"]},
{"p", [], ["2"]},
{"div", [], [{"p", [], ["3"]}, {"p", [], ["4"]}]},
{"p", [], ["5"]}
{"html", [],
[
{"head", [], []},
{"body", [],
[
{"div", [],
[
{"p", [], ["1"]},
{"p", [], ["2"]},
{"div", [], [{"p", [], ["3"]}, {"p", [], ["4"]}]},
{"p", [], ["5"]}
]}
]}
]}
]}
]}
)
"""
end
@@ -40,13 +40,14 @@ defmodule DynamicSupervisorTest do
describe "init/1" do
test "set default options" do
assert DynamicSupervisor.init(strategy: :one_for_one) ==
{:ok, %{
strategy: :one_for_one,
intensity: 3,
period: 5,
max_children: :infinity,
extra_arguments: []
}}
{:ok,
%{
strategy: :one_for_one,
intensity: 3,
period: 5,
max_children: :infinity,
extra_arguments: []
}}
end
end
+55 -46
View File
@@ -275,11 +275,10 @@ defmodule ExceptionTest do
{:error, reason} = __MODULE__.Sup.start_link(fn -> return end)
assert Exception.format_exit(reason) =~ "bad child specification, invalid restart type: :foo"
return =
{
:ok,
{{:one_for_one, 1, 1}, [{:child, {:m, :f, []}, :temporary, :foo, :worker, []}]}
}
return = {
:ok,
{{:one_for_one, 1, 1}, [{:child, {:m, :f, []}, :temporary, :foo, :worker, []}]}
}
{:error, reason} = __MODULE__.Sup.start_link(fn -> return end)
assert Exception.format_exit(reason) =~ "bad child specification, invalid shutdown: :foo"
@@ -292,51 +291,47 @@ defmodule ExceptionTest do
{:error, reason} = __MODULE__.Sup.start_link(fn -> return end)
assert Exception.format_exit(reason) =~ "bad child specification, invalid modules: :foo"
return =
{
:ok,
{{:one_for_one, 1, 1}, [{:child, {:m, :f, []}, :temporary, 1, :worker, [{:foo}]}]}
}
return = {
:ok,
{{:one_for_one, 1, 1}, [{:child, {:m, :f, []}, :temporary, 1, :worker, [{:foo}]}]}
}
{:error, reason} = __MODULE__.Sup.start_link(fn -> return end)
assert Exception.format_exit(reason) =~ "bad child specification, invalid module: {:foo}"
return =
return = {
:ok,
{
:ok,
{
{:one_for_one, 1, 1},
[
{:child, {:m, :f, []}, :permanent, 1, :worker, []},
{:child, {:m, :f, []}, :permanent, 1, :worker, []}
]
}
{:one_for_one, 1, 1},
[
{:child, {:m, :f, []}, :permanent, 1, :worker, []},
{:child, {:m, :f, []}, :permanent, 1, :worker, []}
]
}
}
{:error, reason} = __MODULE__.Sup.start_link(fn -> return end)
assert Exception.format_exit(reason) =~
"bad child specification, more than one child specification has the id: :child"
return =
{
:ok,
{{:one_for_one, 1, 1}, [{:child, {Kernel, :exit, [:foo]}, :temporary, 1, :worker, []}]}
}
return = {
:ok,
{{:one_for_one, 1, 1}, [{:child, {Kernel, :exit, [:foo]}, :temporary, 1, :worker, []}]}
}
{:error, reason} = __MODULE__.Sup.start_link(fn -> return end)
assert Exception.format_exit(reason) ==
"shutdown: failed to start child: :child\n ** (EXIT) :foo"
return =
return = {
:ok,
{
:ok,
{
{:one_for_one, 1, 1},
[{:child, {Kernel, :apply, [fn -> {:error, :foo} end, []]}, :temporary, 1, :worker, []}]
}
{:one_for_one, 1, 1},
[{:child, {Kernel, :apply, [fn -> {:error, :foo} end, []]}, :temporary, 1, :worker, []}]
}
}
{:error, reason} = __MODULE__.Sup.start_link(fn -> return end)
@@ -439,34 +434,48 @@ defmodule ExceptionTest do
end
test "annotates args and clauses from mfa" do
{:ok, :def, clauses} = Exception.blame_mfa(Keyword, :pop, [%{}, :key, nil])
import PathHelpers
write_beam(
defmodule Blaming do
def with_elem(x, y) when elem(x, 1) == 0 and elem(x, y) == 1 do
{x, y}
end
def fetch(%module{} = container, key), do: {module, container, key}
def fetch(map, key) when is_map(map), do: {map, key}
def fetch(list, key) when is_list(list) and is_atom(key), do: {list, key}
def fetch(nil, _key), do: nil
require Integer
def even_and_odd(foo, bar) when Integer.is_even(foo) and Integer.is_odd(bar), do: :ok
end
)
:code.delete(Blaming)
:code.purge(Blaming)
{:ok, :def, clauses} = Exception.blame_mfa(Blaming, :with_elem, [1, 2])
assert annotated_clauses_to_string(clauses) == [
"{[+keywords+, +key+, +default+], [-is_list(keywords)-]}"
"{[+x+, +y+], [-elem(x, 1) == 0- and -elem(x, y) == 1-]}"
]
{:ok, :def, clauses} = Exception.blame_mfa(Keyword, :fetch, [[], "oops"])
assert annotated_clauses_to_string(clauses) == [
"{[+keywords+, +key+], [+is_list(keywords)+ and -is_atom(key)-]}"
]
{:ok, :def, clauses} = Exception.blame_mfa(Path, :type, [self()])
assert annotated_clauses_to_string(clauses) == [
"{[+name+], [-is_list(name)-, -is_binary(name)-]}"
]
{:ok, :def, clauses} = Exception.blame_mfa(Access, :fetch, [self(), "oops"])
{:ok, :def, clauses} = Exception.blame_mfa(Blaming, :fetch, [self(), "oops"])
assert annotated_clauses_to_string(clauses) == [
"{[-%module{} = container-, +key+], []}",
"{[+map+, +key+], [-is_map(map)-]}",
"{[+list+, +key+], [-is_list(list)- and -is_atom(key)-]}",
"{[+list+, +key+], [-is_list(list)-]}",
"{[-nil-, +_key+], []}"
]
{:ok, :def, clauses} = Exception.blame_mfa(Blaming, :even_and_odd, [1, 1])
assert annotated_clauses_to_string(clauses) == [
"{[+foo+, +bar+], [+is_integer(foo)+ and -Bitwise.band(foo, 1) == 0- and (+is_integer(bar)+ and +Bitwise.band(bar, 1) == 1+)]}"
]
{:ok, :defmacro, clauses} = Exception.blame_mfa(Kernel, :!, [true])
assert annotated_clauses_to_string(clauses) == [
+3
View File
@@ -194,14 +194,17 @@ defmodule Inspect.NumberTest do
test "hex" do
assert inspect(100, base: :hex) == "0x64"
assert inspect(-100, base: :hex) == "-0x64"
end
test "octal" do
assert inspect(100, base: :octal) == "0o144"
assert inspect(-100, base: :octal) == "-0o144"
end
test "binary" do
assert inspect(86, base: :binary) == "0b1010110"
assert inspect(-86, base: :binary) == "-0b1010110"
end
test "float" do
+67 -115
View File
@@ -4,98 +4,75 @@ defmodule Kernel.GuardTest do
use ExUnit.Case, async: true
describe "Kernel.defguard(p) usage" do
test "successfully defines guard" do
defmodule(Success, do: defguard(foo(bar, baz) when bar + baz))
end
defmodule Guards.In.Macros do
defguard is_foo(atom) when atom == :foo
test "successfully defines private guard" do
defmodule PrivateSuccess do
defguardp foo(bar, baz) when bar + baz
def fizz(a, b) when foo(a, b), do: :buzz
defmacro is_compile_time_foo(atom) when is_foo(atom) do
quote do: unquote(__MODULE__).is_foo(unquote(atom))
end
end
test "guards can be used in other macros in the same module" do
defmodule Guards.In.Macros do
defguard is_foo(atom) when atom == :foo
require Guards.In.Macros
assert Guards.In.Macros.is_foo(:foo)
refute Guards.In.Macros.is_foo(:baz)
assert Guards.In.Macros.is_compile_time_foo(:foo)
end
defmacro is_foobar(atom) when is_foo(atom) do
quote bind_quoted: [atom: atom], do: is_foo(atom)
end
defmodule Guards.In.Funs do
defguard is_foo(atom) when atom == :foo
def is_foobar(atom) when is_foo(atom) do
is_foo(atom)
end
end
test "guards can be used in other funs in the same module" do
defmodule Guards.In.Funs do
defguard is_foo(atom) when atom == :foo
require Guards.In.Funs
assert Guards.In.Funs.is_foo(:foo)
refute Guards.In.Funs.is_foo(:bar)
end
def is_foobar(atom) when is_foo(atom) do
is_foo(atom)
defmodule Macros.In.Guards do
defmacro is_foo(atom) do
quote do
unquote(atom) == :foo
end
end
defguard is_foobar(atom) when is_foo(atom) or atom == :bar
end
test "macros can be used in other guards in the same module" do
defmodule Macros.In.Guards do
defmacro is_foo(atom) do
quote do
unquote(atom) == :foo
end
end
require Macros.In.Guards
assert Macros.In.Guards.is_foobar(:foo)
assert Macros.In.Guards.is_foobar(:bar)
refute Macros.In.Guards.is_foobar(:baz)
end
defguard is_foobar(atom) when is_foo(atom) or atom == :bar
end
defmodule Guards.In.Guards do
defguard is_foo(atom) when atom == :foo
defguard is_foobar(atom) when is_foo(atom) or atom == :bar
end
test "guards can be used in other guards in the same module" do
defmodule Guards.In.Guards do
defguard is_foo(atom) when atom == :foo
defguard is_foobar(atom) when is_foo(atom) or atom == :bar
end
require Guards.In.Guards
assert Guards.In.Guards.is_foobar(:foo)
assert Guards.In.Guards.is_foobar(:bar)
refute Guards.In.Guards.is_foobar(:baz)
end
# The below works––and is supposed to be equivalent to the above.
# However it doesn't look like any defguard is getting enough context about
# the caller env to recognize that the above 3 cases should be valid,
# raising CompileError undefined function is_foo/1.
# I suspect that inaccurate information is being provided to :elixir_expand.expand/2,
# making it think these are remote, rather than local, calls, based on the backtrace
# through :elixir_expand.expand_remote/7.
test "macros can be used in other macros" do
defmodule Macros.In.Macros do
defmacro is_foo(atom) do
quote do
case Macro.Env.in_guard?(__CALLER__) do
true ->
unquote(atom) == :foo
false ->
atom = unquote(atom)
atom == :foo
end
end
end
defmacro is_foobar(atom) do
quote do
case Macro.Env.in_guard?(__CALLER__) do
true ->
is_foo(unquote(atom)) or unquote(atom) == :bar
false ->
atom = unquote(atom)
is_foo(atom) or atom == :bar
end
end
end
end
defmodule Default.Args do
defguard is_divisible(value, remainder \\ 2)
when is_integer(value) and rem(value, remainder) == 0
end
test "permits default values in args" do
defmodule Default.Args do
defguard is_divisible(value, remainder \\ 2)
when is_integer(value) and rem(value, remainder) == 0
end
require Default.Args
assert Default.Args.is_divisible(2)
refute Default.Args.is_divisible(1)
assert Default.Args.is_divisible(3, 3)
refute Default.Args.is_divisible(3, 4)
end
test "doesn't allow matching in args" do
@@ -124,45 +101,18 @@ defmodule Kernel.GuardTest do
end
end
test "defguard defines guards that work inside and outside guard clauses" do
defmodule Integer.Guards do
defguard is_even(value) when is_integer(value) and rem(value, 2) == 0
defmodule Integer.Private.Guards do
defguardp is_even(value) when is_integer(value) and rem(value, 2) == 0
def is_even_and_large?(value) when is_even(value) and value > 100, do: true
def is_even_and_large?(_), do: false
def is_even_and_large?(value) when is_even(value) and value > 100, do: true
def is_even_and_large?(_), do: false
def is_even_and_small?(value) do
if is_even(value) and value <= 100, do: true, else: false
end
defmodule Integer.Utils do
import Integer.Guards
def is_even_and_small?(value) do
if is_even(value) and value <= 100, do: true, else: false
end
end
assert Integer.Guards.is_even_and_large?(102)
refute Integer.Guards.is_even_and_large?(98)
refute Integer.Guards.is_even_and_large?(99)
refute Integer.Guards.is_even_and_large?(103)
assert Integer.Utils.is_even_and_small?(98)
refute Integer.Utils.is_even_and_small?(99)
refute Integer.Utils.is_even_and_small?(102)
refute Integer.Utils.is_even_and_small?(103)
end
test "defguardp defines private guards that work inside and outside guard clauses" do
defmodule Integer.Private.Guards do
defguardp is_even(value) when is_integer(value) and rem(value, 2) == 0
def is_even_and_large?(value) when is_even(value) and value > 100, do: true
def is_even_and_large?(_), do: false
def is_even_and_small?(value) do
if is_even(value) and value <= 100, do: true, else: false
end
end
assert Integer.Private.Guards.is_even_and_large?(102)
refute Integer.Private.Guards.is_even_and_large?(98)
refute Integer.Private.Guards.is_even_and_large?(99)
@@ -412,13 +362,13 @@ defmodule Kernel.GuardTest do
Code.string_to_quoted("""
case Macro.Env.in_guard? __CALLER__ do
true -> quote do
unquote(foo) + unquote(bar) + unquote(baz)
:erlang.+(:erlang.+(unquote(foo), unquote(bar)), unquote(baz))
end
false -> quote do
foo = unquote(foo)
bar = unquote(bar)
baz = unquote(baz)
foo + bar + baz
:erlang.+(:erlang.+(foo, bar), baz)
end
end
""")
@@ -434,12 +384,12 @@ defmodule Kernel.GuardTest do
Code.string_to_quoted("""
case Macro.Env.in_guard? __CALLER__ do
true -> quote do
unquote(foo) + unquote(bar)
:erlang.+(unquote(foo), unquote(bar))
end
false -> quote do
foo = unquote(foo)
bar = unquote(bar)
foo + bar
:erlang.+(foo, bar)
end
end
""")
@@ -453,16 +403,18 @@ defmodule Kernel.GuardTest do
{:ok, goal} =
Code.string_to_quoted("""
case Macro.Env.in_guard? __CALLER__ do
true -> quote do
unquote(foo) + unquote(foo) + unquote(bar) + unquote(baz)
end
false -> quote do
foo = unquote(foo)
bar = unquote(bar)
baz = unquote(baz)
foo + foo + bar + baz
end
case(Macro.Env.in_guard?(__CALLER__)) do
true ->
quote() do
:erlang.+(:erlang.+(:erlang.+(unquote(foo), unquote(foo)), unquote(bar)), unquote(baz))
end
false ->
quote() do
foo = unquote(foo)
bar = unquote(bar)
baz = unquote(baz)
:erlang.+(:erlang.+(:erlang.+(foo, foo), bar), baz)
end
end
""")
@@ -8,6 +8,20 @@ defmodule Kernel.WarningTest do
capture_io(:stderr, fun)
end
test "outdented heredoc" do
output =
capture_err(fn ->
Code.eval_string("""
'''
outdented
'''
""")
end)
assert output =~ "outdented heredoc line"
assert output =~ "nofile:2"
end
test "unused variable" do
output =
capture_err(fn ->
+8
View File
@@ -243,6 +243,14 @@ defmodule KernelTest do
assert_raise BadBooleanError, fn -> 0 or 1 end
end
test "if/2 boolean optimization does not leak variables during expansion" do
if false do
:ok
else
assert __ENV__.vars == []
end
end
describe "in/2" do
test "with literals on right side" do
assert 2 in [1, 2, 3]
+25 -9
View File
@@ -217,14 +217,30 @@ defmodule ListTest do
end
end
test "myers_difference/2" do
assert List.myers_difference([], []) == []
assert List.myers_difference([], [1, 2, 3]) == [ins: [1, 2, 3]]
assert List.myers_difference([1, 2, 3], []) == [del: [1, 2, 3]]
assert List.myers_difference([1, 2, 3], [1, 2, 3]) == [eq: [1, 2, 3]]
assert List.myers_difference([1, 2, 3], [1, 4, 2, 3]) == [eq: [1], ins: [4], eq: [2, 3]]
assert List.myers_difference([1, 4, 2, 3], [1, 2, 3]) == [eq: [1], del: [4], eq: [2, 3]]
assert List.myers_difference([1], [[1]]) == [del: [1], ins: [[1]]]
assert List.myers_difference([[1]], [1]) == [del: [[1]], ins: [1]]
describe "myers_difference/2" do
test "follows paper implementation" do
assert List.myers_difference([], []) == []
assert List.myers_difference([], [1, 2, 3]) == [ins: [1, 2, 3]]
assert List.myers_difference([1, 2, 3], []) == [del: [1, 2, 3]]
assert List.myers_difference([1, 2, 3], [1, 2, 3]) == [eq: [1, 2, 3]]
assert List.myers_difference([1, 2, 3], [1, 4, 2, 3]) == [eq: [1], ins: [4], eq: [2, 3]]
assert List.myers_difference([1, 4, 2, 3], [1, 2, 3]) == [eq: [1], del: [4], eq: [2, 3]]
assert List.myers_difference([1], [[1]]) == [del: [1], ins: [[1]]]
assert List.myers_difference([[1]], [1]) == [del: [[1]], ins: [1]]
end
test "rearranges inserts and equals for smaller diffs" do
assert List.myers_difference([3, 2, 0, 2], [2, 2, 0, 2]) ==
[del: [3], ins: [2], eq: [2, 0, 2]]
assert List.myers_difference([3, 2, 1, 0, 2], [2, 1, 2, 1, 0, 2]) ==
[del: [3], ins: [2, 1], eq: [2, 1, 0, 2]]
assert List.myers_difference([3, 2, 2, 1, 0, 2], [2, 2, 1, 2, 1, 0, 2]) ==
[del: [3], eq: [2, 2, 1], ins: [2, 1], eq: [0, 2]]
assert List.myers_difference([3, 2, 0, 2], [2, 2, 1, 0, 2]) ==
[del: [3], eq: [2], ins: [2, 1], eq: [0, 2]]
end
end
end
+3 -3
View File
@@ -196,7 +196,7 @@ defmodule MacroTest do
quoted =
quote context: Kernel do
case 1 do
unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
unquote(temp_var) when :"Elixir.Kernel".in(unquote(temp_var), [false, nil]) -> false
unquote(temp_var) -> unquote(temp_var)
end
end
@@ -210,7 +210,7 @@ defmodule MacroTest do
quoted =
quote context: Kernel do
case 1 do
unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
unquote(temp_var) when :"Elixir.Kernel".in(unquote(temp_var), [false, nil]) -> false
unquote(temp_var) -> unquote(temp_var)
end
end
@@ -248,7 +248,7 @@ defmodule MacroTest do
quoted =
quote context: Kernel do
case 1 do
unquote(temp_var) when unquote(temp_var) in [false, nil] -> false
unquote(temp_var) when :"Elixir.Kernel".in(unquote(temp_var), [false, nil]) -> false
unquote(temp_var) -> unquote(temp_var)
end
end
+3
View File
@@ -200,6 +200,9 @@ defmodule MapTest do
assert_raise BadStructError, "expected a struct named MapTest.ExternalUser, got: %{}", fn ->
%ExternalUser{map | name: "meg"}
end
assert Code.eval_string("%struct{} = %ExternalUser{}", [], __ENV__) ==
{%ExternalUser{}, [struct: ExternalUser]}
end
test "structs with variable name" do
+10 -4
View File
@@ -168,11 +168,17 @@ defmodule StringTest do
end
test "downcase/1 with greek final sigma" do
assert String.downcase("ΣΣ") == "ςς"
assert String.downcase("ΣΣ ΣΣ") == "ςς ςς"
assert String.downcase("Σ") == "σ"
assert String.downcase("ΣΣ") == "σσ"
assert String.downcase("Σ ΣΣ") == "σ σσ"
assert String.downcase("ΜΕΣ'ΑΠΟ") == "μεσ'απο"
assert String.downcase("ΑΣ'ΤΟΥΣ") == "ασ'τουσ"
assert String.downcase("ΣΣ", :greek) == "σς"
assert String.downcase("ΣΣ ΣΣ", :greek) == "σς σς"
assert String.downcase("Σ", :greek) == "σ"
assert String.downcase("Σ ΣΣ", :greek) == "σ σς"
assert String.downcase("Σ ΣΑΣ Σ", :greek) == "σ σας σ"
assert String.downcase("ΜΕΣ'ΑΠΟ", :greek) == "μεσ'απο"
assert String.downcase("ΑΣ'ΤΟΥΣ", :greek) == "ασ'τους"
end
test "downcase/1 with ascii" do
@@ -22,19 +22,6 @@ defmodule Task.SupervisorTest do
number
end
def sleep_and_return_ancestor(number, :another_arg) do
sleep_and_return_ancestor(number)
end
def sleep_and_return_ancestor(number) do
Process.sleep(number)
{:dictionary, dictionary} = Process.info(self(), :dictionary)
dictionary
|> Keyword.get(:"$ancestors")
|> List.first()
end
test "can be supervised directly", config do
modules = [{Task.Supervisor, name: config.test}]
assert {:ok, _} = Supervisor.start_link(modules, strategy: :one_for_one)
@@ -319,80 +306,6 @@ defmodule Task.SupervisorTest do
refute_received _
end
test "streams an enumerable with fun and supervisor fun", %{supervisor: supervisor} do
{:ok, other_supervisor} = Task.Supervisor.start_link()
assert fn i -> if rem(i, 2) == 0, do: supervisor, else: other_supervisor end
|> Task.Supervisor.async_stream(1..4, &sleep_and_return_ancestor/1, @opts)
|> Enum.to_list() ==
[ok: other_supervisor, ok: supervisor, ok: other_supervisor, ok: supervisor]
end
test "streams an enumerable with mfa and supervisor fun", %{supervisor: supervisor} do
{:ok, other_supervisor} = Task.Supervisor.start_link()
fun = :sleep_and_return_ancestor
assert fn i -> if rem(i, 2) == 0, do: supervisor, else: other_supervisor end
|> Task.Supervisor.async_stream(1..4, __MODULE__, fun, [], @opts)
|> Enum.to_list() ==
[ok: other_supervisor, ok: supervisor, ok: other_supervisor, ok: supervisor]
end
test "streams an enumerable with mfa with args and supervisor fun", %{supervisor: supervisor} do
{:ok, other_supervisor} = Task.Supervisor.start_link()
fun = :sleep_and_return_ancestor
assert fn i -> if rem(i, 2) == 0, do: supervisor, else: other_supervisor end
|> Task.Supervisor.async_stream(1..4, __MODULE__, fun, [:another_arg], @opts)
|> Enum.to_list() ==
[ok: other_supervisor, ok: supervisor, ok: other_supervisor, ok: supervisor]
end
test "streams an enumerable with fun and executes supervisor fun in monitor process",
context do
%{supervisor: supervisor} = context
parent = self()
supervisor_fun = fn _i ->
{:links, links} = Process.info(self(), :links)
assert parent in links
send(parent, {parent, self()})
supervisor
end
assert supervisor_fun
|> Task.Supervisor.async_stream(1..4, &sleep_and_return_ancestor/1, @opts)
|> Enum.to_list() == [ok: supervisor, ok: supervisor, ok: supervisor, ok: supervisor]
receive do
{^parent, linked} ->
for _ <- 1..3, do: assert_received({^parent, ^linked})
after
0 ->
flunk("Did not receive any message from monitor process.")
end
end
test "streams an enumerable with fun and bad supervisor fun" do
Process.flag(:trap_exit, true)
stream =
fn _i -> raise "bad" end
|> Task.Supervisor.async_stream(1..4, &sleep_and_return_ancestor/1, @opts)
assert {{%RuntimeError{message: "bad"}, _stacktrace}, _mfa} = catch_exit(Stream.run(stream))
refute_received _
stream =
fn _i -> :not_a_supervisor end
|> Task.Supervisor.async_stream(1..4, &sleep_and_return_ancestor/1, @opts)
assert {{:noproc, _stacktrace}, _mfa} = catch_exit(Stream.run(stream))
refute_received _
end
end
describe "async_stream_nolink" do
+94 -31
View File
@@ -24,10 +24,39 @@ rangify = fn [head | tail] ->
[{first, last} | acc]
end
acc = {[], [], [], %{}, %{}}
# A character is case ignorable if:
#
# Word_Break(C) = MidLetter or MidNumLet or Single_Quote, or
# General_Category(C) = Nonspacing_Mark (Mn), Enclosing_Mark (Me), Format (Cf),
# Modifier_Letter (Lm), or Modifier_Symbol (Sk).
#
# Word breaks are defined below based on TR29 (http://unicode.org/reports/tr29/).
# The categories are computed later.
case_ignorable = [
0x0027,
0x002E,
0x2018,
0x2019,
0x2024,
0xFE52,
0xFF07,
0xFF0E,
0x00B7,
0x0387,
0x05F4,
0x2027,
0x003A,
0xFE13,
0xFE55,
0xFF1A
]
{codes, letters, non_breakable, decompositions, combining_classes} =
Enum.reduce(File.stream!(data_path), acc, fn line, {cacc, lacc, wacc, dacc, kacc} ->
acc = {[], [], case_ignorable, [], %{}, %{}}
cased_letter_categories = :binary.compile_pattern(["Ll", "Lt", "Lu"])
case_ignorable_categories = :binary.compile_pattern(["Mn", "Me", "Cf", "Lm", "Sk"])
{codes, cased_letters, case_ignorable, non_breakable, decompositions, combining_classes} =
Enum.reduce(File.stream!(data_path), acc, fn line, {cacc, lacc, iacc, wacc, dacc, kacc} ->
[
codepoint,
_name,
@@ -55,10 +84,19 @@ acc = {[], [], [], %{}, %{}}
cacc
end
lacc =
case category do
"L" <> _ -> [String.to_integer(codepoint, 16) | lacc]
_ -> lacc
cased_letter_categories = :binary.compile_pattern(["Ll", "Lt", "Lu"])
case_ignorable_categories = :binary.compile_pattern(["Mn", "Me", "Cf", "Lm", "Sk"])
{lacc, iacc} =
cond do
match?({0, _}, :binary.match(category, cased_letter_categories)) ->
{[String.to_integer(codepoint, 16) | lacc], iacc}
match?({0, _}, :binary.match(category, case_ignorable_categories)) ->
{lacc, [String.to_integer(codepoint, 16) | iacc]}
true ->
{lacc, iacc}
end
wacc =
@@ -88,7 +126,7 @@ acc = {[], [], [], %{}, %{}}
{n, ""} -> Map.put(kacc, String.to_integer(codepoint, 16), n)
end
{cacc, lacc, wacc, dacc, kacc}
{cacc, lacc, iacc, wacc, dacc, kacc}
end)
defmodule String.Casing do
@@ -111,63 +149,88 @@ defmodule String.Casing do
def downcase(<<unquote(sigma), rest::bits>>, acc, mode) do
downcased =
case mode == :greek and starts_with_letter?(rest) do
true -> 0x03C3
false -> 0x03C2
if mode == :greek and cased_letter_list?(acc) and not cased_letter_binary?(rest) do
<<0x03C2::utf8>>
else
<<0x03C3::utf8>>
end
downcase(rest, <<acc::binary, downcased::utf8>>, mode)
downcase(rest, [downcased | acc], mode)
end
for {codepoint, _upper, lower, _title} <- codes,
lower && lower != codepoint,
codepoint not in @conditional_downcase do
def downcase(<<unquote(codepoint), rest::bits>>, acc, mode) do
downcase(rest, acc <> unquote(lower), mode)
downcase(rest, [unquote(lower) | acc], mode)
end
end
def downcase(<<char, rest::bits>>, acc, mode) do
downcase(rest, <<acc::binary, char>>, mode)
downcase(rest, [<<char>> | acc], mode)
end
def downcase("", acc, _mode), do: acc
def downcase("", acc, _mode), do: IO.iodata_to_binary(:lists.reverse(acc))
# Sigma handling
defp starts_with_letter?(<<codepoint::utf8, _::bits>>) do
letter?(codepoint)
end
defp starts_with_letter?(_) do
false
end
for {first, last} <- rangify.(letters) do
if first == last do
defp letter?(unquote(first)), do: true
defp cased_letter_binary?(<<codepoint::utf8, rest::bits>>) do
if case_ignorable?(codepoint) do
cased_letter_binary?(rest)
else
defp letter?(codepoint)
cased_letter?(codepoint)
end
end
defp cased_letter_binary?(_), do: false
defp cased_letter_list?([<<codepoint::utf8>> | rest]) do
if case_ignorable?(codepoint) do
cased_letter_list?(rest)
else
cased_letter?(codepoint)
end
end
defp cased_letter_list?(_), do: false
for {first, last} <- rangify.(cased_letters) do
if first == last do
defp cased_letter?(unquote(first)), do: true
else
defp cased_letter?(codepoint)
when codepoint >= unquote(first) and codepoint <= unquote(last),
do: true
end
end
defp letter?(_), do: false
defp cased_letter?(_), do: false
for {first, last} <- rangify.(case_ignorable) do
if first == last do
defp case_ignorable?(unquote(first)), do: true
else
defp case_ignorable?(codepoint)
when codepoint >= unquote(first) and codepoint <= unquote(last),
do: true
end
end
defp case_ignorable?(_), do: false
# Upcase
for {codepoint, upper, _lower, _title} <- codes, upper && upper != codepoint do
def upcase(<<unquote(codepoint), rest::bits>>, acc, mode) do
upcase(rest, acc <> unquote(upper), mode)
upcase(rest, [unquote(upper) | acc], mode)
end
end
def upcase(<<char, rest::bits>>, acc, mode) do
upcase(rest, <<acc::binary, char>>, mode)
upcase(rest, [char | acc], mode)
end
def upcase("", acc, _mode), do: acc
def upcase("", acc, _mode), do: IO.iodata_to_binary(:lists.reverse(acc))
# Titlecase once
+9 -3
View File
@@ -670,11 +670,14 @@ defmodule ExUnit.Assertions do
@doc """
Asserts that `value1` and `value2` differ by no more than `delta`.
This difference is inclusive, so the test will pass if the difference
and the `delta` are equal.
## Examples
assert_in_delta 1.1, 1.5, 0.2
assert_in_delta 10, 15, 4
assert_in_delta 10, 15, 2
assert_in_delta 10, 15, 5
"""
def assert_in_delta(value1, value2, delta, message \\ nil)
@@ -689,9 +692,9 @@ defmodule ExUnit.Assertions do
message =
message ||
"Expected the difference between #{inspect(value1)} and " <>
"#{inspect(value2)} (#{inspect(diff)}) to be less than #{inspect(delta)}"
"#{inspect(value2)} (#{inspect(diff)}) to be less than or equal to #{inspect(delta)}"
assert diff < delta, message
assert diff <= delta, message
end
@doc """
@@ -840,6 +843,9 @@ defmodule ExUnit.Assertions do
@doc """
Asserts `value1` and `value2` are not within `delta`.
This difference is exclusive, so the test will fail if the difference
and the delta are equal.
If you supply `message`, information about the values will
automatically be appended to it.
+6 -1
View File
@@ -667,11 +667,16 @@ defmodule ExUnit.AssertionsTest do
end
end
test "assert in delta works with equal values and a delta of zero" do
assert_in_delta(10, 10, 0)
end
test "assert in delta error" do
"This should never be tested" = assert_in_delta(10, 12, 1)
rescue
error in [ExUnit.AssertionError] ->
"Expected the difference between 10 and 12 (2) to be less than 1" = error.message
"Expected the difference between 10 and 12 (2) to be less than or equal to 1" =
error.message
end
test "assert in delta with message" do
+26 -10
View File
@@ -660,43 +660,59 @@ defmodule IEx do
## Examples
The examples below will use `break!`, assuming that you are setting
a breakpoint directly from your IEx shell. But you can set up a break
from anywhere by using the fully qualified name `IEx.break!`.
The following sets up a breakpoint on `URI.decode_query/2`:
IEx.break!(URI, :decode_query, 2)
break! URI, :decode_query, 2
This call will setup a breakpoint that stops once.
To set a breakpoint that will stop 10 times:
IEx.break!(URI, :decode_query, 2, 10)
break! URI, :decode_query, 2, 10
`IEx.break!/2` is a convenience macro that allows breakpoints
to be given in the `Mod.fun/arity` format:
require IEx
IEx.break!(URI.decode_query/2)
break! URI.decode_query/2
Or to set a breakpoint that will stop 10 times:
IEx.break!(URI.decode_query/2, 10)
break! URI.decode_query/2, 10
This function returns the breakpoint ID and will raise if there
is an error setting up the breakpoint.
## Patterns and guards
`IEx.break!/2` allows patterns to be given, triggering the
breakpoint only in some occasions. For example, to trigger
the breakpoint only when the first argument is the "foo=bar"
string:
IEx.break!(URI.decode_query("foo=bar", _))
break! URI.decode_query("foo=bar", _)
Or to trigger is whenever the second argument is a map with
Or to trigger it whenever the second argument is a map with
more than one element:
IEx.break!(URI.decode_query(_, map) when map_size(map) > 0)
break! URI.decode_query(_, map) when map_size(map) > 0
Only a single break point can be set per function. So if you call
`IEx.break!` multiple times with different patterns, only the last
pattern is kept.
This function returns the breakpoint ID and will raise if there
is an error setting up the breakpoint.
Notice that, while patterns may be given to macros, remember that
macros receive ASTs as arguments, and not values. For example, if
you try to break on a macro with the following pattern:
break! MyModule.some_macro(pid) when pid == self()
This breakpoint will never be reached, because a macro never receives
a PID. Even if you call the macro as `MyModule.some_macro(self())`,
the macro will receive the AST representing the `self()` call, and not
the PID itself.
## Breaks and mix test
+12 -3
View File
@@ -81,9 +81,18 @@ defmodule IEx.CLI do
if Node.alive?() do
case :rpc.call(remote, :code, :ensure_loaded, [IEx]) do
{:badrpc, reason} ->
abort(
"Could not contact remote node #{remote}, reason: #{inspect(reason)}. Aborting..."
)
suggestion =
if Atom.to_string(remote) =~ "@" do
""
else
"Make sure the node given to --remsh is in the node@host format. "
end
message =
"Could not contact remote node #{remote}, reason: #{inspect(reason)}. " <>
suggestion <> "Aborting..."
abort(message)
{:module, IEx} ->
{mod, fun, args} = remote_start_mfa()
+2
View File
@@ -264,12 +264,14 @@ defmodule IEx.Pry do
GenServer.start_link(__MODULE__, :ok, name: @server)
end
@impl true
def init(:ok) do
Process.flag(:trap_exit, true)
:ets.new(@table, [:named_table, :public, write_concurrency: true])
{:ok, @initial_counter}
end
@impl true
def handle_call({:break, module, fa, condition, breaks}, _from, counter) do
# If there is a match for the given module and fa, we
# use the ref, otherwise we create a new one.
-28
View File
@@ -495,34 +495,6 @@ defmodule IEx.HelpersTest do
cleanup_modules([Impl, MyBehaviour])
end
test "prints documentation for delegates" do
filename = "delegate.ex"
content = """
defmodule Delegator do
defdelegate func1, to: Delegated
@doc "Delegator func2 doc"
defdelegate func2, to: Delegated
end
defmodule Delegated do
def func1, do: 1
def func2, do: 2
end
"""
with_file(filename, content, fn ->
assert c(filename, ".") |> Enum.sort() == [Delegated, Delegator]
assert capture_io(fn -> h(Delegator.func1()) end) ==
"* def func1()\n\nSee `Delegated.func1/0`.\n"
assert capture_io(fn -> h(Delegator.func2()) end) ==
"* def func2()\n\nDelegator func2 doc\n"
end)
after
cleanup_modules([Delegated, Delegator])
end
test "prints modules compiled without docs" do
Code.compiler_options(docs: false)
+21 -10
View File
@@ -130,6 +130,13 @@ defmodule Logger do
in the queue is reduced to `sync_threshold * 0.75` messages.
Defaults to 20 messages.
* `:discard_threshold` - if the `Logger` manager has more than
`:discard_threshold` messages in its queue, `Logger` will change
to *discard mode* and messages will be discarded directly in the
clients. `Logger` will return to *sync mode* once the number of
messages in the queue is reduced to `discard_threshold * 0.75`
messages. Defaults to 500 messages.
* `:translator_inspect_opts` - when translating OTP reports and
errors, the last message and state must be inspected in the
error reports. This configuration allow developers to change
@@ -159,21 +166,25 @@ defmodule Logger do
in Erlang syntax until the Logger application kicks in and
uninstalls SASL's logger in favor of its own. Defaults to `false`.
* `:discard_threshold_for_error_logger` - a value that, when
reached, triggers the error logger to discard messages. This
value must be a positive number that represents the maximum
number of messages accepted per second. Once above this
threshold, the [`:error_logger`](http://erlang.org/doc/man/error_logger.html)
enters discard mode for the remainder of that second. Defaults to 500 messages.
* `:discard_threshold_for_error_logger` - if `:error_logger` has more than
`discard_threshold` messages in its inbox, messages will be dropped
until the message queue goes down to `discard_threshold * 0.75`
entries. The threshold will be checked once again after 10% of thereshold
messages are processed, to avoid messages from being constantly dropped.
For exmaple, if the thereshold is 500 (the default) and the inbox has
600 messages, 250 messages will dropped, bringing the inbox down to
350 (0.75 * threshold) entries and 50 (0.1 * theshold) messages will
be processed before the threshold is checked once again.
For example, to configure `Logger` to redirect all [`:error_logger`](http://erlang.org/doc/man/error_logger.html)
messages using a `config/config.exs` file:
For example, to configure `Logger` to redirect all
[`:error_logger`](http://erlang.org/doc/man/error_logger.html) messages
using a `config/config.exs` file:
config :logger,
handle_otp_reports: true,
handle_sasl_reports: true
Furthermore, `Logger` allows messages sent by OTP's [`:error_logger`](http://erlang.org/doc/man/error_logger.html)
Furthermore, `Logger` allows messages sent by OTP's `:error_logger`
to be translated into an Elixir format via translators. Translators
can be dynamically added at any time with the `add_translator/1`
and `remove_translator/1` APIs. Check `Logger.Translator` for more
@@ -600,7 +611,7 @@ defmodule Logger do
%{mode: mode, truncate: truncate, level: min_level, utc_log: utc_log?} =
Logger.Config.__data__()
if compare_levels(level, min_level) != :lt do
if compare_levels(level, min_level) != :lt and mode != :discard do
metadata = [pid: self()] ++ Keyword.merge(pdict, metadata)
{message, metadata} = normalize_message(chardata_or_fun, metadata)
truncated = truncate(message, truncate)
+45 -16
View File
@@ -105,10 +105,28 @@ defmodule Logger.Config do
{:ok, state}
new_mode ->
if new_mode == :discard do
message =
"Logger has #{message_queue_length()} messages in its queue, " <>
"which is above :discard_threshold. Messages will be discarded " <>
"until the message queue goes back to 75% of the threshold size"
log(:warn, message, state)
end
if mode == :discard do
log(:warn, "Logger has stopped discarding messages", state)
end
{:ok, persist(%{state | mode: new_mode})}
end
end
defp log(level, message, state) do
event = {Logger, message, Logger.Utils.timestamp(state.utc_log), pid: self()}
:gen_event.notify(self(), {level, Process.group_leader(), event})
end
def handle_call(:backends, state) do
{:ok, Application.get_env(:logger, :backends), state}
end
@@ -162,18 +180,27 @@ defmodule Logger.Config do
## Helpers
defp compute_mode(state) do
{:message_queue_len, len} = Process.info(self(), :message_queue_len)
%{
mode: mode,
async_threshold: async_threshold,
sync_threshold: sync_threshold,
keep_threshold: keep_threshold,
discard_threshold: discard_threshold
} = state
cond do
len > state.sync_threshold and state.mode == :async ->
:sync
Logger.Utils.compute_mode(
mode,
message_queue_length(),
async_threshold,
sync_threshold,
keep_threshold,
discard_threshold
)
end
len < state.async_threshold and state.mode == :sync ->
:async
true ->
state.mode
end
defp message_queue_length() do
{:message_queue_len, messages} = Process.info(self(), :message_queue_len)
messages
end
defp update_backends(fun) do
@@ -196,22 +223,24 @@ defmodule Logger.Config do
sync_threshold = Application.get_env(:logger, :sync_threshold)
async_threshold = trunc(sync_threshold * 0.75)
discard_threshold = Application.get_env(:logger, :discard_threshold)
keep_threshold = trunc(discard_threshold * 0.75)
state = %{
level: level,
mode: mode,
truncate: truncate,
utc_log: utc_log,
sync_threshold: sync_threshold,
async_threshold: async_threshold,
sync_threshold: sync_threshold,
keep_threshold: keep_threshold,
discard_threshold: discard_threshold,
translators: translators
}
case compute_mode(state) do
^mode ->
persist(state)
new_mode ->
persist(%{state | mode: new_mode})
^mode -> persist(state)
new_mode -> persist(%{state | mode: new_mode})
end
end
+43 -40
View File
@@ -1,10 +1,7 @@
defmodule Logger.ErrorHandler do
@moduledoc false
@behaviour :gen_event
require Logger
def init({otp?, sasl?, threshold}) do
# We store the Logger PID in the state because when we are shutting
# down the Logger application, the Logger process may be terminated
@@ -14,11 +11,10 @@ defmodule Logger.ErrorHandler do
state = %{
otp: otp?,
sasl: sasl?,
threshold: threshold,
discard_threshold: threshold,
keep_threshold: trunc(threshold * 0.75),
logger: Process.whereis(Logger),
last_length: 0,
last_time: :os.timestamp(),
dropped: 0
skip: 0
}
{:ok, state}
@@ -31,7 +27,7 @@ defmodule Logger.ErrorHandler do
end
def handle_event(event, state) do
state = check_threshold(state)
state = check_threshold_unless_skipping(state)
log_event(event, state)
{:ok, state}
end
@@ -84,10 +80,15 @@ defmodule Logger.ErrorHandler do
defp log_event(_, _state), do: :ok
defp log_event(level, kind, gl, pid, {type, _} = data, state) do
%{level: min_level, truncate: truncate, utc_log: utc_log?, translators: translators} =
Logger.Config.__data__()
%{
mode: mode,
level: min_level,
truncate: truncate,
utc_log: utc_log?,
translators: translators
} = Logger.Config.__data__()
with log when log != :lt <- Logger.compare_levels(level, min_level),
with true <- Logger.compare_levels(level, min_level) != :lt and mode != :discard,
{:ok, message} <- translate(translators, min_level, level, kind, data, truncate) do
message = Logger.Utils.truncate(message, truncate)
@@ -106,31 +107,35 @@ defmodule Logger.ErrorHandler do
defp ensure_pid(pid) when is_pid(pid), do: pid
defp ensure_pid(_), do: self()
defp check_threshold(state) do
%{last_time: last_time, last_length: last_length, dropped: dropped, threshold: threshold} =
state
defp check_threshold_unless_skipping(%{skip: 0} = state) do
check_threshold(state)
end
{m, s, _} = current_time = :os.timestamp()
defp check_threshold_unless_skipping(%{skip: skip} = state) do
%{state | skip: skip - 1}
end
def check_threshold(state) do
%{discard_threshold: discard_threshold, keep_threshold: keep_threshold} = state
current_length = message_queue_length()
cond do
match?({^m, ^s, _}, last_time) and current_length - last_length > threshold ->
count = drop_messages(current_time, 0)
%{state | dropped: dropped + count, last_length: message_queue_length()}
if current_length >= discard_threshold do
to_drop = current_length - keep_threshold
drop_messages(to_drop)
match?({^m, ^s, _}, last_time) ->
state
message =
"Logger dropped #{to_drop} OTP/SASL messages as it had #{current_length} messages in " <>
"its inbox, exceeding the amount of :discard_threshold #{discard_threshold} messages. " <>
"The number of messages was reduced to #{keep_threshold} (75% of the threshold)"
true ->
if dropped > 0 do
message =
"Logger dropped #{dropped} OTP/SASL messages as it " <>
"exceeded the amount of #{threshold} messages/second"
%{utc_log: utc_log?} = Logger.Config.__data__()
event = {Logger, message, Logger.Utils.timestamp(utc_log?), pid: self()}
:gen_event.notify(state.logger, {:warn, Process.group_leader(), event})
Logger.warn(message)
end
%{state | dropped: 0, last_time: current_time, last_length: current_length}
# We won't check the threshold for the next 10% of the threshold messages
%{state | skip: trunc(discard_threshold * 0.1)}
else
state
end
end
@@ -139,17 +144,15 @@ defmodule Logger.ErrorHandler do
len
end
defp drop_messages({m, s, _} = last_time, count) do
case :os.timestamp() do
{^m, ^s, _} ->
receive do
{:notify, _event} -> drop_messages(last_time, count + 1)
after
0 -> count
end
defp drop_messages(0) do
:ok
end
_ ->
count
defp drop_messages(count) do
receive do
{:notify, _event} -> drop_messages(count - 1)
after
0 -> :ok
end
end
+4 -9
View File
@@ -76,15 +76,10 @@ defmodule Logger.Translator do
end
end
def translate(
_min_level,
:info,
:report,
{
:std_info,
[application: app, exited: reason, type: _type]
}
) do
def translate(_min_level, :info, :report, {
:std_info,
[application: app, exited: reason, type: _type]
}) do
{:ok, "Application #{app} exited: #{Application.format_error(reason)}"}
end
+31
View File
@@ -1,6 +1,37 @@
defmodule Logger.Utils do
@moduledoc false
@doc """
Computes the logging mode.
The result may be either :sync, :async or :discard.
"""
@spec compute_mode(
mode,
non_neg_integer(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer(),
non_neg_integer()
) :: mode
when mode: :sync | :async | :discard
def compute_mode(
mode,
messages,
async_threshold,
sync_threshold,
keep_threshold,
discard_threshold
) do
case mode do
_ when messages >= discard_threshold -> :discard
:discard when messages > keep_threshold -> :discard
_ when messages >= sync_threshold -> :sync
:sync when messages > async_threshold -> :sync
_ -> :async
end
end
@doc """
Truncates a `chardata` into `n` bytes.
+2 -1
View File
@@ -20,11 +20,12 @@ defmodule Logger.MixProject do
backends: [:console],
translators: [{Logger.Translator, :translate}],
sync_threshold: 20,
discard_threshold: 500,
handle_otp_reports: true,
handle_sasl_reports: false,
discard_threshold_for_error_logger: 500,
compile_time_purge_level: :debug,
compile_time_application: nil,
discard_threshold_for_error_logger: 500,
translator_inspect_opts: [],
console: []
]
+41
View File
@@ -0,0 +1,41 @@
defmodule Logger.ConfigTest do
use Logger.Case
require Logger
test "log/2 relies on sync_threshold" do
Logger.remove_backend(:console)
Logger.configure(sync_threshold: 0)
for _ <- 1..1000, do: Logger.log(:info, "some message")
after
Logger.configure(sync_threshold: 20)
Logger.add_backend(:console)
end
test "log/2 relies on discard_threshold" do
Logger.remove_backend(:console)
Logger.configure(discard_threshold: 0)
for _ <- 1..1000, do: Logger.log(:info, "some message")
after
Logger.configure(discard_threshold: 10000)
Logger.add_backend(:console)
end
test "restarts Logger.Config on Logger exits" do
Process.whereis(Logger) |> Process.exit(:kill)
wait_for_logger()
wait_for_handler(Logger, Logger.Config)
wait_for_handler(:error_logger, Logger.ErrorHandler)
end
test "Logger.Config updates config on config_change/3" do
:ok = Logger.configure(level: :debug)
try do
Application.put_env(:logger, :level, :error)
assert Logger.App.config_change([level: :error], [], []) === :ok
assert Logger.level() === :error
after
Logger.configure(level: :debug)
end
end
end
+42
View File
@@ -11,6 +11,48 @@ defmodule Logger.UtilsTest do
|> :io_lib.unscan_format()
end
describe "compute_mode/2" do
test "starting async" do
assert compute_mode(:async, 0, 15, 20, 7500, 10000) == :async
assert compute_mode(:async, 10, 15, 20, 7500, 10000) == :async
assert compute_mode(:async, 18, 15, 20, 7500, 10000) == :async
assert compute_mode(:async, 20, 15, 20, 7500, 10000) == :sync
assert compute_mode(:async, 30, 15, 20, 7500, 10000) == :sync
assert compute_mode(:async, 8000, 15, 20, 7500, 10000) == :sync
assert compute_mode(:async, 10000, 15, 20, 7500, 10000) == :discard
end
test "starting sync" do
assert compute_mode(:sync, 0, 15, 20, 7500, 10000) == :async
assert compute_mode(:sync, 10, 15, 20, 7500, 10000) == :async
assert compute_mode(:sync, 18, 15, 20, 7500, 10000) == :sync
assert compute_mode(:sync, 20, 15, 20, 7500, 10000) == :sync
assert compute_mode(:sync, 30, 15, 20, 7500, 10000) == :sync
assert compute_mode(:sync, 8000, 15, 20, 7500, 10000) == :sync
assert compute_mode(:sync, 10000, 15, 20, 7500, 10000) == :discard
end
test "starting discard (with sync)" do
assert compute_mode(:discard, 0, 15, 20, 7500, 10000) == :async
assert compute_mode(:discard, 10, 15, 20, 7500, 10000) == :async
assert compute_mode(:discard, 18, 15, 20, 7500, 10000) == :async
assert compute_mode(:discard, 20, 15, 20, 7500, 10000) == :sync
assert compute_mode(:discard, 30, 15, 20, 7500, 10000) == :sync
assert compute_mode(:discard, 8000, 15, 20, 7500, 10000) == :discard
assert compute_mode(:discard, 10000, 15, 20, 7500, 10000) == :discard
end
test "starting discard (without sync)" do
assert compute_mode(:discard, 0, 75000, 100_000, 7500, 10000) == :async
assert compute_mode(:discard, 10, 75000, 100_000, 7500, 10000) == :async
assert compute_mode(:discard, 18, 75000, 100_000, 7500, 10000) == :async
assert compute_mode(:discard, 20, 75000, 100_000, 7500, 10000) == :async
assert compute_mode(:discard, 30, 75000, 100_000, 7500, 10000) == :async
assert compute_mode(:discard, 8000, 75000, 100_000, 7500, 10000) == :discard
assert compute_mode(:discard, 10000, 75000, 100_000, 7500, 10000) == :discard
end
end
test "truncate/2" do
# ASCII binaries
assert truncate("foo", 4) == "foo"
+2 -34
View File
@@ -253,7 +253,6 @@ defmodule LoggerTest do
# This should not warn, even if the Logger call is purged from the AST.
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
Code.eval_string("""
defmodule Unused do
require Logger
@@ -261,13 +260,10 @@ defmodule LoggerTest do
Logger.debug(["a: ", inspect(a), ", b: ", inspect(b)])
end
end
""")
end) == ""
assert Unused.hello(1, 2) == :ok
assert LoggerTest.Unused.hello(1, 2) == :ok
after
:code.purge(Unused)
:code.delete(Unused)
Logger.configure(compile_time_purge_level: :debug)
end
@@ -336,16 +332,7 @@ defmodule LoggerTest do
end) =~ "he�lo"
end
test "log/2 relies on sync_threshold" do
Logger.remove_backend(:console)
Logger.configure(sync_threshold: 0)
for _ <- 1..1000, do: Logger.log(:info, "some message")
after
Logger.configure(sync_threshold: 20)
Logger.add_backend(:console)
end
test "stop the application silently" do
test "stops the application silently" do
Application.put_env(:logger, :backends, [])
Logger.App.stop()
Application.start(:logger)
@@ -361,23 +348,4 @@ defmodule LoggerTest do
Logger.App.stop()
Application.start(:logger)
end
test "restarts Logger.Config on Logger exits" do
Process.whereis(Logger) |> Process.exit(:kill)
wait_for_logger()
wait_for_handler(Logger, Logger.Config)
wait_for_handler(:error_logger, Logger.ErrorHandler)
end
test "Logger.Config updates config on config_change/3" do
:ok = Logger.configure(level: :debug)
try do
Application.put_env(:logger, :level, :error)
assert Logger.App.config_change([level: :error], [], []) === :ok
assert Logger.level() === :error
after
Logger.configure(level: :debug)
end
end
end
+1 -1
View File
@@ -162,7 +162,7 @@ defmodule Mix.Compilers.Erlang do
Removes compiled files for the given `manifest`.
"""
def clean(manifest) do
Enum.each(read_manifest(manifest), &File.rm/1)
Enum.each(read_manifest(manifest), fn {file, _} -> File.rm(file) end)
File.rm(manifest)
end
+2 -2
View File
@@ -36,8 +36,8 @@ defmodule Mix.Dep.Fetcher do
fn %Mix.Dep{app: app} = dep, acc, new_lock ->
# Only fetch if dependency is in given names or if lock has
# been changed for dependency by remote converger
if app in names or lock[app] != new_lock[app] do
# been changed for dependency by remote converger or it is new
if app in names or lock[app] != new_lock[app] or is_nil(lock[app]) do
do_fetch(dep, acc, new_lock)
else
{dep, acc, new_lock}
-1
View File
@@ -64,7 +64,6 @@ defmodule Mix.Dep.Loader do
"""
def load(%Mix.Dep{manager: manager, scm: scm, opts: opts} = dep, children) do
manager = scm_manager(scm, opts) || manager || infer_manager(opts[:dest])
dep = %{dep | manager: manager, status: scm_status(scm, opts)}
{dep, children} =
+15 -3
View File
@@ -11,11 +11,15 @@ defmodule Mix.Tasks.Format do
If any of the files is `-`, then the output is read from stdin
and written to stdout.
## Formatting options
Formatting is done with the `Code.format_string!/2` function.
For complete list of formatting options please refer to its
description.
A `.formatter.exs` file can also be defined for customizing input
files and the formatter itself.
## Options
## Task-specific options
* `--check-formatted` - check that the file is already formatted.
This is useful in pre-commit hooks and CI scripts if you want to
@@ -210,11 +214,19 @@ defmodule Mix.Tasks.Format do
defp assert_valid_dep_and_fetch_path(dep, deps_paths) when is_atom(dep) do
case Map.fetch(deps_paths, dep) do
{:ok, path} ->
path
if File.dir?(path) do
path
else
Mix.raise(
"Unavailable dependency #{inspect(dep)} given to :import_deps in the formatter configuration. " <>
"The dependency cannot be found in the filesystem, please run mix deps.get and try again"
)
end
:error ->
Mix.raise(
"Found a dependency in :import_deps that the project doesn't depend on: #{inspect(dep)}"
"Unknown dependency #{inspect(dep)} given to :import_deps in the formatter configuration. " <>
"The dependency is not listed in your mix.exs file"
)
end
end
+11 -3
View File
@@ -332,7 +332,7 @@ defmodule Mix.Tasks.Xref do
# If the module is loaded, we will use the faster function_exported?/3
# check for exports and __info__/1 for deprecated
if function_exported?(module, :__info__, 1) do
{module, module.__info__(:deprecated)}
{module, load_deprecated_from_module(module)}
else
{module, []}
end
@@ -341,14 +341,14 @@ defmodule Mix.Tasks.Xref do
with [_ | _] = file <- :code.which(module),
{:ok, {^module, [{:exports, exports}, {'ExDp', deprecated}]}} <-
:beam_lib.chunks(file, [:exports, 'ExDp'], [:allow_missing_chunks]) do
{exports, load_deprecated(deprecated)}
{exports, load_deprecated_from_chunk(deprecated)}
else
_ -> {:unknown_module, []}
end
end
end
defp load_deprecated(chunk) do
defp load_deprecated_from_chunk(chunk) do
if is_binary(chunk) do
{:elixir_deprecated_v1, deprecated} = :erlang.binary_to_term(chunk)
deprecated
@@ -357,6 +357,14 @@ defmodule Mix.Tasks.Xref do
end
end
defp load_deprecated_from_module(module) do
try do
module.__info__(:deprecated)
rescue
_ -> []
end
end
defp unreachable_mfa(exports, module, func, arity) do
cond do
skip_unreachable?(module, func, arity) ->
+4 -6
View File
@@ -66,13 +66,11 @@ defmodule Mix.Tasks.App.StartTest do
Mix.Tasks.Compile.run([])
Mix.Tasks.App.Start.run([])
assert_received {:mix_shell, :error, [
"You have configured application :app_unknown_sample" <> _
]}
assert_received {:mix_shell, :error,
["You have configured application :app_unknown_sample" <> _]}
refute_received {:mix_shell, :error, [
"You have configured application :app_loaded_sample" <> _
]}
refute_received {:mix_shell, :error,
["You have configured application :app_loaded_sample" <> _]}
end
end
+31 -27
View File
@@ -22,33 +22,7 @@ defmodule Mix.Tasks.Compile.ErlangTest do
end
end
test "compilation continues if one file fails to compile" do
in_fixture "compile_erlang", fn ->
file = Path.absname("src/zzz.erl")
File.write!(file, """
-module(zzz).
def zzz(), do: b
""")
capture_io(fn ->
assert {:error, [diagnostic]} = Mix.Tasks.Compile.Erlang.run([])
assert %Mix.Task.Compiler.Diagnostic{
compiler_name: "erl_parse",
file: ^file,
message: "syntax error before: zzz",
position: 2,
severity: :error
} = diagnostic
end)
assert File.regular?("_build/dev/lib/sample/ebin/b.beam")
assert File.regular?("_build/dev/lib/sample/ebin/c.beam")
end
end
test "compiles src/b.erl and src/c.erl" do
test "compiles and cleans src/b.erl and src/c.erl" do
in_fixture "compile_erlang", fn ->
assert Mix.Tasks.Compile.Erlang.run(["--verbose"]) == {:ok, []}
assert_received {:mix_shell, :info, ["Compiled src/b.erl"]}
@@ -63,6 +37,10 @@ defmodule Mix.Tasks.Compile.ErlangTest do
assert Mix.Tasks.Compile.Erlang.run(["--force", "--verbose"]) == {:ok, []}
assert_received {:mix_shell, :info, ["Compiled src/b.erl"]}
assert_received {:mix_shell, :info, ["Compiled src/c.erl"]}
assert Mix.Tasks.Compile.Erlang.clean()
refute File.regular?("_build/dev/lib/sample/ebin/b.beam")
refute File.regular?("_build/dev/lib/sample/ebin/c.beam")
end
end
@@ -100,6 +78,32 @@ defmodule Mix.Tasks.Compile.ErlangTest do
end
end
test "continues even if one file fails to compile" do
in_fixture "compile_erlang", fn ->
file = Path.absname("src/zzz.erl")
File.write!(file, """
-module(zzz).
def zzz(), do: b
""")
capture_io(fn ->
assert {:error, [diagnostic]} = Mix.Tasks.Compile.Erlang.run([])
assert %Mix.Task.Compiler.Diagnostic{
compiler_name: "erl_parse",
file: ^file,
message: "syntax error before: zzz",
position: 2,
severity: :error
} = diagnostic
end)
assert File.regular?("_build/dev/lib/sample/ebin/b.beam")
assert File.regular?("_build/dev/lib/sample/ebin/c.beam")
end
end
test "saves warnings between builds" do
in_fixture "compile_erlang", fn ->
file = Path.absname("src/has_warning.erl")
+33 -7
View File
@@ -231,7 +231,7 @@ defmodule Mix.Tasks.DepsGitTest do
Mix.Project.push(GitApp)
# Get Git repo first revision
[last, first | _] = get_git_repo_revs()
[last, first | _] = get_git_repo_revs("git_repo")
in_fixture "no_mixfile", fn ->
Mix.Dep.Lock.write(%{git_repo: {:git, fixture_path("git_repo"), first, []}})
@@ -257,7 +257,7 @@ defmodule Mix.Tasks.DepsGitTest do
test "updates the repo when the lock updates" do
Mix.Project.push(GitApp)
[last, first | _] = get_git_repo_revs()
[last, first | _] = get_git_repo_revs("git_repo")
in_fixture "no_mixfile", fn ->
Mix.Dep.Lock.write(%{git_repo: {:git, fixture_path("git_repo"), first, []}})
@@ -349,7 +349,7 @@ defmodule Mix.Tasks.DepsGitTest do
test "updates the repo and the lock when the mixfile updates" do
Mix.Project.push(GitApp)
[last, first | _] = get_git_repo_revs()
[last, first | _] = get_git_repo_revs("git_repo")
in_fixture "no_mixfile", fn ->
# Move to the first version
@@ -382,6 +382,32 @@ defmodule Mix.Tasks.DepsGitTest do
purge([GitRepo, GitRepo.MixProject])
end
test "fetches children on updates" do
Mix.Project.push(DepsOnGitApp)
# Get Git repo first revision
[last, first | _] = get_git_repo_revs("deps_on_git_repo")
in_fixture "no_mixfile", fn ->
Mix.Dep.Lock.write(%{deps_on_git_repo: {:git, fixture_path("deps_on_git_repo"), first, []}})
Mix.Tasks.Deps.Get.run([])
assert File.exists?("deps/deps_on_git_repo/mix.exs")
refute File.exists?("deps/git_repo/lib/git_repo.ex")
assert File.read!("mix.lock") =~ first
Mix.Task.clear()
Mix.ProjectStack.clear_cache()
purge([DepsOnGitRepo.MixProject])
Mix.Tasks.Deps.Update.run(["deps_on_git_repo"])
assert File.exists?("deps/git_repo/lib/git_repo.ex")
assert File.read!("mix.lock") =~ last
end
after
purge([GitRepo, GitRepo.MixProject])
end
test "does not attempt to compile projects that could not be retrieved" do
Mix.Project.push(GitErrorApp)
@@ -394,7 +420,7 @@ defmodule Mix.Tasks.DepsGitTest do
test "does not load bad mixfiles on get" do
Mix.Project.push(GitApp)
[last, _, bad | _] = get_git_repo_revs()
[last, _, bad | _] = get_git_repo_revs("git_repo")
in_fixture "no_mixfile", fn ->
Mix.Dep.Lock.write(%{git_repo: {:git, fixture_path("git_repo"), bad, []}})
@@ -428,7 +454,7 @@ defmodule Mix.Tasks.DepsGitTest do
test "does not load bad mixfiles on update" do
Mix.Project.push(GitApp)
[last, _, bad | _] = get_git_repo_revs()
[last, _, bad | _] = get_git_repo_revs("git_repo")
in_fixture "no_mixfile", fn ->
Mix.Dep.Lock.write(%{git_repo: {:git, fixture_path("git_repo"), bad, []}})
@@ -448,8 +474,8 @@ defmodule Mix.Tasks.DepsGitTest do
Mix.Project.push(name, file)
end
defp get_git_repo_revs() do
File.cd!(fixture_path("git_repo"), fn ->
defp get_git_repo_revs(repo) do
File.cd!(fixture_path(repo), fn ->
Regex.split(~r(\r?\n), System.cmd("git", ["log", "--format=%H"]) |> elem(0))
end)
end
+12 -4
View File
@@ -209,14 +209,12 @@ defmodule Mix.Tasks.FormatTest do
"""
manifest_path = Path.join(Mix.Project.manifest_path(), "cached_formatter_deps")
assert File.regular?(manifest_path)
# Let's check that the manifest gets updated if it's stale.
File.touch!(manifest_path, {{1970, 1, 1}, {0, 0, 0}})
Mix.Tasks.Format.run(["a.ex"])
assert File.stat!(manifest_path).mtime > {{1970, 1, 1}, {0, 0, 0}}
end
end
@@ -225,13 +223,23 @@ defmodule Mix.Tasks.FormatTest do
Mix.Project.push(__MODULE__.FormatWithDepsApp)
in_tmp context.test, fn ->
File.write!(".formatter.exs", """
[import_deps: [:my_dep]]
""")
message =
"Unavailable dependency :my_dep given to :import_deps in the formatter configuration. " <>
"The dependency cannot be found in the filesystem, please run mix deps.get and try again"
assert_raise Mix.Error, message, fn -> Mix.Tasks.Format.run([]) end
File.write!(".formatter.exs", """
[import_deps: [:nonexistent_dep]]
""")
message =
"Found a dependency in :import_deps that the project doesn't " <>
"depend on: :nonexistent_dep"
"Unknown dependency :nonexistent_dep given to :import_deps in the formatter configuration. " <>
"The dependency is not listed in your mix.exs file"
assert_raise Mix.Error, message, fn -> Mix.Tasks.Format.run([]) end
end
+4 -6
View File
@@ -12,9 +12,8 @@ defmodule Mix.UmbrellaTest do
Mix.Project.in_project(:umbrella, ".", fn _ ->
assert Mix.Project.apps_paths() == %{bar: "apps/bar", foo: "apps/foo"}
assert_received {:mix_shell, :error, [
"warning: path \"apps/dont_error_on_missing_mixfile\"" <> _
]}
assert_received {:mix_shell, :error,
["warning: path \"apps/dont_error_on_missing_mixfile\"" <> _]}
refute_received {:mix_shell, :error, ["warning: path \"apps/dont_error_on_files\"" <> _]}
end)
@@ -219,9 +218,8 @@ defmodule Mix.UmbrellaTest do
assert_received {:mix_shell, :error, ["Dependencies have diverged:"]}
assert_received {:mix_shell, :error, [
" the dependency foo in mix.exs is overriding a child" <> _
]}
assert_received {:mix_shell, :error,
[" the dependency foo in mix.exs is overriding a child" <> _]}
end)
end
end
+25 -6
View File
@@ -61,7 +61,7 @@ defmodule MixTest.Case do
end
def fixture_path(extension) do
Path.join(fixture_path(), extension)
Path.join(fixture_path(), extension |> to_string() |> String.replace(":", ""))
end
def tmp_path do
@@ -69,7 +69,7 @@ defmodule MixTest.Case do
end
def tmp_path(extension) do
Path.join(tmp_path(), to_string(extension))
Path.join(tmp_path(), extension |> to_string() |> String.replace(":", ""))
end
def purge(modules) do
@@ -280,6 +280,28 @@ target = Path.expand("fixtures/deps_on_git_repo", __DIR__)
unless File.dir?(target) do
File.mkdir_p!(Path.join(target, "lib"))
File.write!(Path.join(target, "mix.exs"), """
## Auto-generated fixture
defmodule DepsOnGitRepo.MixProject do
use Mix.Project
def project do
[
app: :deps_on_git_repo,
version: "0.1.0",
]
end
end
""")
File.cd!(target, fn ->
System.cmd("git", ~w[init])
System.cmd("git", ~w[config user.email "mix@example.com"])
System.cmd("git", ~w[config user.name "mix-repo"])
System.cmd("git", ~w[add .])
System.cmd("git", ~w[commit -m without-dep])
end)
File.write!(Path.join(target, "mix.exs"), """
## Auto-generated fixture
defmodule DepsOnGitRepo.MixProject do
@@ -303,11 +325,8 @@ unless File.dir?(target) do
""")
File.cd!(target, fn ->
System.cmd("git", ~w[init])
System.cmd("git", ~w[config user.email "mix@example.com"])
System.cmd("git", ~w[config user.name "mix-repo"])
System.cmd("git", ~w[add .])
System.cmd("git", ~w[commit -m "ok"])
System.cmd("git", ~w[commit -m with-dep])
end)
end