Compare commits

...
78 Commits
Author SHA1 Message Date
José Valim d30c5c0185 Release v1.11.3 2021-01-04 16:47:18 +01:00
José Valim 3fc334ebc3 Support all literals in guards, closes #10613 2021-01-02 17:19:30 +01:00
Julian Doherty df8955c0dd Improve Enum.chunk_while/4 docs to clarify after_fun semantics (#10595)
- add `{:halt, acc}` tuple return doc for `chunk_fun`
- explain `after_fun` semantics in more detail
- format return tuples for `chunk_fun` and `after_fun` as markdown lists
- added note that `acc` in `after_fun` return tuple is ignored
- note that `Enum.chunk_while/4` returns a list of *chunks*, not a list
  of lists (emitted chunks can be anything, not necessarily just lists)
2021-01-02 17:09:25 +01:00
José Valim 6b87a54825 Clarify writing documentation guide, closes #10610 2021-01-02 17:09:20 +01:00
José Valim 72ec902554 Revert "Add support for new rebar3 output env option (#10018)"
This option will be enabled on Elixir v1.12 instead.

This reverts commit fc9f7690c1.
2020-12-21 13:01:34 +01:00
José Valim 2c9fff0f93 Update CHANGELOG 2020-12-17 13:23:40 +01:00
José Valim c42f7af02b Do not match on error_info on OTP 24 2020-12-17 13:21:32 +01:00
José Valim 9c77960a26 Consider macros and module attributes on the left side of ++ on ExUnit.Diff
Closes #10520
2020-12-17 13:17:19 +01:00
José Valim a20f413841 Do not warn for attributes used in after_compile
Closes #10579.
2020-12-17 13:08:07 +01:00
José Valim ffbae20014 Improve xref error message, closes #10582 2020-12-17 08:18:16 +01:00
José Valim 9c81578ab5 Update CHANGELOG 2020-12-13 09:24:38 +01:00
José Valim a9f7b8e18c Run formatter 2020-12-13 09:15:02 +01:00
José Valim 61c3c34f44 Do not discard context from with, closes #10538 2020-12-13 09:14:39 +01:00
José Valim d8faf5bb7b Fix bootstrapping 2020-12-13 09:13:47 +01:00
José Valim 9d03032cfd Add line and column to EEx tokenizer 2020-12-13 09:06:15 +01:00
José Valim 3c4c68b3d5 Fix variable overriding in records, closes #10540 2020-12-13 09:05:09 +01:00
José Valim bed0a68176 Do not use unicode chars if ANSI is disabled
See https://github.com/elixir-lang/elixir/issues/10481#issuecomment-742448132
2020-12-10 12:19:19 +01:00
José Valim 60642c340d Improve error message on do-block in module attr, closes #10544 2020-12-10 12:19:14 +01:00
Eric Meadows-Jönsson 35e4263934 Fix docs tests on OTP 23 (#10510) 2020-11-29 10:19:53 +01:00
José Valim 43327512da Do not use private and removed ram_file:compress/1 (#10439) 2020-11-29 10:19:22 +01:00
José Valim 32692f14ff Fix test on Erlang master 2020-11-29 09:51:00 +01:00
Eric Meadows-Jönsson f80bd3c478 Fix intermittent test failure caused by archive warning (#10493) 2020-11-29 09:43:36 +01:00
José Valim 23934b48ba Fix failure on Erlang/OTP master 2020-11-29 09:43:28 +01:00
Dominic Letz c370a8a724 Add :compressed to stream_modes spec (#10490)
:compressed is already supported here so that gzipped files
can be streamed `File.stream!("log.gz", [:compressed])`.
2020-11-29 09:43:17 +01:00
José Valim 8c2237f80d Handle in memory modules in Xref.calls, closes #10502 2020-11-21 08:34:30 +01:00
José Valim a5e9c39023 Do not raise when checking for ambiguity with static_atoms_encoder, closes #10501 2020-11-20 12:43:14 +01:00
José Valim 6d918f5fb4 Wrap around :file instead of calling internals 2020-11-20 07:14:43 +01:00
Eksperimental 4347468d8a Update GitHub actions/checkout to v2 (#10496) 2020-11-18 13:21:49 +01:00
José Valim f044def5a1 Handle recursive vars in map values (#10498) 2020-11-18 12:42:23 +01:00
José Valim 05c9ccad12 Check for stacktrace at compile-time and runtime 2020-11-03 20:18:50 +01:00
José Valim 7533e56c1b Release v1.11.2 2020-11-03 15:23:56 +01:00
José Valim 3f083795b6 Do not trace protocol impl checks
A more complete solution is found in master but this
approach is less disruptive in the v1.11 branch.
2020-11-02 11:35:51 +01:00
José Valim 1bbc959d1a Prune tracers when fetching __ENV__ inside functions, closes #10466 2020-10-30 13:30:17 +01:00
José Valim 1603f21b0a Fix race condition when loading test helper, closes #10464 2020-10-29 19:29:06 +01:00
José Valim 02c3957667 Clarify wording about siblings in umbrella 2020-10-22 10:53:14 +02:00
José Valim eeab9f5aa1 Ensure manually set tracers are kept on compilation
Closes #10451.
2020-10-22 08:50:10 +02:00
José Valim 3f3e987a23 Handle aliases with code recursion
It addresses a regression introduced in 97e8b0242d.

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

Previously it was saying

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

Now

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

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

Fixes: #10415

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

This causes a crash on logger config update.

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

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

3. Properly process all variables found missing on step 2
2020-09-14 22:45:34 +02:00
José Valim 54ba096d34 Remove boolean and number as builtin types (#10329) 2020-09-14 22:45:34 +02:00
José Valim 6042fd0bcc Infer -> Unify 2020-09-14 22:45:34 +02:00
Eksperimental 475b73b46f Use proper casing in CHANGELOG (#10331) 2020-09-14 15:24:51 +02:00
José Valim 3cf6069ad9 Fix typo in CHANGELOG 2020-09-14 09:43:19 +02:00
José Valim 48cee5aaa5 Release v1.11.0-rc.0 2020-09-14 09:16:34 +02:00
88 changed files with 1463 additions and 778 deletions
+6 -5
View File
@@ -13,6 +13,7 @@ jobs:
name: Linux, ${{ matrix.otp_release }}, Ubuntu 16.04
continue-on-error: ${{ matrix.development }}
strategy:
fail-fast: false
matrix:
otp_release: ['OTP-23.0', 'OTP-22.3', 'OTP-22.0', 'OTP-21.3.8', 'OTP-21.0']
development: [false]
@@ -23,17 +24,17 @@ jobs:
development: true
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 50
- name: Install Erlang/OTP
run: |
cd $RUNNER_TEMP
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-14.04/${{ matrix.otp_release }}.tar.gz
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-16.04/${{ matrix.otp_release }}.tar.gz
mkdir -p otp
tar zxf otp.tar.gz -C otp --strip-components=1
otp/Install -minimal $(pwd)/otp
echo "::add-path::$(pwd)/otp/bin"
echo "$(pwd)/otp/bin" >> $GITHUB_PATH
- name: Compile Elixir
run: |
rm -rf .git
@@ -61,7 +62,7 @@ jobs:
steps:
- name: Configure Git
run: git config --global core.autocrlf input
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 50
- name: Cache Erlang/OTP package
@@ -88,7 +89,7 @@ jobs:
name: Check POSIX-compliant
runs-on: ubuntu-16.04
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
with:
fetch-depth: 50
- name: Install Shellcheck
+84 -9
View File
@@ -14,7 +14,7 @@ Elixir v1.11 builds on top of the recently added compilation tracers to track ap
These two conditions may seem contradictory. After all, if a module is available, it must have come from a dependency. This is not true in two scenarios:
* Modules from Elixir and Erlang/OTP are always available - even if their applications are not explicitly listed as a dependency
* Modules from Elixir and Erlang/OTP are always available - even if their applications are not listed as a dependency
* In an umbrella project, because all child applications are compiled within the same VM, you may have a module from a sibling project available, even if you don't depend on said sibling
@@ -36,7 +36,7 @@ on :ssl. To fix this, you must do one of:
to your "def project" in mix.exs
```
This comes with extra benefits in umbrella projects, as it requires child applications to explicitly list their dependencies, completely rejecting cyclic dependencies between siblings.
This comes with extra benefits in umbrella projects, as it requires applications to depend on the siblings they depend on, which will fail if there are any cyclic dependencies.
## Compiler checks: data constructors
@@ -243,7 +243,78 @@ The Calendar module ships with a new `Calendar.strftime/3` function, which provi
Mix also includes two new tasks: `mix app.config`, for application runtime configuration, and `mix test.coverage`, which generates aggregated coverage reports for umbrella projects and for test suites partitioned across processes.
## v1.11.0-dev
## v1.11.3 (2021-01-04)
### 1. Enhancements
#### Elixir
* [Macro] Add `Macro.unique_var/2` and `Macro.generate_unique_arguments/2`
### 2. Bug fixes
#### Elixir
* [Code] Do not raise when checking for operator ambiguity when `:static_atoms_encoder` is set in `Code.string_to_quoted`
* [Kernel] Emit undefined function warnings from `with`
* [Kernel] Do not fail type checking when literals are used in guards
* [Module] Do not warn for attributes used in `@after_compile`
* [Record] Make sure nested record names do not clobber each other
#### ExUnit
* [ExUnit.Assertions] Do not crash if there are macros and module attributes on the left side of `++`
#### IEx
* [IEx.Helpers] Do not use Unicode chars if ANSI is disabled
#### Mix
* [mix deps.compile] Fix compatibility with rebar v3.14
* [mix release] Do not use private `ram_file:compress/1`
* [mix xref] Do not crash when retrieving calls for modules in memory
## v1.11.2 (2020-11-03)
### 1. Bug fixes
#### Elixir
* [Code] Do not crash when getting docs for missing `erts` appdir
* [Kernel] Raise meaningful error if `:erlang.is_record` is used in guards
* [Kernel] Prune tracers when fetching `__ENV__` inside functions
#### Mix
* [mix] Fix regression where aliases could not call themselves recursively
* [mix compile] Do not discard tracers that are set programatically
## v1.11.1 (2020-10-16)
### 1. Bug fixes
#### Elixir
* [Code] Ignore tracers if lexical tracker is dead or explicitly nil when evaling code with an environment
* [GenServer] Do not show warning when using `super` in `GenServer.child_spec/1`
* [Kernel] Do not crash when :reduce is set to `nil` in comprehensions
* [Kernel] Fix a scenario where undefined function warnings were not being emitted
#### IEx
* [IEx.Helpers] Properly handle tags inside typespec when showing Erlang docs
#### Logger
* [Logger] Do not deadlock Logger if handler crashes on sync mode
#### Mix
* [Mix] Add inet6 fallback to Mix usage of httpc
* [mix compile.app] Do not list apps that do not match the current target
## v1.11.0 (2020-10-06)
### 1. Enhancements
@@ -284,6 +355,7 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
* [ExUnit] Add `@tag :tmp_dir` support to ExUnit. The temporary directory is automatically created and pruned before each test
* [ExUnit] Add file and line to ExUnit's `--trace`
* [ExUnit.Assertion] Allow receive timeouts to be computed at runtime
* [ExUnit.Case] Add `register_test/6` to speed up compilation of custom tests
* [ExUnit.Doctest] Allow users to add tags to doctests
#### IEx
@@ -305,17 +377,17 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
* [mix] Allow requirements for a Mix task to be listed via the `@requirements` module attribute
* [mix] Allow optional dependencies to be defined in `:extra_applications` and `:applications`
* [mix app.config] Add new `mix app.config` task that compiles applications and loads runtime configuration
* [mix archive.install] Support `--repo` option on hex packages
* [mix archive.install] Support `--repo` option on Hex packages
* [mix compile] Support the `__mix_recompile__?/0` callback for custom behaviour on when Mix should recompile a given module
* [mix compile.elixir] Mark modules for path dependencies as "Export dependencies" if they changed but their public interface is the same
* [mix compile.elixir] Track application boundaries in the Elixir compiler. If you invoke code from Erlang or Elixir standard libraries and you don't depend on the proper applications, a warning will be emitted. A warning will also be emitted if you invoke code from an umbrella sibling that you don't depend on - effectively forbidding cyclic dependencies between apps
* [mix deps] Sort the dependencies alphabetically before printing
* [mix deps] Use `origin/HEAD` as the default git ref in dependencies
* [mix deps] Use `origin/HEAD` as the default Git ref in dependencies
* [mix deps] Redact Git `username`/`password` in output log
* [mix deps] Support rebar3's `git_subdir` resource type
* [mix deps.compile] Allow local deps to be skipped on `mix deps.compile`
* [mix deps.unlock] Print which dependencies get unlocked when using the `--unused` flag
* [mix escript.install] Support `--repo` option on hex packages
* [mix escript.install] Support `--repo` option on Hex packages
* [mix new] Add `@impl` to application generated by `mix new --sup`
* [mix release] Enable overriding `sys.config` location via `RELEASE_SYS_CONFIG` env var
* [mix release] Boot a release under configuration in interactive mode and then swap to embedded mode (if running on Erlang/OTP 23+)
@@ -343,9 +415,12 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
* [Kernel] Properly parse `&//2` (i.e. the capture of the division operator)
* [Kernel] Raise `CompileError` when trying to define reserved types
* [Kernel] Improve compiler error message when using `|` in a `def` signature
* [Kernel] Improve error message when trying to use invalid list operators in guards
* [Kernel.SpecialForms] Add `|/2` to the list of special forms to avoid inconsistent behaviour on overrides
* [Keyword] Enforce keys to be atoms in `Keyword.keys/1`
* [URI] `URI.decode_query/2` emits an empty string for parameters without values, according to https://url.spec.whatwg.org/#application/x-www-form-urlencoded
* [Record] Keep lexical ordering when creating records
* [Registry] Do not crash when a process with key-value has been registered using `:via` and it fails to start on `init`
* [URI] `URI.decode_query/2` emits an empty string for parameters without values, according to [URL's living standard](https://url.spec.whatwg.org/#application/x-www-form-urlencoded) - note this behaviour is not specified in the spec implemented by the URI module, so the living standard was chosen
* [Version] Add defaults and enforce keys in `Version` struct
#### ExUnit
@@ -357,7 +432,7 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
#### IEx
* [IEx] Fix tokenizer emitting repeated warnings in the REPL
* [IEx] Ensure `dot_iex_path` is preserved when restarting the evaluator
* [IEx] Ensure `--dot-iex` is preserved when restarting the evaluator and after shell respawn
* [IEx.Pry] Ensure `IEx.pry` can be triggered more than twice when invoked from the same process
#### Mix
@@ -395,7 +470,7 @@ Mix also includes two new tasks: `mix app.config`, for application runtime confi
* [Supervisor] Deprecate `Supervisor.start_child/2` and `Supervisor.terminate_child/2` in favor of `DynamicSupervisor`
* [Supervisor.Spec] Deprecate `Supervisor.Spec.worker/3` and `Supervisor.Spec.supervisor/3` in favor of the new typespecs
* [System] Deprecate `System.stracktrace/0` in favor of `__STACKTRACE__`
* [System] Deprecate `System.stacktrace/0` in favor of `__STACKTRACE__`
#### Mix
+1 -2
View File
@@ -2,8 +2,7 @@ PREFIX ?= /usr/local
TEST_FILES ?= "*_test.exs"
SHARE_PREFIX ?= $(PREFIX)/share
MAN_PREFIX ?= $(SHARE_PREFIX)/man
#CANONICAL := vMAJOR.MINOR/
CANONICAL ?= master/
CANONICAL := v1.11/
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
ERLC := erlc -I lib/elixir/include
ERL_MAKE := if [ -n "$(ERLC_OPTS)" ]; then ERL_COMPILER_OPTIONS=$(ERLC_OPTS) erl -make; else erl -make; fi
+2 -3
View File
@@ -6,12 +6,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
| Elixir version | Support
| -------------- | ------------------------------
| 1.11 | Development
| 1.10 | Bug fixes and security patches
| 1.11 | Bug fixes and security patches
| 1.10 | Security patches only
| 1.9 | Security patches only
| 1.8 | Security patches only
| 1.7 | Security patches only
| 1.6 | Security patches only
## Announcements
+1 -1
View File
@@ -1 +1 @@
1.11.0-dev
1.11.3
+2 -2
View File
@@ -41,7 +41,7 @@ defmodule EEx.Compiler do
# Generates the buffers by handling each expression from the tokenizer.
# It returns Macro.t/0 or it raises.
defp generate_buffer([{:text, chars} | rest], buffer, scope, state) do
defp generate_buffer([{:text, _line, _column, chars} | rest], buffer, scope, state) do
buffer = state.engine.handle_text(buffer, IO.chardata_to_string(chars))
generate_buffer(rest, buffer, scope, state)
end
@@ -179,7 +179,7 @@ defmodule EEx.Compiler do
# Look middle expressions that immediately follow a start_expr
defp look_ahead_middle(
[{:text, text}, {:middle_expr, line, _column, _, chars} | rest] = tokens,
[{:text, _, _, text}, {:middle_expr, line, _, _, chars} | rest] = tokens,
start,
contents
) do
+64 -38
View File
@@ -6,7 +6,7 @@ defmodule EEx.Tokenizer do
@type column :: non_neg_integer
@type marker :: '=' | '/' | '|' | ''
@type token ::
{:text, content}
{:text, line, column, content}
| {:expr | :start_expr | :middle_expr | :end_expr, line, column, marker, content}
| {:eof, line, column}
@@ -17,7 +17,7 @@ defmodule EEx.Tokenizer do
It returns {:ok, list} with the following tokens:
* `{:text, content}`
* `{:text, line, column, content}`
* `{:expr, line, column, marker, content}`
* `{:start_expr, line, column, marker, content}`
* `{:middle_expr, line, column, marker, content}`
@@ -36,8 +36,11 @@ defmodule EEx.Tokenizer do
def tokenize(list, line, column, opts)
when is_list(list) and is_integer(line) and line >= 0 and is_integer(column) and column >= 0 do
column = opts.indentation + column
{list, line, column} = (opts.trim && trim_init(list, line, column)) || {list, line, column}
tokenize(list, line, column, opts, [], [])
{list, line, column} =
(opts.trim && trim_init(list, line, column, opts)) || {list, line, column}
tokenize(list, line, column, opts, [{line, column}], [])
end
defp tokenize('<%%' ++ t, line, column, opts, buffer, acc) do
@@ -53,7 +56,8 @@ defmodule EEx.Tokenizer do
{rest, new_line, new_column, buffer} =
trim_if_needed(rest, new_line, new_column, opts, buffer)
tokenize(rest, new_line, new_column, opts, buffer, acc)
acc = tokenize_text(buffer, acc)
tokenize(rest, new_line, new_column, opts, [{new_line, new_column}], acc)
end
end
@@ -65,10 +69,10 @@ defmodule EEx.Tokenizer do
error
{:ok, expr, new_line, new_column, rest} ->
key =
{key, expr} =
case :elixir_tokenizer.tokenize(expr, 1, file: "eex", check_terminators: false) do
{:ok, tokens} -> token_key(tokens)
{:error, _, _, _} -> :expr
{:ok, tokens} -> token_key(tokens, expr)
{:error, _, _, _} -> {:expr, expr}
end
{rest, new_line, new_column, buffer} =
@@ -76,7 +80,7 @@ defmodule EEx.Tokenizer do
acc = tokenize_text(buffer, acc)
final = {key, line, column, marker, expr}
tokenize(rest, new_line, new_column, opts, [], [final | acc])
tokenize(rest, new_line, new_column, opts, [{new_line, new_column}], [final | acc])
end
end
@@ -122,87 +126,109 @@ defmodule EEx.Tokenizer do
end
# Receives tokens and check if it is a start, middle or an end token.
defp token_key(tokens) do
defp token_key(tokens, expr) do
case {tokens, Enum.reverse(tokens)} do
{[{:end, _} | _], [{:do, _} | _]} ->
:middle_expr
{:middle_expr, expr}
{_, [{:do, _} | _]} ->
:start_expr
{:start_expr, maybe_append_space(expr)}
{_, [{:block_identifier, _, _} | _]} ->
:middle_expr
{:middle_expr, maybe_append_space(expr)}
{[{:end, _} | _], [{:stab_op, _, _} | _]} ->
:middle_expr
{:middle_expr, expr}
{_, [{:stab_op, _, _} | reverse_tokens]} ->
fn_index = Enum.find_index(reverse_tokens, &match?({:fn, _}, &1)) || :infinity
end_index = Enum.find_index(reverse_tokens, &match?({:end, _}, &1)) || :infinity
if end_index > fn_index do
:start_expr
{:start_expr, expr}
else
:middle_expr
{:middle_expr, expr}
end
{tokens, _} ->
case Enum.drop_while(tokens, &closing_bracket?/1) do
[{:end, _} | _] -> :end_expr
_ -> :expr
[{:end, _} | _] -> {:end_expr, expr}
_ -> {:expr, expr}
end
end
end
defp maybe_append_space([?\s]), do: [?\s]
defp maybe_append_space([h]), do: [h, ?\s]
defp maybe_append_space([h | t]), do: [h | maybe_append_space(t)]
defp closing_bracket?({closing, _}) when closing in ~w"( [ {"a, do: true
defp closing_bracket?(_), do: false
# Tokenize the buffered text by appending
# it to the given accumulator.
defp tokenize_text([], acc) do
defp tokenize_text([{_line, _column}], acc) do
acc
end
defp tokenize_text(buffer, acc) do
[{:text, Enum.reverse(buffer)} | acc]
[{line, column} | buffer] = Enum.reverse(buffer)
[{:text, line, column, buffer} | acc]
end
defp trim_if_needed(rest, line, column, opts, buffer) do
if opts.trim do
buffer = trim_left(buffer, 0)
{rest, line, column} = trim_right(rest, line, column, 0)
{rest, line, column} = trim_right(rest, line, column, 0, opts)
{rest, line, column, buffer}
else
{rest, line, column, buffer}
end
end
defp trim_init([h | t], line, column) when h in @spaces, do: trim_init(t, line, column + 1)
defp trim_init([?\r, ?\n | t], line, _column), do: trim_init(t, line + 1, 1)
defp trim_init([?\n | t], line, _column), do: trim_init(t, line + 1, 1)
defp trim_init([?<, ?% | _] = rest, line, column), do: {rest, line, column}
defp trim_init(_, _, _), do: false
defp trim_init([h | t], line, column, opts) when h in @spaces,
do: trim_init(t, line, column + 1, opts)
defp trim_init([?\r, ?\n | t], line, _column, opts),
do: trim_init(t, line + 1, opts.indentation + 1, opts)
defp trim_init([?\n | t], line, _column, opts),
do: trim_init(t, line + 1, opts.indentation + 1, opts)
defp trim_init([?<, ?% | _] = rest, line, column, _opts),
do: {rest, line, column}
defp trim_init(_, _, _, _), do: false
defp trim_left(buffer, count) do
case trim_whitespace(buffer) do
[?\n, ?\r | rest] -> trim_left(rest, count + 1)
[?\n | rest] -> trim_left(rest, count + 1)
case trim_whitespace(buffer, 0) do
{[?\n, ?\r | rest], _} -> trim_left(rest, count + 1)
{[?\n | rest], _} -> trim_left(rest, count + 1)
_ when count > 0 -> [?\n | buffer]
_ -> buffer
end
end
defp trim_right(rest, line, column, count) do
case trim_whitespace(rest) do
[?\r, ?\n | rest] -> trim_right(rest, line + 1, 1, count + 1)
[?\n | rest] -> trim_right(rest, line + 1, 1, count + 1)
[] -> {[], line, column + length(rest)}
_ when count > 0 -> {[?\n | rest], line - 1, column}
_ -> {rest, line, column}
defp trim_right(rest, line, column, last_column, opts) do
case trim_whitespace(rest, column) do
{[?\r, ?\n | rest], column} ->
trim_right(rest, line + 1, opts.indentation + 1, column + 1, opts)
{[?\n | rest], column} ->
trim_right(rest, line + 1, opts.indentation + 1, column, opts)
{[], column} ->
{[], line, column}
_ when last_column > 0 ->
{[?\n | rest], line - 1, last_column}
_ ->
{rest, line, column}
end
end
defp trim_whitespace([h | t]) when h in @spaces, do: trim_whitespace(t)
defp trim_whitespace(list), do: list
defp trim_whitespace([h | t], column) when h in @spaces, do: trim_whitespace(t, column + 1)
defp trim_whitespace(list, column), do: {list, column}
end
+40 -39
View File
@@ -7,36 +7,36 @@ defmodule EEx.TokenizerTest do
@opts %{indentation: 0, trim: false}
test "simple chars lists" do
assert T.tokenize('foo', 1, 1, @opts) == {:ok, [{:text, 'foo'}, {:eof, 1, 4}]}
assert T.tokenize('foo', 1, 1, @opts) == {:ok, [{:text, 1, 1, 'foo'}, {:eof, 1, 4}]}
end
test "simple strings" do
assert T.tokenize("foo", 1, 1, @opts) == {:ok, [{:text, 'foo'}, {:eof, 1, 4}]}
assert T.tokenize("foo", 1, 1, @opts) == {:ok, [{:text, 1, 1, 'foo'}, {:eof, 1, 4}]}
end
test "strings with embedded code" do
assert T.tokenize('foo <% bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '', ' bar '}, {:eof, 1, 14}]}
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '', ' bar '}, {:eof, 1, 14}]}
end
test "strings with embedded equals code" do
assert T.tokenize('foo <%= bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '=', ' bar '}, {:eof, 1, 15}]}
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '=', ' bar '}, {:eof, 1, 15}]}
end
test "strings with embedded slash code" do
assert T.tokenize('foo <%/ bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '/', ' bar '}, {:eof, 1, 15}]}
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '/', ' bar '}, {:eof, 1, 15}]}
end
test "strings with embedded pipe code" do
assert T.tokenize('foo <%| bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo '}, {:expr, 1, 5, '|', ' bar '}, {:eof, 1, 15}]}
{:ok, [{:text, 1, 1, 'foo '}, {:expr, 1, 5, '|', ' bar '}, {:eof, 1, 15}]}
end
test "strings with more than one line" do
assert T.tokenize('foo\n<%= bar %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo\n'}, {:expr, 2, 1, '=', ' bar '}, {:eof, 2, 11}]}
{:ok, [{:text, 1, 1, 'foo\n'}, {:expr, 2, 1, '=', ' bar '}, {:eof, 2, 11}]}
end
test "strings with more than one line and expression with more than one line" do
@@ -48,11 +48,11 @@ defmodule EEx.TokenizerTest do
'''
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:expr, 1, 5, '=', ' bar\n\nbaz '},
{:text, '\n'},
{:text, 3, 7, '\n'},
{:expr, 4, 1, '', ' foo '},
{:text, '\n'},
{:text, 4, 10, '\n'},
{:eof, 5, 1}
]
@@ -61,21 +61,21 @@ defmodule EEx.TokenizerTest do
test "quotation" do
assert T.tokenize('foo <%% true %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo <% true %>'}, {:eof, 1, 16}]}
{:ok, [{:text, 1, 1, 'foo <% true %>'}, {:eof, 1, 16}]}
end
test "quotation with do/end" do
assert T.tokenize('foo <%% true do %>bar<%% end %>', 1, 1, @opts) ==
{:ok, [{:text, 'foo <% true do %>bar<% end %>'}, {:eof, 1, 32}]}
{:ok, [{:text, 1, 1, 'foo <% true do %>bar<% end %>'}, {:eof, 1, 32}]}
end
test "quotation with interpolation" do
exprs = [
{:text, 'a <% b '},
{:text, 1, 1, 'a <% b '},
{:expr, 1, 9, '=', ' c '},
{:text, ' '},
{:text, 1, 17, ' '},
{:expr, 1, 18, '=', ' d '},
{:text, ' e %> f'},
{:text, 1, 26, ' e %> f'},
{:eof, 1, 33}
]
@@ -84,7 +84,7 @@ defmodule EEx.TokenizerTest do
test "improperly formatted quotation with interpolation" do
exprs = [
{:text, '<%% a <%= b %> c %>'},
{:text, 1, 1, '<%% a <%= b %> c %>'},
{:eof, 1, 22}
]
@@ -93,7 +93,7 @@ defmodule EEx.TokenizerTest do
test "eex comments" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:eof, 1, 16}
]
@@ -102,7 +102,8 @@ defmodule EEx.TokenizerTest do
test "eex comments with do/end" do
exprs = [
{:text, 'foo bar'},
{:text, 1, 1, 'foo '},
{:text, 1, 19, 'bar'},
{:eof, 1, 32}
]
@@ -111,7 +112,7 @@ defmodule EEx.TokenizerTest do
test "elixir comments" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:expr, 1, 5, [], ' true # this is a boolean '},
{:eof, 1, 35}
]
@@ -122,7 +123,7 @@ defmodule EEx.TokenizerTest do
test "elixir comments with do/end" do
exprs = [
{:start_expr, 1, 1, [], ' if true do # startif '},
{:text, 'text'},
{:text, 1, 27, 'text'},
{:end_expr, 1, 31, [], ' end # closeif '},
{:eof, 1, 50}
]
@@ -133,9 +134,9 @@ defmodule EEx.TokenizerTest do
test "strings with embedded do end" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:start_expr, 1, 5, '', ' if true do '},
{:text, 'bar'},
{:text, 1, 21, 'bar'},
{:end_expr, 1, 24, '', ' end '},
{:eof, 1, 33}
]
@@ -145,12 +146,12 @@ defmodule EEx.TokenizerTest do
test "strings with embedded -> end" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:start_expr, 1, 5, '', ' cond do '},
{:middle_expr, 1, 18, '', ' false -> '},
{:text, 'bar'},
{:text, 1, 32, 'bar'},
{:middle_expr, 1, 35, '', ' true -> '},
{:text, 'baz'},
{:text, 1, 48, 'baz'},
{:end_expr, 1, 51, '', ' end '},
{:eof, 1, 60}
]
@@ -162,9 +163,9 @@ defmodule EEx.TokenizerTest do
test "strings with multiple callbacks" do
exprs = [
{:start_expr, 1, 1, '=', ' a fn -> '},
{:text, 'foo'},
{:text, 1, 15, 'foo'},
{:middle_expr, 1, 18, '', ' end, fn -> '},
{:text, 'bar'},
{:text, 1, 34, 'bar'},
{:end_expr, 1, 37, '', ' end '},
{:eof, 1, 46}
]
@@ -176,9 +177,9 @@ defmodule EEx.TokenizerTest do
test "strings with callback followed by do block" do
exprs = [
{:start_expr, 1, 1, '=', ' a fn -> '},
{:text, 'foo'},
{:text, 1, 15, 'foo'},
{:middle_expr, 1, 18, '', ' end do '},
{:text, 'bar'},
{:text, 1, 30, 'bar'},
{:end_expr, 1, 33, '', ' end '},
{:eof, 1, 42}
]
@@ -188,11 +189,11 @@ defmodule EEx.TokenizerTest do
test "strings with embedded keywords blocks" do
exprs = [
{:text, 'foo '},
{:text, 1, 1, 'foo '},
{:start_expr, 1, 5, '', ' if true do '},
{:text, 'bar'},
{:text, 1, 21, 'bar'},
{:middle_expr, 1, 24, '', ' else '},
{:text, 'baz'},
{:text, 1, 34, 'baz'},
{:end_expr, 1, 37, '', ' end '},
{:eof, 1, 46}
]
@@ -206,9 +207,9 @@ defmodule EEx.TokenizerTest do
exprs = [
{:start_expr, 1, 2, '=', ' if true do '},
{:text, '\n TRUE \n'},
{:text, 1, 20, '\n TRUE \n'},
{:middle_expr, 3, 3, '', ' else '},
{:text, '\n FALSE \n'},
{:text, 3, 13, '\n FALSE \n'},
{:end_expr, 5, 3, '', ' end '},
{:eof, 7, 3}
]
@@ -218,7 +219,7 @@ defmodule EEx.TokenizerTest do
test "trim mode with comment" do
exprs = [
{:text, '\n123'},
{:text, 1, 19, '\n123'},
{:eof, 2, 4}
]
@@ -227,9 +228,9 @@ defmodule EEx.TokenizerTest do
test "trim mode with CRLF" do
exprs = [
{:text, '0\n'},
{:text, 1, 1, '0\n'},
{:expr, 2, 3, '=', ' 12 '},
{:text, '\n34'},
{:text, 2, 15, '\n34'},
{:eof, 3, 3}
]
@@ -238,9 +239,9 @@ defmodule EEx.TokenizerTest do
test "trim mode set to false" do
exprs = [
{:text, ' '},
{:text, 1, 1, ' '},
{:expr, 1, 2, '=', ' 12 '},
{:text, ' \n'},
{:text, 1, 11, ' \n'},
{:eof, 2, 1}
]
+11
View File
@@ -127,6 +127,17 @@ defmodule EExTest do
end
test "trim mode with no spaces" do
string = """
<%=if true do%>
this
<%else%>
that
<%end%>
"""
expected = "\n this\n"
assert_eval(expected, string, [], trim: true)
string = """
<%=cond do%>
<%false ->%>
+2 -2
View File
@@ -111,9 +111,9 @@ defmodule Behaviour do
end
end
defp __behaviour__doc_value(:none), do: nil
defp __behaviour__doc_value(:hidden), do: false
defp __behaviour__doc_value(%{"en" => doc}), do: doc
defp __behaviour__doc_value(:hidden), do: false
defp __behaviour__doc_value(_), do: nil
import unquote(__MODULE__)
end
+6
View File
@@ -8,6 +8,12 @@ defmodule Calendar.ISO do
applied for all time, consequently the dates give different results
before the year 1583 from when the Gregorian calendar was adopted.
Given this is the default calendar used by Elixir, it has one
difference compared to the ISO8601 specification in that it allows
a whitespace instead of `T` as a seperator between date and times
both when parsing and formatting. Strict formatting can be done
by using the `to_iso8601` found in `NaiveDateTime` and `DateTime`.
Note that while ISO 8601 allows times and datetimes to specify
24:00:00 as the zero hour of the next day, this notation is not
supported by Elixir.
+11 -3
View File
@@ -1348,7 +1348,7 @@ defmodule Code do
| {:error, :module_not_found | :chunk_not_found | {:invalid_chunk, binary}}
when annotation: :erl_anno.anno(),
beam_language: :elixir | :erlang | atom(),
doc_content: %{required(binary) => binary} | :none | :hidden,
doc_content: %{optional(binary) => binary} | :none | :hidden,
doc_element:
{{kind :: atom, function_name :: atom, arity}, annotation, signature, doc_content,
metadata},
@@ -1373,8 +1373,16 @@ defmodule Code do
:error ->
case :code.which(module) do
:preloaded ->
path = Path.join([:code.lib_dir(:erts), "doc", "chunks", "#{module}.chunk"])
fetch_docs_from_chunk(path)
# The erts directory is not necessarily included in releases
# unless it is listed as an extra application.
case :code.lib_dir(:erts) do
path when is_list(path) ->
path = Path.join([path, "doc", "chunks", "#{module}.chunk"])
fetch_docs_from_chunk(path)
{:error, _} ->
{:error, :chunk_not_found}
end
_ ->
{:error, :module_not_found}
+6 -3
View File
@@ -64,8 +64,8 @@ defmodule Config do
## config/runtime.exs
For runtime configuration, you can use the `config/runtime.exs` file.
It is executed after your Mix project is compiled and also before a
release (assembled with `mix release`) starts.
It is executed right before applications start in both Mix and releases
(assembled with `mix release`).
"""
@opts_key {__MODULE__, :opts}
@@ -160,7 +160,10 @@ defmodule Config do
end
@doc """
Returns the environemnt this configuration file is executed on.
Returns the environment this configuration file is executed on.
In Mix projects this function returns the environment this configuration
file is executed on. In releases, the environment when `mix release` ran.
This is most often used to execute conditional code:
+31 -23
View File
@@ -473,15 +473,25 @@ defmodule Enum do
@doc """
Chunks the `enumerable` with fine grained control when every chunk is emitted.
`chunk_fun` receives the current element and the accumulator and
must return `{:cont, chunk, acc}` to emit the given chunk and
continue with accumulator or `{:cont, acc}` to not emit any chunk
and continue with the return accumulator.
`chunk_fun` receives the current element and the accumulator and must return:
`after_fun` is invoked when iteration is done and must also return
`{:cont, chunk, acc}` or `{:cont, acc}`.
* `{:cont, chunk, acc}` to emit a chunk and continue with the accumulator
* `{:cont, acc}` to not emit any chunk and continue with the accumulator
* `{:halt, acc}` to halt chunking over the `enumerable`.
Returns a list of lists.
`after_fun` is invoked with the final accumulator when iteration is
finished (or `halt`ed) to handle any trailing elements that were returned
as part of an accumulator, but were not emited as a chunk by `chunk_fun`.
It must return:
* `{:cont, chunk, acc}` to emit a chunk. The chunk will be appended to the
list of already emitted chunks.
* `{:cont, acc}` to not emit a chunk
The `acc` in `after_fun` is required in order to mirror the tuple format
from `chunk_fun` but it will be discarded since the traversal is complete.
Returns a list of emitted chunks.
## Examples
@@ -498,6 +508,8 @@ defmodule Enum do
...> end
iex> Enum.chunk_while(1..10, [], chunk_fun, after_fun)
[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
iex> Enum.chunk_while([1, 2, 3, 5, 7], [], chunk_fun, after_fun)
[[1, 2], [3, 5, 7]]
"""
@doc since: "1.5.0"
@@ -512,7 +524,7 @@ defmodule Enum do
{_, {res, acc}} =
Enumerable.reduce(enumerable, {:cont, {[], acc}}, fn entry, {buffer, acc} ->
case chunk_fun.(entry, acc) do
{:cont, emit, acc} -> {:cont, {[emit | buffer], acc}}
{:cont, chunk, acc} -> {:cont, {[chunk | buffer], acc}}
{:cont, acc} -> {:cont, {buffer, acc}}
{:halt, acc} -> {:halt, {buffer, acc}}
end
@@ -520,7 +532,7 @@ defmodule Enum do
case after_fun.(acc) do
{:cont, _acc} -> :lists.reverse(res)
{:cont, elem, _acc} -> :lists.reverse([elem | res])
{:cont, chunk, _acc} -> :lists.reverse([chunk | res])
end
end
@@ -1593,7 +1605,11 @@ defmodule Enum do
defp max_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) != :lt)
@doc false
@spec max_by(t, (element -> any), (() -> empty_result)) :: element | empty_result
@spec max_by(
t,
(element -> any),
(() -> empty_result) | (element, element -> boolean) | module()
) :: element | empty_result
when empty_result: any
def max_by(enumerable, fun, empty_fallback)
when is_function(fun, 1) and is_function(empty_fallback, 0) do
@@ -1644,12 +1660,6 @@ defmodule Enum do
nil
"""
@spec max_by(
t,
(element -> any),
(element, element -> boolean) | module()
) :: element | empty_result
when empty_result: any
@spec max_by(
t,
(element -> any),
@@ -1767,7 +1777,11 @@ defmodule Enum do
defp min_sort_fun(module) when is_atom(module), do: &(module.compare(&1, &2) != :gt)
@doc false
@spec min_by(t, (element -> any), (() -> empty_result)) :: element | empty_result
@spec min_by(
t,
(element -> any),
(() -> empty_result) | (element, element -> boolean) | module()
) :: element | empty_result
when empty_result: any
def min_by(enumerable, fun, empty_fallback)
when is_function(fun, 1) and is_function(empty_fallback, 0) do
@@ -1818,12 +1832,6 @@ defmodule Enum do
nil
"""
@spec min_by(
t,
(element -> any),
(element, element -> boolean) | module()
) :: element | empty_result
when empty_result: any
@spec min_by(
t,
(element -> any),
+2
View File
@@ -110,6 +110,8 @@ defmodule File do
@type stream_mode ::
encoding_mode()
| :append
| :compressed
| :trim_bom
| {:read_ahead, pos_integer | false}
| {:delayed_write, non_neg_integer, non_neg_integer}
+21 -6
View File
@@ -1,7 +1,8 @@
defmodule IO.ANSI.Docs do
@moduledoc false
@bullet_text "• "
@bullet_text_unicode "• "
@bullet_text_ascii "* "
@bullets [?*, ?-, ?+]
@spaces [" ", "\n", "\t"]
@@ -219,7 +220,10 @@ defmodule IO.ANSI.Docs do
end
defp traverse_erlang_html({:dt, _, entries}, indent, options) do
["#{indent} ", @bullet_text | handle_erlang_html_text(entries, indent <> " ", options)]
[
"#{indent} ",
bullet_text(options) | handle_erlang_html_text(entries, indent <> " ", options)
]
end
defp traverse_erlang_html({:dd, _, entries}, indent, options) do
@@ -231,12 +235,19 @@ defmodule IO.ANSI.Docs do
types =
for {:li, _, lines} <- entries,
line <- lines,
do: ["#{indent} ", line, ?\n]
do: ["#{indent} ", traverse_erlang_html(line, indent <> " ", options), ?\n]
["#{indent}Typespecs:\n\n", types, ?\n]
if types != [] do
["#{indent}Typespecs:\n\n", types, ?\n]
else
[]
end
else
for {:li, _, lines} <- entries do
["#{indent} ", @bullet_text | handle_erlang_html_text(lines, indent <> " ", options)]
[
"#{indent} ",
bullet_text(options) | handle_erlang_html_text(lines, indent <> " ", options)
]
end
end
end
@@ -420,7 +431,7 @@ defmodule IO.ANSI.Docs do
case stripped do
<<bullet, ?\s, item::binary>> when bullet in @bullets ->
write_text(text, indent, options)
process_list(@bullet_text, item, rest, count, indent, options)
process_list(bullet_text(options), item, rest, count, indent, options)
<<d1, ?., ?\s, item::binary>> when d1 in ?0..?9 ->
write_text(text, indent, options)
@@ -926,6 +937,10 @@ defmodule IO.ANSI.Docs do
end
end
defp bullet_text(options) do
if options[:enabled], do: @bullet_text_unicode, else: @bullet_text_ascii
end
defp color(style, colors) do
IO.ANSI.format_fragment(colors[style], colors[:enabled])
end
+29 -2
View File
@@ -3123,7 +3123,15 @@ defmodule Kernel do
not function? and __CALLER__.context == :match ->
raise ArgumentError,
"invalid write attribute syntax, you probably meant to use: @#{name} expression"
"""
invalid write attribute syntax. If you want to define an attribute, don't do this:
@foo = :value
Instead, do this:
@foo :value
"""
# Typespecs attributes are currently special cased by the compiler
is_list(args) and typespec?(name) ->
@@ -3219,7 +3227,26 @@ defmodule Kernel do
end
end
# All other cases
# Error cases
defp do_at([{call, meta, ctx_or_args}, [{:do, _} | _] = kw], _meta, name, _function?, _env) do
args =
cond do
is_atom(ctx_or_args) -> []
true -> ctx_or_args
end
code = "\n@#{name} (#{Macro.to_string({call, meta, args ++ [kw]})})"
raise ArgumentError, """
expected 0 or 1 argument for @#{name}, got 2.
It seems you are trying to use the do-syntax with @module attributes \
but the do-block is binding to the attribute name. You probably want \
to wrap the argument value in parentheses, like this:
#{String.replace(code, "\n", "\n ")}
"""
end
defp do_at(args, _meta, name, _function?, _env) do
raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
end
+2 -3
View File
@@ -23,9 +23,8 @@ defmodule Kernel.Typespec do
for {{:type, name, arity}, _, _, doc, _} <- docs do
case doc do
%{"en" => doc_string} -> {{name, arity}, doc_string}
:none -> {{name, arity}, nil}
# Hidden or unknown format are ignored
_ -> {{name, arity}, false}
:hidden -> {{name, arity}, false}
_ -> {{name, arity}, nil}
end
end
+14 -5
View File
@@ -668,10 +668,19 @@ defmodule Keyword do
@doc since: "1.11.0"
@spec replace(t, key, value) :: t
def replace(keywords, key, value) when is_list(keywords) and is_atom(key) do
case :lists.keyfind(key, 1, keywords) do
{^key, _} -> [{key, value} | delete(keywords, key)]
false -> keywords
end
do_replace(keywords, key, value)
end
defp do_replace([{key, _} | keywords], key, value) do
[{key, value} | delete(keywords, key)]
end
defp do_replace([{_, _} = e | keywords], key, value) do
[e | do_replace(keywords, key, value)]
end
defp do_replace([], _key, _value) do
[]
end
@doc """
@@ -704,7 +713,7 @@ defmodule Keyword do
[e | replace!(keywords, key, value, original)]
end
defp replace!([], key, _value, original) when is_atom(key) do
defp replace!([], key, _value, original) do
raise(KeyError, key: key, term: original)
end
+58 -7
View File
@@ -337,8 +337,13 @@ defmodule Macro do
end
@doc """
Generates AST nodes for a given number of required argument variables using
`Macro.var/2`.
Generates AST nodes for a given number of required argument
variables using `Macro.var/2`.
Note the arguments are not unique. If you later on want
to access this same varibles, you can invoke this function
with the same inputs. Use `generate_unique_arguments/2` to
generate a unique arguments that can't be overridden.
## Examples
@@ -349,19 +354,47 @@ defmodule Macro do
@doc since: "1.5.0"
@spec generate_arguments(0, context :: atom) :: []
@spec generate_arguments(pos_integer, context) :: [{atom, [], context}, ...] when context: atom
def generate_arguments(amount, context)
def generate_arguments(amount, context), do: generate_arguments(amount, context, &var/2)
def generate_arguments(0, context) when is_atom(context), do: []
@doc """
Generates AST nodes for a given number of required argument
variables using `Macro.unique_var/2`.
def generate_arguments(amount, context)
when is_integer(amount) and amount > 0 and is_atom(context) do
for id <- 1..amount, do: var(String.to_atom("arg" <> Integer.to_string(id)), context)
## Examples
iex> [var1, var2] = Macro.generate_unique_arguments(2, __MODULE__)
iex> {:arg1, [counter: c1], __MODULE__} = var1
iex> {:arg2, [counter: c2], __MODULE__} = var2
iex> is_integer(c1) and is_integer(c2)
true
"""
@doc since: "1.11.3"
@spec generate_unique_arguments(0, context :: atom) :: []
@spec generate_unique_arguments(pos_integer, context) :: [
{atom, [counter: integer], context},
...
]
when context: atom
def generate_unique_arguments(amount, context),
do: generate_arguments(amount, context, &unique_var/2)
defp generate_arguments(0, context, _fun) when is_atom(context), do: []
defp generate_arguments(amount, context, fun)
when is_integer(amount) and amount > 0 and is_atom(context) do
for id <- 1..amount, do: fun.(String.to_atom("arg" <> Integer.to_string(id)), context)
end
@doc """
Generates an AST node representing the variable given
by the atoms `var` and `context`.
Note this variable is not unique. If you later on want
to access this same varible, you can invoke `var/2`
again with the same argument. Use `unique_var/2` to
generate a unique variable that can't be overridden.
## Examples
In order to build a variable, a context is expected.
@@ -383,6 +416,24 @@ defmodule Macro do
{var, [], context}
end
@doc """
Generates an AST node representing a unique variable
given by the atoms `var` and `context`.
## Examples
iex> {:foo, [counter: c], __MODULE__} = Macro.unique_var(:foo, __MODULE__)
iex> is_integer(c)
true
"""
@doc since: "1.11.3"
@spec unique_var(var, context) :: {var, [counter: integer], context}
when var: atom, context: atom
def unique_var(var, context) when is_atom(var) and is_atom(context) do
{var, [counter: :elixir_module.next_counter(context)], context}
end
@doc """
Performs a depth-first traversal of quoted expressions
using an accumulator.
+6 -6
View File
@@ -6,7 +6,7 @@ defmodule Module.Types do
end
import Module.Types.Helpers
alias Module.Types.{Expr, Pattern, Infer}
alias Module.Types.{Expr, Pattern, Unify}
@doc false
def warnings(module, file, defs, no_warn_undefined, cache) do
@@ -57,7 +57,7 @@ defmodule Module.Types do
end
defp warnings_from_clause(args, guards, body, def_expr, stack, context) do
head_stack = push_expr_stack(def_expr, stack)
head_stack = Unify.push_expr_stack(def_expr, stack)
with {:ok, _types, context} <- Pattern.of_head(args, guards, head_stack, context),
{:ok, _type, context} <- Expr.of_expr(body, stack, context) do
@@ -300,9 +300,9 @@ defmodule Module.Types do
[
"incompatible types:\n\n ",
Infer.format_type(left, simplify_left?),
Unify.format_type(left, simplify_left?),
" !~ ",
Infer.format_type(right, simplify_right?),
Unify.format_type(right, simplify_right?),
"\n\n",
format_expr(expr, location),
traces,
@@ -352,7 +352,7 @@ defmodule Module.Types do
"where \"",
Macro.to_string(var),
"\" was given the type ",
Infer.format_type(type, simplify?),
Unify.format_type(type, simplify?),
hint,
" in:\n\n # ",
format_location(location),
@@ -518,8 +518,8 @@ defmodule Module.Types do
defp map_type?(_other), do: false
defp atom_type?(:atom), do: true
defp atom_type?(:boolean), do: true
defp atom_type?({:atom, _}), do: false
defp atom_type?({:union, union}), do: Enum.all?(union, &atom_type?/1)
defp atom_type?(_other), do: false
defp integer_type?(:integer), do: true
+61 -55
View File
@@ -2,7 +2,7 @@ defmodule Module.Types.Expr do
@moduledoc false
alias Module.Types.{Of, Pattern}
import Module.Types.{Helpers, Infer}
import Module.Types.{Helpers, Unify}
def of_expr(expr, %{context: stack_context} = stack, context) when stack_context != :expr do
of_expr(expr, %{stack | context: :expr}, context)
@@ -109,8 +109,7 @@ defmodule Module.Types.Expr do
# var
def of_expr(var, _stack, context) when is_var(var) do
{type, context} = new_var(var, context)
{:ok, type, context}
{:ok, get_var!(var, context), context}
end
# {left, right}
@@ -204,7 +203,7 @@ defmodule Module.Types.Expr do
stack = push_expr_stack(expr, stack)
with {:ok, _expr_type, context} <- of_expr(case_expr, stack, context),
:ok <- of_clauses(clauses, stack, context),
{:ok, context} <- of_clauses(clauses, stack, context),
do: {:ok, :dynamic, context}
end
@@ -213,7 +212,7 @@ defmodule Module.Types.Expr do
stack = push_expr_stack(expr, stack)
case of_clauses(clauses, stack, context) do
:ok -> {:ok, :dynamic, context}
{:ok, context} -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
end
end
@@ -225,29 +224,37 @@ defmodule Module.Types.Expr do
def of_expr({:try, _meta, [blocks]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
result =
each_ok(blocks, fn
{:rescue, clauses} ->
each_ok(clauses, fn
{:->, _, [[{:in, _, [var, _exceptions]}], body]} ->
{result, context} =
reduce_ok(blocks, context, fn
{:rescue, clauses}, context ->
reduce_ok(clauses, context, fn
{:->, _, [[{:in, _, [var, _exceptions]}], body]}, context = acc ->
{_type, context} = new_pattern_var(var, context)
of_expr_ok(body, stack, context)
{:->, _, [[var], body]} ->
with {:ok, context} <- of_expr_context(body, stack, context) do
{:ok, keep_warnings(acc, context)}
end
{:->, _, [[var], body]}, context = acc ->
{_type, context} = new_pattern_var(var, context)
of_expr_ok(body, stack, context)
with {:ok, context} <- of_expr_context(body, stack, context) do
{:ok, keep_warnings(acc, context)}
end
end)
{block, body} when block in @try_blocks ->
of_expr_ok(body, stack, context)
{block, body}, context = acc when block in @try_blocks ->
with {:ok, context} <- of_expr_context(body, stack, context) do
{:ok, keep_warnings(acc, context)}
end
{block, clauses} when block in @try_clause_blocks ->
{block, clauses}, context when block in @try_clause_blocks ->
of_clauses(clauses, stack, context)
end)
case result do
:ok -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
:error -> {:error, context}
end
end
@@ -255,33 +262,42 @@ defmodule Module.Types.Expr do
def of_expr({:receive, _meta, [blocks]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
result =
each_ok(blocks, fn
{:do, {:__block__, _, []}} ->
:ok
{result, context} =
reduce_ok(blocks, context, fn
{:do, {:__block__, _, []}}, context ->
{:ok, context}
{:do, clauses} ->
{:do, clauses}, context ->
of_clauses(clauses, stack, context)
{:after, [{:->, _meta, [head, body]}]} ->
{:after, [{:->, _meta, [head, body]}]}, context = acc ->
with {:ok, _type, context} <- of_expr(head, stack, context),
{:ok, _type, _context} <- of_expr(body, stack, context),
do: :ok
{:ok, _type, context} <- of_expr(body, stack, context),
do: {:ok, keep_warnings(acc, context)}
end)
case result do
:ok -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
:error -> {:error, context}
end
end
# for pat <- expr do expr end
def of_expr({:for, _meta, args} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
{clauses, [[{:do, block} | opts]]} = Enum.split(args, -1)
case reduce_ok(args, context, &for_clause(&1, stack, &2)) do
{:ok, _context} -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
with {:ok, context} <- reduce_ok(clauses, context, &for_clause(&1, stack, &2)),
{:ok, context} <- reduce_ok(opts, context, &for_option(&1, stack, &2)) do
if Keyword.has_key?(opts, :reduce) do
with {:ok, context} <- of_clauses(block, stack, context) do
{:ok, :dynamic, context}
end
else
with {:ok, _type, context} <- of_expr(block, stack, context) do
{:ok, :dynamic, context}
end
end
end
end
@@ -290,7 +306,7 @@ defmodule Module.Types.Expr do
stack = push_expr_stack(expr, stack)
case reduce_ok(clauses, context, &with_clause(&1, stack, &2)) do
{:ok, _context} -> {:ok, :dynamic, context}
{:ok, context} -> {:ok, :dynamic, context}
{:error, reason} -> {:error, reason}
end
end
@@ -379,6 +395,13 @@ defmodule Module.Types.Expr do
do: {:ok, context}
end
defp for_clause({:<<>>, _, [{:<-, _, [pattern, expr]}]}, stack, context) do
# TODO: the compiler guarantees pattern is a binary but we need to check expr is a binary
with {:ok, _pattern_type, context} <- Pattern.of_pattern(pattern, stack, context),
{:ok, _expr_type, context} <- of_expr(expr, stack, context),
do: {:ok, context}
end
defp for_clause(list, stack, context) when is_list(list) do
reduce_ok(list, context, &for_option(&1, stack, &2))
end
@@ -399,17 +422,6 @@ defmodule Module.Types.Expr do
{:ok, context}
end
defp for_option({:do, [{:->, _, [pattern, body]}]}, stack, context) do
case Pattern.of_pattern(pattern, stack, context) do
{:ok, _pattern_type, context} -> of_expr_context(body, stack, context)
{:error, reason} -> {:error, reason}
end
end
defp for_option({:do, body}, stack, context) do
of_expr_context(body, stack, context)
end
defp with_clause({:<-, _, [left, expr]}, stack, context) do
{pattern, guards} = extract_head([left])
@@ -433,22 +445,23 @@ defmodule Module.Types.Expr do
end
defp with_option({:else, clauses}, stack, context) do
case of_clauses(clauses, stack, context) do
:ok -> {:ok, context}
{:error, reason} -> {:error, reason}
end
of_clauses(clauses, stack, context)
end
defp of_clauses(clauses, stack, context) do
each_ok(clauses, fn {:->, _meta, [head, body]} ->
reduce_ok(clauses, context, fn {:->, _meta, [head, body]}, context = acc ->
{patterns, guards} = extract_head(head)
with {:ok, _, context} <- Pattern.of_head(patterns, guards, stack, context),
{:ok, _expr_type, _context} <- of_expr(body, stack, context),
do: :ok
{:ok, _expr_type, context} <- of_expr(body, stack, context),
do: {:ok, keep_warnings(acc, context)}
end)
end
defp keep_warnings(context, %{warnings: warnings}) do
%{context | warnings: warnings}
end
defp extract_head([{:when, _meta, args}]) do
case Enum.split(args, -1) do
{patterns, [guards]} -> {patterns, flatten_when(guards)}
@@ -475,13 +488,6 @@ defmodule Module.Types.Expr do
end
end
defp of_expr_ok(expr, stack, context) do
case of_expr(expr, stack, context) do
{:ok, _type, _context} -> :ok
{:error, reason} -> {:error, reason}
end
end
defp new_pattern_var({:_, _meta, var_context}, context) when is_atom(var_context) do
{:dynamic, context}
end
-23
View File
@@ -25,16 +25,6 @@ defmodule Module.Types.Helpers do
def get_meta({_, meta, _}), do: meta
def get_meta(_other), do: []
@doc """
Push expression to stack.
The expression stack is used to give the context where a type variable
was refined when show a type conflict error.
"""
def push_expr_stack(expr, stack) do
%{stack | last_expr: expr}
end
@doc """
Like `Enum.reduce/3` but only continues while `fun` returns `{:ok, acc}`
and stops on `{:error, reason}`.
@@ -91,19 +81,6 @@ defmodule Module.Types.Helpers do
defp do_map_ok([], acc, _fun), do: {:ok, Enum.reverse(acc)}
@doc """
Like `Enum.each/2` but only continues while `fun` returns `:ok`
and stops on `{:error, reason}`.
"""
def each_ok([head | tail], fun) do
case fun.(head) do
:ok -> each_ok(tail, fun)
{:error, reason} -> {:error, reason}
end
end
def each_ok([], _fun), do: :ok
@doc """
Like `Enum.map_reduce/3` but only continues while `fun` returns `{:ok, elem, acc}`
and stops on `{:error, reason}`.
+21 -16
View File
@@ -6,17 +6,27 @@ defmodule Module.Types.Of do
@prefix quote(do: ...)
@suffix quote(do: ...)
alias Module.Types.Infer
alias Module.ParallelChecker
import Module.Types.Helpers
import Module.Types.Unify
@doc """
Handles open maps (with dynamic => dynamic).
"""
def open_map(args, stack, context, fun) do
with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
{:ok, {:map, pairs_to_unions(pairs, context) ++ [{:optional, :dynamic, :dynamic}]}, context}
pairs =
for {key, value} <- pairs, not has_unbound_var?(key, context) do
if singleton?(key, context) do
{key, value}
else
{key, to_union([value, :dynamic], context)}
end
end
triplets = pairs_to_unions(pairs, [], context) ++ [{:optional, :dynamic, :dynamic}]
{:ok, {:map, triplets}, context}
end
end
@@ -25,7 +35,7 @@ defmodule Module.Types.Of do
"""
def closed_map(args, stack, context, fun) do
with {:ok, pairs, context} <- map_pairs(args, stack, context, fun) do
{:ok, {:map, pairs_to_unions(pairs, context)}, context}
{:ok, {:map, closed_to_unions(pairs, context)}, context}
end
end
@@ -37,10 +47,10 @@ defmodule Module.Types.Of do
end)
end
defp pairs_to_unions([{key, value}], _context), do: [{:required, key, value}]
defp closed_to_unions([{key, value}], _context), do: [{:required, key, value}]
defp pairs_to_unions(pairs, context) do
case Enum.split_with(pairs, fn {key, _value} -> Infer.has_unbound_var?(key, context) end) do
defp closed_to_unions(pairs, context) do
case Enum.split_with(pairs, fn {key, _value} -> has_unbound_var?(key, context) end) do
{[], pairs} -> pairs_to_unions(pairs, [], context)
{[_ | _], pairs} -> pairs_to_unions([{:dynamic, :dynamic} | pairs], [], context)
end
@@ -57,17 +67,17 @@ defmodule Module.Types.Of do
find_subtype_values(ahead, key, context) ++
find_subtype_values(behind, key, context)
pairs_to_unions(ahead, [{key, Infer.to_union(all_values, context)} | behind], context)
pairs_to_unions(ahead, [{key, to_union(all_values, context)} | behind], context)
end
defp pairs_to_unions([], acc, context) do
acc
|> Enum.sort(&Infer.subtype?(elem(&1, 0), elem(&2, 0), context))
|> Enum.sort(&subtype?(elem(&1, 0), elem(&2, 0), context))
|> Enum.map(fn {key, value} -> {:required, key, value} end)
end
defp find_subtype_values(pairs, key, context) do
for {pair_key, pair_value} <- pairs, Infer.subtype?(pair_key, key, context), do: pair_value
for {pair_key, pair_value} <- pairs, subtype?(pair_key, key, context), do: pair_value
end
defp find_matching_values([{key, value} | ahead], key, acc, values) do
@@ -153,16 +163,11 @@ defmodule Module.Types.Of do
true ->
with {:ok, type, context} <- fun.(expr, stack, context),
{:ok, _type, context} <- Infer.unify(type, expected_type, stack, context),
{:ok, _type, context} <- unify(type, expected_type, stack, context),
do: {:ok, context}
end
end
# TODO: Remove this clause once we properly handle comprehensions
defp binary_segment({:<-, _, _}, _stack, context, _fun) do
{:ok, context}
end
# Collect binary type specifiers,
# from `<<pattern::integer-size(10)>>` collect `integer`
defp collect_binary_specifier({:-, _meta, [left, right]}, fun) do
@@ -173,7 +178,7 @@ defmodule Module.Types.Of do
fun.(other)
end
defp binary_type(:expr, {:float, _, _}), do: :number
defp binary_type(:expr, {:float, _, _}), do: {:union, [:integer, :float]}
defp binary_type(:expr, {:utf8, _, _}), do: {:union, [:integer, :binary]}
defp binary_type(:expr, {:utf16, _, _}), do: {:union, [:integer, :binary]}
defp binary_type(:expr, {:utf32, _, _}), do: {:union, [:integer, :binary]}
+192 -191
View File
@@ -2,7 +2,7 @@ defmodule Module.Types.Pattern do
@moduledoc false
alias Module.Types.Of
import Module.Types.{Helpers, Infer}
import Module.Types.{Helpers, Unify}
@doc """
Handles patterns and guards at once.
@@ -24,113 +24,14 @@ defmodule Module.Types.Pattern do
of_pattern(pattern, %{stack | context: :pattern}, context)
end
# :atom
def of_pattern(atom, _stack, context) when is_atom(atom) do
{:ok, {:atom, atom}, context}
end
# 12
def of_pattern(literal, _stack, context) when is_integer(literal) do
{:ok, :integer, context}
end
# 1.2
def of_pattern(literal, _stack, context) when is_float(literal) do
{:ok, :float, context}
end
# "..."
def of_pattern(literal, _stack, context) when is_binary(literal) do
{:ok, :binary, context}
end
# <<...>>>
def of_pattern({:<<>>, _meta, args}, stack, context) do
result = Of.binary(args, stack, context, &of_pattern/3)
case result do
{:ok, context} -> {:ok, :binary, context}
{:error, reason} -> {:error, reason}
end
end
# left | []
def of_pattern({:|, _meta, [left_expr, []]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
of_pattern(left_expr, stack, context)
end
# left | right
def of_pattern({:|, _meta, [left_expr, right_expr]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
case of_pattern(left_expr, stack, context) do
{:ok, left, context} ->
case of_pattern(right_expr, stack, context) do
{:ok, {:list, right}, context} ->
{:ok, to_union([left, right], context), context}
{:ok, right, context} ->
{:ok, to_union([left, right], context), context}
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
# []
def of_pattern([], _stack, context) do
{:ok, {:list, :dynamic}, context}
end
# [expr, ...]
def of_pattern(exprs, stack, context) when is_list(exprs) do
stack = push_expr_stack(exprs, stack)
case map_reduce_ok(exprs, context, &of_pattern(&1, stack, &2)) do
{:ok, types, context} -> {:ok, {:list, to_union(types, context)}, context}
{:error, reason} -> {:error, reason}
end
end
# left ++ right
def of_pattern(
{{:., _meta1, [:erlang, :++]}, _meta2, [left_expr, right_expr]} = expr,
stack,
context
) do
stack = push_expr_stack(expr, stack)
case of_pattern(left_expr, stack, context) do
{:ok, {:list, left}, context} ->
case of_pattern(right_expr, stack, context) do
{:ok, {:list, right}, context} ->
{:ok, {:list, to_union([left, right], context)}, context}
{:ok, right, context} ->
{:ok, {:list, to_union([left, right], context)}, context}
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
# _
def of_pattern({:_, _meta, atom}, _stack, context) when is_atom(atom) do
{:ok, :dynamic, context}
end
# ^var
def of_pattern({:^, _meta, [var]}, stack, context) do
of_pattern(var, stack, context)
def of_pattern({:^, _meta, [var]}, _stack, context) do
{:ok, get_var!(var, context), context}
end
# var
@@ -139,21 +40,6 @@ defmodule Module.Types.Pattern do
{:ok, type, context}
end
# {left, right}
def of_pattern({left, right}, stack, context) do
of_pattern({:{}, [], [left, right]}, stack, context)
end
# {...}
def of_pattern({:{}, _meta, exprs} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
case map_reduce_ok(exprs, context, &of_pattern(&1, stack, &2)) do
{:ok, types, context} -> {:ok, {:tuple, length(types), types}, context}
{:error, reason} -> {:error, reason}
end
end
# left = right
def of_pattern({:=, _meta, [left_expr, right_expr]} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
@@ -163,23 +49,6 @@ defmodule Module.Types.Pattern do
do: unify(left_type, right_type, stack, context)
end
# %{...}
def of_pattern({:%{}, _meta, args} = expr, stack, context) do
stack = push_expr_stack(expr, stack)
Of.open_map(args, stack, context, &of_pattern/3)
end
# %Struct{...}
def of_pattern({:%, meta1, [module, {:%{}, _meta2, args}]} = expr, stack, context)
when is_atom(module) do
stack = push_expr_stack(expr, stack)
with {:ok, struct, context} <- Of.struct(module, meta1, context),
{:ok, map, context} <- Of.open_map(args, stack, context, &of_pattern/3) do
unify(map, struct, stack, context)
end
end
# %_{...}
def of_pattern(
{:%, _meta1, [{:_, _meta2, var_context}, {:%{}, _meta3, args}]} = expr,
@@ -194,22 +63,22 @@ defmodule Module.Types.Pattern do
end
end
# %^var{...}
def of_pattern({:%, meta1, [{:^, _meta2, [var]}, args]}, stack, context) do
of_pattern({:%, meta1, [var, args]}, stack, context)
end
# %var{...}
def of_pattern({:%, _meta1, [var, {:%{}, _meta2, args}]} = expr, stack, context) do
# %var{...} and %^var{...}
def of_pattern({:%, _meta1, [var, {:%{}, _meta2, args}]} = expr, stack, context)
when not is_atom(var) do
stack = push_expr_stack(expr, stack)
with {var_type, context} = new_var(var, context),
with {:ok, var_type, context} = of_pattern(var, stack, context),
{:ok, _, context} <- unify(var_type, :atom, stack, context),
{:ok, {:map, pairs}, context} <- Of.open_map(args, stack, context, &of_pattern/3) do
{:ok, {:map, [{:required, {:atom, :__struct__}, var_type} | pairs]}, context}
end
end
def of_pattern(expr, stack, context) do
of_shared(expr, stack, context, &of_pattern/3)
end
def unify_kinds(:required, _), do: :required
def unify_kinds(_, :required), do: :required
def unify_kinds(:optional, :optional), do: :optional
@@ -217,43 +86,45 @@ defmodule Module.Types.Pattern do
## GUARDS
# TODO: Some guards can be changed to intersection types or higher order types
@boolean {:union, [{:atom, true}, {:atom, false}]}
@number {:union, [:integer, :float]}
@guard_functions %{
{:is_atom, 1} => {[:atom], :boolean},
{:is_binary, 1} => {[:binary], :boolean},
{:is_bitstring, 1} => {[:binary], :boolean},
{:is_boolean, 1} => {[:boolean], :boolean},
{:is_float, 1} => {[:float], :boolean},
{:is_function, 1} => {[:fun], :boolean},
{:is_function, 2} => {[:fun, :integer], :boolean},
{:is_integer, 1} => {[:integer], :boolean},
{:is_list, 1} => {[{:list, :dynamic}], :boolean},
{:is_map, 1} => {[{:map, [{:optional, :dynamic, :dynamic}]}], :boolean},
{:is_atom, 1} => {[:atom], @boolean},
{:is_binary, 1} => {[:binary], @boolean},
{:is_bitstring, 1} => {[:binary], @boolean},
{:is_boolean, 1} => {[@boolean], @boolean},
{:is_float, 1} => {[:float], @boolean},
{:is_function, 1} => {[:fun], @boolean},
{:is_function, 2} => {[:fun, :integer], @boolean},
{:is_integer, 1} => {[:integer], @boolean},
{:is_list, 1} => {[{:list, :dynamic}], @boolean},
{:is_map, 1} => {[{:map, [{:optional, :dynamic, :dynamic}]}], @boolean},
{:is_map_key, 2} => {[:dynamic, {:map, [{:optional, :dynamic, :dynamic}]}], :dynamic},
{:is_number, 1} => {[:number], :boolean},
{:is_pid, 1} => {[:pid], :boolean},
{:is_port, 1} => {[:port], :boolean},
{:is_reference, 1} => {[:reference], :boolean},
{:is_tuple, 1} => {[:tuple], :boolean},
{:<, 2} => {[:dynamic, :dynamic], :boolean},
{:"=<", 2} => {[:dynamic, :dynamic], :boolean},
{:>, 2} => {[:dynamic, :dynamic], :boolean},
{:>=, 2} => {[:dynamic, :dynamic], :boolean},
{:"/=", 2} => {[:dynamic, :dynamic], :boolean},
{:"=/=", 2} => {[:dynamic, :dynamic], :boolean},
{:==, 2} => {[:dynamic, :dynamic], :boolean},
{:"=:=", 2} => {[:dynamic, :dynamic], :boolean},
{:*, 2} => {[:number, :number], :number},
{:+, 1} => {[:number], :number},
{:+, 2} => {[:number, :number], :number},
{:-, 1} => {[:number], :number},
{:-, 2} => {[:number, :number], :number},
{:/, 2} => {[:number, :number], :number},
{:abs, 1} => {[:number], :number},
{:ceil, 1} => {[:number], :integer},
{:floor, 1} => {[:number], :integer},
{:round, 1} => {[:number], :integer},
{:trunc, 1} => {[:number], :integer},
{:is_number, 1} => {[@number], @boolean},
{:is_pid, 1} => {[:pid], @boolean},
{:is_port, 1} => {[:port], @boolean},
{:is_reference, 1} => {[:reference], @boolean},
{:is_tuple, 1} => {[:tuple], @boolean},
{:<, 2} => {[:dynamic, :dynamic], @boolean},
{:"=<", 2} => {[:dynamic, :dynamic], @boolean},
{:>, 2} => {[:dynamic, :dynamic], @boolean},
{:>=, 2} => {[:dynamic, :dynamic], @boolean},
{:"/=", 2} => {[:dynamic, :dynamic], @boolean},
{:"=/=", 2} => {[:dynamic, :dynamic], @boolean},
{:==, 2} => {[:dynamic, :dynamic], @boolean},
{:"=:=", 2} => {[:dynamic, :dynamic], @boolean},
{:*, 2} => {[@number, @number], @number},
{:+, 1} => {[@number], @number},
{:+, 2} => {[@number, @number], @number},
{:-, 1} => {[@number], @number},
{:-, 2} => {[@number, @number], @number},
{:/, 2} => {[@number, @number], @number},
{:abs, 1} => {[@number], @number},
{:ceil, 1} => {[@number], :integer},
{:floor, 1} => {[@number], :integer},
{:round, 1} => {[@number], :integer},
{:trunc, 1} => {[@number], :integer},
{:element, 2} => {[:integer, :tuple], :dynamic},
{:hd, 1} => {[{:list, :dynamic}], :dynamic},
{:length, 1} => {[{:list, :dynamic}], :integer},
@@ -265,7 +136,7 @@ defmodule Module.Types.Pattern do
{:binary_part, 3} => {[:binary, :integer, :integer], :binary},
{:bit_size, 1} => {[:binary], :integer},
{:byte_size, 1} => {[:binary], :integer},
{:size, 1} => {[{:union, [:binary, :tuple]}], :boolean},
{:size, 1} => {[{:union, [:binary, :tuple]}], @boolean},
{:div, 2} => {[:integer, :integer], :integer},
{:rem, 2} => {[:integer, :integer], :integer},
{:node, 0} => {[], :atom},
@@ -276,15 +147,15 @@ defmodule Module.Types.Pattern do
{:bxor, 2} => {[:integer, :integer], :integer},
{:bsl, 2} => {[:integer, :integer], :integer},
{:bsr, 2} => {[:integer, :integer], :integer},
{:or, 2} => {[:boolean, :boolean], :boolean},
{:and, 2} => {[:boolean, :boolean], :boolean},
{:xor, 2} => {[:boolean, :boolean], :boolean},
{:not, 1} => {[:boolean], :boolean}
{:or, 2} => {[@boolean, @boolean], @boolean},
{:and, 2} => {[@boolean, @boolean], @boolean},
{:xor, 2} => {[@boolean, @boolean], @boolean},
{:not, 1} => {[@boolean], @boolean}
# Following guards are matched explicitly to handle
# type guard functions such as is_atom/1
# {:andalso, 2} => {[:boolean, :boolean], :boolean}
# {:orelse, 2} => {[:boolean, :boolean], :boolean}
# {:andalso, 2} => {[@boolean, @boolean], @boolean}
# {:orelse, 2} => {[@boolean, @boolean], @boolean}
}
@type_guards [
@@ -320,9 +191,9 @@ defmodule Module.Types.Pattern do
with {:ok, left_type, left_context} <- of_guard(left, stack, fresh_context),
{:ok, right_type, right_context} <- of_guard(right, stack, fresh_context),
{:ok, context} <- merge_context_and(context, stack, left_context, right_context),
{:ok, _, context} <- unify(left_type, :boolean, stack, context),
{:ok, _, context} <- unify(right_type, :boolean, stack, context),
do: {:ok, :boolean, context}
{:ok, _, context} <- unify(left_type, @boolean, stack, context),
{:ok, _, context} <- unify(right_type, @boolean, stack, context),
do: {:ok, @boolean, context}
end
def of_guard({{:., _, [:erlang, :orelse]}, _, [left, right]} = expr, stack, context) do
@@ -332,8 +203,8 @@ defmodule Module.Types.Pattern do
with {:ok, left_type, left_context} <- of_guard(left, stack, fresh_context),
{:ok, _right_type, right_context} <- of_guard(right, stack, fresh_context),
{:ok, context} <- merge_context_or(context, stack, left_context, right_context),
{:ok, _, context} <- unify(left_type, :boolean, stack, context),
do: {:ok, :boolean, context}
{:ok, _, context} <- unify(left_type, @boolean, stack, context),
do: {:ok, @boolean, context}
end
# The unary operators + and - are special cased to avoid common warnings until
@@ -404,10 +275,8 @@ defmodule Module.Types.Pattern do
{:ok, type, context}
end
# other literals
def of_guard(expr, stack, context) do
# Fall back to of_pattern/3 for literals
of_pattern(expr, stack, context)
of_shared(expr, stack, context, &of_guard/3)
end
defp fresh_context(context) do
@@ -553,4 +422,136 @@ defmodule Module.Types.Pattern do
defp type_guard?(name) do
name in @type_guards
end
## Shared
# :atom
defp of_shared(atom, _stack, context, _fun) when is_atom(atom) do
{:ok, {:atom, atom}, context}
end
# 12
defp of_shared(literal, _stack, context, _fun) when is_integer(literal) do
{:ok, :integer, context}
end
# 1.2
defp of_shared(literal, _stack, context, _fun) when is_float(literal) do
{:ok, :float, context}
end
# "..."
defp of_shared(literal, _stack, context, _fun) when is_binary(literal) do
{:ok, :binary, context}
end
# <<...>>>
defp of_shared({:<<>>, _meta, args}, stack, context, fun) do
case Of.binary(args, stack, context, fun) do
{:ok, context} -> {:ok, :binary, context}
{:error, reason} -> {:error, reason}
end
end
# left | []
defp of_shared({:|, _meta, [left_expr, []]} = expr, stack, context, fun) do
stack = push_expr_stack(expr, stack)
fun.(left_expr, stack, context)
end
# left | right
defp of_shared({:|, _meta, [left_expr, right_expr]} = expr, stack, context, fun) do
stack = push_expr_stack(expr, stack)
case fun.(left_expr, stack, context) do
{:ok, left, context} ->
case fun.(right_expr, stack, context) do
{:ok, {:list, right}, context} ->
{:ok, to_union([left, right], context), context}
{:ok, right, context} ->
{:ok, to_union([left, right], context), context}
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
# []
defp of_shared([], _stack, context, _fun) do
{:ok, {:list, :dynamic}, context}
end
# [expr, ...]
defp of_shared(exprs, stack, context, fun) when is_list(exprs) do
stack = push_expr_stack(exprs, stack)
case map_reduce_ok(exprs, context, &fun.(&1, stack, &2)) do
{:ok, types, context} -> {:ok, {:list, to_union(types, context)}, context}
{:error, reason} -> {:error, reason}
end
end
# left ++ right
defp of_shared(
{{:., _meta1, [:erlang, :++]}, _meta2, [left_expr, right_expr]} = expr,
stack,
context,
fun
) do
stack = push_expr_stack(expr, stack)
case fun.(left_expr, stack, context) do
{:ok, {:list, left}, context} ->
case fun.(right_expr, stack, context) do
{:ok, {:list, right}, context} ->
{:ok, {:list, to_union([left, right], context)}, context}
{:ok, right, context} ->
{:ok, {:list, to_union([left, right], context)}, context}
{:error, reason} ->
{:error, reason}
end
{:error, reason} ->
{:error, reason}
end
end
# {left, right}
defp of_shared({left, right}, stack, context, fun) do
of_shared({:{}, [], [left, right]}, stack, context, fun)
end
# {...}
defp of_shared({:{}, _meta, exprs} = expr, stack, context, fun) do
stack = push_expr_stack(expr, stack)
case map_reduce_ok(exprs, context, &fun.(&1, stack, &2)) do
{:ok, types, context} -> {:ok, {:tuple, length(types), types}, context}
{:error, reason} -> {:error, reason}
end
end
# %{...}
defp of_shared({:%{}, _meta, args} = expr, stack, context, fun) do
stack = push_expr_stack(expr, stack)
Of.open_map(args, stack, context, fun)
end
# %Struct{...}
defp of_shared({:%, meta1, [module, {:%{}, _meta2, args}]} = expr, stack, context, fun)
when is_atom(module) do
stack = push_expr_stack(expr, stack)
with {:ok, struct, context} <- Of.struct(module, meta1, context),
{:ok, map, context} <- Of.open_map(args, stack, context, fun) do
unify(map, struct, stack, context)
end
end
end
@@ -1,4 +1,4 @@
defmodule Module.Types.Infer do
defmodule Module.Types.Unify do
@moduledoc false
import Module.Types.Helpers
@@ -21,11 +21,6 @@ defmodule Module.Types.Infer do
# {:union, [type]}
# {:map, [{:required | :optional, key_type, value_type}]}
#
# TODO: Those types should be removed:
#
# :boolean
# :number
#
# Once new types are added, they should be considered in:
#
# * unify (all)
@@ -171,8 +166,11 @@ defmodule Module.Types.Infer do
end
case unify_result do
{:ok, var_type, context} ->
context = refine_var(var, var_type, stack, context)
{:ok, {:var, ^var}, context} ->
{:ok, {:var, var}, context}
{:ok, res_type, context} ->
context = refine_var(var, res_type, stack, context)
{:ok, {:var, var}, context}
{:error, reason} ->
@@ -306,6 +304,23 @@ defmodule Module.Types.Infer do
defp error(type, reason, context), do: {:error, {type, reason, context}}
@doc """
Push expression to stack.
The expression stack is used to give the context where a type variable
was refined when show a type conflict error.
"""
def push_expr_stack(expr, stack) do
%{stack | last_expr: expr}
end
@doc """
Gets a variable.
"""
def get_var!(var, context) do
Map.fetch!(context.vars, var_name(var))
end
@doc """
Adds a variable to the typing context and returns its type variable.
If the variable has already been added, return the existing type variable.
@@ -487,14 +502,33 @@ defmodule Module.Types.Infer do
def has_unbound_var?(_type, _context), do: false
@doc """
Returns true if it is a singleton type.
Only atoms are singleton types. Unbound vars are not
considered singleton types.
"""
def singleton?({:var, var}, context) do
case context.types do
%{^var => :unbound} -> false
%{^var => type} -> singleton?(type, context)
end
end
def singleton?({:atom, _}, _context), do: true
def singleton?(_type, _context), do: false
@doc """
Checks if the first argument is a subtype of the second argument.
This function assumes that:
* dynamic is not considered a subtype of all other types but the top type
* unbound variables are not subtype of anything
* dynamic is not considered a subtype of all other types but the top type.
This allows this function can be used for ordering, in other cases, you
may need to check for both sides
"""
def subtype?(type, type, _context), do: true
@@ -515,11 +549,6 @@ defmodule Module.Types.Infer do
def subtype?(_, :dynamic, _context), do: true
def subtype?({:atom, atom}, :atom, _context) when is_atom(atom), do: true
def subtype?({:atom, boolean}, :boolean, _context) when is_boolean(boolean), do: true
def subtype?(:boolean, :atom, _context), do: true
def subtype?(:float, :number, _context), do: true
def subtype?(:integer, :number, _context), do: true
# Composite
def subtype?({:tuple, _, _}, :tuple, _context), do: true
+4 -16
View File
@@ -573,27 +573,15 @@ defmodule Path do
@moduledoc false
def read_link_info(file) do
call({:read_link_info, file})
:file.read_link_info(file)
end
def list_dir(dir) do
case call({:list_dir, dir}) do
{:ok, files} ->
{:ok, for(file <- files, hd(file) != ?., do: file)}
other ->
other
case :file.list_dir(dir) do
{:ok, files} -> {:ok, for(file <- files, hd(file) != ?., do: file)}
other -> other
end
end
@compile {:inline, call: 1}
defp call(tuple) do
x = :erlang.dt_spread_tag(true)
y = :gen_server.call(:file_server_2, tuple)
:erlang.dt_restore_tag(x)
y
end
end
@doc """
+2 -2
View File
@@ -447,12 +447,12 @@ defmodule Record do
end
end
defp hoist_expressions(keyword, %{context: nil}) do
defp hoist_expressions(keyword, %{context: nil, module: module}) do
Enum.map_reduce(keyword, [], fn {key, expr}, acc ->
if simple_argument?(expr) do
{{key, expr}, acc}
else
var = Macro.var(key, __MODULE__)
var = Macro.unique_var(key, module)
{{key, var}, [{:=, [], [var, expr]} | acc]}
end
end)
+1 -1
View File
@@ -280,7 +280,7 @@ defmodule Regex do
Returns `true` if the given `term` is a regex.
Otherwise returns `false`.
"""
# TODO: Remove this on Elixir v1.15
# TODO: deprecate permanently on Elixir v1.15
@doc deprecated: "Use Kernel.is_struct/2 or pattern match on %Regex{} instead"
def regex?(term)
def regex?(%Regex{}), do: true
+9 -1
View File
@@ -600,9 +600,17 @@ defmodule System do
"""
# TODO: Once Erlang/OTP 23 is required, remove conditional, and update @doc accordingly.
# The warning is emitted by the compiler - so a @doc annotation is enough
# Note Elixir may be compiled in an earlier Erlang version but runs on a
# newer one, so we need the check at compilation time and runtime.
@doc deprecated: "Use __STACKTRACE__ instead"
if function_exported?(:erlang, :get_stacktrace, 0) do
def stacktrace, do: apply(:erlang, :get_stacktrace, [])
def stacktrace do
if function_exported?(:erlang, :get_stacktrace, 0) do
apply(:erlang, :get_stacktrace, [])
else
[]
end
end
else
def stacktrace, do: []
end
+32
View File
@@ -491,6 +491,38 @@ defmodule Task do
stream = Task.async_stream(collection, Mod, :expensive_fun, [], ordered: false)
Stream.run(stream)
## Attention: async + take
Given items in an async stream are processed concurrently, doing
`async_stream` followed by `Enum.take/2` may cause more items than
requested to be processed. Let's see an example:
1..100
|> Task.async_stream(fn i ->
Process.sleep(100)
IO.puts(to_string(i))
end)
|> Enum.take(10)
For a machine with 8 cores, the above will process 16 items instead
of 10. The reason is that `async_stream/5` always have 8 elements
processing at once. So by the time `Enum` says it got all elements
it needed, there are still 6 elements left to be processed.
The solution here is to use `Stream.take/2` instead of `Enum.take/2`
to filter elements before-hand:
1..100
|> Stream.take(10)
|> Task.async_stream(fn i ->
Process.sleep(100)
IO.puts(to_string(i))
end)
|> Enum.to_list()
If for some reason you cannot take the elements before hand,
you can use `:max_concurrency` to limit how many elements
may be over processed at the cost of reducing concurrency.
"""
@doc since: "1.4.0"
@spec async_stream(Enumerable.t(), module, atom, [term], keyword) :: Enumerable.t()
@@ -174,4 +174,4 @@ Version | Deprecated feature | Replaced by (ava
[v1.8]: https://github.com/elixir-lang/elixir/blob/v1.8/CHANGELOG.md#4-hard-deprecations
[v1.9]: https://github.com/elixir-lang/elixir/blob/v1.9/CHANGELOG.md#4-hard-deprecations
[v1.10]: https://github.com/elixir-lang/elixir/blob/v1.10/CHANGELOG.md#4-hard-deprecations
[v1.11]: https://github.com/elixir-lang/elixir/blob/master/CHANGELOG.md#4-hard-deprecations
[v1.11]: https://github.com/elixir-lang/elixir/blob/v1.11/CHANGELOG.md#4-hard-deprecations
+9 -2
View File
@@ -127,11 +127,18 @@ Conveniently, Elixir allows developers to hide modules and functions from the do
end
end
However, keep in mind that adding `@doc false` does not make the function private. The function above can still be invoked as `MyApp.Sample.add(1, 2)`. Not only that, if `MyApp.Sample` is imported, the `add/2` function will also be imported into the caller. For those reasons, be cautious when adding `@doc false` to functions, instead use one of these two options:
In case you don't want to hide a whole module, you can hide functions individually:
defmodule MyApp.Sample do
@doc false
def add(a, b), do: a + b
end
However, keep in mind `@moduledoc false` or `@doc false` do not make a function private. The function above can still be invoked as `MyApp.Sample.add(1, 2)`. Not only that, if `MyApp.Sample` is imported, the `add/2` function will also be imported into the caller. For those reasons, be cautious when adding `@doc false` to functions, instead use one of these two options:
* Move the undocumented function to a module with `@moduledoc false`, like `MyApp.Hidden`, ensuring the function won't be accidentally exposed or imported. Remember you can use `@moduledoc false` to hide a whole module and still document each function with `@doc`. Tools will still ignore the module.
* Start the function name with one or two underscores, for example, `__add__/2`, and add `@doc false`. The compiler does not import functions with leading underscores and they hint to anyone reading the code of their intended private usage.
* Start the function name with one or two underscores, for example, `__add__/2`. Functions starting with underscore are autoamtically treated as hidden, although you can also be explicit and add `@doc false`. The compiler does not import functions with leading underscores and they hint to anyone reading the code of their intended private usage.
## Code.fetch_docs/1
+8 -6
View File
@@ -204,21 +204,23 @@ env_for_eval(Env, Opts) ->
false -> nil
end,
Tracers = case lists:keyfind(tracers, 1, Opts) of
TempTracers = case lists:keyfind(tracers, 1, Opts) of
{tracers, TracersOpt} when is_list(TracersOpt) -> TracersOpt;
false -> []
end,
LexicalTracker = case lists:keyfind(lexical_tracker, 1, Opts) of
%% If there is a dead PID or lexical tracker is nil,
%% we assume the tracers also cannot be (re)used.
{LexicalTracker, Tracers} = case lists:keyfind(lexical_tracker, 1, Opts) of
{lexical_tracker, Pid} when is_pid(Pid) ->
case is_process_alive(Pid) of
true -> Pid;
false -> nil
true -> {Pid, TempTracers};
false -> {nil, []}
end;
{lexical_tracker, nil} ->
nil;
{nil, []};
false ->
nil
{nil, TempTracers}
end,
FA = case lists:keyfind(function, 1, Opts) of
+1 -1
View File
@@ -166,8 +166,8 @@ bootstrap_files() ->
<<"lib/elixir/lib/module/locals_tracker.ex">>,
<<"lib/elixir/lib/module/parallel_checker.ex">>,
<<"lib/elixir/lib/module/types/helpers.ex">>,
<<"lib/elixir/lib/module/types/unify.ex">>,
<<"lib/elixir/lib/module/types/of.ex">>,
<<"lib/elixir/lib/module/types/infer.ex">>,
<<"lib/elixir/lib/module/types/pattern.ex">>,
<<"lib/elixir/lib/module/types/expr.ex">>,
<<"lib/elixir/lib/module/types.ex">>,
+8 -2
View File
@@ -469,7 +469,7 @@ expand(Other, E) ->
escape_env_entries(Meta, #{current_vars := {Read, Write}, unused_vars := {Unused, Version}} = Env0) ->
Env1 = case Env0 of
#{function := nil} -> Env0;
_ -> Env0#{lexical_tracker := nil}
_ -> Env0#{lexical_tracker := nil, tracers := []}
end,
Current = {maybe_escape_map(Read), maybe_escape_map(Write)},
Env2 = Env1#{current_vars := Current, unused_vars := {maybe_escape_map(Unused), Version}},
@@ -631,7 +631,13 @@ maybe_warn_underscored_var_access(Meta, Name, Kind, E) ->
maybe_warn_deprecated_super_in_gen_server_callback(Meta, Function, SuperMeta, E) ->
case lists:keyfind(context, 1, SuperMeta) of
{context, 'Elixir.GenServer'} ->
elixir_errors:form_warn(Meta, E, ?MODULE, {super_in_genserver, Function});
case Function of
{child_spec, 1} ->
ok;
_ ->
elixir_errors:form_warn(Meta, E, ?MODULE, {super_in_genserver, Function})
end;
_ ->
ok
+1 -1
View File
@@ -139,9 +139,9 @@ compile(Line, Module, Block, Vars, E) ->
},
Binary = elixir_erl:compile(ModuleMap),
warn_unused_attributes(File, DataSet, DataBag, PersistedAttributes),
autoload_module(Module, Binary, CompileOpts, NE),
eval_callbacks(Line, DataBag, after_compile, [NE, Binary], NE),
warn_unused_attributes(File, DataSet, DataBag, PersistedAttributes),
make_module_available(Module, Binary, ModuleMap),
{module, Module, Binary, Result}
catch
+4
View File
@@ -327,6 +327,10 @@ guard_rewrite(Receiver, DotMeta, Right, Meta, Args) ->
_ -> {error, {invalid_guard, Receiver, Right, length(Args)}}
end.
%% erlang:is_record/2-3 are compiler guards in Erlang which we
%% need to explicitly forbid as they are allowed in erl_internal.
allowed_guard(is_record, 2) -> false;
allowed_guard(is_record, 3) -> false;
allowed_guard(Right, Arity) ->
erl_internal:guard_bif(Right, Arity) orelse elixir_utils:guard_op(Right, Arity).
+8 -8
View File
@@ -477,8 +477,8 @@ tokenize([$:, H | T] = Original, Line, Column, Scope, Tokens) when ?is_quote(H)
tokenize([$: | String] = Original, Line, Column, Scope, Tokens) ->
case tokenize_identifier(String, Line, Column, Scope, false) of
{_Kind, Atom, Rest, Length, _Ascii, _Special} ->
NewScope = maybe_warn_for_ambiguous_bang_before_equals(atom, Atom, Rest, Scope, Line),
{_Kind, Unencoded, Atom, Rest, Length, _Ascii, _Special} ->
NewScope = maybe_warn_for_ambiguous_bang_before_equals(atom, Unencoded, Rest, Scope, Line),
Token = {atom, {Line, Column, nil}, Atom},
tokenize(Rest, Line, Column + 1 + Length, NewScope, [Token | Tokens]);
empty ->
@@ -559,7 +559,7 @@ tokenize([$. | T], Line, Column, Scope, Tokens) ->
tokenize(String, Line, Column, Scope, Tokens) ->
case tokenize_identifier(String, Line, Column, Scope, not previous_was_dot(Tokens)) of
{Kind, Atom, Rest, Length, Ascii, Special} ->
{Kind, Unencoded, Atom, Rest, Length, Ascii, Special} ->
HasAt = lists:member($@, Special),
case Rest of
@@ -583,7 +583,7 @@ tokenize(String, Line, Column, Scope, Tokens) ->
tokenize_alias(Rest, Line, Column, Atom, Length, Ascii, Special, Scope, Tokens);
_ when Kind == identifier ->
NewScope = maybe_warn_for_ambiguous_bang_before_equals(identifier, Atom, Rest, Scope, Line),
NewScope = maybe_warn_for_ambiguous_bang_before_equals(identifier, Unencoded, Rest, Scope, Line),
Token = check_call_identifier(Line, Column, Atom, Rest),
tokenize(Rest, Line, Column + Length, NewScope, [Token | Tokens]);
@@ -1142,7 +1142,7 @@ tokenize_identifier(String, Line, Column, Scope, MaybeKeyword) ->
{keyword, Atom, Type} ->
{keyword, Atom, Type, Rest, Length};
{ok, Atom} ->
{Kind, Atom, Rest, Length, Ascii, Special};
{Kind, Acc, Atom, Rest, Length, Ascii, Special};
{error, _Reason} = Error ->
Error
end;
@@ -1462,11 +1462,11 @@ maybe_warn_too_many_of_same_char(_Token, _Rest, _Line, Scope) ->
Scope.
%% TODO: Turn into an error on v2.0
maybe_warn_for_ambiguous_bang_before_equals(Kind, Atom, [$= | _], Scope, Line) ->
maybe_warn_for_ambiguous_bang_before_equals(Kind, Unencoded, [$= | _], Scope, Line) ->
{What, Identifier} =
case Kind of
atom -> {"atom", [$: | atom_to_list(Atom)]};
identifier -> {"identifier", atom_to_list(Atom)}
atom -> {"atom", [$: | Unencoded]};
identifier -> {"identifier", Unencoded}
end,
case lists:last(Identifier) of
+1 -1
View File
@@ -27,9 +27,9 @@ guard_op('orelse', 2) ->
guard_op(Op, Arity) ->
try erl_internal:op_type(Op, Arity) of
arith -> true;
list -> true;
comp -> true;
bool -> true;
list -> false;
send -> false
catch
_:_ -> false
+21 -3
View File
@@ -85,11 +85,21 @@ defmodule CodeTest do
assert CodeTest.Sample.eval_quoted_info() == {CodeTest.Sample, "sample.ex", 13}
end
test "eval_quoted/2 with a %Macro.Env{} struct as the second argument" do
test "eval_quoted/2 with %Macro.Env{} at runtime" do
alias :lists, as: MyList
quoted = quote(do: MyList.flatten([[1, 2, 3]]))
assert Code.eval_quoted(quote(do: MyList.flatten([[1, 2, 3]])), [], __ENV__) ==
{[1, 2, 3], []}
assert Code.eval_quoted(quoted, [], __ENV__) == {[1, 2, 3], []}
# Let's check it discards tracers since the lexical tracker is explicitly nil
assert Code.eval_quoted(quoted, [], %{__ENV__ | tracers: [:bad]}) == {[1, 2, 3], []}
end
test "eval_quoted/2 with %Macro.Env{} at compile time" do
defmodule CompileTimeEnv do
alias String.Chars
{"foo", []} = Code.eval_string("Chars.to_string(:foo)", [], __ENV__)
end
end
test "eval_file/1" do
@@ -214,6 +224,14 @@ defmodule CodeTest do
Code.string_to_quoted("there_is_no_such_var", static_atoms_encoder: encoder)
end
test "static_atoms_encoder ambiguities" do
encoder = fn string, _meta -> {:ok, {:atom, string}} end
# We check a=1 for precedence issues with a!=1, make sure it works
assert Code.string_to_quoted!("a = 1", static_atoms_encoder: encoder)
assert Code.string_to_quoted!("a=1", static_atoms_encoder: encoder)
end
test "static_atoms_encoder does not encode keywords" do
encoder = fn atom, _meta -> raise "shouldn't be invoked for #{atom}" end
+11
View File
@@ -116,6 +116,17 @@ defmodule EnumTest do
[[0], [1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
assert Enum.chunk_while([5, 7, 9, 11], [], chunk_fun, after_fun) == [[5, 7, 9]]
assert Enum.chunk_while([1, 2, 3, 5, 7], [], chunk_fun, after_fun) == [[1, 2], [3, 5, 7]]
chunk_fn2 = fn
-1, acc -> {:cont, acc, 0}
i, acc -> {:cont, acc + i}
end
after_fn2 = fn acc -> {:cont, acc, 0} end
assert Enum.chunk_while([1, -1, 2, 3, -1, 4, 5, 6], 0, chunk_fn2, after_fn2) == [1, 5, 15]
end
test "concat/1" do
@@ -192,6 +192,11 @@ defmodule IO.ANSI.DocsTest do
assert result == " • one\n • two\n • three\n\e[0m"
end
test "* list is converted without ansi" do
result = format_markdown("* one\n* two\n* three\n", enabled: false)
assert result == " * one\n * two\n * three"
end
test "* list surrounded by text is converted" do
result = format_markdown("Count:\n\n* one\n* two\n* three\n\nDone")
assert result == "Count:\n\e[0m\n • one\n • two\n • three\n\e[0m\nDone\n\e[0m"
@@ -727,6 +732,8 @@ defmodule IO.ANSI.DocsTest do
end
test "typespecs" do
assert format_erlang([{:ul, [class: "types"], [{:li, [], []}]}]) == ""
assert format_erlang([{:ul, [class: "types"], [{:li, [], ["Hello"]}, {:li, [], ["World"]}]}]) ==
"""
Typespecs:
@@ -735,6 +742,17 @@ defmodule IO.ANSI.DocsTest do
World
"""
assert format_erlang([
{:ul, [class: "types"], [{:li, [], ["Hello", {:code, [], ["World"]}]}]}
]) ==
"""
Typespecs:
Hello
\e[36mWorld\e[0m
"""
end
test "extra markup" do
@@ -240,6 +240,19 @@ defmodule Kernel.ComprehensionTest do
assert acc == %{1 => 'olleh', 3 => 'olleh'}
end
test "for comprehensions with matched reduce" do
acc =
for entry <- [1, 2, 3], reduce: {:ok, nil} do
{:ok, _} ->
{:ok, entry}
{:error, _} = error ->
error
end
assert acc == {:ok, 3}
end
## List generators (inlined by the compiler)
test "list for comprehensions" do
+2 -12
View File
@@ -692,16 +692,6 @@ defmodule Kernel.ErrorsTest do
end
end
test "match attribute in module" do
msg = "invalid write attribute syntax, you probably meant to use: @foo expression"
assert_raise ArgumentError, msg, fn ->
defmodule MatchAttributeInModule do
@foo = 42
end
end
end
test "invalid case clauses" do
assert_eval_raise CompileError,
"nofile:1: expected one argument for :do clauses (->) in \"case\"",
@@ -828,7 +818,7 @@ defmodule Kernel.ErrorsTest do
"nofile:3: function exit/1 imported from both :erlang and Kernel, call is ambiguous",
'''
defmodule Kernel.ErrorsTest.FunctionImportConflict do
import :erlang, warn: false
import :erlang, only: [exit: 1], warn: false
def foo, do: exit(:test)
end
'''
@@ -1202,7 +1192,7 @@ defmodule Kernel.ErrorsTest do
rescue
ArgumentError ->
assert [
{:erlang, :apply, [1, :foo, []], []},
{:erlang, :apply, [1, :foo, []], _},
{__MODULE__, :bad_remote_call, 1, [file: _, line: _]} | _
] = __STACKTRACE__
end
@@ -294,6 +294,7 @@ defmodule Kernel.ExpansionTest do
test "__ENV__" do
env = %{__ENV__ | line: 0}
assert expand_env(quote(do: __ENV__), env) == {Macro.escape(env), env}
assert %{lexical_tracker: nil, tracers: []} = __ENV__
end
test "__ENV__.accessor" do
@@ -302,6 +303,9 @@ defmodule Kernel.ExpansionTest do
assert expand_env(quote(do: __ENV__.unknown), env) ==
{quote(do: unquote(Macro.escape(env)).unknown), env}
assert __ENV__.lexical_tracker == nil
assert __ENV__.tracers == []
end
end
@@ -310,6 +310,38 @@ defmodule Kernel.GuardTest do
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.is_record/2 inside guards",
fn ->
defmodule IsRecord2Usage do
defguard foo(rec) when :erlang.is_record(rec, :tag)
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.is_record/3 inside guards",
fn ->
defmodule IsRecord3Usage do
defguard foo(rec) when :erlang.is_record(rec, :tag, 7)
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.\+\+/2 inside guards",
fn ->
defmodule ListSubtractionUsage do
defguard foo(list) when list ++ []
end
end
assert_raise CompileError,
~r"cannot invoke remote function :erlang\.\-\-/2 inside guards",
fn ->
defmodule ListSubtractionUsage do
defguard foo(list) when list -- []
end
end
assert_raise CompileError, ~r"invalid expression in guard", fn ->
defmodule LocalCallUsage do
defguard foo(local, call) when local.(call)
+23 -9
View File
@@ -933,24 +933,22 @@ defmodule Kernel.WarningTest do
test "eval failure warning" do
assert capture_err(fn ->
assert_raise ArgumentError, fn ->
Code.eval_string("""
defmodule Sample do
Atom.to_string "abc"
end
""")
Code.eval_string("""
defmodule Sample1 do
def foo, do: Atom.to_string "abc"
end
""")
end) =~ ~r"this expression will fail with ArgumentError\n.*nofile:2"
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
defmodule Sample2 do
def foo, do: 1 + nil
end
""")
end) =~ ~r"this expression will fail with ArithmeticError\n.*nofile:2"
after
purge([Sample])
purge([Sample1, Sample2])
end
test "undefined function for behaviour" do
@@ -1709,7 +1707,7 @@ defmodule Kernel.WarningTest do
end
end
test "deprecated GenServer super" do
test "deprecated GenServer super on callbacks" do
assert capture_err(fn ->
Code.eval_string("""
defmodule Sample do
@@ -1725,6 +1723,22 @@ defmodule Kernel.WarningTest do
purge(Sample)
end
test "super is allowed on GenServer.child_spec/1" do
refute capture_err(fn ->
Code.eval_string("""
defmodule Sample do
use GenServer
def child_spec(opts) do
super(opts)
end
end
""")
end) =~ "calling super for GenServer callback child_spec/1 is deprecated"
after
purge(Sample)
end
test "nested comparison operators" do
message =
capture_err(fn ->
+34 -10
View File
@@ -592,16 +592,6 @@ defmodule KernelTest do
_ = a
end
test "setting attribute with uppercase" do
message = "module attributes set via @ cannot start with an uppercase letter"
assert_raise ArgumentError, message, fn ->
defmodule UpcaseAttrSample do
@Upper
end
end
end
test "in module body" do
defmodule InSample do
@foo [:a, :b]
@@ -782,6 +772,40 @@ defmodule KernelTest do
end
end
describe "@" do
test "setting attribute with do-block" do
exception =
catch_error(
defmodule UpcaseAttrSample do
@foo quote do
:ok
end
end
)
assert exception.message =~ "expected 0 or 1 argument for @foo, got 2"
assert exception.message =~ "You probably want to wrap the argument value in parentheses"
end
test "setting attribute with uppercase" do
message = "module attributes set via @ cannot start with an uppercase letter"
assert_raise ArgumentError, message, fn ->
defmodule UpcaseAttrSample do
@Upper
end
end
end
test "matching attribute" do
assert_raise ArgumentError, ~r"invalid write attribute syntax", fn ->
defmodule MatchAttributeInModule do
@foo = 42
end
end
end
end
describe "defdelegate" do
defdelegate my_flatten(list), to: List, as: :flatten
@@ -250,27 +250,43 @@ defmodule Module.Types.ExprTest do
) == {:ok, {:var, 0}}
end
test "for" do
assert quoted_expr(
[list],
for(
foo <- list,
is_integer(foo),
do: foo == 123
)
) == {:ok, :dynamic}
assert quoted_expr(
[list, bar],
(
describe "for comprehension" do
test "with generators and filters" do
assert quoted_expr(
[list],
for(
foo <- list,
is_integer(bar),
is_integer(foo),
do: foo == 123
)
) == {:ok, :dynamic}
end
bar
)
) == {:ok, {:var, 0}}
test "with unused return" do
assert quoted_expr(
[list, bar],
(
for(
foo <- list,
is_integer(bar),
do: foo == 123
)
bar
)
) == {:ok, {:var, 0}}
end
test "with reduce" do
assert quoted_expr(
[],
for(i <- [1, 2, 3], do: (acc -> i + acc), reduce: 0)
) == {:ok, :dynamic}
assert quoted_expr(
[],
for(i <- [1, 2, 3], do: (_ -> i), reduce: nil)
) == {:ok, :dynamic}
end
end
end
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.IntegrationTest do
use ExUnit.Case
@@ -297,7 +297,7 @@ defmodule Module.Types.MapTest do
end
end
test "with bound var keys" do
test "map creation with bound var keys" do
assert quoted_expr(
[atom, bool, true = var],
[is_atom(atom) and is_boolean(bool)],
@@ -307,7 +307,8 @@ defmodule Module.Types.MapTest do
{:map,
[
{:required, {:atom, true}, {:atom, true}},
{:required, :boolean, {:union, [{:atom, :bool}, {:atom, true}]}},
{:required, {:union, [atom: true, atom: false]},
{:union, [{:atom, :bool}, {:atom, true}]}},
{:required, :atom, {:union, [{:atom, :atom}, {:atom, :bool}, {:atom, true}]}}
]}}
@@ -320,7 +321,8 @@ defmodule Module.Types.MapTest do
{:map,
[
{:required, {:atom, true}, {:atom, true}},
{:required, :boolean, {:union, [{:atom, :bool}, {:atom, true}]}},
{:required, {:union, [atom: true, atom: false]},
{:union, [{:atom, :bool}, {:atom, true}]}},
{:required, :atom, {:union, [{:atom, :atom}, {:atom, :bool}, {:atom, true}]}}
]}}
@@ -333,12 +335,13 @@ defmodule Module.Types.MapTest do
{:map,
[
{:required, {:atom, true}, {:atom, true}},
{:required, :boolean, {:union, [{:atom, :bool}, {:atom, true}]}},
{:required, {:union, [atom: true, atom: false]},
{:union, [{:atom, :bool}, {:atom, true}]}},
{:required, :atom, {:union, [{:atom, :atom}, {:atom, :bool}, {:atom, true}]}}
]}}
end
test "with unbound var keys" do
test "map creation with unbound var keys" do
assert quoted_expr(
[var, struct],
(
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.PatternTest do
use ExUnit.Case, async: true
@@ -118,13 +118,18 @@ defmodule Module.Types.PatternTest do
{:map, [{:required, {:atom, :a}, {:atom, :b}}, {:optional, :dynamic, :dynamic}]}}
assert quoted_pattern(%{123 => a}) ==
{:ok, {:map, [{:required, :integer, {:var, 0}}, {:optional, :dynamic, :dynamic}]}}
{:ok,
{:map,
[
{:required, :integer, {:union, [{:var, 0}, :dynamic]}},
{:optional, :dynamic, :dynamic}
]}}
assert quoted_pattern(%{123 => :foo, 456 => :bar}) ==
{:ok,
{:map,
[
{:required, :integer, {:union, [{:atom, :foo}, {:atom, :bar}]}},
{:required, :integer, :dynamic},
{:optional, :dynamic, :dynamic}
]}}
@@ -285,10 +290,10 @@ defmodule Module.Types.PatternTest do
{:ok, [{:union, [:tuple, :atom]}]}
assert quoted_head([x], [is_boolean(x) and is_atom(x)]) ==
{:ok, [:boolean]}
{:ok, [{:union, [atom: true, atom: false]}]}
assert quoted_head([x], [is_atom(x) and is_boolean(x)]) ==
{:ok, [:boolean]}
{:ok, [{:union, [atom: true, atom: false]}]}
assert quoted_head([x], [is_atom(x) > :foo]) == {:ok, [var: 0]}
@@ -307,7 +312,7 @@ defmodule Module.Types.PatternTest do
assert {:error, {:unable_unify, {:tuple, :atom, _}}} =
quoted_head([x], [is_tuple(x) and is_atom(x)])
assert {:error, {:unable_unify, {:boolean, :tuple, _}}} =
assert {:error, {:unable_unify, {{:union, [atom: true, atom: false]}, :tuple, _}}} =
quoted_head([x], [is_tuple(is_atom(x))])
end
@@ -322,13 +327,16 @@ defmodule Module.Types.PatternTest do
assert {:error, {:unable_unify, {{:atom, :foo}, {:list, :dynamic}, _}}} =
quoted_head([x], [length(:foo)])
assert {:error, {:unable_unify, {:boolean, {:list, :dynamic}, _}}} =
assert {:error,
{:unable_unify, {{:union, [atom: true, atom: false]}, {:list, :dynamic}, _}}} =
quoted_head([x], [length(is_tuple(x))])
assert {:error, {:unable_unify, {:boolean, :tuple, _}}} =
assert {:error, {:unable_unify, {{:union, [atom: true, atom: false]}, :tuple, _}}} =
quoted_head([x], [elem(is_tuple(x), 0)])
assert {:error, {:unable_unify, {:boolean, :number, _}}} =
assert {:error,
{:unable_unify,
{{:union, [atom: true, atom: false]}, {:union, [:integer, :float]}, _}}} =
quoted_head([x], [elem({}, is_tuple(x))])
assert quoted_head([x], [elem({}, 1)]) == {:ok, [var: 0]}
@@ -371,7 +379,11 @@ defmodule Module.Types.PatternTest do
assert quoted_head([%{true: bool}], [is_boolean(bool)]) ==
{:ok,
[
{:map, [{:required, {:atom, true}, :boolean}, {:optional, :dynamic, :dynamic}]}
{:map,
[
{:required, {:atom, true}, {:union, [atom: true, atom: false]}},
{:optional, :dynamic, :dynamic}
]}
]}
assert quoted_head([%{true: true} = foo, %{false: false} = foo]) ==
@@ -1,4 +1,4 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.TypesTest do
use ExUnit.Case, async: true
@@ -13,20 +13,21 @@ defmodule Module.Types.TypesTest do
expr = TypeHelper.expand_expr(patterns, guards, body, __CALLER__)
quote do
unquote(Macro.escape(expr))
|> Module.Types.TypesTest.__expr__()
|> to_warning()
Module.Types.TypesTest.__expr__(unquote(Macro.escape(expr)))
end
end
def __expr__({patterns, guards, body}) do
with {:ok, _types, context} <-
Pattern.of_head(patterns, guards, TypeHelper.new_stack(), TypeHelper.new_context()),
{:ok, type, context} <- Expr.of_expr(body, TypeHelper.new_stack(), context) do
flunk("expexted error, got: #{inspect(Types.lift_type(type, context))}")
{:ok, _type, context} <- Expr.of_expr(body, TypeHelper.new_stack(), context) do
case context.warnings do
[warning] -> to_message(:warning, warning)
_ -> :none
end
else
{:error, {type, reason, context}} ->
{:error, {type, reason, context}}
to_message(:error, {type, reason, context})
end
end
@@ -48,12 +49,18 @@ defmodule Module.Types.TypesTest do
min
end
defp to_warning({:error, {type, reason, context}}) do
defp to_message(:warning, {module, warning, _location}) do
warning
|> module.format_warning()
|> IO.iodata_to_binary()
end
defp to_message(:error, {type, reason, context}) do
{Module.Types, error, _location} = Module.Types.error_to_warning(type, reason, context)
error
|> Module.Types.format_warning()
|> List.to_string()
|> IO.iodata_to_binary()
|> String.trim_trailing("\nConflict found at")
end
@@ -69,6 +76,17 @@ defmodule Module.Types.TypesTest do
assert Types.expr_to_string(quote(do: :erlang.element(:erlang.+(a, 1), b))) == "elem(b, a)"
end
test "undefined function warnings" do
assert warning([], URI.unknown("foo")) ==
"URI.unknown/1 is undefined or private"
assert warning([], if(true, do: URI.unknown("foo"))) ==
"URI.unknown/1 is undefined or private"
assert warning([], try(do: :ok, after: URI.unknown("foo"))) ==
"URI.unknown/1 is undefined or private"
end
describe "function head warnings" do
test "warns on literals" do
string = warning([var = 123, var = "abc"], var)
@@ -452,4 +470,20 @@ defmodule Module.Types.TypesTest do
"""
end
end
describe "regressions" do
test "recursive map fields" do
assert warning(
[queried],
with(
true <- is_nil(queried.foo.bar),
_ = queried.foo
) do
%{foo: %{other_id: _other_id} = foo} = queried
%{other_id: id} = foo
%{id: id}
end
) == :none
end
end
end
@@ -1,8 +1,8 @@
Code.require_file("../../test_helper.exs", __DIR__)
Code.require_file("type_helper.exs", __DIR__)
defmodule Module.Types.InferTest do
defmodule Module.Types.UnifyTest do
use ExUnit.Case, async: true
import Module.Types.Infer
import Module.Types.Unify
alias Module.Types
defp unify_lift(left, right, context \\ new_context()) do
@@ -53,27 +53,17 @@ defmodule Module.Types.InferTest do
assert unify_lift(:integer, :integer) == {:ok, :integer}
assert unify_lift(:binary, :binary) == {:ok, :binary}
assert unify_lift(:atom, :atom) == {:ok, :atom}
assert unify_lift(:boolean, :boolean) == {:ok, :boolean}
assert {:error, {:unable_unify, {:integer, :boolean, _}}} = unify_lift(:integer, :boolean)
assert {:error, {:unable_unify, {:integer, :atom, _}}} = unify_lift(:integer, :atom)
end
test "subtype undirected" do
assert unify_lift(:boolean, :atom) == {:ok, :boolean}
assert unify_lift(:atom, :boolean) == {:ok, :boolean}
assert unify_lift(:boolean, {:atom, true}) == {:ok, {:atom, true}}
assert unify_lift({:atom, true}, :boolean) == {:ok, {:atom, true}}
assert unify_lift(:atom, {:atom, true}) == {:ok, {:atom, true}}
assert unify_lift({:atom, true}, :atom) == {:ok, {:atom, true}}
end
test "subtype directed" do
assert unify_directed_lift(:boolean, :atom) == {:ok, :boolean}
assert unify_directed_lift({:atom, true}, :boolean) == {:ok, {:atom, true}}
assert unify_directed_lift({:atom, true}, :atom) == {:ok, {:atom, true}}
assert {:error, _} = unify_directed_lift(:atom, :boolean)
assert {:error, _} = unify_directed_lift(:boolean, {:atom, true})
assert {:error, _} = unify_directed_lift(:atom, {:atom, true})
end
@@ -83,8 +73,8 @@ defmodule Module.Types.InferTest do
assert unify_lift({:tuple, 1, [:integer]}, {:tuple, 1, [:integer]}) ==
{:ok, {:tuple, 1, [:integer]}}
assert unify_lift({:tuple, 1, [:boolean]}, {:tuple, 1, [:atom]}) ==
{:ok, {:tuple, 1, [:boolean]}}
assert unify_lift({:tuple, 1, [{:atom, :foo}]}, {:tuple, 1, [:atom]}) ==
{:ok, {:tuple, 1, [{:atom, :foo}]}}
assert {:error, {:unable_unify, {{:tuple, 1, [:integer]}, {:tuple, 0, []}, _}}} =
unify_lift({:tuple, 1, [:integer]}, {:tuple, 0, []})
@@ -134,10 +124,10 @@ defmodule Module.Types.InferTest do
{:ok, {:map, [{:required, :integer, :atom}]}}
assert unify_lift(
{:map, [{:required, {:atom, :foo}, :boolean}]},
{:map, [{:required, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:required, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:required, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:required, {:atom, :foo}, {:atom, :bar}}]}}
assert {:error,
{:unable_unify,
@@ -166,28 +156,28 @@ defmodule Module.Types.InferTest do
test "map required/optional key" do
assert unify_lift(
{:map, [{:required, {:atom, :foo}, :boolean}]},
{:map, [{:required, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:required, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:required, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:required, {:atom, :foo}, {:atom, :bar}}]}}
assert unify_lift(
{:map, [{:optional, {:atom, :foo}, :boolean}]},
{:map, [{:optional, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:required, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:required, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:required, {:atom, :foo}, {:atom, :bar}}]}}
assert unify_lift(
{:map, [{:required, {:atom, :foo}, :boolean}]},
{:map, [{:required, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:optional, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:required, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:required, {:atom, :foo}, {:atom, :bar}}]}}
assert unify_lift(
{:map, [{:optional, {:atom, :foo}, :boolean}]},
{:map, [{:optional, {:atom, :foo}, {:atom, :bar}}]},
{:map, [{:optional, {:atom, :foo}, :atom}]}
) ==
{:ok, {:map, [{:optional, {:atom, :foo}, :boolean}]}}
{:ok, {:map, [{:optional, {:atom, :foo}, {:atom, :bar}}]}}
end
test "map with subtyped keys" do
@@ -277,8 +267,8 @@ defmodule Module.Types.InferTest do
assert unify_lift({:union, [:integer, :atom]}, {:union, [:atom, :integer]}) ==
{:ok, {:union, [:integer, :atom]}}
assert unify_lift({:union, [:atom]}, {:union, [:boolean]}) == {:ok, {:union, [:boolean]}}
assert unify_lift({:union, [:boolean]}, {:union, [:atom]}) == {:ok, {:union, [:boolean]}}
assert unify_lift({:union, [:atom]}, {:union, [{:atom, :bar}]}) ==
{:ok, {:union, [{:atom, :bar}]}}
assert {:error, {:unable_unify, {{:union, [:integer]}, {:union, [:atom]}, _}}} =
unify_lift({:union, [:integer]}, {:union, [:atom]})
@@ -360,11 +350,22 @@ defmodule Module.Types.InferTest do
assert {{:var, 2}, var_context} = new_var({:baz, [version: 2], nil}, var_context)
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
assert {:ok, {:var, _}, _context} = unify({:var, 1}, {:var, 0}, context)
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 0}, context)
assert context.types[0] == {:var, 1}
assert context.types[1] == {:var, 0}
assert {:ok, {:var, _}, context} = unify({:var, 0}, :tuple, var_context)
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 0}, context)
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, context)
assert context.types[0] == {:var, 1}
assert context.types[1] == :tuple
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 2}, context)
assert {:ok, {:var, _}, _context} = unify({:var, 2}, {:var, 0}, context)
assert context.types[0] == :unbound
assert context.types[1] == {:var, 0}
assert context.types[2] == {:var, 1}
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
@@ -425,15 +426,11 @@ defmodule Module.Types.InferTest do
describe "subtype?/3" do
test "with simple types" do
assert subtype?({:atom, :foo}, :atom, new_context())
assert subtype?({:atom, true}, :boolean, new_context())
assert subtype?({:atom, true}, :atom, new_context())
assert subtype?(:boolean, :atom, new_context())
refute subtype?(:integer, :binary, new_context())
refute subtype?(:atom, {:atom, :foo}, new_context())
refute subtype?(:boolean, {:atom, true}, new_context())
refute subtype?(:atom, {:atom, true}, new_context())
refute subtype?(:atom, :boolean, new_context())
end
test "with composite types" do
@@ -503,8 +500,7 @@ defmodule Module.Types.InferTest do
test "to_union/2" do
assert to_union([:atom], new_context()) == :atom
assert to_union([:integer, :integer], new_context()) == :integer
assert to_union([:boolean, :atom], new_context()) == :atom
assert to_union([{:atom, :foo}, :boolean, :atom], new_context()) == :atom
assert to_union([{:atom, :foo}, {:atom, :bar}, :atom], new_context()) == :atom
assert to_union([:binary, :atom], new_context()) == {:union, [:binary, :atom]}
assert to_union([:atom, :binary, :atom], new_context()) == {:union, [:atom, :binary]}
+7 -8
View File
@@ -95,17 +95,16 @@ defmodule ModuleTest do
end
test "supports read access to module from __after_compile__/2" do
contents =
quote do
@after_compile __MODULE__
@foo 42
defmodule ModuleTest.NoRaise do
@after_compile __MODULE__
@foo 42
def __after_compile__(%Macro.Env{module: module}, bin) when is_binary(bin) do
Module.get_attribute(module, :foo)
end
def __after_compile__(%Macro.Env{module: module}, bin) when is_binary(bin) do
send(self(), Module.get_attribute(module, :foo))
end
end
Module.create(ModuleTest.NoRaise, contents, __ENV__)
assert_received 42
end
test "in memory modules are tagged as so" do
+14 -1
View File
@@ -156,7 +156,20 @@ defmodule RecordTest do
)
assert user == {RecordTest, :name, {RecordTest, :inner_name, :inner_age}}
assert Process.info(self(), :messages) == {:messages, [:inner_age, :inner_name, :name]}
assert for(_ <- 1..3, do: assert_receive(_)) == [:inner_age, :inner_name, :name]
user =
user(
name: send(self(), :name),
age:
user(
age: send(self(), :inner_age),
name: send(self(), :inner_name)
)
)
assert user == {RecordTest, :name, {RecordTest, :inner_name, :inner_age}}
assert for(_ <- 1..3, do: assert_receive(_)) == [:name, :inner_age, :inner_name]
end
Record.defrecord(
+83 -49
View File
@@ -259,41 +259,10 @@ defmodule ExUnit.Case do
end
quote do
unless Module.has_attribute?(__MODULE__, :ex_unit_tests) do
tag_check =
[:moduletag, :describetag, :tag]
|> Enum.any?(&Module.has_attribute?(__MODULE__, &1))
if tag_check do
raise "you must set @tag, @describetag, and @moduletag after the call to \"use ExUnit.Case\""
end
attributes = [
:ex_unit_tests,
:tag,
:describetag,
:moduletag,
:ex_unit_registered_test_attributes,
:ex_unit_registered_describe_attributes,
:ex_unit_registered_module_attributes,
:ex_unit_used_describes
]
Enum.each(attributes, &Module.register_attribute(__MODULE__, &1, accumulate: true))
@before_compile ExUnit.Case
@after_compile ExUnit.Case
@ex_unit_async false
@ex_unit_describe nil
unless ExUnit.Case.__register__(__MODULE__, unquote(opts)) do
use ExUnit.Callbacks
end
async = unquote(opts)[:async]
if is_boolean(async) do
@ex_unit_async async
end
import ExUnit.Callbacks
import ExUnit.Assertions
import ExUnit.Case, only: [describe: 2, test: 1, test: 2, test: 3]
@@ -301,6 +270,49 @@ defmodule ExUnit.Case do
end
end
@doc false
def __register__(module, opts) do
registered? = Module.has_attribute?(module, :ex_unit_tests)
unless registered? do
tag_check = Enum.any?([:moduletag, :describetag, :tag], &Module.has_attribute?(module, &1))
if tag_check do
raise "you must set @tag, @describetag, and @moduletag after the call to \"use ExUnit.Case\""
end
attributes = [
:ex_unit_tests,
:tag,
:describetag,
:moduletag,
:ex_unit_registered_test_attributes,
:ex_unit_registered_describe_attributes,
:ex_unit_registered_module_attributes,
:ex_unit_used_describes
]
Enum.each(attributes, &Module.register_attribute(module, &1, accumulate: true))
attributes = [
before_compile: ExUnit.Case,
after_compile: ExUnit.Case,
ex_unit_async: false,
ex_unit_describe: nil
]
Enum.each(attributes, fn {k, v} -> Module.put_attribute(module, k, v) end)
end
async? = opts[:async]
if is_boolean(async?) do
Module.put_attribute(module, :ex_unit_async, async?)
end
registered?
end
@doc """
Defines a test with `message`.
@@ -333,9 +345,17 @@ defmodule ExUnit.Case do
var = Macro.escape(var)
contents = Macro.escape(contents, unquote: true)
%{module: mod, file: file, line: line} = __CALLER__
quote bind_quoted: [var: var, contents: contents, message: message] do
name = ExUnit.Case.register_test(__ENV__, :test, message, [])
quote bind_quoted: [
var: var,
contents: contents,
message: message,
mod: mod,
file: file,
line: line
] do
name = ExUnit.Case.register_test(mod, file, line, :test, message, [])
def unquote(name)(unquote(var)), do: unquote(contents)
end
end
@@ -354,8 +374,10 @@ defmodule ExUnit.Case do
"""
defmacro test(message) do
%{module: mod, file: file, line: line} = __CALLER__
quote bind_quoted: binding() do
name = ExUnit.Case.register_test(__ENV__, :test, message, [:not_implemented])
name = ExUnit.Case.register_test(mod, file, line, :test, message, [:not_implemented])
def unquote(name)(_), do: flunk("Not implemented")
end
end
@@ -423,23 +445,14 @@ defmodule ExUnit.Case do
"""
defmacro describe(message, do: block) do
quote do
ExUnit.Case.__describe__(__MODULE__, __ENV__.line, unquote(message))
try do
ExUnit.Case.__describe__(__MODULE__, __ENV__.line, unquote(message), fn ->
unquote(block)
after
@ex_unit_describe nil
Module.delete_attribute(__MODULE__, :describetag)
for attribute <- Module.get_attribute(__MODULE__, :ex_unit_registered_describe_attributes) do
Module.delete_attribute(__MODULE__, attribute)
end
end
end)
end
end
@doc false
def __describe__(module, line, message) do
def __describe__(module, line, message, fun) do
if Module.get_attribute(module, :ex_unit_describe) do
raise "cannot call \"describe\" inside another \"describe\". See the documentation " <>
"for ExUnit.Case.describe/2 on named setups and how to handle hierarchies"
@@ -463,7 +476,17 @@ defmodule ExUnit.Case do
Module.put_attribute(module, :ex_unit_describe, {line, message})
Module.put_attribute(module, :ex_unit_used_describes, message)
:ok
try do
fun.()
after
Module.put_attribute(module, :ex_unit_describe, nil)
Module.delete_attribute(module, :describetag)
for attribute <- Module.get_attribute(module, :ex_unit_registered_describe_attributes) do
Module.delete_attribute(module, attribute)
end
end
end
@doc false
@@ -496,7 +519,7 @@ defmodule ExUnit.Case do
display. You can use `ExUnit.plural_rule/2` to set a custom
pluralization.
"""
def register_test(%{module: mod, file: file, line: line}, test_type, name, tags) do
def register_test(mod, file, line, test_type, name, tags) do
unless Module.has_attribute?(mod, :ex_unit_tests) do
raise "cannot define #{test_type}. Please make sure you have invoked " <>
"\"use ExUnit.Case\" in the current module"
@@ -557,6 +580,17 @@ defmodule ExUnit.Case do
name
end
@doc """
Reigsters a test with the given environment.
This function is deprecated in favor of register_test/6 which performs
better under tight loops by avoiding `__ENV__`.
"""
@doc deprecated: "Use register_test/6 instead"
def register_test(%{module: mod, file: file, line: line}, test_type, name, tags) do
register_test(mod, file, line, test_type, name, tags)
end
@doc """
Registers a new attribute to be used during `ExUnit.Case` tests.
+8 -2
View File
@@ -70,8 +70,14 @@ defmodule ExUnit.Diff do
diff_quoted(number, right, env)
end
defp diff_quoted({:++, _, _} = left, right, env) when is_list(right) do
diff_maybe_improper_list(left, right, env)
defp diff_quoted({:++, meta, [prefix, suffix]}, right, env) when is_list(right) do
case prefix do
{_, [expanded: expanded] ++ _, _} ->
diff_maybe_improper_list({:++, meta, [expanded, suffix]}, right, env)
_ ->
diff_maybe_improper_list({:++, meta, [prefix, suffix]}, right, env)
end
end
defp diff_quoted({:{}, _, left}, right, env) when is_tuple(right) do
+7 -2
View File
@@ -345,7 +345,6 @@ defmodule ExUnit.DiffTest do
refute_diff([:a, :b] ++ [:c] = [:a, :b], "[:a, :b] ++ [-:c-]", "[:a, :b]")
refute_diff([:a, :c] ++ [:b] = [:a, :b], "[:a, -:c-] ++ [:b]", "[:a, :b]")
refute_diff([:a] ++ [:b] ++ [:c] = [:a, :b], "[:a] ++ [:b] ++ [-:c-]", "[:a, :b]")
assert_diff([:a] ++ :b = [:a | :b], [])
@@ -353,10 +352,16 @@ defmodule ExUnit.DiffTest do
refute_diff([:a, :b] ++ :c = [:a, :b, :c], "[:a, :b] ++ -:c-", "[:a, :b, +:c+]")
refute_diff([:a] ++ [:b] ++ :c = [:a, :b, :c], "[:a] ++ [:b] ++ -:c-", "[:a, :b, +:c+]")
refute_diff([:a] ++ [:b] = :a, "-[:a] ++ [:b]-", "+:a+")
end
@a [:a]
test "concat lists with module attributes" do
assert_diff(@a ++ [:b] = [:a, :b], [])
refute_diff(@a ++ [:b] = [:a], "[:a] ++ [-:b-]", "[:a]")
refute_diff(@a ++ [:b] = [:b], "[-:a-] ++ [:b]", "[:b]")
end
test "mixed lists" do
refute_diff([:a | :b] = [:a, :b], "[:a | -:b-]", "[:a, +:b+]")
refute_diff([:a, :b] = [:a | :b], "[:a, -:b-]", "[:a | +:b+]")
+2 -1
View File
@@ -778,9 +778,10 @@ defmodule IEx.Introspection do
doc && IO.ANSI.Docs.print(doc, format, opts)
end
defp translate_doc(%{"en" => doc}), do: doc
defp translate_doc(%{}), do: nil
defp translate_doc(:none), do: nil
defp translate_doc(:hidden), do: nil
defp translate_doc(%{"en" => doc}), do: doc
defp no_beam(module) do
case Code.ensure_loaded(module) do
+6 -6
View File
@@ -242,8 +242,8 @@ defmodule IEx.Server do
end
end
defp handle_take_over({:respawn, evaluator}, _state, evaluator, evaluator_ref, input, _callback) do
rerun([], evaluator, evaluator_ref, input)
defp handle_take_over({:respawn, evaluator}, state, evaluator, evaluator_ref, input, _callback) do
rerun(state.evaluator_options, evaluator, evaluator_ref, input)
end
defp handle_take_over({:continue, evaluator}, state, evaluator, evaluator_ref, input, _callback) do
@@ -254,18 +254,18 @@ defmodule IEx.Server do
defp handle_take_over(
{:DOWN, evaluator_ref, :process, evaluator, :normal},
_state,
state,
evaluator,
evaluator_ref,
input,
_callback
) do
rerun([], evaluator, evaluator_ref, input)
rerun(state.evaluator_options, evaluator, evaluator_ref, input)
end
defp handle_take_over(
{:DOWN, evaluator_ref, :process, evaluator, reason},
_state,
state,
evaluator,
evaluator_ref,
input,
@@ -281,7 +281,7 @@ defmodule IEx.Server do
io_error("** (IEx.Error) #{type} when printing EXIT message: #{inspect(detail)}")
end
rerun([], evaluator, evaluator_ref, input)
rerun(state.evaluator_options, evaluator, evaluator_ref, input)
end
defp handle_take_over(_, state, _evaluator, _evaluator_ref, _input, callback) do
+3 -1
View File
@@ -62,7 +62,9 @@ defmodule Logger.App do
@doc false
def config_change(changed, _new, _removed) do
Logger.configure(changed)
# All other config has already been persisted, we only need to
# update the level and reload the logger state.
Logger.configure(Keyword.take(changed, [:level]))
end
@doc """
+11 -2
View File
@@ -147,8 +147,17 @@ defmodule Logger.Handler do
end
end
defp notify(:sync, msg), do: :gen_event.sync_notify(Logger, msg)
defp notify(:async, msg), do: :gen_event.notify(Logger, msg)
defp notify(:sync, msg) do
pid = Process.whereis(Logger)
# If we are within the logger process itself,
# we cannot use sync notify as that will deadlock.
if pid == self(), do: :gen_event.notify(pid, msg), else: :gen_event.sync_notify(pid, msg)
end
defp notify(:async, msg) do
:gen_event.notify(Logger, msg)
end
defp truncate(data, n) when is_list(data) do
Logger.Utils.truncate(data, n)
+33 -9
View File
@@ -62,7 +62,7 @@ defmodule LoggerTest do
Logger.add_backend({UnknownBackend, :hello})
end
test "logs or writes to stderr on failed backends" do
test "logs or writes to stderr on failed call on async mode" do
assert {:ok, _} = Logger.add_backend({MyBackend, :hello})
assert capture_log(fn ->
@@ -86,6 +86,32 @@ defmodule LoggerTest do
Logger.add_backend(:console)
end
test "logs or writes to stderr on failed call on sync mode" do
Logger.configure(sync_threshold: 0)
assert {:ok, _} = Logger.add_backend({MyBackend, :hello})
assert capture_log(fn ->
:gen_event.call(Logger, {MyBackend, :hello}, :error)
wait_for_handler(Logger, {MyBackend, :hello})
end) =~
":gen_event handler {LoggerTest.MyBackend, :hello} installed in Logger terminating"
assert :ok = Logger.remove_backend(:console)
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
:gen_event.call(Logger, {MyBackend, :hello}, :error)
wait_for_handler(Logger, {MyBackend, :hello})
end) =~
":gen_event handler {LoggerTest.MyBackend, :hello} installed in Logger terminating"
# Flush logs before reattaching to avoid OTP reports
Logger.flush()
after
Logger.configure(sync_threshold: 20)
Logger.remove_backend({MyBackend, :hello})
Logger.add_backend(:console)
end
test "level/0" do
assert Logger.level() == :debug
@@ -644,14 +670,12 @@ defmodule LoggerTest do
end
test "updates config on config_change/3" do
:ok = Logger.configure(level: :debug)
try do
assert Logger.App.config_change([level: :error], [], []) === :ok
assert Logger.level() === :error
after
Logger.configure(level: :debug)
end
assert Application.put_env(:logger, :level, :error) == :ok
assert :application_controller.config_change(logger: [level: :debug]) == :ok
assert Logger.level() == :error
after
Application.put_env(:logger, :level, :debug)
Logger.configure(level: :debug)
end
describe "OTP integration" do
+4 -2
View File
@@ -78,7 +78,9 @@ defmodule Mix.CLI do
defp run_task(name, args) do
try do
ensure_no_slashes(name)
Mix.Tasks.Loadconfig.load_default()
# We must go through the task instead of invoking the module directly
# because projects like Nerves use this to invoke it early.
Mix.Task.run("loadconfig")
Mix.Task.run(name, args)
rescue
# We only rescue exceptions in the Mix namespace, all
@@ -144,7 +146,7 @@ defmodule Mix.CLI do
path = Path.join(Mix.Utils.mix_config(), "config.exs")
if File.regular?(path) do
Mix.Tasks.Loadconfig.load_imports(path)
Mix.Tasks.Loadconfig.load_compile(path)
end
end
@@ -25,6 +25,12 @@ defmodule Mix.Compilers.ApplicationTracer do
:ok
end
# Also skip __impl__ calls inside protocols as they are meant
# to invert dependencies.
def trace({_, _, _, :__impl__, _}, _env) do
:ok
end
def trace({type, meta, module, function, arity}, env)
when type in [:remote_function, :remote_macro, :imported_function, :imported_macro] do
# Unknown modules need to be looked up and filtered later
@@ -110,8 +116,8 @@ defmodule Mix.Compilers.ApplicationTracer do
def format_warning({:undefined_app, app, module, function, arity}) do
"""
#{Exception.format_mfa(module, function, arity)} defined in application :#{app} \
is used by the current application but the current application does not directly \
depend on :#{app}. To fix this, you must do one of:
is used by the current application but the current application does not depend \
on :#{app}. To fix this, you must do one of:
1. If :#{app} is part of Erlang/Elixir, you must include it under \
:extra_applications inside "def application" in your mix.exs
@@ -120,7 +126,7 @@ defmodule Mix.Compilers.ApplicationTracer do
in your mix.exs
3. In case you don't want to add a requirement to :#{app}, you may \
optionally skip this warning by adding [xref: [exclude: #{inspect(module)}] \
optionally skip this warning by adding [xref: [exclude: [#{inspect(module)}]]] \
to your "def project" in mix.exs
"""
end
-13
View File
@@ -75,19 +75,6 @@ defmodule Mix.Dep do
system_env: keyword
}
@doc """
Receives the project configuration and returns
a map saying if dependencies are runtime or compile time.
"""
def deps_opts(config) do
for config_dep <- Keyword.get(config, :deps, []),
do: {elem(config_dep, 0), dep_opts(config_dep)}
end
defp dep_opts({_app, opts}) when is_list(opts), do: opts
defp dep_opts({_app, _req, opts}) when is_list(opts), do: opts
defp dep_opts(_), do: []
@doc """
Returns loaded dependencies from the cache for the current environment.
+13 -2
View File
@@ -36,7 +36,7 @@ defmodule Mix.Local do
def path_for(:escript), do: Mix.path_for(:escripts)
@doc """
Appends archives paths into Erlang code path.
Appends archive paths to the Erlang code path.
"""
def append_archives do
for archive <- archives_ebins() do
@@ -48,7 +48,18 @@ defmodule Mix.Local do
end
@doc """
Appends Mix paths into Erlang code path.
Removes archive paths from Erlang code path.
"""
def remove_archives do
for archive <- archives_ebins() do
Code.delete_path(archive)
end
:ok
end
@doc """
Appends Mix paths to the Erlang code path.
"""
def append_paths do
Enum.each(mix_paths(), &Code.append_path(&1))
+1 -5
View File
@@ -808,11 +808,7 @@ defmodule Mix.Release do
{:ok, {_, chunks}} ->
chunks = for {name, chunk} <- chunks, is_binary(chunk), do: {name, chunk}
{:ok, binary} = :beam_lib.build_module(chunks)
{:ok, fd} = :ram_file.open(binary, [:write, :binary])
{:ok, _} = :ram_file.compress(fd)
{:ok, binary} = :ram_file.get_file(fd)
:ok = :ram_file.close(fd)
{:ok, binary}
{:ok, :zlib.gzip(binary)}
{:error, _, _} = error ->
error
+8 -7
View File
@@ -355,12 +355,12 @@ defmodule Mix.Task do
alias = Mix.Project.config()[:aliases][String.to_atom(task)]
cond do
is_nil(alias) ->
run_task(proj, task, args)
Mix.TasksServer.run({:alias, task, proj}) ->
alias && Mix.TasksServer.run({:alias, task, proj}) ->
run_alias(List.wrap(alias), args, proj, task, :ok)
!Mix.TasksServer.get({:task, task, proj}) ->
run_task(proj, task, args)
true ->
:noop
end
@@ -371,8 +371,8 @@ defmodule Mix.Task do
# 2. Otherwise we compile and load dependencies
# 3. Finally, we compile the current project in hope it is available.
module =
get_task_or_run(proj, task, fn -> Mix.Task.run("deps.loadpaths") end) ||
get_task_or_run(proj, task, fn -> Mix.Task.run("compile", []) end) ||
get_task_or_run(proj, task, fn -> run("deps.loadpaths") end) ||
get_task_or_run(proj, task, fn -> run("compile", []) end) ||
get!(task)
recursive = recursive(module)
@@ -450,7 +450,8 @@ defmodule Mix.Task do
run_alias(t, alias_args, proj, original_task, res)
end
defp run_alias([], _alias_task, _proj, _original_task, res) do
defp run_alias([], _alias_args, proj, original_task, res) do
Mix.TasksServer.put({:task, original_task, proj})
res
end
+1 -1
View File
@@ -34,7 +34,7 @@ defmodule Mix.Tasks.App.Config do
runtime = config[:config_path] |> Path.dirname() |> Path.join("runtime.exs")
if File.exists?(runtime) do
Mix.Tasks.Loadconfig.load_file(runtime)
Mix.Tasks.Loadconfig.load_runtime(runtime)
end
if opts[:preload_modules] do
+20 -3
View File
@@ -354,14 +354,15 @@ defmodule Mix.Tasks.Compile.App do
defp apps_from_runtime_prod_deps(properties, config) do
included_applications = Keyword.get(properties, :included_applications, [])
for {app, opts} <- Mix.Dep.deps_opts(config),
for {app, opts} <- deps_opts(config),
runtime_app?(opts),
app not in included_applications,
do: {app, if(Keyword.get(opts, :optional, false), do: :optional, else: :required)}
end
defp runtime_app?(opts) do
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) and matching_only?(opts)
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) and matching_only?(opts) and
matching_target?(opts)
end
defp matching_only?(opts) do
@@ -371,6 +372,22 @@ defmodule Mix.Tasks.Compile.App do
end
end
defp matching_target?(opts) do
case Keyword.fetch(opts, :targets) do
{:ok, value} -> Mix.target() in List.wrap(value)
:error -> true
end
end
defp deps_opts(config) do
for config_dep <- Keyword.get(config, :deps, []),
do: {elem(config_dep, 0), dep_opts(config_dep)}
end
defp dep_opts({_app, opts}) when is_list(opts), do: opts
defp dep_opts({_app, _req, opts}) when is_list(opts), do: opts
defp dep_opts(_), do: []
## Helpers for loading and manipulating apps
@doc false
@@ -386,7 +403,7 @@ defmodule Mix.Tasks.Compile.App do
Keyword.get(properties, :extra_applications, [])
project_apps(properties, config, extra, fn ->
config |> Mix.Dep.deps_opts() |> Keyword.keys()
config |> deps_opts() |> Keyword.keys()
end)
end
+10 -5
View File
@@ -103,12 +103,13 @@ defmodule Mix.Tasks.Compile.Elixir do
manifest = manifest()
configs = [Mix.Project.config_mtime() | Mix.Tasks.Compile.Erlang.manifests()]
force = opts[:force] || Mix.Utils.stale?(configs, [manifest])
{tracers, opts} = pop_tracers(opts)
opts =
(project[:elixirc_options] || [])
|> Keyword.merge(opts)
|> xref_exclude_opts(project)
|> tracers_opts()
|> tracers_opts(tracers)
|> profile_opts()
Mix.Compilers.Elixir.compile(manifest, srcs, dest, [:ex], force, opts)
@@ -134,17 +135,21 @@ defmodule Mix.Tasks.Compile.Elixir do
end
end
defp tracers_opts(opts) do
defp pop_tracers(opts) do
case Keyword.pop_values(opts, :tracer) do
{[], opts} ->
opts
{[], opts}
{tracers, opts} ->
tracers = Enum.map(tracers, &Module.concat([&1]))
Keyword.update(opts, :tracers, tracers, &(tracers ++ &1))
{Enum.map(tracers, &Module.concat([&1])), opts}
end
end
defp tracers_opts(opts, tracers) do
tracers = tracers ++ Code.get_compiler_option(:tracers)
Keyword.update(opts, :tracers, tracers, &(tracers ++ &1))
end
defp profile_opts(opts) do
case Keyword.fetch(opts, :profile) do
{:ok, "time"} -> Keyword.put(opts, :profile, :time)
+2 -8
View File
@@ -197,14 +197,8 @@ defmodule Mix.Tasks.Deps.Compile do
config_path = Path.join(dep_path, "mix.rebar.config")
lib_path = Path.join(config[:env_path], "lib/*/ebin")
# REBAR_BARE_COMPILER_OUTPUT_DIR is only honored by rebar3 >= 3.14
env = [
{"REBAR_CONFIG", config_path},
{"TERM", "dumb"},
{"REBAR_BARE_COMPILER_OUTPUT_DIR", dep_path}
]
cmd = "#{rebar_cmd(dep)} bare compile --paths #{lib_path}"
env = [{"REBAR_CONFIG", config_path}, {"TERM", "dumb"}]
cmd = "#{rebar_cmd(dep)} bare compile --paths=#{inspect(lib_path)}"
File.mkdir_p!(dep_path)
File.write!(config_path, rebar_config(dep))
+14 -7
View File
@@ -29,35 +29,42 @@ defmodule Mix.Tasks.Loadconfig do
case args do
[] -> load_default()
[file] -> load_imports(file)
[file] -> load_compile(file)
end
end
@doc false
def load_default do
defp load_default do
config = Mix.Project.config()
if File.regular?(config[:config_path]) or config[:config_path] != "config/config.exs" do
load_imports(config[:config_path])
load_compile(config[:config_path])
else
[]
end
end
@doc false
def load_imports(file) do
# Loads compile-time configuration, they support imports, and are not deep merged.
def load_compile(file) do
{config, files} = Config.Reader.read_imports!(file, env: Mix.env(), target: Mix.target())
Mix.ProjectStack.loaded_config(persist_apps(config, file), files)
config
end
@doc false
def load_file(file) do
# Loads runtime configuration, they do not support imports, and are deep merged.
def load_runtime(file) do
config = Config.Reader.read!(file, env: Mix.env(), target: Mix.target(), imports: :disabled)
Mix.ProjectStack.loaded_config(persist_apps(config, file), [])
Mix.ProjectStack.loaded_config(persist_apps(hydrate_apps(config), file), [])
config
end
defp hydrate_apps(config) do
for {app, pairs} <- config do
hd(Config.Reader.merge([{app, Application.get_all_env(app)}], [{app, pairs}]))
end
end
defp persist_apps(config, file) do
Application.put_all_env(config, persistent: true)
apps = Keyword.keys(config)
+7 -1
View File
@@ -154,7 +154,13 @@ defmodule Mix.Tasks.Run do
defp process_config(opts) do
for {:config, value} <- opts do
Mix.Tasks.Loadconfig.load_imports(value)
# TODO: Remove on v2.0.
IO.warn(
"the --config flag is deprecated. If you need to handle multiple configurations, " <>
"it is preferable to dynamically import them in your config files"
)
Mix.Tasks.Loadconfig.load_compile(value)
end
:ok
+2 -1
View File
@@ -252,7 +252,8 @@ defmodule Mix.Tasks.Xref do
{:ok, {_, [debug_info: debug_info]}} <- :beam_lib.chunks(path, [:debug_info]),
{:debug_info_v1, backend, data} <- debug_info,
{:ok, %{definitions: defs}} <- backend.debug_info(:elixir_v1, module, data, []),
do: walk_definitions(module, source, defs)
do: walk_definitions(module, source, defs),
else: (_ -> [])
end
defp walk_definitions(module, file, definitions) do
+4
View File
@@ -21,6 +21,10 @@ defmodule Mix.TasksServer do
Agent.update(@name, &Map.put(&1, tuple, true), @timeout)
end
def get(tuple) do
Agent.get(@name, &Map.get(&1, tuple), @timeout)
end
def delete_many(many) do
Agent.update(@name, &Map.drop(&1, many), @timeout)
end
+23 -4
View File
@@ -588,7 +588,7 @@ defmodule Mix.Utils do
# Starting an HTTP client profile allows us to scope
# the effects of using an HTTP proxy to this function
{:ok, _pid} = :inets.start(:httpc, [{:profile, :mix}])
{:ok, _pid} = :inets.start(:httpc, profile: :mix)
headers = [{'user-agent', 'Mix/#{System.version()}'}]
request = {:binary.bin_to_list(path), headers}
@@ -601,7 +601,28 @@ defmodule Mix.Utils do
# If a proxy environment variable was supplied add a proxy to httpc.
http_options = [relaxed: true] ++ proxy_config(path)
case :httpc.request(:get, request, http_options, [body_format: :binary], :mix) do
case httpc_request(request, http_options) do
{:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}
when inet in [:inet, :inet6] and reason in [:ehostunreach, :enetunreach] ->
:httpc.set_options([ipfamily: fallback(inet)], :mix)
request |> httpc_request(http_options) |> httpc_response()
response ->
httpc_response(response)
end
after
:inets.stop(:httpc, :mix)
end
defp fallback(:inet), do: :inet6
defp fallback(:inet6), do: :inet
defp httpc_request(request, http_options) do
:httpc.request(:get, request, http_options, [body_format: :binary], :mix)
end
defp httpc_response(response) do
case response do
{:ok, {{_, status, _}, _, body}} when status in 200..299 ->
{:ok, body}
@@ -611,8 +632,6 @@ defmodule Mix.Utils do
{:error, reason} ->
{:remote, "httpc request failed with: #{inspect(reason)}"}
end
after
:inets.stop(:httpc, :mix)
end
defp file?(path) do
+10 -2
View File
@@ -10,11 +10,14 @@ defmodule Mix.AliasesTest do
h: "hello",
p: &inspect/1,
compile: "hello",
cmd: &call_cmd/1,
help: ["help", "hello"],
"nested.h": [&Mix.shell().info(inspect(&1)), "h foo bar"]
]
]
end
defp call_cmd(args), do: Mix.Task.run("cmd", args)
end
setup do
@@ -50,13 +53,18 @@ defmodule Mix.AliasesTest do
assert Mix.Task.run("compile", []) == :noop
end
test "run alias override with recursion" do
test "run alias override with name-recursion" do
assert Mix.Task.rerun("help", []) == "Hello, World!"
assert_received {:mix_shell, :info, ["mix test" <> _]}
# Arguments are passed to the recursive task and not the last one.
assert ExUnit.CaptureIO.capture_io(fn ->
Mix.Task.rerun("help", ["test"]) == "Hello, World!"
Mix.Task.rerun("help", ["test"])
end) =~ "mix test"
end
test "run alias override with code-recursion" do
assert Mix.Task.rerun("cmd", ["echo", "hello"]) == :ok
assert_received {:mix_shell, :run, ["hello" <> _]}
end
end
+5 -1
View File
@@ -17,11 +17,13 @@ defmodule Mix.Tasks.App.ConfigTest do
File.write!("config/config.exs", """
import Config
config :sample, from_compile: :compile, from_runtime: :compile
config :sample, :nested, from_compile: :compile, from_runtime: :compile
""")
File.write!("config/runtime.exs", """
import Config
config :sample, from_runtime: :runtime
config :sample, :nested, from_compile: :compile, from_runtime: :compile
""")
Mix.Task.run("loadconfig")
@@ -30,10 +32,12 @@ defmodule Mix.Tasks.App.ConfigTest do
assert Application.get_all_env(:sample) |> Enum.sort() == [
from_compile: :compile,
from_env: :env,
from_runtime: :runtime
from_runtime: :runtime,
nested: [from_compile: :compile, from_runtime: :compile]
]
end)
after
Application.delete_env(:sample, :nested, persistent: true)
Application.delete_env(:sample, :from_env, persistent: true)
Application.delete_env(:sample, :from_compile, persistent: true)
Application.delete_env(:sample, :from_runtime, persistent: true)
+5 -1
View File
@@ -19,7 +19,11 @@ defmodule Mix.Tasks.ArchiveTest do
File.rm_rf!(tmp_path("userhome"))
System.put_env("MIX_ARCHIVES", tmp_path("userhome/.mix/archives/"))
Mix.Project.push(ArchiveProject)
:ok
on_exit(fn ->
Mix.Local.remove_archives()
System.delete_env("MIX_ARCHIVES")
end)
end
test "archive build" do
+4 -2
View File
@@ -40,7 +40,9 @@ defmodule Mix.Tasks.Compile.AppTest do
{:ok6, path: "../ok", optional: true},
{:ok7, path: "../ok", optional: false},
{:ok8, path: "../ok", app: false},
{:ok9, path: "../ok"}
{:ok9, path: "../ok"},
{:ok10, path: "../ok", targets: [:will_never_be_listed]},
{:ok11, path: "../ok", targets: [Mix.target()]}
]
end
end
@@ -130,7 +132,7 @@ defmodule Mix.Tasks.Compile.AppTest do
properties = parse_resource_file(:custom_deps)
assert properties[:applications] ==
[:kernel, :stdlib, :elixir, :logger, :ok1, :ok3, :ok4, :ok7]
[:kernel, :stdlib, :elixir, :logger, :ok1, :ok3, :ok4, :ok7, :ok11]
end)
end
+14 -1
View File
@@ -57,6 +57,19 @@ defmodule Mix.Tasks.Compile.ElixirTest do
Code.put_compiler_option(:tracers, [])
end
test "compiles a project with a previously set custom tracer" do
Process.register(self(), __MODULE__)
Code.put_compiler_option(:tracers, [__MODULE__])
in_fixture("no_mixfile", fn ->
Mix.Tasks.Compile.Elixir.run([])
assert_received {:alias_reference, _meta, A}
assert_received {:alias_reference, _meta, B}
end)
after
Code.put_compiler_option(:tracers, [])
end
test "warns when Logger is used but not depended on" do
in_fixture("no_mixfile", fn ->
File.write!("lib/a.ex", """
@@ -67,7 +80,7 @@ defmodule Mix.Tasks.Compile.ElixirTest do
""")
message =
"Logger.info/1 defined in application :logger is used by the current application but the current application does not directly depend on :logger"
"Logger.info/1 defined in application :logger is used by the current application but the current application does not depend on :logger"
assert capture_io(:stderr, fn ->
Mix.Task.run("compile", [])
+13 -2
View File
@@ -359,6 +359,11 @@ defmodule Mix.Tasks.ReleaseTest do
config = [releases: [runtime_config: [reboot_system_after_config: true]]]
Mix.Project.in_project(:release_test, ".", config, fn _ ->
File.write!("config/config.exs", """
#{File.read!("config/config.exs")}
config :release_test, :runtime, keep: :static, override: :static
""")
File.write!("config/runtime.exs", """
import Config
@@ -366,7 +371,11 @@ defmodule Mix.Tasks.ReleaseTest do
raise "file should not be loaded while assembling release"
end
config :release_test, :runtime, {:was_set, config_env(), config_target()}
config :release_test, :runtime,
override: :runtime,
config_env: config_env(),
config_target: config_target()
config :release_test, :encoding, {:runtime, :time_μs, :"£", "£", '£'}
""")
@@ -401,7 +410,9 @@ defmodule Mix.Tasks.ReleaseTest do
release_mode: "embedded",
release_node: "runtime_config",
release_vsn: "0.1.0",
runtime_config: {:ok, {:was_set, :dev, :host}},
runtime_config:
{:ok,
[keep: :static, override: :runtime, config_env: :dev, config_target: :host]},
static_config: {:ok, :was_set},
sys_config_env: sys_config_env,
sys_config_init: sys_config_init
-14
View File
@@ -3,24 +3,10 @@ Code.require_file("../../test_helper.exs", __DIR__)
defmodule Mix.Tasks.RunTest do
use MixTest.Case
import ExUnit.CaptureIO
setup do
Mix.Project.push(MixTest.Case.Sample)
end
test "loads configuration", context do
in_tmp(context.test, fn ->
config = fixture_path("configs/good_config.exs")
expr = "IO.puts(Application.get_env(:my_app, :key))"
assert capture_io(fn -> Mix.Task.run("run", ["--config", config, "--eval", expr]) end) ==
"value\n"
end)
after
Application.delete_env(:my_app, :key)
end
test "run requires files before evaling commands", context do
git_repo = fixture_path("git_repo/lib/git_repo.ex")
+19 -1
View File
@@ -69,11 +69,29 @@ defmodule Mix.Tasks.XrefTest do
assert_all_calls(files, output)
end
defp assert_all_calls(files, expected) do
test "returns empty on cover compiled modules" do
files = %{
"lib/a.ex" => """
defmodule A do
def a, do: A.a()
end
"""
}
assert_all_calls(files, [], fn ->
:cover.start()
:cover.compile_beam_directory(to_charlist(Mix.Project.compile_path()))
end)
after
:cover.stop()
end
defp assert_all_calls(files, expected, after_compile \\ fn -> :ok end) do
in_fixture("no_mixfile", fn ->
generate_files(files)
Mix.Task.run("compile")
after_compile.()
assert Enum.sort(Mix.Tasks.Xref.calls()) == Enum.sort(expected)
end)
end