Compare commits
112
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29fdd09c0f | ||
|
|
a1934d6cd5 | ||
|
|
26627272a9 | ||
|
|
0c4cc6c992 | ||
|
|
a5b730a900 | ||
|
|
4afc03bece | ||
|
|
e236cbb164 | ||
|
|
927b10df80 | ||
|
|
bbe04b4ab9 | ||
|
|
9153d73f2b | ||
|
|
6d4811ad1c | ||
|
|
94326a3544 | ||
|
|
ef527cad43 | ||
|
|
e91b1fa66b | ||
|
|
863e66884d | ||
|
|
54516c5684 | ||
|
|
08d2bb170d | ||
|
|
c2a0d8ead6 | ||
|
|
a16517edfb | ||
|
|
26136e53c6 | ||
|
|
6654f1b922 | ||
|
|
e30f8df8fe | ||
|
|
2fe5b77498 | ||
|
|
f1a5e6a0b1 | ||
|
|
fadf5c6519 | ||
|
|
e975613c9d | ||
|
|
b1a1dd04a5 | ||
|
|
e4e2f1a79e | ||
|
|
138b442e07 | ||
|
|
119580f544 | ||
|
|
9fd97c4663 | ||
|
|
78cee0e384 | ||
|
|
fb0379287c | ||
|
|
def65ab120 | ||
|
|
dc8cfcd4c9 | ||
|
|
84c8d23710 | ||
|
|
36c3f7594d | ||
|
|
bdaffc380b | ||
|
|
5d79b34cb5 | ||
|
|
14cddf357b | ||
|
|
92eee10bcf | ||
|
|
9b7b7d655d | ||
|
|
f40aa10436 | ||
|
|
c521bdb91a | ||
|
|
bdbdf52122 | ||
|
|
7ff97fb930 | ||
|
|
af378de03f | ||
|
|
82c264bc36 | ||
|
|
4fbed9fe12 | ||
|
|
1dbf121a78 | ||
|
|
694b9b0218 | ||
|
|
0a3df5068c | ||
|
|
9e177fd592 | ||
|
|
8422af4a2e | ||
|
|
b79c5a3b4c | ||
|
|
3bb0a8379d | ||
|
|
fe0d57c4da | ||
|
|
727a958c48 | ||
|
|
aa93d654ab | ||
|
|
f5b71a79b2 | ||
|
|
173fc52610 | ||
|
|
7f7a8bca99 | ||
|
|
0929a431d4 | ||
|
|
f1e5d770ea | ||
|
|
bceda78a42 | ||
|
|
6d829120c3 | ||
|
|
785c29210e | ||
|
|
7db2daea72 | ||
|
|
0fcee760d8 | ||
|
|
e63b0b6217 | ||
|
|
9e195f5251 | ||
|
|
60320941aa | ||
|
|
871e737583 | ||
|
|
3785e6f71b | ||
|
|
a89f8a9913 | ||
|
|
93bfbdfc4e | ||
|
|
c032839468 | ||
|
|
52cf991302 | ||
|
|
0c6b44798b | ||
|
|
4c12bfcb14 | ||
|
|
fde96ce8dc | ||
|
|
bc40b6ebb5 | ||
|
|
3a34f243b8 | ||
|
|
f322d26b7d | ||
|
|
dcaa468c47 | ||
|
|
cc2d05654f | ||
|
|
c22f7343f0 | ||
|
|
858835f9f7 | ||
|
|
367d906403 | ||
|
|
df5bc5abc8 | ||
|
|
1ca5d789bb | ||
|
|
aab3543dc3 | ||
|
|
9b254e6830 | ||
|
|
0dd933b0a9 | ||
|
|
9fd85b06dc | ||
|
|
ec81110b3e | ||
|
|
094a76d514 | ||
|
|
7d737bc5ec | ||
|
|
3eb074282a | ||
|
|
b8732bd248 | ||
|
|
2252a8ff55 | ||
|
|
9a9cf419a1 | ||
|
|
0f92512030 | ||
|
|
e0806f9d6c | ||
|
|
5db9d55926 | ||
|
|
3cb3d41368 | ||
|
|
a3a081c949 | ||
|
|
207350fb44 | ||
|
|
6f7a68c9e3 | ||
|
|
0936fb45d5 | ||
|
|
bccfca4ffe | ||
|
|
417255fe1b |
@@ -30,7 +30,7 @@ jobs:
|
|||||||
- otp: 26
|
- otp: 26
|
||||||
otp_version: '26.0'
|
otp_version: '26.0'
|
||||||
build_docs: build_docs
|
build_docs: build_docs
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create_draft_release:
|
create_draft_release:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
steps:
|
steps:
|
||||||
@@ -40,7 +40,7 @@ jobs:
|
|||||||
- otp: 26
|
- otp: 26
|
||||||
otp_version: '26.0'
|
otp_version: '26.0'
|
||||||
build_docs: build_docs
|
build_docs: build_docs
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
@@ -56,7 +56,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
gh release upload --clobber "${{ github.ref_name }}" \
|
gh release upload --clobber "${{ github.ref_name }}" \
|
||||||
elixir-otp-${{ matrix.otp }}.zip \
|
elixir-otp-${{ matrix.otp }}.zip \
|
||||||
elixir-otp-${{ matrix.otp }}.zip.sha{1,256}sum
|
elixir-otp-${{ matrix.otp }}.zip.sha{1,256}sum \
|
||||||
|
elixir-otp-${{ matrix.otp }}.exe \
|
||||||
|
elixir-otp-${{ matrix.otp }}.exe.sha{1,256}sum
|
||||||
- name: Upload Docs to GitHub
|
- name: Upload Docs to GitHub
|
||||||
if: ${{ matrix.build_docs }}
|
if: ${{ matrix.build_docs }}
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -22,6 +22,20 @@ runs:
|
|||||||
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
|
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
|
||||||
shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
|
shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
|
||||||
echo "$PWD/bin" >> $GITHUB_PATH
|
echo "$PWD/bin" >> $GITHUB_PATH
|
||||||
|
- name: Install NSIS
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y nsis
|
||||||
|
- name: Build Elixir Windows Installer
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
export OTP_VERSION=${{ inputs.otp_version }}
|
||||||
|
export ELIXIR_ZIP=$PWD/elixir-otp-${{ inputs.otp }}.zip
|
||||||
|
(cd lib/elixir/scripts/windows_installer && ./build.sh)
|
||||||
|
mv lib/elixir/scripts/windows_installer/tmp/elixir-otp-${{ inputs.otp }}.exe .
|
||||||
|
shasum -a 1 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha1sum
|
||||||
|
shasum -a 256 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha256sum
|
||||||
- name: Get latest stable ExDoc version
|
- name: Get latest stable ExDoc version
|
||||||
if: ${{ inputs.build_docs }}
|
if: ${{ inputs.build_docs }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
+170
-24
@@ -68,12 +68,20 @@ In Elixir v1.15, the new reports will look like:
|
|||||||
error: undefined function foo/0 (there is no such import)
|
error: undefined function foo/0 (there is no such import)
|
||||||
my_file.exs:1
|
my_file.exs:1
|
||||||
|
|
||||||
** (CompileError) nofile: cannot compile file (errors have been logged)
|
** (CompileError) my_file.exs: cannot compile file (errors have been logged)
|
||||||
|
|
||||||
A new function, called `Code.with_diagnostics/2`, has been added so this
|
A new function, called `Code.with_diagnostics/2`, has been added so this
|
||||||
information can be leveraged by editors, allowing them to point to several
|
information can be leveraged by editors, allowing them to point to several
|
||||||
errors at once.
|
errors at once.
|
||||||
|
|
||||||
|
### Potential incompatibilities
|
||||||
|
|
||||||
|
As part of this effort, the behaviour where undefined variables were
|
||||||
|
transformed into nullary function calls, often leading to confusing error
|
||||||
|
reports, has been disabled during project compilation. You can invoke
|
||||||
|
`Code.compiler_options(on_undefined_variable: :warn)` at the top of
|
||||||
|
your `mix.exs` to bring the old behaviour back.
|
||||||
|
|
||||||
## Integration with Erlang/OTP logger
|
## Integration with Erlang/OTP logger
|
||||||
|
|
||||||
This release provides additional features such as global logger
|
This release provides additional features such as global logger
|
||||||
@@ -100,61 +108,185 @@ config :logger, :default_formatter,
|
|||||||
format: "$time $message $metadata"
|
format: "$time $message $metadata"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use `Logger.Backends.Console` or other backends, they are
|
If you use `Logger.Backends.Console` with a custom device or other
|
||||||
still fully supported and functional. If you implement your own
|
backends, they are still fully supported and functional. If you
|
||||||
backends, you want to consider migrating to
|
implement your own backends, you want to consider migrating to
|
||||||
[`:logger_backends`](https://github.com/elixir-lang/logger_backends)
|
[`:logger_backends`](https://github.com/elixir-lang/logger_backends)
|
||||||
in the long term.
|
in the long term.
|
||||||
|
|
||||||
See the new `Logger` documentation for more information on the
|
See the new `Logger` documentation for more information on the
|
||||||
new features and on compatibility.
|
new features and on compatibility.
|
||||||
|
|
||||||
## v1.15.0-rc.1 (2022-05-29)
|
## v1.15.8 (2024-05-21)
|
||||||
|
|
||||||
|
### 1. Bug fixes
|
||||||
|
|
||||||
|
#### Elixir
|
||||||
|
|
||||||
|
* [bin/elixir] Properly handle the `--dbg` flag in Elixir's CLI
|
||||||
|
* [System] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows via `System.cmd/3`
|
||||||
|
* [Port] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows
|
||||||
|
* [URI] Ensure `:undefined` fields are properly converted to `nil` when invoking Erlang's API
|
||||||
|
|
||||||
|
#### Logger
|
||||||
|
|
||||||
|
* [Logger] Ensure translators are persisted across logger restarts
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [mix compile] Ensure compile paths are accessible during compilation
|
||||||
|
|
||||||
|
## v1.15.7 (2023-10-14)
|
||||||
|
|
||||||
### 1. Enhancements
|
### 1. Enhancements
|
||||||
|
|
||||||
#### Elixir
|
#### Elixir
|
||||||
|
|
||||||
* [File] Support distributed `File.Stream`
|
* [Elixir] Allow code evaluation across Elixir versions
|
||||||
* [Module] Add `Module.get_last_attribute/3`
|
|
||||||
* [Task] Reduce footprint of tasks by avoiding unecessary work during spawning
|
|
||||||
|
|
||||||
#### ExUnit
|
### 2. Bug fixes
|
||||||
|
|
||||||
* [ExUnit.Case] Add `ExUnit.Case.get_last_registered_test/1`
|
#### EEx
|
||||||
|
|
||||||
|
* [EEx] Do not emit duplicate warnings from tokenizer
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [mix format] Correctly match file to subdirectory in `Mix.Tasks.Format.formatter_for_file/2`
|
||||||
|
|
||||||
|
## v1.15.6 (2023-09-20)
|
||||||
|
|
||||||
|
This release also includes fixes to the Windows installer.
|
||||||
|
|
||||||
|
### 1. Bug fixes
|
||||||
|
|
||||||
|
#### EEx
|
||||||
|
|
||||||
|
* [EEx] Do not crash when printing tokenizer warnings
|
||||||
|
|
||||||
|
#### Elixir
|
||||||
|
|
||||||
|
* [Code] Fix formatter for nested `*` in bitstrings
|
||||||
|
* [Code] Improve feedback when an invalid block is given `Code.quoted_to_algebra/2`
|
||||||
|
* [Kernel] Trace functions before they are inlined
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [mix compile] Ensure `:extra_applications` declare in umbrella projects are loaded
|
||||||
|
* [mix deps.get] Do not check for invalid applications before deps.get
|
||||||
|
* [mix deps.update] Do not check for invalid applications before deps.update
|
||||||
|
* [mix format] Load plugins when invoking the formatter from an IDE
|
||||||
|
|
||||||
|
## v1.15.5 (2023-08-28)
|
||||||
|
|
||||||
|
### 1. Enhancements
|
||||||
|
|
||||||
|
#### IEx
|
||||||
|
|
||||||
|
* [IEx.Autocomplete] Speed up loading of struct suggestions
|
||||||
|
|
||||||
### 2. Bug fixes
|
### 2. Bug fixes
|
||||||
|
|
||||||
#### Elixir
|
#### Elixir
|
||||||
|
|
||||||
* [Code] Ensure `:on_undefined_variable` option works as advertised (regression)
|
* [Code.Fragment] Fix `Code.Fragment.surround_context/2` for aliases and submodules of non-aliases
|
||||||
* [Code] Format paths in `Code.with_diagnostic/2` as relative paths (regression)
|
* [Kernel] Ensure stacktrace is included when necessary when rescuing multiple exceptions in the same branch
|
||||||
* [Kernel] Raise when macros are given to dialyzer
|
* [Kernel] Fix index in error message for unused optional arguments
|
||||||
* [Kernel] Support bitstring specifiers as map keys in pattern (regression)
|
|
||||||
* [Module] Ensure that `Module.get_attribute/3` returns `nil` and not the given default value when an attribute has been explicitly set as `nil`
|
|
||||||
* [Task] Do not double log Task failure reports
|
|
||||||
|
|
||||||
#### ExUnit
|
#### ExUnit
|
||||||
|
|
||||||
* [ExUnit.CaptureLog] Allow capturing deprecated log level (regression)
|
* [ExUnit.Diff] Fix scenario where diff would not show up due to a timed-out loop
|
||||||
* [ExUnit.DocTest] Ensure proper line is returned when failing to parse doctest results
|
|
||||||
|
|
||||||
#### IEx
|
#### IEx
|
||||||
|
|
||||||
* [IEx] Fix IO operations not returning when booting IEx (regression)
|
* [IEx] Force group leader to run as a binary and unicode in IEx
|
||||||
|
|
||||||
#### Mix
|
#### Mix
|
||||||
|
|
||||||
* [mix deps] Ensure dependencies with `included_applications` can be loaded (regression)
|
* [mix compile] Do not assume `blake` is always available
|
||||||
* [mix format] Ensure proper formatter options are returned for files (regression)
|
* [mix format] Load and compile plugins if specified in subdirectories
|
||||||
|
|
||||||
### 3. Soft deprecations
|
## v1.15.4 (2023-07-18)
|
||||||
|
|
||||||
|
### 1. Bug fixes
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [mix archive.build] Disable protocol consolidation when building archives on archive.install
|
||||||
|
* [mix compile] Track removed files per local dependency (this addresses a bug where files depending on modules from path dependencies always recompiled)
|
||||||
|
* [mix release] Do not strip relevant chunks from Erlang/OTP 26
|
||||||
|
|
||||||
|
## v1.15.3 (2023-07-15)
|
||||||
|
|
||||||
|
### 1. Enhancements
|
||||||
|
|
||||||
#### Elixir
|
#### Elixir
|
||||||
|
|
||||||
* [Kernel] Require pin variable when accessing variable inside binary size in match
|
* [Kernel] Improve stacktraces when executing unnested Elixir code in a file
|
||||||
|
|
||||||
## v1.15.0-rc.0 (2022-05-22)
|
#### Mix
|
||||||
|
|
||||||
|
* [Mix] Allow to opt-out of starting apps in `Mix.install/2`
|
||||||
|
|
||||||
|
### 2. Bug fixes
|
||||||
|
|
||||||
|
#### Elixir
|
||||||
|
|
||||||
|
* [Code] Ensure `with_diagnostics` propagate warnings from inner Erlang passes
|
||||||
|
|
||||||
|
#### IEx
|
||||||
|
|
||||||
|
* [IEx] Fix `--remsh` on Erlang/OTP 25 and earlier
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [mix compile.elixir] Ensure `__mix_recompile__?` callbacks are properly invoked
|
||||||
|
|
||||||
|
## v1.15.2 (2023-07-01)
|
||||||
|
|
||||||
|
### 1. Bug fixes
|
||||||
|
|
||||||
|
#### IEx
|
||||||
|
|
||||||
|
* [IEx] Fix CLI being unable to boot on Windows
|
||||||
|
|
||||||
|
## v1.15.1 (2023-06-30)
|
||||||
|
|
||||||
|
### 1. Enhancements
|
||||||
|
|
||||||
|
* [Code] `Code.string_to_quoted/2` honors `:static_atoms_encoder` for multi-letter sigils
|
||||||
|
|
||||||
|
### 2. Bug fixes
|
||||||
|
|
||||||
|
#### ExUnit
|
||||||
|
|
||||||
|
* [ExUnit.CaptureLog] Fix race condition on concurrent `capture_log`
|
||||||
|
* [ExUnit.CaptureLog] Respect options passed to nested `capture_log` calls
|
||||||
|
* [ExUnit.Doctest] Properly compile doctests without results terminated by fences
|
||||||
|
* [ExUnit.Doctest] Allow variables defined in doctests to be used in expectation
|
||||||
|
|
||||||
|
#### IEx
|
||||||
|
|
||||||
|
* [IEx] Ensure `pry` works on Erlang/OTP 25 and earlier while IEx is booting
|
||||||
|
* [IEx] `Code.Fragment.surround_context` considers surround context around spaces and parens
|
||||||
|
|
||||||
|
#### Logger
|
||||||
|
|
||||||
|
* [Logger] Do not assume Logger has been loaded at compile-time
|
||||||
|
* [Logger.Formatter] Properly handle `:function` as metadata
|
||||||
|
|
||||||
|
#### Mix
|
||||||
|
|
||||||
|
* [mix compile] Ensure the current project is available on the code path after its Elixir sources are compiled
|
||||||
|
* [mix compile] Guarantee yecc/leex are available when emitting warnings from previous runs
|
||||||
|
* [mix compile] Fix bug where an external resource was deleted after its
|
||||||
|
mtime was successfully retrieved
|
||||||
|
* [mix compile] Track removed modules and exports across local deps
|
||||||
|
* [mix deps] Fix an issue where dependencies could not be started in an umbrella projects
|
||||||
|
* [mix release] Properly handle optional dependencies when there is a conflict in the application start mode
|
||||||
|
* [mix release] Remove `--werl` from release scripts on Erlang/OTP 26
|
||||||
|
|
||||||
|
## v1.15.0 (2023-06-19)
|
||||||
|
|
||||||
### 1. Enhancements
|
### 1. Enhancements
|
||||||
|
|
||||||
@@ -175,6 +307,7 @@ new features and on compatibility.
|
|||||||
* [Date] Add `Date.before?/2` and `Date.after?/2`
|
* [Date] Add `Date.before?/2` and `Date.after?/2`
|
||||||
* [DateTime] Add `DateTime.before?/2` and `DateTime.after?/2`
|
* [DateTime] Add `DateTime.before?/2` and `DateTime.after?/2`
|
||||||
* [DateTime] Support precision in `DateTime.utc_now/2`
|
* [DateTime] Support precision in `DateTime.utc_now/2`
|
||||||
|
* [File] Support distributed `File.Stream`
|
||||||
* [Inspect] `Inspect` now renders `'charlists'` as `~c"charlists"` by default
|
* [Inspect] `Inspect` now renders `'charlists'` as `~c"charlists"` by default
|
||||||
* [Kernel] Break down `case` and `cond` inside `dbg/2`
|
* [Kernel] Break down `case` and `cond` inside `dbg/2`
|
||||||
* [Kernel] Add `t:nonempty_binary/0` and `t:nonempty_bitstring/0`
|
* [Kernel] Add `t:nonempty_binary/0` and `t:nonempty_bitstring/0`
|
||||||
@@ -195,6 +328,8 @@ new features and on compatibility.
|
|||||||
* [NaiveDateTime] Add `NaiveDateTime.beginning_of_day/1` and `NaiveDateTime.end_of_day/1`
|
* [NaiveDateTime] Add `NaiveDateTime.beginning_of_day/1` and `NaiveDateTime.end_of_day/1`
|
||||||
* [NaiveDateTime] Add `NaiveDateTime.before?/2` and `NaiveDateTime.after?/2`
|
* [NaiveDateTime] Add `NaiveDateTime.before?/2` and `NaiveDateTime.after?/2`
|
||||||
* [NaiveDateTime] Support precision in `NaiveDateTime.utc_now/2`
|
* [NaiveDateTime] Support precision in `NaiveDateTime.utc_now/2`
|
||||||
|
* [Module] Mark functions as generated in "Docs" chunk
|
||||||
|
* [Module] Add `Module.get_last_attribute/3`
|
||||||
* [OptionParser] Support `:return_separator` option
|
* [OptionParser] Support `:return_separator` option
|
||||||
* [Process] Add `Process.alias/0,1` and `Process.unalias/1`
|
* [Process] Add `Process.alias/0,1` and `Process.unalias/1`
|
||||||
* [Range] Add `Range.split/2`
|
* [Range] Add `Range.split/2`
|
||||||
@@ -204,6 +339,7 @@ new features and on compatibility.
|
|||||||
* [System] Support `:lines` in `System.cmd/3` to capture output line by line
|
* [System] Support `:lines` in `System.cmd/3` to capture output line by line
|
||||||
* [Task] Remove head of line blocking on `Task.yield_many/2`
|
* [Task] Remove head of line blocking on `Task.yield_many/2`
|
||||||
* [Task] Enable selective receive optimizations in Erlang/OTP 26+
|
* [Task] Enable selective receive optimizations in Erlang/OTP 26+
|
||||||
|
* [Task] Reduce tasks footprint by avoiding unecessary work during spawning
|
||||||
* [Task.Supervisor] Do not copy args on temporary `Task.Supervisor.start_child/2`
|
* [Task.Supervisor] Do not copy args on temporary `Task.Supervisor.start_child/2`
|
||||||
* [Time] Add `Time.before?/2` and `Time.after?/2`
|
* [Time] Add `Time.before?/2` and `Time.after?/2`
|
||||||
* [URI] Add `URI.append_path/2`
|
* [URI] Add `URI.append_path/2`
|
||||||
@@ -212,7 +348,9 @@ new features and on compatibility.
|
|||||||
|
|
||||||
* [ExUnit] Add more color configuration to ExUnit CLI formatter
|
* [ExUnit] Add more color configuration to ExUnit CLI formatter
|
||||||
* [ExUnit.Callbacks] Accept `{module, function}` tuples in ExUnit `setup` callbacks
|
* [ExUnit.Callbacks] Accept `{module, function}` tuples in ExUnit `setup` callbacks
|
||||||
|
* [ExUnit.Case] Add `ExUnit.Case.get_last_registered_test/1`
|
||||||
* [ExUnit.Doctest] Add `ExUnit.DocTest.doctest_file/2`
|
* [ExUnit.Doctest] Add `ExUnit.DocTest.doctest_file/2`
|
||||||
|
* [ExUnit.Doctest] Include `doctest_data` in doctest tags
|
||||||
* [ExUnit.Formatter] When comparing two anonymous functions, defined at the same place but capturing a different environment, we will now also diff the environments
|
* [ExUnit.Formatter] When comparing two anonymous functions, defined at the same place but capturing a different environment, we will now also diff the environments
|
||||||
|
|
||||||
#### IEx
|
#### IEx
|
||||||
@@ -267,16 +405,22 @@ new features and on compatibility.
|
|||||||
* [Kernel] Expand macros on the left side of -> in `try/rescue`
|
* [Kernel] Expand macros on the left side of -> in `try/rescue`
|
||||||
* [Kernel] Raise on misplaced `...` inside typespecs
|
* [Kernel] Raise on misplaced `...` inside typespecs
|
||||||
* [Kernel] Do not import `behaviour_info` and `module_info` functions from Erlang modules
|
* [Kernel] Do not import `behaviour_info` and `module_info` functions from Erlang modules
|
||||||
|
* [Kernel] Raise when macros are given to dialyzer
|
||||||
* [Kernel.ParallelCompiler] Make sure compiler doesn't crash when there are stray messages in the inbox
|
* [Kernel.ParallelCompiler] Make sure compiler doesn't crash when there are stray messages in the inbox
|
||||||
* [Kernel.ParallelCompiler] Track compile and runtime warnings separately
|
* [Kernel.ParallelCompiler] Track compile and runtime warnings separately
|
||||||
|
* [Module] Ensure that `Module.get_attribute/3` returns `nil` and not the given default value when an attribute has been explicitly set as `nil`
|
||||||
* [System] Fix race condition when a script would terminate before `System.stop/1` executes
|
* [System] Fix race condition when a script would terminate before `System.stop/1` executes
|
||||||
|
* [Task] Do not double log Task failure reports
|
||||||
* [URI] Make sure `URI.merge/2` works accordingly with relative paths
|
* [URI] Make sure `URI.merge/2` works accordingly with relative paths
|
||||||
|
|
||||||
#### ExUnit
|
#### ExUnit
|
||||||
|
|
||||||
* [ExUnit] Fix crash when `@tag capture_log: true` was set to true and the Logger application was shut down in the middle of the test
|
* [ExUnit] Fix crash when `@tag capture_log: true` was set to true and the Logger application was shut down in the middle of the test
|
||||||
* [ExUnit] Do not merge context as tags inside the runner to reduce memory usage when emitting events to formatters
|
* [ExUnit] Do not merge context as tags inside the runner to reduce memory usage when emitting events to formatters
|
||||||
|
* [ExUnit] Mark test cases as invalid when an exit occurs during `setup_all`
|
||||||
* [ExUnit] Do not expand or collect vars from quote in ExUnit assertions
|
* [ExUnit] Do not expand or collect vars from quote in ExUnit assertions
|
||||||
|
* [ExUnit.DocTest] Ensure proper line is returned when failing to parse doctest results
|
||||||
|
* [ExUnit.Doctest] Fix line information when a doctest with multiple assertions fails
|
||||||
|
|
||||||
#### IEx
|
#### IEx
|
||||||
|
|
||||||
@@ -288,6 +432,7 @@ new features and on compatibility.
|
|||||||
|
|
||||||
* [mix compile] Include `cwd` in compiler cache key
|
* [mix compile] Include `cwd` in compiler cache key
|
||||||
* [mix release] Fix Windows service when invoking `erlsrv.exe` in path with spaces
|
* [mix release] Fix Windows service when invoking `erlsrv.exe` in path with spaces
|
||||||
|
* [mix xref] Raise early if `mix xref` is used at the umbrella root
|
||||||
|
|
||||||
### 3. Soft deprecations (no warnings emitted)
|
### 3. Soft deprecations (no warnings emitted)
|
||||||
|
|
||||||
@@ -295,6 +440,7 @@ new features and on compatibility.
|
|||||||
|
|
||||||
* [File] `File.cp/3` and `File.cp_r/3` with a function as third argument
|
* [File] `File.cp/3` and `File.cp_r/3` with a function as third argument
|
||||||
is deprecated in favor of a keyword list
|
is deprecated in favor of a keyword list
|
||||||
|
* [Kernel] Require pin variable when accessing variable inside binary size in match
|
||||||
* [Kernel.ParallelCompiler] Require the `:return_diagnostics` option to be
|
* [Kernel.ParallelCompiler] Require the `:return_diagnostics` option to be
|
||||||
set to true when compiling or requiring code
|
set to true when compiling or requiring code
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ PREFIX ?= /usr/local
|
|||||||
TEST_FILES ?= "*_test.exs"
|
TEST_FILES ?= "*_test.exs"
|
||||||
SHARE_PREFIX ?= $(PREFIX)/share
|
SHARE_PREFIX ?= $(PREFIX)/share
|
||||||
MAN_PREFIX ?= $(SHARE_PREFIX)/man
|
MAN_PREFIX ?= $(SHARE_PREFIX)/man
|
||||||
#CANONICAL := MAJOR.MINOR/
|
CANONICAL := 1.15/
|
||||||
CANONICAL ?= main/
|
CANONICAL ?= main/
|
||||||
DOCS_FORMAT ?= html
|
DOCS_FORMAT ?= html
|
||||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
6. Copy the relevant bits from /CHANGELOG.md to the GitHub release and publish it
|
6. Copy the relevant bits from /CHANGELOG.md to the GitHub release and publish it
|
||||||
|
|
||||||
7. Add the release to `elixir.csv` with the minimum supported OTP version (all releases), update `erlang.csv` to the latest supported OTP version, and `_data/elixir-versions.yml` (except for RCs) files in `elixir-lang/elixir-lang.github.com`
|
7. Update `_data/elixir-versions.yml` (except for RCs) in `elixir-lang/elixir-lang.github.com`
|
||||||
|
|
||||||
## Creating a new vMAJOR.MINOR branch (after first rc)
|
## Creating a new vMAJOR.MINOR branch (after first rc)
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -6,12 +6,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
|||||||
|
|
||||||
Elixir version | Support
|
Elixir version | Support
|
||||||
:------------- | :-----------------------------
|
:------------- | :-----------------------------
|
||||||
1.15 | Development
|
1.15 | Bug fixes and security patches
|
||||||
1.14 | Bug fixes and security patches
|
1.14 | Security patches only
|
||||||
1.13 | Security patches only
|
1.13 | Security patches only
|
||||||
1.12 | Security patches only
|
1.12 | Security patches only
|
||||||
1.11 | Security patches only
|
1.11 | Security patches only
|
||||||
1.10 | Security patches only
|
|
||||||
|
|
||||||
## Announcements
|
## Announcements
|
||||||
|
|
||||||
|
|||||||
+11
-6
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
ELIXIR_VERSION=1.15.0-rc.1
|
ELIXIR_VERSION=1.15.8
|
||||||
|
|
||||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||||
cat <<USAGE >&2
|
cat <<USAGE >&2
|
||||||
@@ -94,6 +94,7 @@ starts_with () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ERL_EXEC="erl"
|
ERL_EXEC="erl"
|
||||||
|
MODE="cli"
|
||||||
I=1
|
I=1
|
||||||
E=0
|
E=0
|
||||||
LENGTH=$#
|
LENGTH=$#
|
||||||
@@ -104,13 +105,17 @@ while [ $I -le $LENGTH ]; do
|
|||||||
S=0
|
S=0
|
||||||
C=0
|
C=0
|
||||||
case "$1" in
|
case "$1" in
|
||||||
+elixirc|+iex)
|
+elixirc)
|
||||||
C=1
|
C=1
|
||||||
;;
|
;;
|
||||||
-v|--no-halt|--dbg)
|
+iex)
|
||||||
|
C=1
|
||||||
|
MODE="iex"
|
||||||
|
;;
|
||||||
|
-v|--no-halt)
|
||||||
C=1
|
C=1
|
||||||
;;
|
;;
|
||||||
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex)
|
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
|
||||||
C=2
|
C=2
|
||||||
;;
|
;;
|
||||||
--rpc-eval)
|
--rpc-eval)
|
||||||
@@ -223,12 +228,12 @@ fi
|
|||||||
ERTS_BIN=
|
ERTS_BIN=
|
||||||
ERTS_BIN="$ERTS_BIN"
|
ERTS_BIN="$ERTS_BIN"
|
||||||
|
|
||||||
set -- "$ERTS_BIN$ERL_EXEC" -noshell -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS -s elixir start_cli $ERL "$@"
|
set -- "$ERTS_BIN$ERL_EXEC" -noshell -elixir_root "$SCRIPT_PATH"/../lib -pa "$SCRIPT_PATH"/../lib/elixir/ebin $ELIXIR_ERL_OPTIONS -s elixir start_$MODE $ERL "$@"
|
||||||
|
|
||||||
if [ -n "$RUN_ERL_PIPE" ]; then
|
if [ -n "$RUN_ERL_PIPE" ]; then
|
||||||
ESCAPED=""
|
ESCAPED=""
|
||||||
for PART in "$@"; do
|
for PART in "$@"; do
|
||||||
ESCAPED="$ESCAPED $(echo "$PART" | sed 's@[^a-zA-Z0-9_/-]@\\&@g')"
|
ESCAPED="$ESCAPED $(printf '%s' "$PART" | sed 's@[^a-zA-Z0-9_/-]@\\&@g')"
|
||||||
done
|
done
|
||||||
mkdir -p "$RUN_ERL_PIPE"
|
mkdir -p "$RUN_ERL_PIPE"
|
||||||
mkdir -p "$RUN_ERL_LOG"
|
mkdir -p "$RUN_ERL_LOG"
|
||||||
|
|||||||
+8
-6
@@ -1,6 +1,6 @@
|
|||||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||||
|
|
||||||
set ELIXIR_VERSION=1.15.0-rc.1
|
set ELIXIR_VERSION=1.15.8
|
||||||
|
|
||||||
setlocal enabledelayedexpansion
|
setlocal enabledelayedexpansion
|
||||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||||
@@ -81,9 +81,6 @@ set beforeExtra=
|
|||||||
rem Option which determines whether the loop is over
|
rem Option which determines whether the loop is over
|
||||||
set endLoop=0
|
set endLoop=0
|
||||||
|
|
||||||
rem Designates which mode / Elixir component to run as
|
|
||||||
set runMode="elixir"
|
|
||||||
|
|
||||||
rem Designates the path to the current script
|
rem Designates the path to the current script
|
||||||
set SCRIPT_PATH=%~dp0
|
set SCRIPT_PATH=%~dp0
|
||||||
|
|
||||||
@@ -106,7 +103,7 @@ if !endLoop! == 1 (
|
|||||||
)
|
)
|
||||||
rem ******* EXECUTION OPTIONS **********************
|
rem ******* EXECUTION OPTIONS **********************
|
||||||
if !par!=="--werl" (set useWerl=1 && goto startloop)
|
if !par!=="--werl" (set useWerl=1 && goto startloop)
|
||||||
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && goto startloop)
|
if !par!=="+iex" (set parsElixir=!parsElixir! +iex && set useIEx=1 && goto startloop)
|
||||||
if !par!=="+elixirc" (set parsElixir=!parsElixir! +elixirc && goto startloop)
|
if !par!=="+elixirc" (set parsElixir=!parsElixir! +elixirc && goto startloop)
|
||||||
rem ******* EVAL PARAMETERS ************************
|
rem ******* EVAL PARAMETERS ************************
|
||||||
if ""==!par:-e=! (
|
if ""==!par:-e=! (
|
||||||
@@ -164,8 +161,13 @@ reg query HKCU\Console /v VirtualTerminalLevel 2>nul | findstr /e "0x1" >nul 2>n
|
|||||||
if %errorlevel% == 0 (
|
if %errorlevel% == 0 (
|
||||||
set beforeExtra=-elixir ansi_enabled true !beforeExtra!
|
set beforeExtra=-elixir ansi_enabled true !beforeExtra!
|
||||||
)
|
)
|
||||||
|
if defined useIEx (
|
||||||
|
set beforeExtra=-s elixir start_iex !beforeExtra!
|
||||||
|
) else (
|
||||||
|
set beforeExtra=-s elixir start_cli !beforeExtra!
|
||||||
|
)
|
||||||
|
|
||||||
set beforeExtra=-noshell -elixir_root !SCRIPT_PATH!..\lib -pa !SCRIPT_PATH!..\lib\elixir\ebin -s elixir start_cli !beforeExtra!
|
set beforeExtra=-noshell -elixir_root "!SCRIPT_PATH!..\lib" -pa "!SCRIPT_PATH!..\lib\elixir\ebin" !beforeExtra!
|
||||||
|
|
||||||
if defined ELIXIR_CLI_DRY_RUN (
|
if defined ELIXIR_CLI_DRY_RUN (
|
||||||
if defined useWerl (
|
if defined useWerl (
|
||||||
|
|||||||
@@ -30,4 +30,4 @@ readlink_f () {
|
|||||||
|
|
||||||
SELF=$(readlink_f "$0")
|
SELF=$(readlink_f "$0")
|
||||||
SCRIPT_PATH=$(dirname "$SELF")
|
SCRIPT_PATH=$(dirname "$SELF")
|
||||||
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user elixir" -e ":elixir.start_iex()" +iex "$@"
|
exec "$SCRIPT_PATH"/elixir --no-halt --erl "-user elixir" +iex "$@"
|
||||||
|
|||||||
+1
-1
@@ -24,6 +24,6 @@ goto end
|
|||||||
|
|
||||||
:run
|
:run
|
||||||
if defined IEX_WITH_WERL (set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
|
if defined IEX_WITH_WERL (set __ELIXIR_IEX_FLAGS=--werl) else (set __ELIXIR_IEX_FLAGS=)
|
||||||
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" -e ":elixir.start_iex()" +iex %__ELIXIR_IEX_FLAGS% %*
|
call "%~dp0\elixir.bat" --no-halt --erl "-user elixir" +iex %__ELIXIR_IEX_FLAGS% %*
|
||||||
:end
|
:end
|
||||||
endlocal
|
endlocal
|
||||||
|
|||||||
@@ -71,11 +71,9 @@ defmodule EEx.Compiler do
|
|||||||
{:ok, expr, new_line, new_column, rest} ->
|
{:ok, expr, new_line, new_column, rest} ->
|
||||||
{key, expr} =
|
{key, expr} =
|
||||||
case :elixir_tokenizer.tokenize(expr, 1, file: "eex", check_terminators: false) do
|
case :elixir_tokenizer.tokenize(expr, 1, file: "eex", check_terminators: false) do
|
||||||
{:ok, _line, _column, warnings, tokens} ->
|
{:ok, _line, _column, _warnings, tokens} ->
|
||||||
Enum.each(Enum.reverse(warnings), fn {location, file, msg} ->
|
# We ignore warnings because the code will be tokenized
|
||||||
:elixir_errors.erl_warn(location, file, msg)
|
# again later with the right line+column info
|
||||||
end)
|
|
||||||
|
|
||||||
token_key(tokens, expr)
|
token_key(tokens, expr)
|
||||||
|
|
||||||
{:error, _, _, _, _} ->
|
{:error, _, _, _, _} ->
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ defmodule EEx.TokenizerTest do
|
|||||||
|
|
||||||
@opts [indentation: 0, trim: false]
|
@opts [indentation: 0, trim: false]
|
||||||
|
|
||||||
test "simple chars lists" do
|
test "simple charlists" do
|
||||||
assert EEx.tokenize(~c"foo", @opts) ==
|
assert EEx.tokenize(~c"foo", @opts) ==
|
||||||
{:ok, [{:text, ~c"foo", %{column: 1, line: 1}}, {:eof, %{column: 4, line: 1}}]}
|
{:ok, [{:text, ~c"foo", %{column: 1, line: 1}}, {:eof, %{column: 4, line: 1}}]}
|
||||||
end
|
end
|
||||||
|
|||||||
+28
-31
@@ -463,17 +463,13 @@ defmodule EExTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "when middle expression has a modifier" do
|
test "when trying to use marker '|' without implementation" do
|
||||||
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
msg =
|
||||||
EEx.compile_string("foo <%= if true do %>true<%= else %>false<% end %>")
|
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/
|
||||||
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "when end expression has a modifier" do
|
assert_raise EEx.SyntaxError, msg, fn ->
|
||||||
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
EEx.compile_string("<%| true %>")
|
||||||
EEx.compile_string("foo <%= if true do %>true<% else %>false<%= end %>")
|
end
|
||||||
end) =~
|
|
||||||
~s[unexpected beginning of EEx tag \"<%=\" on \"<%= end %>\"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "when trying to use marker '/' without implementation" do
|
test "when trying to use marker '/' without implementation" do
|
||||||
@@ -485,17 +481,6 @@ defmodule EExTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "when trying to use marker '|' without implementation" do
|
|
||||||
msg =
|
|
||||||
~r/unsupported EEx syntax <%| %> \(the syntax is valid but not supported by the current EEx engine\)/
|
|
||||||
|
|
||||||
assert_raise EEx.SyntaxError, msg, fn ->
|
|
||||||
EEx.compile_string("<%| true %>")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "error messages" do
|
|
||||||
test "honor line numbers" do
|
test "honor line numbers" do
|
||||||
assert_raise EEx.SyntaxError,
|
assert_raise EEx.SyntaxError,
|
||||||
"nofile:100:6: expected closing '%>' for EEx expression",
|
"nofile:100:6: expected closing '%>' for EEx expression",
|
||||||
@@ -516,18 +501,30 @@ defmodule EExTest do
|
|||||||
EEx.compile_string("foo <%= bar", file: "my_file.eex")
|
EEx.compile_string("foo <%= bar", file: "my_file.eex")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "when <%!-- is not closed" do
|
describe "warnings" do
|
||||||
message = """
|
test "when middle expression has a modifier" do
|
||||||
my_file.eex:1:5: expected closing '--%>' for EEx expression
|
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||||
|
|
EEx.compile_string("foo <%= if true do %>true<%= else %>false<% end %>")
|
||||||
1 | foo <%!-- bar
|
end) =~ ~s[unexpected beginning of EEx tag \"<%=\" on \"<%= else %>\"]
|
||||||
| ^\
|
end
|
||||||
"""
|
|
||||||
|
|
||||||
assert_raise EEx.SyntaxError, message, fn ->
|
test "when end expression has a modifier" do
|
||||||
EEx.compile_string("foo <%!-- bar", file: "my_file.eex")
|
assert ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||||
end
|
EEx.compile_string("foo <%= if true do %>true<% else %>false<%= end %>")
|
||||||
|
end) =~
|
||||||
|
~s[unexpected beginning of EEx tag \"<%=\" on \"<%= end %>\"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "from tokenizer" do
|
||||||
|
warning =
|
||||||
|
ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||||
|
EEx.compile_string(~s'<%= :"foo" %>', file: "tokenizer.ex")
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert warning =~ "found quoted atom \"foo\" but the quotes are not required"
|
||||||
|
assert warning =~ "tokenizer.ex:1:5"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -191,9 +191,13 @@ defmodule Access do
|
|||||||
case __STACKTRACE__ do
|
case __STACKTRACE__ do
|
||||||
[unquote(top) | _] ->
|
[unquote(top) | _] ->
|
||||||
reason =
|
reason =
|
||||||
"#{inspect(unquote(module))} does not implement the Access behaviour. " <>
|
"""
|
||||||
"If you are using get_in/put_in/update_in, you can specify the field " <>
|
#{inspect(unquote(module))} does not implement the Access behaviour
|
||||||
"to be accessed using Access.key!/1"
|
|
||||||
|
You can use the "struct.field" syntax to access struct fields. \
|
||||||
|
You can also use Access.key!/1 to access struct fields dynamically \
|
||||||
|
inside get_in/put_in/update_in\
|
||||||
|
"""
|
||||||
|
|
||||||
%{unquote(exception) | reason: reason}
|
%{unquote(exception) | reason: reason}
|
||||||
|
|
||||||
@@ -326,9 +330,23 @@ defmodule Access do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get(list, key, _default) when is_list(list) and is_integer(key) do
|
||||||
|
raise ArgumentError, """
|
||||||
|
the Access module does not support accessing lists by index, got: #{inspect(key)}
|
||||||
|
|
||||||
|
Accessing a list by index is typically discouraged in Elixir, \
|
||||||
|
instead we prefer to use the Enum module to manipulate lists \
|
||||||
|
as a whole. If you really must access a list element by index, \
|
||||||
|
you can Enum.at/1 or the functions in the List module\
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
def get(list, key, _default) when is_list(list) do
|
def get(list, key, _default) when is_list(list) do
|
||||||
raise ArgumentError,
|
raise ArgumentError, """
|
||||||
"the Access calls for keywords expect the key to be an atom, got: " <> inspect(key)
|
the Access module supports only keyword lists (with atom keys), got: #{inspect(key)}
|
||||||
|
|
||||||
|
If you want to search lists of tuples, use List.keyfind/3\
|
||||||
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(nil, _key, default) do
|
def get(nil, _key, default) do
|
||||||
@@ -377,10 +395,26 @@ defmodule Access do
|
|||||||
Map.get_and_update(map, key, fun)
|
Map.get_and_update(map, key, fun)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_and_update(list, key, fun) when is_list(list) do
|
def get_and_update(list, key, fun) when is_list(list) and is_atom(key) do
|
||||||
Keyword.get_and_update(list, key, fun)
|
Keyword.get_and_update(list, key, fun)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_and_update(list, key, _fun) when is_list(list) and is_integer(key) do
|
||||||
|
raise ArgumentError, """
|
||||||
|
the Access module does not support accessing lists by index, got: #{inspect(key)}
|
||||||
|
|
||||||
|
Accessing a list by index is typically discouraged in Elixir, \
|
||||||
|
instead we prefer to use the Enum module to manipulate lists \
|
||||||
|
as a whole. If you really must mostify a list element by index, \
|
||||||
|
you can Access.at/1 or the functions in the List module\
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_and_update(list, key, _fun) when is_list(list) do
|
||||||
|
raise ArgumentError,
|
||||||
|
"the Access module supports only keyword lists (with atom keys), got: " <> inspect(key)
|
||||||
|
end
|
||||||
|
|
||||||
def get_and_update(nil, key, _fun) do
|
def get_and_update(nil, key, _fun) do
|
||||||
raise ArgumentError, "could not put/update key #{inspect(key)} on a nil value"
|
raise ArgumentError, "could not put/update key #{inspect(key)} on a nil value"
|
||||||
end
|
end
|
||||||
@@ -507,6 +541,21 @@ defmodule Access do
|
|||||||
iex> get_in(map, [Access.key!(:user), Access.key!(:unknown)])
|
iex> get_in(map, [Access.key!(:user), Access.key!(:unknown)])
|
||||||
** (KeyError) key :unknown not found in: %{name: \"john\"}
|
** (KeyError) key :unknown not found in: %{name: \"john\"}
|
||||||
|
|
||||||
|
The examples above could be partially written as:
|
||||||
|
|
||||||
|
iex> map = %{user: %{name: "john"}}
|
||||||
|
iex> map.user.name
|
||||||
|
"john"
|
||||||
|
iex> get_and_update_in(map.user.name, fn prev ->
|
||||||
|
...> {prev, String.upcase(prev)}
|
||||||
|
...> end)
|
||||||
|
{"john", %{user: %{name: "JOHN"}}}
|
||||||
|
|
||||||
|
However, it is not possible to remove fields using the dot notation,
|
||||||
|
as it is implified those fields must also be present. In any case,
|
||||||
|
`Access.key!/1` is useful when the key is not known in advance
|
||||||
|
and must be accessed dynamically.
|
||||||
|
|
||||||
An error is raised if the accessed structure is not a map/struct:
|
An error is raised if the accessed structure is not a map/struct:
|
||||||
|
|
||||||
iex> get_in([], [Access.key!(:foo)])
|
iex> get_in([], [Access.key!(:foo)])
|
||||||
|
|||||||
@@ -1136,7 +1136,10 @@ defmodule Code do
|
|||||||
* syntax keywords (`fn`, `do`, `else`, and so on)
|
* syntax keywords (`fn`, `do`, `else`, and so on)
|
||||||
|
|
||||||
* atoms containing interpolation (`:"#{1 + 1} is two"`), as these
|
* atoms containing interpolation (`:"#{1 + 1} is two"`), as these
|
||||||
atoms are constructed at runtime.
|
atoms are constructed at runtime
|
||||||
|
|
||||||
|
* atoms used to represent single-letter sigils like `:sigil_X`
|
||||||
|
(but multi-letter sigils like `:sigil_XYZ` are encoded).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec string_to_quoted(List.Chars.t(), keyword) ::
|
@spec string_to_quoted(List.Chars.t(), keyword) ::
|
||||||
|
|||||||
@@ -431,7 +431,7 @@ defmodule Code.Formatter do
|
|||||||
{color("nil", nil, state.inspect_opts), state}
|
{color("nil", nil, state.inspect_opts), state}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp quoted_to_algebra({:__block__, meta, _} = block, _context, state) do
|
defp quoted_to_algebra({:__block__, meta, args} = block, _context, state) when is_list(args) do
|
||||||
{block, state} = block_to_algebra(block, line(meta), closing_line(meta), state)
|
{block, state} = block_to_algebra(block, line(meta), closing_line(meta), state)
|
||||||
{surround("(", block, ")"), state}
|
{surround("(", block, ")"), state}
|
||||||
end
|
end
|
||||||
@@ -1419,7 +1419,9 @@ defmodule Code.Formatter do
|
|||||||
|
|
||||||
defp bitstring_segment_to_algebra({{:"::", _, [segment, spec]}, i}, state, last) do
|
defp bitstring_segment_to_algebra({{:"::", _, [segment, spec]}, i}, state, last) do
|
||||||
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
|
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
|
||||||
{spec, state} = bitstring_spec_to_algebra(spec, state, state.normalize_bitstring_modifiers)
|
|
||||||
|
{spec, state} =
|
||||||
|
bitstring_spec_to_algebra(spec, state, state.normalize_bitstring_modifiers, :"::")
|
||||||
|
|
||||||
spec = wrap_in_parens_if_inspected_atom(spec)
|
spec = wrap_in_parens_if_inspected_atom(spec)
|
||||||
spec = if i == last, do: bitstring_wrap_parens(spec, i, last), else: spec
|
spec = if i == last, do: bitstring_wrap_parens(spec, i, last), else: spec
|
||||||
@@ -1438,15 +1440,17 @@ defmodule Code.Formatter do
|
|||||||
{bitstring_wrap_parens(doc, i, last), state}
|
{bitstring_wrap_parens(doc, i, last), state}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp bitstring_spec_to_algebra({op, _, [left, right]}, state, normalize_modifiers)
|
defp bitstring_spec_to_algebra({op, _, [left, right]}, state, normalize_modifiers, paren_op)
|
||||||
when op in [:-, :*] do
|
when op in [:-, :*] do
|
||||||
normalize_modifiers = normalize_modifiers && op != :*
|
normalize_modifiers = normalize_modifiers && op != :*
|
||||||
{left, state} = bitstring_spec_to_algebra(left, state, normalize_modifiers)
|
{left, state} = bitstring_spec_to_algebra(left, state, normalize_modifiers, op)
|
||||||
{right, state} = bitstring_spec_element_to_algebra(right, state, normalize_modifiers)
|
{right, state} = bitstring_spec_element_to_algebra(right, state, normalize_modifiers)
|
||||||
{concat(concat(left, Atom.to_string(op)), right), state}
|
doc = concat(concat(left, Atom.to_string(op)), right)
|
||||||
|
doc = if paren_op == :*, do: wrap_in_parens(doc), else: doc
|
||||||
|
{doc, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp bitstring_spec_to_algebra(spec, state, normalize_modifiers) do
|
defp bitstring_spec_to_algebra(spec, state, normalize_modifiers, _paren_op) do
|
||||||
bitstring_spec_element_to_algebra(spec, state, normalize_modifiers)
|
bitstring_spec_element_to_algebra(spec, state, normalize_modifiers)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,6 @@ defmodule Code.Fragment do
|
|||||||
This module provides conveniences for analyzing fragments of
|
This module provides conveniences for analyzing fragments of
|
||||||
textual code and extract available information whenever possible.
|
textual code and extract available information whenever possible.
|
||||||
|
|
||||||
Most of the functions in this module provide a best-effort
|
|
||||||
and may not be accurate under all circumstances. Read each
|
|
||||||
documentation for more information.
|
|
||||||
|
|
||||||
This module should be considered experimental.
|
This module should be considered experimental.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -623,7 +619,7 @@ defmodule Code.Fragment do
|
|||||||
{reversed_pre, post} = adjust_position(reversed_pre, post)
|
{reversed_pre, post} = adjust_position(reversed_pre, post)
|
||||||
|
|
||||||
case take_identifier(post, []) do
|
case take_identifier(post, []) do
|
||||||
{_, [], _} ->
|
:none ->
|
||||||
maybe_operator(reversed_pre, post, line, opts)
|
maybe_operator(reversed_pre, post, line, opts)
|
||||||
|
|
||||||
{:identifier, reversed_post, rest} ->
|
{:identifier, reversed_post, rest} ->
|
||||||
@@ -631,7 +627,7 @@ defmodule Code.Fragment do
|
|||||||
reversed = reversed_post ++ reversed_pre
|
reversed = reversed_post ++ reversed_pre
|
||||||
|
|
||||||
case codepoint_cursor_context(reversed, opts) do
|
case codepoint_cursor_context(reversed, opts) do
|
||||||
{{:struct, acc}, offset} ->
|
{{:struct, acc}, offset} when acc != [] ->
|
||||||
build_surround({:struct, acc}, reversed, line, offset)
|
build_surround({:struct, acc}, reversed, line, offset)
|
||||||
|
|
||||||
{{:alias, acc}, offset} ->
|
{{:alias, acc}, offset} ->
|
||||||
@@ -681,6 +677,9 @@ defmodule Code.Fragment do
|
|||||||
{{:alias, acc}, offset} ->
|
{{:alias, acc}, offset} ->
|
||||||
build_surround({:alias, acc}, reversed, line, offset)
|
build_surround({:alias, acc}, reversed, line, offset)
|
||||||
|
|
||||||
|
{{:alias, parent, acc}, offset} ->
|
||||||
|
build_surround({:alias, parent, acc}, reversed, line, offset)
|
||||||
|
|
||||||
{{:struct, acc}, offset} ->
|
{{:struct, acc}, offset} ->
|
||||||
build_surround({:struct, acc}, reversed, line, offset)
|
build_surround({:struct, acc}, reversed, line, offset)
|
||||||
|
|
||||||
@@ -733,15 +732,31 @@ defmodule Code.Fragment do
|
|||||||
do: take_identifier(t, [h | acc])
|
do: take_identifier(t, [h | acc])
|
||||||
|
|
||||||
defp take_identifier(rest, acc) do
|
defp take_identifier(rest, acc) do
|
||||||
with {[?. | t], _} <- strip_spaces(rest, 0),
|
{stripped, _} = strip_spaces(rest, 0)
|
||||||
|
|
||||||
|
with [?. | t] <- stripped,
|
||||||
{[h | _], _} when h in ?A..?Z <- strip_spaces(t, 0) do
|
{[h | _], _} when h in ?A..?Z <- strip_spaces(t, 0) do
|
||||||
take_alias(rest, acc)
|
take_alias(rest, acc)
|
||||||
else
|
else
|
||||||
_ -> {:identifier, acc, rest}
|
# Consider it an identifier if we are at the end of line
|
||||||
|
# or if we have spaces not followed by . (call) or / (arity)
|
||||||
|
_ when acc == [] and (rest == [] or (hd(rest) in @space and hd(stripped) not in ~c"/.")) ->
|
||||||
|
{:identifier, acc, rest}
|
||||||
|
|
||||||
|
# If we are immediately followed by a container, we are still part of the identifier.
|
||||||
|
# We don't consider << as it _may_ be an operator.
|
||||||
|
_ when acc == [] and hd(stripped) in ~c"({[" ->
|
||||||
|
{:identifier, acc, rest}
|
||||||
|
|
||||||
|
_ when acc == [] ->
|
||||||
|
:none
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:identifier, acc, rest}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp take_alias([h | t], acc) when h not in @non_identifier,
|
defp take_alias([h | t], acc) when h in ?A..?Z or h in ?a..?z or h in ?0..?9 or h == ?_,
|
||||||
do: take_alias(t, [h | acc])
|
do: take_alias(t, [h | acc])
|
||||||
|
|
||||||
defp take_alias(rest, acc) do
|
defp take_alias(rest, acc) do
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ defmodule Config.Provider do
|
|||||||
* Make the runtime value match the compile time one
|
* Make the runtime value match the compile time one
|
||||||
|
|
||||||
* Recompile your project. If the misconfigured application is a dependency, \
|
* Recompile your project. If the misconfigured application is a dependency, \
|
||||||
you may need to run "mix deps.compile #{app} --force"
|
you may need to run "mix deps.clean #{app} --build"
|
||||||
|
|
||||||
* Alternatively, you can disable this check. If you are using releases, you can \
|
* Alternatively, you can disable this check. If you are using releases, you can \
|
||||||
set :validate_compile_env to false in your release configuration. If you are \
|
set :validate_compile_env to false in your release configuration. If you are \
|
||||||
|
|||||||
@@ -3009,13 +3009,16 @@ defmodule Enum do
|
|||||||
|
|
||||||
if first < count and amount > 0 do
|
if first < count and amount > 0 do
|
||||||
amount = Kernel.min(amount, count - first)
|
amount = Kernel.min(amount, count - first)
|
||||||
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
|
amount = amount_with_step(amount, step)
|
||||||
fun.(first, amount, step)
|
fun.(first, amount, step)
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp amount_with_step(amount, 1), do: amount
|
||||||
|
defp amount_with_step(amount, step), do: div(amount - 1, step) + 1
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns a subset list of the given `enumerable`, from `start_index` (zero-based)
|
Returns a subset list of the given `enumerable`, from `start_index` (zero-based)
|
||||||
with `amount` number of elements if available.
|
with `amount` number of elements if available.
|
||||||
@@ -4440,7 +4443,7 @@ defmodule Enum do
|
|||||||
|
|
||||||
if start >= 0 do
|
if start >= 0 do
|
||||||
amount = Kernel.min(amount, count - start)
|
amount = Kernel.min(amount, count - start)
|
||||||
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
|
amount = amount_with_step(amount, step)
|
||||||
fun.(start, amount, step)
|
fun.(start, amount, step)
|
||||||
else
|
else
|
||||||
[]
|
[]
|
||||||
@@ -4448,7 +4451,7 @@ defmodule Enum do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp slice_forward(list, start, amount, step) when is_list(list) do
|
defp slice_forward(list, start, amount, step) when is_list(list) do
|
||||||
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
|
amount = amount_with_step(amount, step)
|
||||||
slice_list(list, start, amount, step)
|
slice_list(list, start, amount, step)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -4458,7 +4461,7 @@ defmodule Enum do
|
|||||||
[]
|
[]
|
||||||
|
|
||||||
{:ok, count, fun} when is_function(fun, 1) ->
|
{:ok, count, fun} when is_function(fun, 1) ->
|
||||||
amount = Kernel.min(amount, count - start)
|
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
|
||||||
enumerable |> fun.() |> slice_exact(start, amount, step, count)
|
enumerable |> fun.() |> slice_exact(start, amount, step, count)
|
||||||
|
|
||||||
# TODO: Deprecate me in Elixir v1.18.
|
# TODO: Deprecate me in Elixir v1.18.
|
||||||
@@ -4473,8 +4476,7 @@ defmodule Enum do
|
|||||||
end
|
end
|
||||||
|
|
||||||
{:ok, count, fun} when is_function(fun, 3) ->
|
{:ok, count, fun} when is_function(fun, 3) ->
|
||||||
amount = Kernel.min(amount, count - start)
|
amount = Kernel.min(amount, count - start) |> amount_with_step(step)
|
||||||
amount = if step == 1, do: amount, else: div(amount - 1, step) + 1
|
|
||||||
fun.(start, amount, step)
|
fun.(start, amount, step)
|
||||||
|
|
||||||
{:error, module} ->
|
{:error, module} ->
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ defmodule Float do
|
|||||||
@moduledoc """
|
@moduledoc """
|
||||||
Functions for working with floating-point numbers.
|
Functions for working with floating-point numbers.
|
||||||
|
|
||||||
|
For mathematical operations on top of floating-points,
|
||||||
|
see Erlang's [`:math`](`:math`) module.
|
||||||
|
|
||||||
## Kernel functions
|
## Kernel functions
|
||||||
|
|
||||||
There are functions related to floating-point numbers on the `Kernel` module
|
There are functions related to floating-point numbers on the `Kernel` module
|
||||||
|
|||||||
@@ -593,11 +593,11 @@ defmodule IO do
|
|||||||
|
|
||||||
Another example where you might want to collect a user input
|
Another example where you might want to collect a user input
|
||||||
every new line and break on an empty line, followed by removing
|
every new line and break on an empty line, followed by removing
|
||||||
redundant new line characters (`"\n"`):
|
redundant new line characters (`"\\n"`):
|
||||||
|
|
||||||
IO.stream(:stdio, :line)
|
IO.stream(:stdio, :line)
|
||||||
|> Enum.take_while(&(&1 != "\n"))
|
|> Enum.take_while(&(&1 != "\\n"))
|
||||||
|> Enum.map(&String.replace(&1, "\n", ""))
|
|> Enum.map(&String.replace(&1, "\\n", ""))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec stream(device, :line | pos_integer) :: Enumerable.t()
|
@spec stream(device, :line | pos_integer) :: Enumerable.t()
|
||||||
|
|||||||
@@ -3555,10 +3555,12 @@ defmodule Kernel do
|
|||||||
not bootstrapped?(Macro) ->
|
not bootstrapped?(Macro) ->
|
||||||
nil
|
nil
|
||||||
|
|
||||||
not function? and __CALLER__.context == :match ->
|
not function? and (__CALLER__.context == :match or __CALLER__.context == :guard) ->
|
||||||
raise ArgumentError,
|
raise ArgumentError,
|
||||||
"""
|
"""
|
||||||
invalid write attribute syntax. If you want to define an attribute, don't do this:
|
invalid usage of module attributes. Module attributes cannot be used inside \
|
||||||
|
pattern matching (and guards) outside of a function. If you are trying to \
|
||||||
|
define an attribute, do not do this:
|
||||||
|
|
||||||
@foo = :value
|
@foo = :value
|
||||||
|
|
||||||
@@ -4901,17 +4903,6 @@ defmodule Kernel do
|
|||||||
{expanded, nil}
|
{expanded, nil}
|
||||||
end
|
end
|
||||||
|
|
||||||
# We do this so that the block is not tail-call optimized and stacktraces
|
|
||||||
# are not messed up. Basically, we just insert something between the return
|
|
||||||
# value of the block and what is returned by defmodule. Using just ":ok" or
|
|
||||||
# similar doesn't work because it's likely optimized away by the compiler.
|
|
||||||
block =
|
|
||||||
quote do
|
|
||||||
result = unquote(block)
|
|
||||||
:elixir_utils.noop()
|
|
||||||
result
|
|
||||||
end
|
|
||||||
|
|
||||||
escaped =
|
escaped =
|
||||||
case env do
|
case env do
|
||||||
%{function: nil, lexical_tracker: pid} when is_pid(pid) ->
|
%{function: nil, lexical_tracker: pid} when is_pid(pid) ->
|
||||||
|
|||||||
@@ -79,6 +79,27 @@ defmodule Port do
|
|||||||
are for advanced usage within the VM. Also consider using `System.cmd/3`
|
are for advanced usage within the VM. Also consider using `System.cmd/3`
|
||||||
if all you want is to execute a program and retrieve its return value.
|
if all you want is to execute a program and retrieve its return value.
|
||||||
|
|
||||||
|
> #### Windows argument splitting and untrusted arguments {: .warning}
|
||||||
|
>
|
||||||
|
> On Unix systems, arguments are passed to a new operating system
|
||||||
|
> process as an array of strings but on Windows it is up to the child
|
||||||
|
> process to parse them and some Windows programs may apply their own
|
||||||
|
> rules, which are inconsistent with the standard C runtime `argv` parsing
|
||||||
|
>
|
||||||
|
> This is particularly troublesome when invoking `.bat` or `.com` files
|
||||||
|
> as these run implicitly through `cmd.exe`, whose argument parsing is
|
||||||
|
> vulnerable to malicious input and can be used to run arbitrary shell
|
||||||
|
> commands.
|
||||||
|
>
|
||||||
|
> Therefore, if you are running on Windows and you execute batch
|
||||||
|
> files or `.com` applications, you must not pass untrusted input as
|
||||||
|
> arguments to the program. You may avoid accidentally executing them
|
||||||
|
> by explicitly passing the extension of the program you want to run,
|
||||||
|
> such as `.exe`, and double check the program is indeed not a batch
|
||||||
|
> file or `.com` application.
|
||||||
|
>
|
||||||
|
> This affects both `spawn` and `spawn_executable`.
|
||||||
|
|
||||||
### spawn
|
### spawn
|
||||||
|
|
||||||
The `:spawn` tuple receives a binary that is going to be executed as a
|
The `:spawn` tuple receives a binary that is going to be executed as a
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ defmodule Regex do
|
|||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
@deprecated "Use Kernel.is_struct/2 or pattern match on %Regex{} instead"
|
@deprecated "Use Kernel.is_struct(term, Regex) or pattern match on %Regex{} instead"
|
||||||
def regex?(term)
|
def regex?(term)
|
||||||
def regex?(%Regex{}), do: true
|
def regex?(%Regex{}), do: true
|
||||||
def regex?(_), do: false
|
def regex?(_), do: false
|
||||||
|
|||||||
@@ -996,6 +996,25 @@ defmodule System do
|
|||||||
`Port` module describes this problem and possible solutions under
|
`Port` module describes this problem and possible solutions under
|
||||||
the "Zombie processes" section.
|
the "Zombie processes" section.
|
||||||
|
|
||||||
|
> #### Windows argument splitting and untrusted arguments {: .warning}
|
||||||
|
>
|
||||||
|
> On Unix systems, arguments are passed to a new operating system
|
||||||
|
> process as an array of strings but on Windows it is up to the child
|
||||||
|
> process to parse them and some Windows programs may apply their own
|
||||||
|
> rules, which are inconsistent with the standard C runtime `argv` parsing
|
||||||
|
>
|
||||||
|
> This is particularly troublesome when invoking `.bat` or `.com` files
|
||||||
|
> as these run implicitly through `cmd.exe`, whose argument parsing is
|
||||||
|
> vulnerable to malicious input and can be used to run arbitrary shell
|
||||||
|
> commands.
|
||||||
|
>
|
||||||
|
> Therefore, if you are running on Windows and you execute batch
|
||||||
|
> files or `.com` applications, you must not pass untrusted input as
|
||||||
|
> arguments to the program. You may avoid accidentally executing them
|
||||||
|
> by explicitly passing the extension of the program you want to run,
|
||||||
|
> such as `.exe`, and double check the program is indeed not a batch
|
||||||
|
> file or `.com` application.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
iex> System.cmd("echo", ["hello"])
|
iex> System.cmd("echo", ["hello"])
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ defmodule Task.Supervisor do
|
|||||||
|
|
||||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||||
or an integer indicating the timeout value, defaults to 5000 milliseconds.
|
or an integer indicating the timeout value, defaults to 5000 milliseconds.
|
||||||
|
The tasks must trap exits for the timeout to have an effect.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec async(Supervisor.supervisor(), (-> any), Keyword.t()) :: Task.t()
|
@spec async(Supervisor.supervisor(), (-> any), Keyword.t()) :: Task.t()
|
||||||
@@ -183,6 +184,7 @@ defmodule Task.Supervisor do
|
|||||||
|
|
||||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||||
or an integer indicating the timeout value, defaults to 5000 milliseconds.
|
or an integer indicating the timeout value, defaults to 5000 milliseconds.
|
||||||
|
The tasks must trap exits for the timeout to have an effect.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec async(Supervisor.supervisor(), module, atom, [term], Keyword.t()) :: Task.t()
|
@spec async(Supervisor.supervisor(), module, atom, [term], Keyword.t()) :: Task.t()
|
||||||
@@ -208,6 +210,7 @@ defmodule Task.Supervisor do
|
|||||||
|
|
||||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||||
or an integer indicating the timeout value, defaults to 5000 milliseconds.
|
or an integer indicating the timeout value, defaults to 5000 milliseconds.
|
||||||
|
The tasks must trap exits for the timeout to have an effect.
|
||||||
|
|
||||||
## Compatibility with OTP behaviours
|
## Compatibility with OTP behaviours
|
||||||
|
|
||||||
@@ -337,6 +340,7 @@ defmodule Task.Supervisor do
|
|||||||
|
|
||||||
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
* `:shutdown` - `:brutal_kill` if the tasks must be killed directly on shutdown
|
||||||
or an integer indicating the timeout value. Defaults to `5000` milliseconds.
|
or an integer indicating the timeout value. Defaults to `5000` milliseconds.
|
||||||
|
The tasks must trap exits for the timeout to have an effect.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -455,6 +459,7 @@ defmodule Task.Supervisor do
|
|||||||
|
|
||||||
* `:shutdown` - `:brutal_kill` if the task must be killed directly on shutdown
|
* `:shutdown` - `:brutal_kill` if the task must be killed directly on shutdown
|
||||||
or an integer indicating the timeout value, defaults to 5000 milliseconds.
|
or an integer indicating the timeout value, defaults to 5000 milliseconds.
|
||||||
|
The task must trap exits for the timeout to have an effect.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec start_child(Supervisor.supervisor(), (-> any), keyword) ::
|
@spec start_child(Supervisor.supervisor(), (-> any), keyword) ::
|
||||||
|
|||||||
@@ -648,16 +648,16 @@ defmodule URI do
|
|||||||
scheme = String.downcase(scheme, :ascii)
|
scheme = String.downcase(scheme, :ascii)
|
||||||
|
|
||||||
case map do
|
case map do
|
||||||
%{port: port} when port != :undefined ->
|
%{port: port} when is_integer(port) ->
|
||||||
%{uri | scheme: scheme}
|
%{uri | scheme: scheme}
|
||||||
|
|
||||||
%{} ->
|
%{} ->
|
||||||
case default_port(scheme) do
|
%{uri | scheme: scheme, port: default_port(scheme)}
|
||||||
nil -> %{uri | scheme: scheme}
|
|
||||||
port -> %{uri | scheme: scheme, port: port}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
%{port: :undefined} ->
|
||||||
|
%{uri | port: nil}
|
||||||
|
|
||||||
%{} ->
|
%{} ->
|
||||||
uri
|
uri
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,12 +8,11 @@ Elixir applies bug fixes only to the latest minor branch. Security patches are a
|
|||||||
|
|
||||||
Elixir version | Support
|
Elixir version | Support
|
||||||
:------------- | :-----------------------------
|
:------------- | :-----------------------------
|
||||||
1.15 | Development
|
1.15 | Bug fixes and security patches
|
||||||
1.14 | Bug fixes and security patches
|
1.14 | Security patches only
|
||||||
1.13 | Security patches only
|
1.13 | Security patches only
|
||||||
1.12 | Security patches only
|
1.12 | Security patches only
|
||||||
1.11 | Security patches only
|
1.11 | Security patches only
|
||||||
1.10 | Security patches only
|
|
||||||
|
|
||||||
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
|
New releases are announced in the read-only [announcements mailing list](https://groups.google.com/group/elixir-lang-ann). All security releases [will be tagged with `[security]`](https://groups.google.com/forum/#!searchin/elixir-lang-ann/%5Bsecurity%5D%7Csort:date).
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
|
|||||||
Elixir version | Supported Erlang/OTP versions
|
Elixir version | Supported Erlang/OTP versions
|
||||||
:------------- | :-------------------------------
|
:------------- | :-------------------------------
|
||||||
1.15 | 24 - 26
|
1.15 | 24 - 26
|
||||||
1.14 | 23 - 25
|
1.14 | 23 - 25 (and Erlang/OTP 26 from v1.14.5)
|
||||||
1.13 | 22 - 24 (and Erlang/OTP 25 from v1.13.4)
|
1.13 | 22 - 24 (and Erlang/OTP 25 from v1.13.4)
|
||||||
1.12 | 22 - 24
|
1.12 | 22 - 24
|
||||||
1.11 | 21 - 23 (and Erlang/OTP 24 from v1.11.4)
|
1.11 | 21 - 23 (and Erlang/OTP 24 from v1.11.4)
|
||||||
@@ -82,7 +81,7 @@ Version | Deprecated feature | Replaced by (ava
|
|||||||
:-------| :-------------------------------------------------- | :---------------------------------------------------------------
|
:-------| :-------------------------------------------------- | :---------------------------------------------------------------
|
||||||
[v1.15] | `Calendar.ISO.day_of_week/3` | `Calendar.ISO.day_of_week/4` (v1.11)
|
[v1.15] | `Calendar.ISO.day_of_week/3` | `Calendar.ISO.day_of_week/4` (v1.11)
|
||||||
[v1.15] | `Exception.exception?/1` | `Kernel.is_exception/1` (v1.11)
|
[v1.15] | `Exception.exception?/1` | `Kernel.is_exception/1` (v1.11)
|
||||||
[v1.15] | `Regex.regex?/1` | `Kernel.is_struct/2` (v1.11)
|
[v1.15] | `Regex.regex?/1` | `Kernel.is_struct/2` (`Kernel.is_struct(term, Regex)`) (v1.11)
|
||||||
[v1.15] | `Logger.warn/2` | `Logger.warning/2` (v1.11)
|
[v1.15] | `Logger.warn/2` | `Logger.warning/2` (v1.11)
|
||||||
[v1.14] | `use Bitwise` | `import Bitwise` (v1.0)
|
[v1.14] | `use Bitwise` | `import Bitwise` (v1.0)
|
||||||
[v1.14] | `~~~/1` | `bnot/2` (v1.0)
|
[v1.14] | `~~~/1` | `bnot/2` (v1.0)
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
tmp/
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Usage:
|
||||||
|
#
|
||||||
|
# With Elixir archive:
|
||||||
|
#
|
||||||
|
# ELIXIR_ZIP=Precompiled.zip OTP_VERSION=25.3.2.2 ./build.sh
|
||||||
|
#
|
||||||
|
# With Elixir version:
|
||||||
|
#
|
||||||
|
# ELIXIR_VERSION=1.14.5 OTP_VERSION=25.3.2.2 ./build.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
OTP_VERSION="${OTP_VERSION:-26.0}"
|
||||||
|
otp_release=`echo "${OTP_VERSION}" | cut -d. -f1`
|
||||||
|
|
||||||
|
mkdir -p tmp
|
||||||
|
|
||||||
|
ELIXIR_VERSION="${ELIXIR_VERSION:-}"
|
||||||
|
if [ -n "${ELIXIR_VERSION}" ]; then
|
||||||
|
ELIXIR_ZIP="tmp/elixir-${ELIXIR_VERSION}-otp-${otp_release}.zip"
|
||||||
|
if [ ! -f "${ELIXIR_ZIP}" ]; then
|
||||||
|
url="https://github.com/elixir-lang/elixir/releases/download/v${ELIXIR_VERSION}/elixir-otp-${otp_release}.zip"
|
||||||
|
echo "downloading ${url}"
|
||||||
|
curl --fail -L -o "${ELIXIR_ZIP}" "${url}"
|
||||||
|
fi
|
||||||
|
basename=elixir-${ELIXIR_VERSION}-otp-${otp_release}
|
||||||
|
else
|
||||||
|
basename=elixir-otp-${otp_release}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "tmp/${basename}" ]; then
|
||||||
|
unzip -d "tmp/${basename}" "${ELIXIR_ZIP}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# brew install makensis
|
||||||
|
# apt install -y nsis
|
||||||
|
# choco install -y nsis
|
||||||
|
export PATH="/c/Program Files (x86)/NSIS:${PATH}"
|
||||||
|
makensis \
|
||||||
|
-X"OutFile tmp\\${basename}.exe" \
|
||||||
|
-DOTP_VERSION=${OTP_VERSION} \
|
||||||
|
-DOTP_RELEASE="${otp_release}" \
|
||||||
|
-DELIXIR_DIR=tmp\\${basename} \
|
||||||
|
installer.nsi
|
||||||
|
|
||||||
|
echo "Installer path: tmp/${basename}.exe"
|
||||||
@@ -0,0 +1,275 @@
|
|||||||
|
!include "MUI2.nsh"
|
||||||
|
!include "StrFunc.nsh"
|
||||||
|
${Using:StrFunc} UnStrStr
|
||||||
|
|
||||||
|
Name "Elixir"
|
||||||
|
ManifestDPIAware true
|
||||||
|
Unicode True
|
||||||
|
InstallDir "$PROGRAMFILES64\Elixir"
|
||||||
|
!define MUI_ICON "assets\drop.ico"
|
||||||
|
|
||||||
|
; Install Page: Install Erlang/OTP
|
||||||
|
|
||||||
|
Page custom CheckOTPPageShow CheckOTPPageLeave
|
||||||
|
|
||||||
|
var InstalledOTPRelease
|
||||||
|
var OTPPath
|
||||||
|
|
||||||
|
var Dialog
|
||||||
|
var NoOTPLabel
|
||||||
|
var NoOTPLabelCreated
|
||||||
|
var OTPMismatchLabel
|
||||||
|
var OTPMismatchLabelCreated
|
||||||
|
var DownloadOTPLink
|
||||||
|
var DownloadOTPLinkCreated
|
||||||
|
var VerifyOTPButton
|
||||||
|
var VerifyOTPButtonCreated
|
||||||
|
Function CheckOTPPageShow
|
||||||
|
!insertmacro MUI_HEADER_TEXT "Checking Erlang/OTP" ""
|
||||||
|
|
||||||
|
nsDialogs::Create 1018
|
||||||
|
Pop $Dialog
|
||||||
|
|
||||||
|
${If} $Dialog == error
|
||||||
|
Abort
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
Call VerifyOTP
|
||||||
|
|
||||||
|
nsDialogs::Show
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function VerifyOTP
|
||||||
|
${If} $NoOTPLabelCreated == "true"
|
||||||
|
ShowWindow $NoOTPLabel ${SW_HIDE}
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${If} $OTPMismatchLabelCreated == "true"
|
||||||
|
ShowWindow $OTPMismatchLabel ${SW_HIDE}
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${If} $DownloadOTPLinkCreated == "true"
|
||||||
|
ShowWindow $DownloadOTPLink ${SW_HIDE}
|
||||||
|
${Else}
|
||||||
|
StrCpy $DownloadOTPLinkCreated "true"
|
||||||
|
${NSD_CreateLink} 0 60u 100% 20u "Download Erlang/OTP ${OTP_RELEASE}"
|
||||||
|
Pop $DownloadOTPLink
|
||||||
|
${NSD_OnClick} $DownloadOTPLink OpenOTPDownloads
|
||||||
|
ShowWindow $DownloadOTPLink ${SW_HIDE}
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${If} $VerifyOTPButtonCreated == "true"
|
||||||
|
ShowWindow $VerifyOTPButton ${SW_HIDE}
|
||||||
|
${Else}
|
||||||
|
StrCpy $VerifyOTPButtonCreated "true"
|
||||||
|
${NSD_CreateButton} 0 80u 25% 12u "Verify Erlang/OTP"
|
||||||
|
Pop $VerifyOTPButton
|
||||||
|
${NSD_OnClick} $VerifyOTPButton VerifyOTP
|
||||||
|
ShowWindow $VerifyOTPButton ${SW_HIDE}
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
StrCpy $0 0
|
||||||
|
loop:
|
||||||
|
EnumRegKey $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" $0
|
||||||
|
StrCmp $1 "" done
|
||||||
|
ReadRegStr $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$1" ""
|
||||||
|
StrCpy $OTPPath $1
|
||||||
|
IntOp $0 $0 + 1
|
||||||
|
goto loop
|
||||||
|
done:
|
||||||
|
|
||||||
|
${If} $OTPPath == ""
|
||||||
|
${If} $NoOTPLabelCreated != "true"
|
||||||
|
StrCpy $NoOTPLabelCreated "true"
|
||||||
|
${NSD_CreateLabel} 0 0 100% 20u "Couldn't find existing Erlang/OTP installation. Click the link below to download and install it before proceeding."
|
||||||
|
Pop $NoOTPLabel
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
ShowWindow $NoOTPLabel ${SW_SHOW}
|
||||||
|
ShowWindow $DownloadOTPLink ${SW_SHOW}
|
||||||
|
ShowWindow $VerifyOTPButton ${SW_SHOW}
|
||||||
|
|
||||||
|
${Else}
|
||||||
|
nsExec::ExecToStack `$OTPPath\bin\erl.exe -noinput -eval "\
|
||||||
|
io:put_chars(erlang:system_info(otp_release)),\
|
||||||
|
halt()."`
|
||||||
|
Pop $0
|
||||||
|
Pop $1
|
||||||
|
|
||||||
|
${If} $0 == 0
|
||||||
|
StrCpy $InstalledOTPRelease $1
|
||||||
|
${If} $InstalledOTPRelease == ${OTP_RELEASE}
|
||||||
|
${NSD_CreateLabel} 0 0 100% 60u "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath. Please proceed."
|
||||||
|
|
||||||
|
${Else}
|
||||||
|
${If} $OTPMismatchLabelCreated != "true"
|
||||||
|
StrCpy $OTPMismatchLabelCreated "true"
|
||||||
|
${NSD_CreateLabel} 0 0 100% 60u "Found existing Erlang/OTP $InstalledOTPRelease installation at $OTPPath but this Elixir installer was precompiled for Erlang/OTP ${OTP_RELEASE}. \
|
||||||
|
$\r$\n$\r$\nYou can either search for another Elixir installer precompiled for Erlang/OTP $InstalledOTPRelease or download Erlang/OTP ${OTP_RELEASE} and install before proceeding."
|
||||||
|
Pop $OTPMismatchLabel
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
ShowWindow $OTPMismatchLabel ${SW_SHOW}
|
||||||
|
ShowWindow $DownloadOTPLink ${SW_SHOW}
|
||||||
|
ShowWindow $VerifyOTPButton ${SW_SHOW}
|
||||||
|
${EndIf}
|
||||||
|
${Else}
|
||||||
|
SetErrorlevel 5
|
||||||
|
MessageBox MB_ICONSTOP "Found existing Erlang/OTP installation at $OTPPath but checking it exited with $0: $1"
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function OpenOTPDownloads
|
||||||
|
ExecShell "open" "https://www.erlang.org/downloads/${OTP_RELEASE}"
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function CheckOTPPageLeave
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
; Install Page: Files
|
||||||
|
|
||||||
|
!insertmacro MUI_PAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
|
||||||
|
; Install Page: Finish
|
||||||
|
|
||||||
|
Page custom FinishPageShow FinishPageLeave
|
||||||
|
|
||||||
|
var AddOTPToPathCheckbox
|
||||||
|
var AddElixirToPathCheckbox
|
||||||
|
Function FinishPageShow
|
||||||
|
!insertmacro MUI_HEADER_TEXT "Finish Setup" ""
|
||||||
|
|
||||||
|
nsDialogs::Create 1018
|
||||||
|
Pop $Dialog
|
||||||
|
|
||||||
|
${If} $Dialog == error
|
||||||
|
Abort
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
; we add to PATH using erlang, so there must be an OTP installed to do so.
|
||||||
|
${If} "$OTPPath" != ""
|
||||||
|
${NSD_CreateCheckbox} 0 0 195u 10u "&Add $INSTDIR\bin to %PATH%"
|
||||||
|
Pop $AddElixirToPathCheckbox
|
||||||
|
SendMessage $AddElixirToPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||||
|
|
||||||
|
${NSD_CreateCheckbox} 0 20u 195u 10u "&Add $OTPPath\bin to %PATH%"
|
||||||
|
Pop $AddOTPToPathCheckbox
|
||||||
|
SendMessage $AddOTPToPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||||
|
|
||||||
|
${NSD_CreateLabel} 0 40u 100% 20u "Note: you need to restart your shell for the environment variable changes to take effect."
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
nsDialogs::Show
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
var PathsToAdd
|
||||||
|
Function FinishPageLeave
|
||||||
|
${NSD_GetState} $AddOTPToPathCheckbox $0
|
||||||
|
${If} $0 <> ${BST_UNCHECKED}
|
||||||
|
StrCpy $PathsToAdd ";$OTPPath\bin"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${NSD_GetState} $AddElixirToPathCheckbox $0
|
||||||
|
${If} $0 <> ${BST_UNCHECKED}
|
||||||
|
StrCpy $PathsToAdd "$PathsToAdd;$INSTDIR\bin"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${If} "$PathsToAdd" != ""
|
||||||
|
nsExec::ExecToStack `"$OTPPath\bin\escript.exe" "$INSTDIR\update_system_path.erl" add "$PathsToAdd"`
|
||||||
|
Pop $0
|
||||||
|
Pop $1
|
||||||
|
${If} $0 != 0
|
||||||
|
SetErrorlevel 5
|
||||||
|
MessageBox MB_ICONSTOP "adding paths failed with $0: $1"
|
||||||
|
Quit
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Section "Install Elixir" SectionElixir
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
File /r "${ELIXIR_DIR}\"
|
||||||
|
File "update_system_path.erl"
|
||||||
|
|
||||||
|
WriteUninstaller "Uninstall.exe"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
; Uninstall Page: Remove from %PATH%
|
||||||
|
|
||||||
|
var RemoveOTPFromPathCheckbox
|
||||||
|
var RemoveElixirFromPathCheckbox
|
||||||
|
Function un.FinishPageShow
|
||||||
|
!insertmacro MUI_HEADER_TEXT "Remove from %PATH%" ""
|
||||||
|
|
||||||
|
StrCpy $0 0
|
||||||
|
loop:
|
||||||
|
EnumRegKey $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang" $0
|
||||||
|
StrCmp $1 "" done
|
||||||
|
ReadRegStr $1 HKLM "SOFTWARE\WOW6432NODE\Ericsson\Erlang\$1" ""
|
||||||
|
StrCpy $OTPPath $1
|
||||||
|
IntOp $0 $0 + 1
|
||||||
|
goto loop
|
||||||
|
done:
|
||||||
|
|
||||||
|
nsDialogs::Create 1018
|
||||||
|
Pop $Dialog
|
||||||
|
|
||||||
|
${If} $Dialog == error
|
||||||
|
Abort
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
ReadRegStr $0 HKCU "Environment" "Path"
|
||||||
|
|
||||||
|
${UnStrStr} $1 "$0" "$INSTDIR\bin"
|
||||||
|
${If} $1 != ""
|
||||||
|
${NSD_CreateCheckbox} 0 0 195u 10u "&Remove $INSTDIR\bin from %PATH%"
|
||||||
|
Pop $RemoveElixirFromPathCheckbox
|
||||||
|
SendMessage $RemoveElixirFromPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${UnStrStr} $1 "$0" "$OTPPath\bin"
|
||||||
|
${If} $1 != ""
|
||||||
|
${NSD_CreateCheckbox} 0 20u 195u 10u "&Remove $OTPPath\bin from %PATH%"
|
||||||
|
Pop $RemoveOTPFromPathCheckbox
|
||||||
|
SendMessage $RemoveOTPFromPathCheckbox ${BM_SETCHECK} ${BST_CHECKED} 0
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
nsDialogs::Show
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
var PathsToRemove
|
||||||
|
Function un.FinishPageLeave
|
||||||
|
${NSD_GetState} $RemoveOTPFromPathCheckbox $1
|
||||||
|
${If} $1 <> ${BST_UNCHECKED}
|
||||||
|
StrCpy $PathsToRemove ";$OTPPath\bin"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${NSD_GetState} $RemoveElixirFromPathCheckbox $1
|
||||||
|
${If} $1 <> ${BST_UNCHECKED}
|
||||||
|
StrCpy $PathsToRemove "$PathsToRemove;$INSTDIR\bin"
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
${If} "$PathsToRemove" != ""
|
||||||
|
nsExec::ExecToStack `"$OTPPath\bin\escript.exe" "$INSTDIR\update_system_path.erl" remove "$PathsToRemove"`
|
||||||
|
Pop $0
|
||||||
|
Pop $1
|
||||||
|
${If} $0 != 0
|
||||||
|
SetErrorlevel 5
|
||||||
|
MessageBox MB_ICONSTOP "removing paths failed with $0: $1"
|
||||||
|
Quit
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
UninstPage custom un.FinishPageShow un.FinishPageLeave
|
||||||
|
|
||||||
|
!insertmacro MUI_UNPAGE_DIRECTORY
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
|
||||||
|
Section "Uninstall"
|
||||||
|
RMDir /r "$INSTDIR"
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
!insertmacro MUI_LANGUAGE "English"
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env escript
|
||||||
|
%%! -noinput
|
||||||
|
|
||||||
|
%% This file is used by the Elixir installer and uninstaller.
|
||||||
|
main(["add", ";" ++ PathsToAdd]) ->
|
||||||
|
{ok, Reg} = win32reg:open([read, write]),
|
||||||
|
ok = win32reg:change_key(Reg, "\\hkey_current_user\\environment"),
|
||||||
|
{ok, SystemPath} = win32reg:value(Reg, "path"),
|
||||||
|
|
||||||
|
NewSystemPath =
|
||||||
|
lists:foldl(
|
||||||
|
fun(Elem, Acc) ->
|
||||||
|
Elem ++ ";" ++
|
||||||
|
binary_to_list(
|
||||||
|
iolist_to_binary(
|
||||||
|
string:replace(Acc, Elem ++ ";", "", all)))
|
||||||
|
end,
|
||||||
|
SystemPath,
|
||||||
|
string:split(PathsToAdd, ";", all)
|
||||||
|
),
|
||||||
|
|
||||||
|
ok = win32reg:set_value(Reg, "Path", NewSystemPath),
|
||||||
|
ok;
|
||||||
|
|
||||||
|
main(["remove", ";" ++ PathsToRemove]) ->
|
||||||
|
{ok, Reg} = win32reg:open([read, write]),
|
||||||
|
ok = win32reg:change_key(Reg, "\\hkey_current_user\\environment"),
|
||||||
|
{ok, SystemPath} = win32reg:value(Reg, "path"),
|
||||||
|
|
||||||
|
NewSystemPath =
|
||||||
|
lists:foldl(
|
||||||
|
fun(Elem, Acc) ->
|
||||||
|
binary_to_list(
|
||||||
|
iolist_to_binary(
|
||||||
|
string:replace(Acc, Elem ++ ";", "", all)))
|
||||||
|
end,
|
||||||
|
SystemPath,
|
||||||
|
string:split(PathsToRemove, ";", all)
|
||||||
|
),
|
||||||
|
|
||||||
|
ok = win32reg:set_value(Reg, "Path", NewSystemPath),
|
||||||
|
ok.
|
||||||
+78
-50
@@ -7,10 +7,11 @@
|
|||||||
-export([
|
-export([
|
||||||
string_to_tokens/5, tokens_to_quoted/3, 'string_to_quoted!'/5,
|
string_to_tokens/5, tokens_to_quoted/3, 'string_to_quoted!'/5,
|
||||||
env_for_eval/1, quoted_to_erl/2, eval_forms/3, eval_quoted/3,
|
env_for_eval/1, quoted_to_erl/2, eval_forms/3, eval_quoted/3,
|
||||||
eval_quoted/4
|
eval_quoted/4, eval_external_handler/3
|
||||||
]).
|
]).
|
||||||
-include("elixir.hrl").
|
-include("elixir.hrl").
|
||||||
-define(system, 'Elixir.System').
|
-define(system, 'Elixir.System').
|
||||||
|
-define(elixir_eval_env, {elixir, eval_env}).
|
||||||
|
|
||||||
%% Top level types
|
%% Top level types
|
||||||
%% TODO: Remove char_list type on v2.0
|
%% TODO: Remove char_list type on v2.0
|
||||||
@@ -192,7 +193,8 @@ start_cli() ->
|
|||||||
'Elixir.Kernel.CLI':main(init:get_plain_arguments()),
|
'Elixir.Kernel.CLI':main(init:get_plain_arguments()),
|
||||||
elixir_config:booted().
|
elixir_config:booted().
|
||||||
|
|
||||||
%% TODO: Delete prim_tty branches and -user on Erlang/OTP 26.
|
%% TODO: Delete prim_tty branches and -user on Erlang/OTP 26
|
||||||
|
%% and add "-e :elixir.start_iex" to bin/iex instead of $MODE.
|
||||||
start() ->
|
start() ->
|
||||||
case code:ensure_loaded(prim_tty) of
|
case code:ensure_loaded(prim_tty) of
|
||||||
{module, _} ->
|
{module, _} ->
|
||||||
@@ -207,6 +209,7 @@ start() ->
|
|||||||
start_iex() ->
|
start_iex() ->
|
||||||
case code:ensure_loaded(prim_tty) of
|
case code:ensure_loaded(prim_tty) of
|
||||||
{module, _} ->
|
{module, _} ->
|
||||||
|
start_cli(),
|
||||||
spawn(fun() ->
|
spawn(fun() ->
|
||||||
elixir_config:wait_until_booted(),
|
elixir_config:wait_until_booted(),
|
||||||
(shell:whereis() =:= undefined) andalso 'Elixir.IEx':cli()
|
(shell:whereis() =:= undefined) andalso 'Elixir.IEx':cli()
|
||||||
@@ -355,8 +358,25 @@ eval_forms(Tree, Binding, OrigE, Opts) ->
|
|||||||
_ -> [Erl]
|
_ -> [Erl]
|
||||||
end,
|
end,
|
||||||
|
|
||||||
ExternalHandler = eval_external_handler(NewE),
|
ExternalHandler = eval_external_handler(),
|
||||||
{value, Value, NewBinding} = erl_eval:exprs(Exprs, ErlBinding, none, ExternalHandler),
|
|
||||||
|
{value, Value, NewBinding} =
|
||||||
|
try
|
||||||
|
%% ?elixir_eval_env is used by the external handler.
|
||||||
|
%%
|
||||||
|
%% The reason why we use the process dictionary to pass the environment
|
||||||
|
%% is because we want to avoid passing closures to erl_eval, as that
|
||||||
|
%% would effectively tie the eval code to the Elixir version and it is
|
||||||
|
%% best if it depends solely on Erlang/OTP.
|
||||||
|
%%
|
||||||
|
%% The downside is that functions that escape the eval context will no
|
||||||
|
%% longer have the original environment they came from.
|
||||||
|
erlang:put(?elixir_eval_env, NewE),
|
||||||
|
erl_eval:exprs(Exprs, ErlBinding, none, ExternalHandler)
|
||||||
|
after
|
||||||
|
erlang:erase(?elixir_eval_env)
|
||||||
|
end,
|
||||||
|
|
||||||
PruneBefore = if Prune -> length(Binding); true -> -1 end,
|
PruneBefore = if Prune -> length(Binding); true -> -1 end,
|
||||||
|
|
||||||
{DumpedBinding, DumpedVars} =
|
{DumpedBinding, DumpedVars} =
|
||||||
@@ -367,52 +387,60 @@ eval_forms(Tree, Binding, OrigE, Opts) ->
|
|||||||
|
|
||||||
%% TODO: Remove conditional once we require Erlang/OTP 25+.
|
%% TODO: Remove conditional once we require Erlang/OTP 25+.
|
||||||
-if(?OTP_RELEASE >= 25).
|
-if(?OTP_RELEASE >= 25).
|
||||||
eval_external_handler(Env) ->
|
eval_external_handler() ->
|
||||||
Fun = fun(Ann, FunOrModFun, Args) ->
|
{value, fun ?MODULE:eval_external_handler/3}.
|
||||||
try
|
|
||||||
case FunOrModFun of
|
|
||||||
{Mod, Fun} -> apply(Mod, Fun, Args);
|
|
||||||
Fun -> apply(Fun, Args)
|
|
||||||
end
|
|
||||||
catch
|
|
||||||
Kind:Reason:Stacktrace ->
|
|
||||||
%% Take everything up to the Elixir module
|
|
||||||
Pruned =
|
|
||||||
lists:takewhile(fun
|
|
||||||
({elixir,_,_,_}) -> false;
|
|
||||||
(_) -> true
|
|
||||||
end, Stacktrace),
|
|
||||||
|
|
||||||
Caller =
|
eval_external_handler(Ann, FunOrModFun, Args) ->
|
||||||
lists:dropwhile(fun
|
try
|
||||||
({elixir,_,_,_}) -> false;
|
case FunOrModFun of
|
||||||
(_) -> true
|
{Mod, Fun} -> apply(Mod, Fun, Args);
|
||||||
end, Stacktrace),
|
Fun -> apply(Fun, Args)
|
||||||
|
|
||||||
%% Now we prune any shared code path from erl_eval
|
|
||||||
{current_stacktrace, Current} =
|
|
||||||
erlang:process_info(self(), current_stacktrace),
|
|
||||||
|
|
||||||
%% We need to make sure that we don't generate more
|
|
||||||
%% frames than supported. So we do our best to drop
|
|
||||||
%% from the Caller, but if the caller has no frames,
|
|
||||||
%% we need to drop from Pruned.
|
|
||||||
{DroppedCaller, ToDrop} =
|
|
||||||
case Caller of
|
|
||||||
[] -> {[], true};
|
|
||||||
_ -> {lists:droplast(Caller), false}
|
|
||||||
end,
|
|
||||||
|
|
||||||
Reversed = drop_common(lists:reverse(Current), lists:reverse(Pruned), ToDrop),
|
|
||||||
File = elixir_utils:characters_to_list(?key(Env, file)),
|
|
||||||
Location = [{file, File}, {line, erl_anno:line(Ann)}],
|
|
||||||
|
|
||||||
%% Add file+line information at the bottom
|
|
||||||
Custom = lists:reverse([{elixir_eval, '__FILE__', 1, Location} | Reversed], DroppedCaller),
|
|
||||||
erlang:raise(Kind, Reason, Custom)
|
|
||||||
end
|
end
|
||||||
end,
|
catch
|
||||||
{value, Fun}.
|
Kind:Reason:Stacktrace ->
|
||||||
|
%% Take everything up to the Elixir module
|
||||||
|
Pruned =
|
||||||
|
lists:takewhile(fun
|
||||||
|
({elixir,_,_,_}) -> false;
|
||||||
|
(_) -> true
|
||||||
|
end, Stacktrace),
|
||||||
|
|
||||||
|
Caller =
|
||||||
|
lists:dropwhile(fun
|
||||||
|
({elixir,_,_,_}) -> false;
|
||||||
|
(_) -> true
|
||||||
|
end, Stacktrace),
|
||||||
|
|
||||||
|
%% Now we prune any shared code path from erl_eval
|
||||||
|
{current_stacktrace, Current} =
|
||||||
|
erlang:process_info(self(), current_stacktrace),
|
||||||
|
|
||||||
|
%% We need to make sure that we don't generate more
|
||||||
|
%% frames than supported. So we do our best to drop
|
||||||
|
%% from the Caller, but if the caller has no frames,
|
||||||
|
%% we need to drop from Pruned.
|
||||||
|
{DroppedCaller, ToDrop} =
|
||||||
|
case Caller of
|
||||||
|
[] -> {[], true};
|
||||||
|
_ -> {lists:droplast(Caller), false}
|
||||||
|
end,
|
||||||
|
|
||||||
|
Reversed = drop_common(lists:reverse(Current), lists:reverse(Pruned), ToDrop),
|
||||||
|
|
||||||
|
%% Add file+line information at the bottom
|
||||||
|
Bottom =
|
||||||
|
case erlang:get(?elixir_eval_env) of
|
||||||
|
#{file := File} ->
|
||||||
|
[{elixir_eval, '__FILE__', 1,
|
||||||
|
[{file, elixir_utils:characters_to_list(File)}, {line, erl_anno:line(Ann)}]}];
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end,
|
||||||
|
|
||||||
|
Custom = lists:reverse(Bottom ++ Reversed, DroppedCaller),
|
||||||
|
erlang:raise(Kind, Reason, Custom)
|
||||||
|
end.
|
||||||
|
|
||||||
%% We need to check if we have dropped any frames.
|
%% We need to check if we have dropped any frames.
|
||||||
%% If we have not dropped frames, then we need to drop one
|
%% If we have not dropped frames, then we need to drop one
|
||||||
@@ -425,8 +453,8 @@ drop_common([], [{?MODULE, _, _, _} | T2], _ToDrop) -> T2;
|
|||||||
drop_common([], [_ | T2], true) -> T2;
|
drop_common([], [_ | T2], true) -> T2;
|
||||||
drop_common([], T2, _) -> T2.
|
drop_common([], T2, _) -> T2.
|
||||||
-else.
|
-else.
|
||||||
eval_external_handler(_Env) ->
|
eval_external_handler() -> none.
|
||||||
none.
|
eval_external_handler(_Ann, _FunOrModFun, _Args) -> error(unused).
|
||||||
-endif.
|
-endif.
|
||||||
|
|
||||||
%% Converts a quoted expression to Erlang abstract format
|
%% Converts a quoted expression to Erlang abstract format
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ quoted(Forms, File, Callback) ->
|
|||||||
|
|
||||||
elixir_lexical:run(
|
elixir_lexical:run(
|
||||||
Env,
|
Env,
|
||||||
fun (LexicalEnv) -> eval_or_compile(Forms, [], LexicalEnv) end,
|
fun (LexicalEnv) -> maybe_fast_compile(Forms, [], LexicalEnv) end,
|
||||||
fun (#{lexical_tracker := Pid}) -> Callback(File, Pid) end
|
fun (#{lexical_tracker := Pid}) -> Callback(File, Pid) end
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ file(File, Callback) ->
|
|||||||
%% Evaluates the given code through the Erlang compiler.
|
%% Evaluates the given code through the Erlang compiler.
|
||||||
%% It may end-up evaluating the code if it is deemed a
|
%% It may end-up evaluating the code if it is deemed a
|
||||||
%% more efficient strategy depending on the code snippet.
|
%% more efficient strategy depending on the code snippet.
|
||||||
eval_or_compile(Forms, Args, E) ->
|
maybe_fast_compile(Forms, Args, E) ->
|
||||||
case (?key(E, module) == nil) andalso allows_fast_compilation(Forms) andalso
|
case (?key(E, module) == nil) andalso allows_fast_compilation(Forms) andalso
|
||||||
(not elixir_config:is_bootstrap()) of
|
(not elixir_config:is_bootstrap()) of
|
||||||
true -> fast_compile(Forms, E);
|
true -> fast_compile(Forms, E);
|
||||||
@@ -40,8 +40,9 @@ eval_or_compile(Forms, Args, E) ->
|
|||||||
end,
|
end,
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
compile(Quoted, ArgsList, E) ->
|
compile(Quoted, ArgsList, #{line := Line} = E) ->
|
||||||
{Expanded, SE, EE} = elixir_expand:expand(Quoted, elixir_env:env_to_ex(E), E),
|
Block = no_tail_optimize([{line, Line}], Quoted),
|
||||||
|
{Expanded, SE, EE} = elixir_expand:expand(Block, elixir_env:env_to_ex(E), E),
|
||||||
elixir_env:check_unused_vars(SE, EE),
|
elixir_env:check_unused_vars(SE, EE),
|
||||||
|
|
||||||
{Module, Fun, Purgeable} =
|
{Module, Fun, Purgeable} =
|
||||||
@@ -55,7 +56,7 @@ spawned_compile(ExExprs, #{line := Line, file := File} = E) ->
|
|||||||
{ErlExprs, _} = elixir_erl_pass:translate(ExExprs, erl_anno:new(Line), S),
|
{ErlExprs, _} = elixir_erl_pass:translate(ExExprs, erl_anno:new(Line), S),
|
||||||
|
|
||||||
Module = retrieve_compiler_module(),
|
Module = retrieve_compiler_module(),
|
||||||
Fun = code_fun(?key(E, module)),
|
Fun = code_fun(?key(E, module)),
|
||||||
Forms = code_mod(Fun, ErlExprs, Line, File, Module, Vars),
|
Forms = code_mod(Fun, ErlExprs, Line, File, Module, Vars),
|
||||||
|
|
||||||
{Module, Binary} = elixir_erl_compiler:noenv_forms(Forms, File, [nowarn_nomatch, no_bool_opt, no_ssa_opt]),
|
{Module, Binary} = elixir_erl_compiler:noenv_forms(Forms, File, [nowarn_nomatch, no_bool_opt, no_ssa_opt]),
|
||||||
@@ -106,15 +107,9 @@ allows_fast_compilation(_) ->
|
|||||||
|
|
||||||
fast_compile({'__block__', _, Exprs}, E) ->
|
fast_compile({'__block__', _, Exprs}, E) ->
|
||||||
lists:foldl(fun(Expr, _) -> fast_compile(Expr, E) end, nil, Exprs);
|
lists:foldl(fun(Expr, _) -> fast_compile(Expr, E) end, nil, Exprs);
|
||||||
fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
|
fast_compile({defmodule, Meta, [Mod, [{do, Block}]]}, NoLineE) ->
|
||||||
E = NoLineE#{line := ?line(Meta)},
|
E = NoLineE#{line := ?line(Meta)},
|
||||||
|
|
||||||
Block = {'__block__', Meta, [
|
|
||||||
{'=', Meta, [{result, Meta, ?MODULE}, TailBlock]},
|
|
||||||
{{'.', Meta, [elixir_utils, noop]}, Meta, []},
|
|
||||||
{result, Meta, ?MODULE}
|
|
||||||
]},
|
|
||||||
|
|
||||||
Expanded = case Mod of
|
Expanded = case Mod of
|
||||||
{'__aliases__', _, _} ->
|
{'__aliases__', _, _} ->
|
||||||
case elixir_aliases:expand_or_concat(Mod, E) of
|
case elixir_aliases:expand_or_concat(Mod, E) of
|
||||||
@@ -129,6 +124,13 @@ fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
|
|||||||
ContextModules = [Expanded | ?key(E, context_modules)],
|
ContextModules = [Expanded | ?key(E, context_modules)],
|
||||||
elixir_module:compile(Expanded, Block, [], false, E#{context_modules := ContextModules}).
|
elixir_module:compile(Expanded, Block, [], false, E#{context_modules := ContextModules}).
|
||||||
|
|
||||||
|
no_tail_optimize(Meta, Block) ->
|
||||||
|
{'__block__', Meta, [
|
||||||
|
{'=', Meta, [{result, Meta, ?MODULE}, Block]},
|
||||||
|
{{'.', Meta, [elixir_utils, noop]}, Meta, []},
|
||||||
|
{result, Meta, ?MODULE}
|
||||||
|
]}.
|
||||||
|
|
||||||
%% Bootstrapper
|
%% Bootstrapper
|
||||||
|
|
||||||
bootstrap() ->
|
bootstrap() ->
|
||||||
|
|||||||
@@ -130,11 +130,15 @@ dispatch_require(Meta, Receiver, Name, Args, S, E, Callback) when is_atom(Receiv
|
|||||||
|
|
||||||
case elixir_rewrite:inline(Receiver, Name, Arity) of
|
case elixir_rewrite:inline(Receiver, Name, Arity) of
|
||||||
{AR, AN} ->
|
{AR, AN} ->
|
||||||
|
elixir_env:trace({remote_function, Meta, Receiver, Name, Arity}, E),
|
||||||
Callback(AR, AN, Args);
|
Callback(AR, AN, Args);
|
||||||
false ->
|
false ->
|
||||||
case expand_require(Meta, Receiver, {Name, Arity}, Args, S, E) of
|
case expand_require(Meta, Receiver, {Name, Arity}, Args, S, E) of
|
||||||
{ok, Receiver, Quoted} -> expand_quoted(Meta, Receiver, Name, Arity, Quoted, S, E);
|
{ok, Receiver, Quoted} ->
|
||||||
error -> Callback(Receiver, Name, Args)
|
expand_quoted(Meta, Receiver, Name, Arity, Quoted, S, E);
|
||||||
|
error ->
|
||||||
|
elixir_env:trace({remote_function, Meta, Receiver, Name, Arity}, E),
|
||||||
|
Callback(Receiver, Name, Args)
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
@@ -510,12 +510,15 @@ get_moduledoc_meta(Set) ->
|
|||||||
|
|
||||||
get_docs(Set, Module, Definitions, Kind) ->
|
get_docs(Set, Module, Definitions, Kind) ->
|
||||||
[{Key,
|
[{Key,
|
||||||
erl_anno:new(Line),
|
maybe_generated(erl_anno:new(Line), Ctx),
|
||||||
[signature_to_binary(Module, Name, Signature)],
|
[signature_to_binary(Module, Name, Signature)],
|
||||||
doc_value(Doc, Name),
|
doc_value(Doc, Name),
|
||||||
Meta
|
Meta
|
||||||
} || {Name, Arity} <- Definitions,
|
} || {Name, Arity} <- Definitions,
|
||||||
{Key, _Ctx, Line, Signature, Doc, Meta} <- ets:lookup(Set, {Kind, Name, Arity})].
|
{Key, Ctx, Line, Signature, Doc, Meta} <- ets:lookup(Set, {Kind, Name, Arity})].
|
||||||
|
|
||||||
|
maybe_generated(Ann, nil) -> Ann;
|
||||||
|
maybe_generated(Ann, _Ctx) -> erl_anno:set_generated(true, Ann).
|
||||||
|
|
||||||
get_callback_docs(Set, Callbacks) ->
|
get_callback_docs(Set, Callbacks) ->
|
||||||
[{Key,
|
[{Key,
|
||||||
|
|||||||
@@ -5,25 +5,42 @@
|
|||||||
spawn(Fun) ->
|
spawn(Fun) ->
|
||||||
CompilerInfo = get(elixir_compiler_info),
|
CompilerInfo = get(elixir_compiler_info),
|
||||||
|
|
||||||
|
CodeDiagnostics =
|
||||||
|
case get(elixir_code_diagnostics) of
|
||||||
|
undefined -> undefined;
|
||||||
|
{_Tail, Log} -> {[], Log}
|
||||||
|
end,
|
||||||
|
|
||||||
{_, Ref} =
|
{_, Ref} =
|
||||||
spawn_monitor(fun() ->
|
spawn_monitor(fun() ->
|
||||||
put(elixir_compiler_info, CompilerInfo),
|
put(elixir_compiler_info, CompilerInfo),
|
||||||
|
put(elixir_code_diagnostics, CodeDiagnostics),
|
||||||
|
|
||||||
try Fun() of
|
try Fun() of
|
||||||
Result -> exit({ok, Result})
|
Result -> exit({ok, Result, get(elixir_code_diagnostics)})
|
||||||
catch
|
catch
|
||||||
Kind:Reason:Stack ->
|
Kind:Reason:Stack ->
|
||||||
exit({Kind, Reason, Stack})
|
exit({Kind, Reason, Stack, get(elixir_code_diagnostics)})
|
||||||
end
|
end
|
||||||
end),
|
end),
|
||||||
|
|
||||||
receive
|
receive
|
||||||
{'DOWN', Ref, process, _, {ok, Result}} ->
|
{'DOWN', Ref, process, _, {ok, Result, Diagnostics}} ->
|
||||||
|
copy_diagnostics(Diagnostics),
|
||||||
Result;
|
Result;
|
||||||
{'DOWN', Ref, process, _, {Kind, Reason, Stack}} ->
|
{'DOWN', Ref, process, _, {Kind, Reason, Stack, Diagnostics}} ->
|
||||||
|
copy_diagnostics(Diagnostics),
|
||||||
erlang:raise(Kind, Reason, Stack)
|
erlang:raise(Kind, Reason, Stack)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
copy_diagnostics(undefined) ->
|
||||||
|
ok;
|
||||||
|
copy_diagnostics({Head, _}) ->
|
||||||
|
case get(elixir_code_diagnostics) of
|
||||||
|
undefined -> ok;
|
||||||
|
{Tail, Log} -> put(elixir_code_diagnostics, {Head ++ Tail, Log})
|
||||||
|
end.
|
||||||
|
|
||||||
forms(Forms, File, Opts) ->
|
forms(Forms, File, Opts) ->
|
||||||
compile(Forms, File, Opts ++ compile:env_compiler_options()).
|
compile(Forms, File, Opts ++ compile:env_compiler_options()).
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ normalize_rescue(Meta, Var, Pattern, Expr, ErlangAliases) ->
|
|||||||
dynamic_normalize(Meta, Var, ?REQUIRES_STACKTRACE);
|
dynamic_normalize(Meta, Var, ?REQUIRES_STACKTRACE);
|
||||||
|
|
||||||
false ->
|
false ->
|
||||||
case lists:splitwith(fun is_normalized_with_stacktrace/1, ErlangAliases) of
|
case lists:partition(fun is_normalized_with_stacktrace/1, ErlangAliases) of
|
||||||
{[], _} -> [];
|
{[], _} -> [];
|
||||||
{_, []} -> {'__STACKTRACE__', Meta, nil};
|
{_, []} -> {'__STACKTRACE__', Meta, nil};
|
||||||
{Some, _} -> dynamic_normalize(Meta, Var, Some)
|
{Some, _} -> dynamic_normalize(Meta, Var, Some)
|
||||||
|
|||||||
@@ -170,13 +170,22 @@ parse_error(Location, File, <<"syntax error before: ">>, Keyword, Input)
|
|||||||
|
|
||||||
%% Produce a human-readable message for errors before a sigil
|
%% Produce a human-readable message for errors before a sigil
|
||||||
parse_error(Location, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full, Input) ->
|
parse_error(Location, File, <<"syntax error before: ">>, <<"{sigil,", _Rest/binary>> = Full, Input) ->
|
||||||
{sigil, _, Sigil, [Content | _], _, _, _} = parse_erl_term(Full),
|
{sigil, _, Atom, [Content | _], _, _, _} = parse_erl_term(Full),
|
||||||
Content2 = case is_binary(Content) of
|
Content2 = case is_binary(Content) of
|
||||||
true -> Content;
|
true -> Content;
|
||||||
false -> <<>>
|
false -> <<>>
|
||||||
end,
|
end,
|
||||||
SigilName = list_to_binary(Sigil),
|
|
||||||
Message = <<"syntax error before: sigil \~", SigilName/binary, " starting with content '", Content2/binary, "'">>,
|
% :static_atoms_encoder might encode :sigil_ atoms as arbitrary terms
|
||||||
|
MaybeSigil = case is_atom(Atom) of
|
||||||
|
true -> case atom_to_binary(Atom) of
|
||||||
|
<<"sigil_", Chars/binary>> -> <<"\~", Chars/binary, " ">>;
|
||||||
|
_ -> <<>>
|
||||||
|
end;
|
||||||
|
false -> <<>>
|
||||||
|
end,
|
||||||
|
|
||||||
|
Message = <<"syntax error before: sigil ", MaybeSigil/binary, "starting with content '", Content2/binary, "'">>,
|
||||||
raise_snippet(Location, File, Input, 'Elixir.SyntaxError', Message);
|
raise_snippet(Location, File, Input, 'Elixir.SyntaxError', Message);
|
||||||
|
|
||||||
%% Binaries (and interpolation) are wrapped in [<<...>>]
|
%% Binaries (and interpolation) are wrapped in [<<...>>]
|
||||||
|
|||||||
@@ -856,10 +856,6 @@ expand_remote(Receiver, DotMeta, Right, Meta, Args, S, SL, #{context := Context}
|
|||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
AttachedDotMeta = attach_context_module(Receiver, DotMeta, E),
|
AttachedDotMeta = attach_context_module(Receiver, DotMeta, E),
|
||||||
|
|
||||||
is_atom(Receiver) andalso
|
|
||||||
elixir_env:trace({remote_function, Meta, Receiver, Right, length(Args)}, E),
|
|
||||||
|
|
||||||
{EArgs, {SA, _}, EA} = mapfold(fun expand_arg/3, {SL, S}, E, Args),
|
{EArgs, {SA, _}, EA} = mapfold(fun expand_arg/3, {SL, S}, E, Args),
|
||||||
|
|
||||||
case rewrite(Context, Receiver, AttachedDotMeta, Right, Meta, EArgs, S) of
|
case rewrite(Context, Receiver, AttachedDotMeta, Right, Meta, EArgs, S) of
|
||||||
|
|||||||
@@ -118,11 +118,11 @@ format_error({function_conflict, {Receiver, {Name, Arity}}}) ->
|
|||||||
format_error({unused_args, {Name, Arity}}) ->
|
format_error({unused_args, {Name, Arity}}) ->
|
||||||
io_lib:format("default values for the optional arguments in ~ts/~B are never used", [Name, Arity]);
|
io_lib:format("default values for the optional arguments in ~ts/~B are never used", [Name, Arity]);
|
||||||
|
|
||||||
format_error({unused_args, {Name, Arity}, 1}) ->
|
format_error({unused_args, {Name, Arity}, Count}) when Arity - Count == 1 ->
|
||||||
io_lib:format("the default value for the first optional argument in ~ts/~B is never used", [Name, Arity]);
|
io_lib:format("the default value for the last optional argument in ~ts/~B is never used", [Name, Arity]);
|
||||||
|
|
||||||
format_error({unused_args, {Name, Arity}, Count}) ->
|
format_error({unused_args, {Name, Arity}, Count}) ->
|
||||||
io_lib:format("the default values for the first ~B optional arguments in ~ts/~B are never used", [Count, Name, Arity]);
|
io_lib:format("the default values for the last ~B optional arguments in ~ts/~B are never used", [Arity - Count, Name, Arity]);
|
||||||
|
|
||||||
format_error({unused_def, {Name, Arity}, defp}) ->
|
format_error({unused_def, {Name, Arity}, defp}) ->
|
||||||
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
|
io_lib:format("function ~ts/~B is unused", [Name, Arity]);
|
||||||
|
|||||||
@@ -508,19 +508,17 @@ beam_location(ModuleAsCharlist) ->
|
|||||||
checker_info() ->
|
checker_info() ->
|
||||||
case get(elixir_checker_info) of
|
case get(elixir_checker_info) of
|
||||||
undefined -> undefined;
|
undefined -> undefined;
|
||||||
_ ->
|
_ -> 'Elixir.Module.ParallelChecker':get()
|
||||||
Log =
|
|
||||||
case erlang:get(elixir_code_diagnostics) of
|
|
||||||
{_, false} -> false;
|
|
||||||
_ -> true
|
|
||||||
end,
|
|
||||||
|
|
||||||
{'Elixir.Module.ParallelChecker':get(), Log}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
spawn_parallel_checker(undefined, _Module, _ModuleMap) ->
|
spawn_parallel_checker(undefined, _Module, _ModuleMap) ->
|
||||||
nil;
|
ok;
|
||||||
spawn_parallel_checker({CheckerInfo, Log}, Module, ModuleMap) ->
|
spawn_parallel_checker(CheckerInfo, Module, ModuleMap) ->
|
||||||
|
Log =
|
||||||
|
case erlang:get(elixir_code_diagnostics) of
|
||||||
|
{_, false} -> false;
|
||||||
|
_ -> true
|
||||||
|
end,
|
||||||
'Elixir.Module.ParallelChecker':spawn(CheckerInfo, Module, ModuleMap, Log).
|
'Elixir.Module.ParallelChecker':spawn(CheckerInfo, Module, ModuleMap, Log).
|
||||||
|
|
||||||
make_module_available(Module, Binary) ->
|
make_module_available(Module, Binary) ->
|
||||||
|
|||||||
@@ -939,11 +939,11 @@ build_access(Expr, {List, Meta}) ->
|
|||||||
|
|
||||||
%% Interpolation aware
|
%% Interpolation aware
|
||||||
|
|
||||||
build_sigil({sigil, Location, Sigil, Parts, Modifiers, Indentation, Delimiter}) ->
|
build_sigil({sigil, Location, Atom, Parts, Modifiers, Indentation, Delimiter}) ->
|
||||||
Meta = meta_from_location(Location),
|
Meta = meta_from_location(Location),
|
||||||
MetaWithDelimiter = [{delimiter, Delimiter} | Meta],
|
MetaWithDelimiter = [{delimiter, Delimiter} | Meta],
|
||||||
MetaWithIndentation = meta_with_indentation(Meta, Indentation),
|
MetaWithIndentation = meta_with_indentation(Meta, Indentation),
|
||||||
{list_to_atom("sigil_" ++ Sigil),
|
{Atom,
|
||||||
MetaWithDelimiter,
|
MetaWithDelimiter,
|
||||||
[{'<<>>', MetaWithIndentation, string_parts(Parts)}, Modifiers]}.
|
[{'<<>>', MetaWithIndentation, string_parts(Parts)}, Modifiers]}.
|
||||||
|
|
||||||
|
|||||||
@@ -1030,8 +1030,8 @@ extract_heredoc_with_interpolation(Line, Column, Scope, Interpol, T, H) ->
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
error ->
|
error ->
|
||||||
Message = "heredoc allows only zero or more whitespace characters followed by a new line after ",
|
Message = "heredoc allows only whitespace characters followed by a new line after opening ",
|
||||||
{error, {Line, Column, io_lib:format(Message, []), [H, H, H]}}
|
{error, {Line, Column + 3, io_lib:format(Message, []), [H, H, H]}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
extract_heredoc_header("\r\n" ++ Rest) ->
|
extract_heredoc_header("\r\n" ++ Rest) ->
|
||||||
@@ -1550,7 +1550,7 @@ tokenize_sigil_name([S | T], NameAcc, Line, Column, Scope, Tokens) when ?is_upca
|
|||||||
tokenize_sigil_name(T, [S | NameAcc], Line, Column + 1, Scope, Tokens);
|
tokenize_sigil_name(T, [S | NameAcc], Line, Column + 1, Scope, Tokens);
|
||||||
% With a lowercase letter and a non-empty NameAcc we return an error.
|
% With a lowercase letter and a non-empty NameAcc we return an error.
|
||||||
tokenize_sigil_name([S | _T] = Original, [_ | _] = NameAcc, _Line, _Column, _Scope, _Tokens) when ?is_downcase(S) ->
|
tokenize_sigil_name([S | _T] = Original, [_ | _] = NameAcc, _Line, _Column, _Scope, _Tokens) when ?is_downcase(S) ->
|
||||||
Message = "invalid sigil name, it should be either a one-letter lowercase letter or a" ++
|
Message = "invalid sigil name, it should be either a one-letter lowercase letter or a" ++
|
||||||
" sequence of uppercase letters only, got: ",
|
" sequence of uppercase letters only, got: ",
|
||||||
{error, Message, [$~] ++ lists:reverse(NameAcc) ++ Original};
|
{error, Message, [$~] ++ lists:reverse(NameAcc) ++ Original};
|
||||||
% We finished the letters, so the name is over.
|
% We finished the letters, so the name is over.
|
||||||
@@ -1561,12 +1561,8 @@ tokenize_sigil_contents([H, H, H | T] = Original, [S | _] = SigilName, Line, Col
|
|||||||
when ?is_quote(H) ->
|
when ?is_quote(H) ->
|
||||||
case extract_heredoc_with_interpolation(Line, Column, Scope, ?is_downcase(S), T, H) of
|
case extract_heredoc_with_interpolation(Line, Column, Scope, ?is_downcase(S), T, H) of
|
||||||
{ok, NewLine, NewColumn, Parts, Rest, NewScope} ->
|
{ok, NewLine, NewColumn, Parts, Rest, NewScope} ->
|
||||||
{Final, Modifiers} = collect_modifiers(Rest, []),
|
|
||||||
Indentation = NewColumn - 4,
|
Indentation = NewColumn - 4,
|
||||||
TokenColumn = Column - 1 - length(SigilName),
|
add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, Parts, Rest, NewScope, Tokens, Indentation, <<H, H, H>>);
|
||||||
Token = {sigil, {Line, TokenColumn, nil}, SigilName, Parts, Modifiers, Indentation, <<H, H, H>>},
|
|
||||||
NewColumnWithModifiers = NewColumn + length(Modifiers),
|
|
||||||
tokenize(Final, NewLine, NewColumnWithModifiers, NewScope, [Token | Tokens]);
|
|
||||||
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
error(Reason, [$~] ++ SigilName ++ Original, Scope, Tokens)
|
error(Reason, [$~] ++ SigilName ++ Original, Scope, Tokens)
|
||||||
@@ -1576,12 +1572,8 @@ tokenize_sigil_contents([H | T] = Original, [S | _] = SigilName, Line, Column, S
|
|||||||
when ?is_sigil(H) ->
|
when ?is_sigil(H) ->
|
||||||
case elixir_interpolation:extract(Line, Column + 1, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
|
case elixir_interpolation:extract(Line, Column + 1, Scope, ?is_downcase(S), T, sigil_terminator(H)) of
|
||||||
{NewLine, NewColumn, Parts, Rest, NewScope} ->
|
{NewLine, NewColumn, Parts, Rest, NewScope} ->
|
||||||
{Final, Modifiers} = collect_modifiers(Rest, []),
|
|
||||||
Indentation = nil,
|
Indentation = nil,
|
||||||
TokenColumn = Column - 1 - length(SigilName),
|
add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, tokens_to_binary(Parts), Rest, NewScope, Tokens, Indentation, <<H>>);
|
||||||
Token = {sigil, {Line, TokenColumn, nil}, SigilName, tokens_to_binary(Parts), Modifiers, Indentation, <<H>>},
|
|
||||||
NewColumnWithModifiers = NewColumn + length(Modifiers),
|
|
||||||
tokenize(Final, NewLine, NewColumnWithModifiers, NewScope, [Token | Tokens]);
|
|
||||||
|
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
Sigil = [$~, S, H],
|
Sigil = [$~, S, H],
|
||||||
@@ -1601,6 +1593,24 @@ tokenize_sigil_contents([H | _] = Original, SigilName, Line, Column, Scope, Toke
|
|||||||
tokenize_sigil_contents([], _SigilName, Line, Column, Scope, Tokens) ->
|
tokenize_sigil_contents([], _SigilName, Line, Column, Scope, Tokens) ->
|
||||||
tokenize([], Line, Column, Scope, Tokens).
|
tokenize([], Line, Column, Scope, Tokens).
|
||||||
|
|
||||||
|
add_sigil_token(SigilName, Line, Column, NewLine, NewColumn, Parts, Rest, Scope, Tokens, Indentation, Delimiter) ->
|
||||||
|
TokenColumn = Column - 1 - length(SigilName),
|
||||||
|
MaybeEncoded = case SigilName of
|
||||||
|
% Single-letter sigils present no risk of atom exhaustion (limited possibilities)
|
||||||
|
[_Char] -> {ok, list_to_atom("sigil_" ++ SigilName)};
|
||||||
|
_ -> unsafe_to_atom("sigil_" ++ SigilName, Line, TokenColumn, Scope)
|
||||||
|
end,
|
||||||
|
case MaybeEncoded of
|
||||||
|
{ok, Atom} ->
|
||||||
|
{Final, Modifiers} = collect_modifiers(Rest, []),
|
||||||
|
Token = {sigil, {Line, TokenColumn, nil}, Atom, Parts, Modifiers, Indentation, Delimiter},
|
||||||
|
NewColumnWithModifiers = NewColumn + length(Modifiers),
|
||||||
|
tokenize(Final, NewLine, NewColumnWithModifiers, Scope, [Token | Tokens]);
|
||||||
|
|
||||||
|
{error, Reason} ->
|
||||||
|
error(Reason, Rest, Scope, Tokens)
|
||||||
|
end.
|
||||||
|
|
||||||
%% Fail early on invalid do syntax. For example, after
|
%% Fail early on invalid do syntax. For example, after
|
||||||
%% most keywords, after comma and so on.
|
%% most keywords, after comma and so on.
|
||||||
tokenize_keyword_terminator(DoLine, DoColumn, do, [{identifier, {Line, Column, Meta}, Atom} | T]) ->
|
tokenize_keyword_terminator(DoLine, DoColumn, do, [{identifier, {Line, Column, Meta}, Atom} | T]) ->
|
||||||
@@ -1796,7 +1806,7 @@ prune_tokens([{OpType, _, _} | _] = Tokens, [], Terminators)
|
|||||||
OpType =:= in_match_op; OpType =:= type_op; OpType =:= dual_op; OpType =:= mult_op;
|
OpType =:= in_match_op; OpType =:= type_op; OpType =:= dual_op; OpType =:= mult_op;
|
||||||
OpType =:= power_op; OpType =:= concat_op; OpType =:= range_op; OpType =:= xor_op;
|
OpType =:= power_op; OpType =:= concat_op; OpType =:= range_op; OpType =:= xor_op;
|
||||||
OpType =:= pipe_op; OpType =:= stab_op; OpType =:= when_op; OpType =:= assoc_op;
|
OpType =:= pipe_op; OpType =:= stab_op; OpType =:= when_op; OpType =:= assoc_op;
|
||||||
OpType =:= rel_op; OpType =:= ternary_op ->
|
OpType =:= rel_op; OpType =:= ternary_op; OpType =:= capture_op ->
|
||||||
{Tokens, Terminators};
|
{Tokens, Terminators};
|
||||||
%%% or we traverse until the end.
|
%%% or we traverse until the end.
|
||||||
prune_tokens([_ | Tokens], Opener, Terminators) ->
|
prune_tokens([_ | Tokens], Opener, Terminators) ->
|
||||||
|
|||||||
@@ -275,6 +275,8 @@ defmodule Code.Formatter.ContainersTest do
|
|||||||
assert_format "<< 1 :: 2 - unit(3) >>", "<<1::2-unit(3)>>"
|
assert_format "<< 1 :: 2 - unit(3) >>", "<<1::2-unit(3)>>"
|
||||||
assert_format "<< 1 :: 2 * 3 - unit(4) >>", "<<1::2*3-unit(4)>>"
|
assert_format "<< 1 :: 2 * 3 - unit(4) >>", "<<1::2*3-unit(4)>>"
|
||||||
assert_format "<< 1 :: 2 - unit(3) - 4 / 5 >>", "<<1::2-unit(3)-(4 / 5)>>"
|
assert_format "<< 1 :: 2 - unit(3) - 4 / 5 >>", "<<1::2-unit(3)-(4 / 5)>>"
|
||||||
|
assert_format "<<0 :: ( x - 1 ) * 5>>", "<<0::(x-1)*5>>"
|
||||||
|
assert_format "<<0 :: 2 * 3 * 4>>", "<<0::(2*3)*4>>"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "in comprehensions" do
|
test "in comprehensions" do
|
||||||
|
|||||||
@@ -428,11 +428,12 @@ defmodule CodeFragmentTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "column out of range" do
|
test "column out of range" do
|
||||||
assert CF.surround_context("hello", {1, 20}) == :none
|
assert CF.surround_context("hello", {1, 20}) ==
|
||||||
|
%{begin: {1, 1}, context: {:local_or_var, ~c"hello"}, end: {1, 6}}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "local_or_var" do
|
test "local_or_var" do
|
||||||
for i <- 1..8 do
|
for i <- 1..9 do
|
||||||
assert CF.surround_context("hello_wo", {1, i}) == %{
|
assert CF.surround_context("hello_wo", {1, i}) == %{
|
||||||
context: {:local_or_var, ~c"hello_wo"},
|
context: {:local_or_var, ~c"hello_wo"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -440,9 +441,9 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("hello_wo", {1, 9}) == :none
|
assert CF.surround_context("hello_wo ", {1, 10}) == :none
|
||||||
|
|
||||||
for i <- 2..9 do
|
for i <- 2..10 do
|
||||||
assert CF.surround_context(" hello_wo", {1, i}) == %{
|
assert CF.surround_context(" hello_wo", {1, i}) == %{
|
||||||
context: {:local_or_var, ~c"hello_wo"},
|
context: {:local_or_var, ~c"hello_wo"},
|
||||||
begin: {1, 2},
|
begin: {1, 2},
|
||||||
@@ -450,9 +451,9 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context(" hello_wo", {1, 10}) == :none
|
assert CF.surround_context(" hello_wo ", {1, 11}) == :none
|
||||||
|
|
||||||
for i <- 1..6 do
|
for i <- 1..7 do
|
||||||
assert CF.surround_context("hello!", {1, i}) == %{
|
assert CF.surround_context("hello!", {1, i}) == %{
|
||||||
context: {:local_or_var, ~c"hello!"},
|
context: {:local_or_var, ~c"hello!"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -460,9 +461,9 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("hello!", {1, 7}) == :none
|
assert CF.surround_context("hello! ", {1, 8}) == :none
|
||||||
|
|
||||||
for i <- 1..5 do
|
for i <- 1..6 do
|
||||||
assert CF.surround_context("안녕_세상", {1, i}) == %{
|
assert CF.surround_context("안녕_세상", {1, i}) == %{
|
||||||
context: {:local_or_var, ~c"안녕_세상"},
|
context: {:local_or_var, ~c"안녕_세상"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -470,7 +471,7 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("안녕_세상", {1, 6}) == :none
|
assert CF.surround_context("안녕_세상 ", {1, 6}) == :none
|
||||||
|
|
||||||
# Keywords are not local or var
|
# Keywords are not local or var
|
||||||
for keyword <- ~w(do end after catch else rescue fn true false nil)c do
|
for keyword <- ~w(do end after catch else rescue fn true false nil)c do
|
||||||
@@ -484,8 +485,38 @@ defmodule CodeFragmentTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "local call" do
|
test "local + operator" do
|
||||||
for i <- 1..8 do
|
for i <- 1..8 do
|
||||||
|
assert CF.surround_context("hello_wo+", {1, i}) == %{
|
||||||
|
context: {:local_or_var, ~c"hello_wo"},
|
||||||
|
begin: {1, 1},
|
||||||
|
end: {1, 9}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
assert CF.surround_context("hello_wo+", {1, 9}) == %{
|
||||||
|
begin: {1, 9},
|
||||||
|
context: {:operator, ~c"+"},
|
||||||
|
end: {1, 10}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i <- 1..9 do
|
||||||
|
assert CF.surround_context("hello_wo +", {1, i}) == %{
|
||||||
|
context: {:local_or_var, ~c"hello_wo"},
|
||||||
|
begin: {1, 1},
|
||||||
|
end: {1, 9}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
assert CF.surround_context("hello_wo +", {1, 10}) == %{
|
||||||
|
begin: {1, 10},
|
||||||
|
context: {:operator, ~c"+"},
|
||||||
|
end: {1, 11}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "local call" do
|
||||||
|
for i <- 1..9 do
|
||||||
assert CF.surround_context("hello_wo(", {1, i}) == %{
|
assert CF.surround_context("hello_wo(", {1, i}) == %{
|
||||||
context: {:local_call, ~c"hello_wo"},
|
context: {:local_call, ~c"hello_wo"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -493,9 +524,9 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("hello_wo(", {1, 9}) == :none
|
assert CF.surround_context("hello_wo(", {1, 10}) == :none
|
||||||
|
|
||||||
for i <- 1..8 do
|
for i <- 1..9 do
|
||||||
assert CF.surround_context("hello_wo (", {1, i}) == %{
|
assert CF.surround_context("hello_wo (", {1, i}) == %{
|
||||||
context: {:local_call, ~c"hello_wo"},
|
context: {:local_call, ~c"hello_wo"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -503,9 +534,10 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("hello_wo (", {1, 9}) == :none
|
assert CF.surround_context("hello_wo (", {1, 10}) == :none
|
||||||
|
assert CF.surround_context("hello_wo (", {1, 11}) == :none
|
||||||
|
|
||||||
for i <- 1..6 do
|
for i <- 1..7 do
|
||||||
assert CF.surround_context("hello!(", {1, i}) == %{
|
assert CF.surround_context("hello!(", {1, i}) == %{
|
||||||
context: {:local_call, ~c"hello!"},
|
context: {:local_call, ~c"hello!"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -513,9 +545,9 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("hello!(", {1, 7}) == :none
|
assert CF.surround_context("hello!(", {1, 8}) == :none
|
||||||
|
|
||||||
for i <- 1..5 do
|
for i <- 1..6 do
|
||||||
assert CF.surround_context("안녕_세상(", {1, i}) == %{
|
assert CF.surround_context("안녕_세상(", {1, i}) == %{
|
||||||
context: {:local_call, ~c"안녕_세상"},
|
context: {:local_call, ~c"안녕_세상"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -523,7 +555,7 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("안녕_세상(", {1, 6}) == :none
|
assert CF.surround_context("안녕_세상(", {1, 7}) == :none
|
||||||
end
|
end
|
||||||
|
|
||||||
test "local arity" do
|
test "local arity" do
|
||||||
@@ -651,7 +683,7 @@ defmodule CodeFragmentTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "alias" do
|
test "alias" do
|
||||||
for i <- 1..8 do
|
for i <- 1..9 do
|
||||||
assert CF.surround_context("HelloWor", {1, i}) == %{
|
assert CF.surround_context("HelloWor", {1, i}) == %{
|
||||||
context: {:alias, ~c"HelloWor"},
|
context: {:alias, ~c"HelloWor"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -659,9 +691,9 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("HelloWor", {1, 9}) == :none
|
assert CF.surround_context("HelloWor ", {1, 10}) == :none
|
||||||
|
|
||||||
for i <- 2..9 do
|
for i <- 2..10 do
|
||||||
assert CF.surround_context(" HelloWor", {1, i}) == %{
|
assert CF.surround_context(" HelloWor", {1, i}) == %{
|
||||||
context: {:alias, ~c"HelloWor"},
|
context: {:alias, ~c"HelloWor"},
|
||||||
begin: {1, 2},
|
begin: {1, 2},
|
||||||
@@ -669,9 +701,9 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context(" HelloWor", {1, 10}) == :none
|
assert CF.surround_context(" HelloWor ", {1, 11}) == :none
|
||||||
|
|
||||||
for i <- 1..9 do
|
for i <- 1..10 do
|
||||||
assert CF.surround_context("Hello.Wor", {1, i}) == %{
|
assert CF.surround_context("Hello.Wor", {1, i}) == %{
|
||||||
context: {:alias, ~c"Hello.Wor"},
|
context: {:alias, ~c"Hello.Wor"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -679,9 +711,9 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("Hello.Wor", {1, 10}) == :none
|
assert CF.surround_context("Hello.Wor ", {1, 11}) == :none
|
||||||
|
|
||||||
for i <- 1..11 do
|
for i <- 1..12 do
|
||||||
assert CF.surround_context("Hello . Wor", {1, i}) == %{
|
assert CF.surround_context("Hello . Wor", {1, i}) == %{
|
||||||
context: {:alias, ~c"Hello.Wor"},
|
context: {:alias, ~c"Hello.Wor"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -689,9 +721,9 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("Hello . Wor", {1, 12}) == :none
|
assert CF.surround_context("Hello . Wor ", {1, 13}) == :none
|
||||||
|
|
||||||
for i <- 1..15 do
|
for i <- 1..16 do
|
||||||
assert CF.surround_context("Foo . Bar . Baz", {1, i}) == %{
|
assert CF.surround_context("Foo . Bar . Baz", {1, i}) == %{
|
||||||
context: {:alias, ~c"Foo.Bar.Baz"},
|
context: {:alias, ~c"Foo.Bar.Baz"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -706,6 +738,14 @@ defmodule CodeFragmentTest do
|
|||||||
end: {3, 5}
|
end: {3, 5}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
for i <- 1..11 do
|
||||||
|
assert CF.surround_context("Foo.Bar.Baz.foo(bar)", {1, i}) == %{
|
||||||
|
context: {:alias, ~c"Foo.Bar.Baz"},
|
||||||
|
begin: {1, 1},
|
||||||
|
end: {1, 12}
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "underscored special forms" do
|
test "underscored special forms" do
|
||||||
@@ -715,17 +755,21 @@ defmodule CodeFragmentTest do
|
|||||||
end: {1, 11}
|
end: {1, 11}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert CF.surround_context("__MODULE__.Foo", {1, 12}) == %{
|
for i <- 1..15 do
|
||||||
context: {:alias, {:local_or_var, ~c"__MODULE__"}, ~c"Foo"},
|
assert CF.surround_context("__MODULE__.Foo", {1, i}) == %{
|
||||||
begin: {1, 1},
|
context: {:alias, {:local_or_var, ~c"__MODULE__"}, ~c"Foo"},
|
||||||
end: {1, 15}
|
begin: {1, 1},
|
||||||
}
|
end: {1, 15}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
assert CF.surround_context("__MODULE__.Foo.Sub", {1, 16}) == %{
|
for i <- 1..19 do
|
||||||
context: {:alias, {:local_or_var, ~c"__MODULE__"}, ~c"Foo.Sub"},
|
assert CF.surround_context("__MODULE__.Foo.Sub", {1, i}) == %{
|
||||||
begin: {1, 1},
|
context: {:alias, {:local_or_var, ~c"__MODULE__"}, ~c"Foo.Sub"},
|
||||||
end: {1, 19}
|
begin: {1, 1},
|
||||||
}
|
end: {1, 19}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
assert CF.surround_context("%__MODULE__{}", {1, 5}) == %{
|
assert CF.surround_context("%__MODULE__{}", {1, 5}) == %{
|
||||||
context: {:struct, {:local_or_var, ~c"__MODULE__"}},
|
context: {:struct, {:local_or_var, ~c"__MODULE__"}},
|
||||||
@@ -771,17 +815,21 @@ defmodule CodeFragmentTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "attribute submodules" do
|
test "attribute submodules" do
|
||||||
assert CF.surround_context("@some.Foo", {1, 8}) == %{
|
for i <- 1..10 do
|
||||||
context: {:alias, {:module_attribute, ~c"some"}, ~c"Foo"},
|
assert CF.surround_context("@some.Foo", {1, i}) == %{
|
||||||
begin: {1, 1},
|
context: {:alias, {:module_attribute, ~c"some"}, ~c"Foo"},
|
||||||
end: {1, 10}
|
begin: {1, 1},
|
||||||
}
|
end: {1, 10}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
assert CF.surround_context("@some.Foo.Sub", {1, 12}) == %{
|
for i <- 1..14 do
|
||||||
context: {:alias, {:module_attribute, ~c"some"}, ~c"Foo.Sub"},
|
assert CF.surround_context("@some.Foo.Sub", {1, i}) == %{
|
||||||
begin: {1, 1},
|
context: {:alias, {:module_attribute, ~c"some"}, ~c"Foo.Sub"},
|
||||||
end: {1, 14}
|
begin: {1, 1},
|
||||||
}
|
end: {1, 14}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
assert CF.surround_context("%@some{}", {1, 5}) == %{
|
assert CF.surround_context("%@some{}", {1, 5}) == %{
|
||||||
context: {:struct, {:module_attribute, ~c"some"}},
|
context: {:struct, {:module_attribute, ~c"some"}},
|
||||||
@@ -858,7 +906,7 @@ defmodule CodeFragmentTest do
|
|||||||
end: {1, 15}
|
end: {1, 15}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i <- 2..9 do
|
for i <- 2..10 do
|
||||||
assert CF.surround_context("%HelloWor", {1, i}) == %{
|
assert CF.surround_context("%HelloWor", {1, i}) == %{
|
||||||
context: {:struct, ~c"HelloWor"},
|
context: {:struct, ~c"HelloWor"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -866,7 +914,7 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("%HelloWor", {1, 10}) == :none
|
assert CF.surround_context("%HelloWor ", {1, 11}) == :none
|
||||||
|
|
||||||
# With dot
|
# With dot
|
||||||
assert CF.surround_context("%Hello.Wor", {1, 1}) == %{
|
assert CF.surround_context("%Hello.Wor", {1, 1}) == %{
|
||||||
@@ -875,7 +923,7 @@ defmodule CodeFragmentTest do
|
|||||||
end: {1, 11}
|
end: {1, 11}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i <- 2..10 do
|
for i <- 2..11 do
|
||||||
assert CF.surround_context("%Hello.Wor", {1, i}) == %{
|
assert CF.surround_context("%Hello.Wor", {1, i}) == %{
|
||||||
context: {:struct, ~c"Hello.Wor"},
|
context: {:struct, ~c"Hello.Wor"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -883,7 +931,7 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("%Hello.Wor", {1, 11}) == :none
|
assert CF.surround_context("%Hello.Wor ", {1, 12}) == :none
|
||||||
|
|
||||||
# With spaces
|
# With spaces
|
||||||
assert CF.surround_context("% Hello . Wor", {1, 1}) == %{
|
assert CF.surround_context("% Hello . Wor", {1, 1}) == %{
|
||||||
@@ -892,7 +940,7 @@ defmodule CodeFragmentTest do
|
|||||||
end: {1, 14}
|
end: {1, 14}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i <- 2..13 do
|
for i <- 2..14 do
|
||||||
assert CF.surround_context("% Hello . Wor", {1, i}) == %{
|
assert CF.surround_context("% Hello . Wor", {1, i}) == %{
|
||||||
context: {:struct, ~c"Hello.Wor"},
|
context: {:struct, ~c"Hello.Wor"},
|
||||||
begin: {1, 1},
|
begin: {1, 1},
|
||||||
@@ -900,7 +948,7 @@ defmodule CodeFragmentTest do
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
assert CF.surround_context("% Hello . Wor", {1, 14}) == :none
|
assert CF.surround_context("% Hello . Wor ", {1, 15}) == :none
|
||||||
end
|
end
|
||||||
|
|
||||||
test "module attributes" do
|
test "module attributes" do
|
||||||
@@ -1098,6 +1146,8 @@ defmodule CodeFragmentTest do
|
|||||||
|
|
||||||
test "keeps operators" do
|
test "keeps operators" do
|
||||||
assert cc2q("1 + 2") == s2q("1 + __cursor__()")
|
assert cc2q("1 + 2") == s2q("1 + __cursor__()")
|
||||||
|
assert cc2q("&foo") == s2q("&__cursor__()")
|
||||||
|
assert cc2q("&foo/") == s2q("&foo/__cursor__()")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "keeps function calls without parens" do
|
test "keeps function calls without parens" do
|
||||||
@@ -1200,11 +1250,6 @@ defmodule CodeFragmentTest do
|
|||||||
assert cc2q("(fn x, y -> x + y end") == s2q("(__cursor__())")
|
assert cc2q("(fn x, y -> x + y end") == s2q("(__cursor__())")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "removes captures" do
|
|
||||||
assert cc2q("[& &1") == s2q("[__cursor__()]")
|
|
||||||
assert cc2q("[&(&1") == s2q("[__cursor__()]")
|
|
||||||
end
|
|
||||||
|
|
||||||
test "removes closed terminators" do
|
test "removes closed terminators" do
|
||||||
assert cc2q("foo([1, 2, 3]") == s2q("foo(__cursor__())")
|
assert cc2q("foo([1, 2, 3]") == s2q("foo(__cursor__())")
|
||||||
assert cc2q("foo({1, 2, 3}") == s2q("foo(__cursor__())")
|
assert cc2q("foo({1, 2, 3}") == s2q("foo(__cursor__())")
|
||||||
|
|||||||
@@ -226,6 +226,11 @@ defmodule Code.Normalizer.QuotedASTTest do
|
|||||||
assert quoted_to_string(quoted) <> "\n" == expected
|
assert quoted_to_string(quoted) <> "\n" == expected
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "invalid block" do
|
||||||
|
assert quoted_to_string({:__block__, [], {:bar, [], []}}) ==
|
||||||
|
"{:__block__, [], {:bar, [], []}}"
|
||||||
|
end
|
||||||
|
|
||||||
test "not in" do
|
test "not in" do
|
||||||
assert quoted_to_string(quote(do: false not in [])) == "false not in []"
|
assert quoted_to_string(quote(do: false not in [])) == "false not in []"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -90,6 +90,28 @@ defmodule CodeTest do
|
|||||||
Code.eval_quoted(quoted, [])
|
Code.eval_quoted(quoted, [])
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "captures unknown local calls" do
|
||||||
|
sample = """
|
||||||
|
defmodule CodeTest.UnknownLocalCall do
|
||||||
|
def perform do
|
||||||
|
foo()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
"""
|
||||||
|
|
||||||
|
assert {:rescued, [%{message: message}]} =
|
||||||
|
Code.with_diagnostics(fn ->
|
||||||
|
try do
|
||||||
|
quoted = Code.string_to_quoted!(sample, columns: true)
|
||||||
|
Code.eval_quoted(quoted, [])
|
||||||
|
rescue
|
||||||
|
_ -> :rescued
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert message =~ "undefined function foo/0"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "eval_string/1,2,3" do
|
describe "eval_string/1,2,3" do
|
||||||
@@ -325,7 +347,12 @@ defmodule CodeTest do
|
|||||||
assert env.versioned_vars == %{}
|
assert env.versioned_vars == %{}
|
||||||
|
|
||||||
assert_receive {:trace, {:on_module, _, _}, %{module: CodeTest.TracingPruning} = trace_env}
|
assert_receive {:trace, {:on_module, _, _}, %{module: CodeTest.TracingPruning} = trace_env}
|
||||||
assert trace_env.versioned_vars == %{{:result, Kernel} => 5, {:x, nil} => 1, {:y, nil} => 4}
|
|
||||||
|
assert trace_env.versioned_vars == %{
|
||||||
|
{:result, :elixir_compiler} => 5,
|
||||||
|
{:x, nil} => 1,
|
||||||
|
{:y, nil} => 4
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "with defguard" do
|
test "with defguard" do
|
||||||
@@ -474,6 +501,14 @@ defmodule CodeTest do
|
|||||||
:code.purge(CompileCrossSample)
|
:code.purge(CompileCrossSample)
|
||||||
:code.delete(CompileCrossSample)
|
:code.delete(CompileCrossSample)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "disables tail call optimization at the root" do
|
||||||
|
try do
|
||||||
|
Code.compile_string("List.flatten(123)")
|
||||||
|
rescue
|
||||||
|
_ -> assert Enum.any?(__STACKTRACE__, &match?({_, :__FILE__, 1, _}, &1))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "format_string/2 returns empty iodata for empty string" do
|
test "format_string/2 returns empty iodata for empty string" do
|
||||||
|
|||||||
@@ -1102,6 +1102,16 @@ defmodule EnumTest do
|
|||||||
[2, 1, 3]
|
[2, 1, 3]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "slice on MapSets" do
|
||||||
|
assert MapSet.new(1..10) |> Enum.slice(0, 2) |> Enum.count() == 2
|
||||||
|
assert MapSet.new(1..3) |> Enum.slice(0, 10) |> Enum.count() == 3
|
||||||
|
assert MapSet.new(1..10) |> Enum.slice(0..1) |> Enum.count() == 2
|
||||||
|
assert MapSet.new(1..3) |> Enum.slice(0..10) |> Enum.count() == 3
|
||||||
|
|
||||||
|
assert MapSet.new(1..10) |> Enum.slice(0..4//2) |> Enum.count() == 3
|
||||||
|
assert MapSet.new(1..10) |> Enum.slice(0..5//2) |> Enum.count() == 3
|
||||||
|
end
|
||||||
|
|
||||||
test "sort/1" do
|
test "sort/1" do
|
||||||
assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5]
|
assert Enum.sort([5, 3, 2, 4, 1]) == [1, 2, 3, 4, 5]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -381,4 +381,30 @@ defmodule Kernel.DocsTest do
|
|||||||
{{:fuz, 0}, :none}
|
{{:fuz, 0}, :none}
|
||||||
] = Enum.sort(function_docs)
|
] = Enum.sort(function_docs)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "generated functions are annotated as such" do
|
||||||
|
write_beam(
|
||||||
|
defmodule ToBeUsed do
|
||||||
|
defmacro __using__(_) do
|
||||||
|
quote do
|
||||||
|
@doc "Hello"
|
||||||
|
def foo, do: :bar
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
write_beam(
|
||||||
|
defmodule WillBeUsing do
|
||||||
|
use ToBeUsed
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
{:docs_v1, _, _, _, _, _, docs} = Code.fetch_docs(WillBeUsing)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
{{:function, :foo, 0}, [generated: true, location: 399], ["foo()"],
|
||||||
|
%{"en" => "Hello"}, %{}}
|
||||||
|
] = docs
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -182,6 +182,9 @@ defmodule Kernel.ParserTest do
|
|||||||
assert Code.string_to_quoted(":there_is_no_such_atom", existing_atoms_only: true) ==
|
assert Code.string_to_quoted(":there_is_no_such_atom", existing_atoms_only: true) ==
|
||||||
{:error,
|
{:error,
|
||||||
{[line: 1, column: 1], "unsafe atom does not exist: ", "there_is_no_such_atom"}}
|
{[line: 1, column: 1], "unsafe atom does not exist: ", "there_is_no_such_atom"}}
|
||||||
|
|
||||||
|
assert Code.string_to_quoted("~UNKNOWN'foo bar'", existing_atoms_only: true) ==
|
||||||
|
{:error, {[line: 1, column: 1], "unsafe atom does not exist: ", "sigil_UNKNOWN"}}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "encodes atoms" do
|
test "encodes atoms" do
|
||||||
@@ -228,6 +231,20 @@ defmodule Kernel.ParserTest do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "encodes multi-letter sigils" do
|
||||||
|
ref = make_ref()
|
||||||
|
|
||||||
|
encoder = fn atom, meta ->
|
||||||
|
assert atom == "sigil_UNKNOWN"
|
||||||
|
assert meta[:line] == 1
|
||||||
|
assert meta[:column] == 1
|
||||||
|
{:ok, ref}
|
||||||
|
end
|
||||||
|
|
||||||
|
assert {:ok, {^ref, [delimiter: "'", line: 1], [{:<<>>, [line: 1], ["abc"]}, []]}} =
|
||||||
|
Code.string_to_quoted("~UNKNOWN'abc'", static_atoms_encoder: encoder)
|
||||||
|
end
|
||||||
|
|
||||||
test "addresses ambiguities" do
|
test "addresses ambiguities" do
|
||||||
encoder = fn string, _meta -> {:ok, {:atom, string}} end
|
encoder = fn string, _meta -> {:ok, {:atom, string}} end
|
||||||
|
|
||||||
@@ -254,6 +271,16 @@ defmodule Kernel.ParserTest do
|
|||||||
Code.string_to_quoted("[do: 1, true: 2, end: 3]", static_atoms_encoder: encoder)
|
Code.string_to_quoted("[do: 1, true: 2, end: 3]", static_atoms_encoder: encoder)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "does not encode one-letter sigils" do
|
||||||
|
encoder = fn atom, _meta -> raise "shouldn't be invoked for #{atom}" end
|
||||||
|
|
||||||
|
assert {:ok, {:sigil_z, [{:delimiter, "'"}, {:line, 1}], [{:<<>>, [line: 1], ["foo"]}, []]}} =
|
||||||
|
Code.string_to_quoted("~z'foo'", static_atoms_encoder: encoder)
|
||||||
|
|
||||||
|
assert {:ok, {:sigil_Z, [{:delimiter, "'"}, {:line, 1}], [{:<<>>, [line: 1], ["foo"]}, []]}} =
|
||||||
|
Code.string_to_quoted("~Z'foo'", static_atoms_encoder: encoder)
|
||||||
|
end
|
||||||
|
|
||||||
test "returns errors on long atoms even when using static_atoms_encoder" do
|
test "returns errors on long atoms even when using static_atoms_encoder" do
|
||||||
atom = String.duplicate("a", 256)
|
atom = String.duplicate("a", 256)
|
||||||
|
|
||||||
@@ -271,6 +298,9 @@ defmodule Kernel.ParserTest do
|
|||||||
|
|
||||||
assert {:error, {[line: 1, column: 1], "Invalid atom name: ", "there_is_no_such_atom"}} =
|
assert {:error, {[line: 1, column: 1], "Invalid atom name: ", "there_is_no_such_atom"}} =
|
||||||
Code.string_to_quoted(":there_is_no_such_atom", static_atoms_encoder: encoder)
|
Code.string_to_quoted(":there_is_no_such_atom", static_atoms_encoder: encoder)
|
||||||
|
|
||||||
|
assert {:error, {[line: 1, column: 1], "Invalid atom name: ", "sigil_UNKNOWN"}} =
|
||||||
|
Code.string_to_quoted("~UNKNOWN'foo bar'", static_atoms_encoder: encoder)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "may return tuples" do
|
test "may return tuples" do
|
||||||
@@ -538,7 +568,7 @@ defmodule Kernel.ParserTest do
|
|||||||
describe "syntax errors" do
|
describe "syntax errors" do
|
||||||
test "invalid heredoc start" do
|
test "invalid heredoc start" do
|
||||||
assert_syntax_error(
|
assert_syntax_error(
|
||||||
~r/nofile:1:1: heredoc allows only zero or more whitespace characters followed by a new line after \"\"\"/,
|
~r/nofile:1:4: heredoc allows only whitespace characters followed by a new line after opening \"\"\"/,
|
||||||
~c"\"\"\"bar\n\"\"\""
|
~c"\"\"\"bar\n\"\"\""
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -224,6 +224,17 @@ defmodule Kernel.RaiseTest do
|
|||||||
assert result == "an exception"
|
assert result == "an exception"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "named function clause (stacktrace) or runtime (no stacktrace) error" do
|
||||||
|
result =
|
||||||
|
try do
|
||||||
|
Access.get("foo", 0)
|
||||||
|
rescue
|
||||||
|
x in [FunctionClauseError, CaseClauseError] -> Exception.message(x)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == "no function clause matching in Access.get/3"
|
||||||
|
end
|
||||||
|
|
||||||
test "with higher precedence than catch" do
|
test "with higher precedence than catch" do
|
||||||
result =
|
result =
|
||||||
try do
|
try do
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ defmodule Kernel.TracersTest do
|
|||||||
require Integer
|
require Integer
|
||||||
true = Integer.is_odd(1)
|
true = Integer.is_odd(1)
|
||||||
{1, ""} = Integer.parse("1")
|
{1, ""} = Integer.parse("1")
|
||||||
|
"foo" = Atom.to_string(:foo)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
assert_receive {{:remote_macro, meta, Integer, :is_odd, 1}, _}
|
assert_receive {{:remote_macro, meta, Integer, :is_odd, 1}, _}
|
||||||
@@ -127,6 +128,10 @@ defmodule Kernel.TracersTest do
|
|||||||
assert_receive {{:remote_function, meta, Integer, :parse, 1}, _}
|
assert_receive {{:remote_function, meta, Integer, :parse, 1}, _}
|
||||||
assert meta[:line] == 3
|
assert meta[:line] == 3
|
||||||
assert meta[:column] == 19
|
assert meta[:column] == 19
|
||||||
|
|
||||||
|
assert_receive {{:remote_function, meta, Atom, :to_string, 1}, _}
|
||||||
|
assert meta[:line] == 4
|
||||||
|
assert meta[:column] == 14
|
||||||
end
|
end
|
||||||
|
|
||||||
test "traces remote via captures" do
|
test "traces remote via captures" do
|
||||||
|
|||||||
@@ -563,17 +563,17 @@ defmodule Kernel.WarningTest do
|
|||||||
end
|
end
|
||||||
""")
|
""")
|
||||||
end) =~
|
end) =~
|
||||||
"the default values for the first 2 optional arguments in b/3 are never used\n nofile:3"
|
"the default value for the last optional argument in b/3 is never used\n nofile:3"
|
||||||
|
|
||||||
assert capture_err(fn ->
|
assert capture_err(fn ->
|
||||||
Code.eval_string(~S"""
|
Code.eval_string(~S"""
|
||||||
defmodule Sample3 do
|
defmodule Sample3 do
|
||||||
def a, do: b(1)
|
def a, do: b(1, 2)
|
||||||
defp b(arg1 \\ 1, arg2 \\ 2, arg3 \\ 3), do: [arg1, arg2, arg3]
|
defp b(arg1, arg2 \\ 2, arg3 \\ 3, arg4 \\ 4), do: [arg1, arg2, arg3, arg4]
|
||||||
end
|
end
|
||||||
""")
|
""")
|
||||||
end) =~
|
end) =~
|
||||||
"the default value for the first optional argument in b/3 is never used\n nofile:3"
|
"the default values for the last 2 optional arguments in b/4 are never used"
|
||||||
|
|
||||||
assert capture_err(fn ->
|
assert capture_err(fn ->
|
||||||
Code.eval_string(~S"""
|
Code.eval_string(~S"""
|
||||||
@@ -587,17 +587,6 @@ defmodule Kernel.WarningTest do
|
|||||||
assert capture_err(fn ->
|
assert capture_err(fn ->
|
||||||
Code.eval_string(~S"""
|
Code.eval_string(~S"""
|
||||||
defmodule Sample5 do
|
defmodule Sample5 do
|
||||||
def a, do: b(1, 2, 3)
|
|
||||||
defp b(arg1 \\ 1, arg2 \\ 2, arg3 \\ 3)
|
|
||||||
|
|
||||||
defp b(arg1, arg2, arg3), do: [arg1, arg2, arg3]
|
|
||||||
end
|
|
||||||
""")
|
|
||||||
end) =~ "default values for the optional arguments in b/3 are never used\n nofile:3"
|
|
||||||
|
|
||||||
assert capture_err(fn ->
|
|
||||||
Code.eval_string(~S"""
|
|
||||||
defmodule Sample6 do
|
|
||||||
def a, do: b(1, 2)
|
def a, do: b(1, 2)
|
||||||
defp b(arg1 \\ 1, arg2 \\ 2, arg3 \\ 3)
|
defp b(arg1 \\ 1, arg2 \\ 2, arg3 \\ 3)
|
||||||
|
|
||||||
@@ -605,9 +594,9 @@ defmodule Kernel.WarningTest do
|
|||||||
end
|
end
|
||||||
""")
|
""")
|
||||||
end) =~
|
end) =~
|
||||||
"the default values for the first 2 optional arguments in b/3 are never used\n nofile:3"
|
"the default value for the last optional argument in b/3 is never used\n nofile:3"
|
||||||
after
|
after
|
||||||
purge([Sample1, Sample2, Sample3, Sample4, Sample5, Sample6])
|
purge([Sample1, Sample2, Sample3, Sample4, Sample5])
|
||||||
end
|
end
|
||||||
|
|
||||||
test "unused import" do
|
test "unused import" do
|
||||||
|
|||||||
@@ -831,11 +831,25 @@ defmodule KernelTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "matching attribute" do
|
test "matching attribute" do
|
||||||
assert_raise ArgumentError, ~r"invalid write attribute syntax", fn ->
|
assert_raise ArgumentError, ~r"invalid usage of module attributes", fn ->
|
||||||
defmodule MatchAttributeInModule do
|
defmodule MatchAttributeInModule do
|
||||||
@foo = 42
|
@foo = 42
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
assert_raise ArgumentError, ~r"invalid usage of module attributes", fn ->
|
||||||
|
defmodule MatchAttributeInModule do
|
||||||
|
@foo 16
|
||||||
|
<<_::@foo>> = "ab"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_raise ArgumentError, ~r"invalid usage of module attributes", fn ->
|
||||||
|
defmodule MatchAttributeInModule do
|
||||||
|
@foo 16
|
||||||
|
<<_::size(@foo)>> = "ab"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -277,6 +277,32 @@ defmodule URITest do
|
|||||||
test "preserves an empty query" do
|
test "preserves an empty query" do
|
||||||
assert URI.new!("http://foo.com/?").query == ""
|
assert URI.new!("http://foo.com/?").query == ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "without scheme, undefined port after host translates to nil" do
|
||||||
|
assert URI.new!("//https://www.example.com") ==
|
||||||
|
%URI{
|
||||||
|
scheme: nil,
|
||||||
|
userinfo: nil,
|
||||||
|
host: "https",
|
||||||
|
port: nil,
|
||||||
|
path: "//www.example.com",
|
||||||
|
query: nil,
|
||||||
|
fragment: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with scheme, undefined port after host translates to nil" do
|
||||||
|
assert URI.new!("myscheme://myhost:/path/info") ==
|
||||||
|
%URI{
|
||||||
|
scheme: "myscheme",
|
||||||
|
userinfo: nil,
|
||||||
|
host: "myhost",
|
||||||
|
port: nil,
|
||||||
|
path: "/path/info",
|
||||||
|
query: nil,
|
||||||
|
fragment: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "http://http://http://@http://http://?http://#http://" do
|
test "http://http://http://@http://http://?http://#http://" do
|
||||||
|
|||||||
@@ -261,22 +261,22 @@ vc_merge_conflict_test() ->
|
|||||||
tokenize_error("<<<<<<< HEAD\n[1, 2, 3]").
|
tokenize_error("<<<<<<< HEAD\n[1, 2, 3]").
|
||||||
|
|
||||||
sigil_terminator_test() ->
|
sigil_terminator_test() ->
|
||||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"/">>}] = tokenize("~r/foo/"),
|
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"/">>}] = tokenize("~r/foo/"),
|
||||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"[">>}] = tokenize("~r[foo]"),
|
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"[">>}] = tokenize("~r[foo]"),
|
||||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"\"">>}] = tokenize("~r\"foo\""),
|
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"\"">>}] = tokenize("~r\"foo\""),
|
||||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "", nil, <<"/">>},
|
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "", nil, <<"/">>},
|
||||||
{comp_op, {1, 9, nil}, '=='},
|
{comp_op, {1, 9, nil}, '=='},
|
||||||
{identifier, {1, 12, _}, bar}] = tokenize("~r/foo/ == bar"),
|
{identifier, {1, 12, _}, bar}] = tokenize("~r/foo/ == bar"),
|
||||||
[{sigil, {1, 1, nil}, "r", [<<"foo">>], "iu", nil, <<"/">>},
|
[{sigil, {1, 1, nil}, sigil_r, [<<"foo">>], "iu", nil, <<"/">>},
|
||||||
{comp_op, {1, 11, nil}, '=='},
|
{comp_op, {1, 11, nil}, '=='},
|
||||||
{identifier, {1, 14, _}, bar}] = tokenize("~r/foo/iu == bar"),
|
{identifier, {1, 14, _}, bar}] = tokenize("~r/foo/iu == bar"),
|
||||||
[{sigil, {1, 1, nil}, "M", [<<"1 2 3">>], "u8", nil, <<"[">>}] = tokenize("~M[1 2 3]u8").
|
[{sigil, {1, 1, nil}, sigil_M, [<<"1 2 3">>], "u8", nil, <<"[">>}] = tokenize("~M[1 2 3]u8").
|
||||||
|
|
||||||
sigil_heredoc_test() ->
|
sigil_heredoc_test() ->
|
||||||
[{sigil, {1, 1, nil}, "S", [<<"sigil heredoc\n">>], "", 0, <<"\"\"\"">>}] = tokenize("~S\"\"\"\nsigil heredoc\n\"\"\""),
|
[{sigil, {1, 1, nil}, sigil_S, [<<"sigil heredoc\n">>], "", 0, <<"\"\"\"">>}] = tokenize("~S\"\"\"\nsigil heredoc\n\"\"\""),
|
||||||
[{sigil, {1, 1, nil}, "S", [<<"sigil heredoc\n">>], "", 0, <<"'''">>}] = tokenize("~S'''\nsigil heredoc\n'''"),
|
[{sigil, {1, 1, nil}, sigil_S, [<<"sigil heredoc\n">>], "", 0, <<"'''">>}] = tokenize("~S'''\nsigil heredoc\n'''"),
|
||||||
[{sigil, {1, 1, nil}, "S", [<<"sigil heredoc\n">>], "", 2, <<"\"\"\"">>}] = tokenize("~S\"\"\"\n sigil heredoc\n \"\"\""),
|
[{sigil, {1, 1, nil}, sigil_S, [<<"sigil heredoc\n">>], "", 2, <<"\"\"\"">>}] = tokenize("~S\"\"\"\n sigil heredoc\n \"\"\""),
|
||||||
[{sigil, {1, 1, nil}, "s", [<<"sigil heredoc\n">>], "", 2, <<"\"\"\"">>}] = tokenize("~s\"\"\"\n sigil heredoc\n \"\"\"").
|
[{sigil, {1, 1, nil}, sigil_s, [<<"sigil heredoc\n">>], "", 2, <<"\"\"\"">>}] = tokenize("~s\"\"\"\n sigil heredoc\n \"\"\"").
|
||||||
|
|
||||||
invalid_sigil_delimiter_test() ->
|
invalid_sigil_delimiter_test() ->
|
||||||
{1, 1, "invalid sigil delimiter: ", Message} = tokenize_error("~s\\"),
|
{1, 1, "invalid sigil delimiter: ", Message} = tokenize_error("~s\\"),
|
||||||
|
|||||||
@@ -549,14 +549,7 @@ defmodule ExUnit.Callbacks do
|
|||||||
end
|
end
|
||||||
|
|
||||||
child_spec = Supervisor.child_spec(child_spec_or_module, opts)
|
child_spec = Supervisor.child_spec(child_spec_or_module, opts)
|
||||||
|
Supervisor.start_child(sup, child_spec)
|
||||||
case Supervisor.start_child(sup, child_spec) do
|
|
||||||
{:error, {:already_started, _pid}} ->
|
|
||||||
{:error, {:duplicate_child_name, child_spec.id}}
|
|
||||||
|
|
||||||
other ->
|
|
||||||
other
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|||||||
@@ -71,11 +71,11 @@ defmodule ExUnit.CaptureServer do
|
|||||||
refs = Map.put(config.log_captures, ref, true)
|
refs = Map.put(config.log_captures, ref, true)
|
||||||
|
|
||||||
{level, opts} = Keyword.pop(opts, :level)
|
{level, opts} = Keyword.pop(opts, :level)
|
||||||
true = :ets.insert(@ets, {ref, string_io, level || :all})
|
{formatter_mod, formatter_config} = Logger.default_formatter(opts)
|
||||||
|
true = :ets.insert(@ets, {ref, string_io, level || :all, formatter_mod, formatter_config})
|
||||||
|
|
||||||
if map_size(refs) == 1 do
|
if map_size(refs) == 1 do
|
||||||
formatter = Logger.default_formatter(opts)
|
:ok = :logger.add_handler(@name, __MODULE__, %{})
|
||||||
:ok = :logger.add_handler(@name, __MODULE__, %{formatter: formatter})
|
|
||||||
|
|
||||||
status =
|
status =
|
||||||
with {:ok, config} <- :logger.get_handler_config(:default),
|
with {:ok, config} <- :logger.get_handler_config(:default),
|
||||||
@@ -226,13 +226,20 @@ defmodule ExUnit.CaptureServer do
|
|||||||
|
|
||||||
## :logger handler callback.
|
## :logger handler callback.
|
||||||
|
|
||||||
def log(event, %{} = config) do
|
def log(event, _config) do
|
||||||
%{formatter: {formatter_mod, formatter_config}} = config
|
for {_ref, string_io, level, formatter_mod, formatter_config} <- :ets.tab2list(@ets),
|
||||||
chardata = formatter_mod.format(event, formatter_config)
|
|
||||||
|
|
||||||
for [string_io, level] <- :ets.match(@ets, {:_, :"$1", :"$2"}),
|
|
||||||
:logger.compare_levels(event.level, level) in [:gt, :eq] do
|
:logger.compare_levels(event.level, level) in [:gt, :eq] do
|
||||||
:ok = IO.write(string_io, chardata)
|
chardata = formatter_mod.format(event, formatter_config)
|
||||||
|
# There is a race condition where the capture_log is removed
|
||||||
|
# but another process is attempting to log to string io device
|
||||||
|
# that no longer exists, so we wrap it in try/catch.
|
||||||
|
try do
|
||||||
|
IO.write(string_io, chardata)
|
||||||
|
rescue
|
||||||
|
_ -> :ok
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -167,6 +167,9 @@ defmodule ExUnit.Case do
|
|||||||
|
|
||||||
* `:doctest_data` - additional metadata about doctests (if a doctest)
|
* `:doctest_data` - additional metadata about doctests (if a doctest)
|
||||||
|
|
||||||
|
* `:test_type` - the test type used when printing test results.
|
||||||
|
It is set by ExUnit to `:test`, `:doctest` and so on, but is customizable.
|
||||||
|
|
||||||
The following tags customize how tests behave:
|
The following tags customize how tests behave:
|
||||||
|
|
||||||
* `:capture_log` - see the "Log Capture" section below
|
* `:capture_log` - see the "Log Capture" section below
|
||||||
@@ -178,9 +181,6 @@ defmodule ExUnit.Case do
|
|||||||
|
|
||||||
* `:tmp_dir` - (since v1.11.0) see the "Tmp Dir" section below
|
* `:tmp_dir` - (since v1.11.0) see the "Tmp Dir" section below
|
||||||
|
|
||||||
* `:test_type` - the test type used when printing test results.
|
|
||||||
It is set by ExUnit to `:test`, `:doctest` and so on, but is customizable.
|
|
||||||
|
|
||||||
## Filters
|
## Filters
|
||||||
|
|
||||||
Tags can also be used to identify specific tests, which can then
|
Tags can also be used to identify specific tests, which can then
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ defmodule ExUnit.CaseTemplate do
|
|||||||
The second argument passed to `use MyCase` gets forwarded to `using/2` too:
|
The second argument passed to `use MyCase` gets forwarded to `using/2` too:
|
||||||
|
|
||||||
defmodule SomeTestCase do
|
defmodule SomeTestCase do
|
||||||
use MyCase, async: true, import_helpers: true, async: true
|
use MyCase, async: true, import_helpers: true
|
||||||
|
|
||||||
test "the truth" do
|
test "the truth" do
|
||||||
# truth/0 comes from MyApp.TestHelpers:
|
# truth/0 comes from MyApp.TestHelpers:
|
||||||
|
|||||||
@@ -200,11 +200,15 @@ defmodule ExUnit.Diff do
|
|||||||
|
|
||||||
{diff, env}
|
{diff, env}
|
||||||
else
|
else
|
||||||
diff_value(left, right, env)
|
non_recursive_diff_value(left, right, env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp diff_value(left, right, env) do
|
defp diff_value(left, right, env) do
|
||||||
|
non_recursive_diff_value(left, right, env)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp non_recursive_diff_value(left, right, env) do
|
||||||
diff_left = escape(left) |> update_diff_meta(true)
|
diff_left = escape(left) |> update_diff_meta(true)
|
||||||
diff_right = escape(right) |> update_diff_meta(true)
|
diff_right = escape(right) |> update_diff_meta(true)
|
||||||
diff = %__MODULE__{equivalent?: false, left: diff_left, right: diff_right}
|
diff = %__MODULE__{equivalent?: false, left: diff_left, right: diff_right}
|
||||||
|
|||||||
@@ -382,9 +382,15 @@ defmodule ExUnit.DocTest do
|
|||||||
last_expr = Macro.to_string(last_expr(expr_ast))
|
last_expr = Macro.to_string(last_expr(expr_ast))
|
||||||
|
|
||||||
quote do
|
quote do
|
||||||
|
# `expr_ast` may introduce variables that may be
|
||||||
|
# used within `expected_ast` so they both need to
|
||||||
|
# unquoted together here
|
||||||
|
value = unquote(expr_ast)
|
||||||
|
expected_value = unquote(expected_ast)
|
||||||
|
|
||||||
ExUnit.DocTest.__test__(
|
ExUnit.DocTest.__test__(
|
||||||
unquote(expr_ast),
|
value,
|
||||||
unquote(expected_ast),
|
expected_value,
|
||||||
unquote(doctest),
|
unquote(doctest),
|
||||||
unquote(last_expr),
|
unquote(last_expr),
|
||||||
unquote(expected),
|
unquote(expected),
|
||||||
@@ -624,57 +630,37 @@ defmodule ExUnit.DocTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp adjust_indent(kind, [line | rest], line_no, adjusted_lines, indent, module)
|
defp adjust_indent(kind, [line | rest], line_no, adjusted_lines, indent, module) do
|
||||||
when kind in [:prompt, :after_prompt] do
|
|
||||||
stripped_line = strip_indent(line, indent)
|
stripped_line = strip_indent(line, indent)
|
||||||
|
trimmed_line = String.trim_leading(line)
|
||||||
|
|
||||||
case String.trim_leading(line) do
|
if kind != :code and trimmed_line != stripped_line do
|
||||||
"" ->
|
n_spaces = if indent == 1, do: "#{indent} space", else: "#{indent} spaces"
|
||||||
:ok
|
|
||||||
|
|
||||||
^stripped_line ->
|
raise Error,
|
||||||
:ok
|
line: line_no,
|
||||||
|
module: module,
|
||||||
|
message: """
|
||||||
|
indentation level mismatch on doctest line: #{inspect(line)}
|
||||||
|
|
||||||
_ ->
|
If you are planning to assert on the result of an `iex>` expression, \
|
||||||
n_spaces = if indent == 1, do: "#{indent} space", else: "#{indent} spaces"
|
make sure the result is indented at the beginning of `iex>`, which \
|
||||||
|
in this case is exactly #{n_spaces}.
|
||||||
|
|
||||||
raise Error,
|
If instead you have an `iex>` expression that spans over multiple lines, \
|
||||||
line: line_no,
|
please make sure that each line after the first one begins with `...>`.
|
||||||
module: module,
|
"""
|
||||||
message: """
|
|
||||||
indentation level mismatch on doctest line: #{inspect(line)}
|
|
||||||
|
|
||||||
If you are planning to assert on the result of an `iex>` expression, \
|
|
||||||
make sure the result is indented at the beginning of `iex>`, which \
|
|
||||||
in this case is exactly #{n_spaces}.
|
|
||||||
|
|
||||||
If instead you have an `iex>` expression that spans over multiple lines, \
|
|
||||||
please make sure that each line after the first one begins with `...>`.
|
|
||||||
"""
|
|
||||||
end
|
end
|
||||||
|
|
||||||
adjusted_lines = [{adjust_prompt(stripped_line, line_no, module), line_no} | adjusted_lines]
|
|
||||||
|
|
||||||
next =
|
|
||||||
cond do
|
|
||||||
kind == :prompt -> :after_prompt
|
|
||||||
String.starts_with?(stripped_line, @iex_prompt ++ @dot_prompt) -> :after_prompt
|
|
||||||
true -> :code
|
|
||||||
end
|
|
||||||
|
|
||||||
adjust_indent(next, rest, line_no + 1, adjusted_lines, indent, module)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp adjust_indent(:code, [line | rest], line_no, adjusted_lines, indent, module) do
|
|
||||||
stripped_line = strip_indent(line, indent)
|
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
stripped_line == "" or String.starts_with?(stripped_line, @fences) ->
|
stripped_line == "" or String.starts_with?(stripped_line, @fences) ->
|
||||||
adjusted_lines = [{"", line_no} | adjusted_lines]
|
adjusted_lines = [{"", line_no} | adjusted_lines]
|
||||||
adjust_indent(:text, rest, line_no + 1, adjusted_lines, 0, module)
|
adjust_indent(:text, rest, line_no + 1, adjusted_lines, 0, module)
|
||||||
|
|
||||||
String.starts_with?(String.trim_leading(stripped_line), @iex_prompt) ->
|
kind == :prompt or String.starts_with?(trimmed_line, @iex_prompt) or
|
||||||
adjust_indent(:prompt, [line | rest], line_no, adjusted_lines, indent, module)
|
(kind == :maybe_prompt and String.starts_with?(trimmed_line, @dot_prompt)) ->
|
||||||
|
line = {adjust_prompt(stripped_line, line_no, module), line_no}
|
||||||
|
adjust_indent(:maybe_prompt, rest, line_no + 1, [line | adjusted_lines], indent, module)
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
adjusted_lines = [{stripped_line, line_no} | adjusted_lines]
|
adjusted_lines = [{stripped_line, line_no} | adjusted_lines]
|
||||||
|
|||||||
@@ -293,8 +293,9 @@ defmodule ExUnit.Runner do
|
|||||||
{test_module, invalid_tests, []}
|
{test_module, invalid_tests, []}
|
||||||
|
|
||||||
{:DOWN, ^module_ref, :process, ^module_pid, error} ->
|
{:DOWN, ^module_ref, :process, ^module_pid, error} ->
|
||||||
|
invalid_tests = Enum.map(tests, &%{&1 | state: {:invalid, test_module}})
|
||||||
test_module = %{test_module | state: failed({:EXIT, module_pid}, error, [])}
|
test_module = %{test_module | state: failed({:EXIT, module_pid}, error, [])}
|
||||||
{test_module, [], []}
|
{test_module, invalid_tests, []}
|
||||||
end
|
end
|
||||||
|
|
||||||
timeout = get_timeout(config, %{})
|
timeout = get_timeout(config, %{})
|
||||||
|
|||||||
@@ -124,6 +124,22 @@ defmodule ExUnit.CallbacksTest do
|
|||||||
"** (MatchError) no match of right hand side value: :error"
|
"** (MatchError) no match of right hand side value: :error"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "doesn't choke on setup_all exits" do
|
||||||
|
defmodule SetupAllExitTest do
|
||||||
|
use ExUnit.Case
|
||||||
|
|
||||||
|
setup_all _ do
|
||||||
|
Process.exit(self(), :error)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "ok" do
|
||||||
|
assert true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert capture_io(fn -> ExUnit.run() end) =~ "1 test, 0 failures, 1 invalid"
|
||||||
|
end
|
||||||
|
|
||||||
test "doesn't choke on dead supervisor" do
|
test "doesn't choke on dead supervisor" do
|
||||||
defmodule StartSupervisedErrorTest do
|
defmodule StartSupervisedErrorTest do
|
||||||
use ExUnit.Case
|
use ExUnit.Case
|
||||||
|
|||||||
@@ -114,6 +114,19 @@ defmodule ExUnit.CaptureLogTest do
|
|||||||
|
|
||||||
assert log == "id=123 | hello"
|
assert log == "id=123 | hello"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@tag capture_log: true
|
||||||
|
test "respect options with capture_log: true" do
|
||||||
|
options = [format: "$metadata| $message", metadata: [:id], colors: [enabled: false]]
|
||||||
|
|
||||||
|
assert {4, log} =
|
||||||
|
with_log(options, fn ->
|
||||||
|
Logger.info("hello", id: 123)
|
||||||
|
2 + 2
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert log == "id=123 | hello"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp wait_capture_removal() do
|
defp wait_capture_removal() do
|
||||||
|
|||||||
@@ -1121,6 +1121,8 @@ defmodule ExUnit.DiffTest do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@compile {:no_warn_undefined, String}
|
||||||
|
|
||||||
test "functions" do
|
test "functions" do
|
||||||
identity = & &1
|
identity = & &1
|
||||||
inspect = inspect(identity)
|
inspect = inspect(identity)
|
||||||
@@ -1130,11 +1132,15 @@ defmodule ExUnit.DiffTest do
|
|||||||
|
|
||||||
refute_diff(identity == :a, "-#{inspect}-", "+:a+")
|
refute_diff(identity == :a, "-#{inspect}-", "+:a+")
|
||||||
refute_diff({identity, identity} == :a, "-{#{inspect}, #{inspect}}", "+:a+")
|
refute_diff({identity, identity} == :a, "-{#{inspect}, #{inspect}}", "+:a+")
|
||||||
|
|
||||||
refute_diff({identity, :a} == {:a, identity}, "{-#{inspect}-, -:a-}", "{+:a+, +#{inspect}+}")
|
refute_diff({identity, :a} == {:a, identity}, "{-#{inspect}-, -:a-}", "{+:a+, +#{inspect}+}")
|
||||||
|
|
||||||
refute_diff(%{identity => identity} == :a, "-%{#{inspect} => #{inspect}}", "+:a+")
|
refute_diff(%{identity => identity} == :a, "-%{#{inspect} => #{inspect}}", "+:a+")
|
||||||
|
|
||||||
|
refute_diff(
|
||||||
|
(&String.to_charlist/1) == (&String.unknown/1),
|
||||||
|
"-&String.to_charlist/1-",
|
||||||
|
"+&String.unknown/1"
|
||||||
|
)
|
||||||
|
|
||||||
refute_diff(
|
refute_diff(
|
||||||
%Opaque{data: identity} == :a,
|
%Opaque{data: identity} == :a,
|
||||||
"-#Opaque<???>-",
|
"-#Opaque<???>-",
|
||||||
|
|||||||
@@ -317,8 +317,7 @@ defmodule ExUnit.DocTestTest.FencedHeredocs do
|
|||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
iex> 1 + 2
|
iex> 3 = 1 + 2
|
||||||
3
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -444,6 +443,16 @@ defmodule ExUnit.DocTestTest.Haiku do
|
|||||||
end
|
end
|
||||||
|> ExUnit.BeamHelpers.write_beam()
|
|> ExUnit.BeamHelpers.write_beam()
|
||||||
|
|
||||||
|
defmodule ExUnit.DocTestTest.VariableInExpectation do
|
||||||
|
@doc """
|
||||||
|
iex> num = 1
|
||||||
|
iex> ExUnit.DocTestTest.VariableInExpectation.inc(num)
|
||||||
|
num + 1
|
||||||
|
"""
|
||||||
|
def inc(num), do: num + 1
|
||||||
|
end
|
||||||
|
|> ExUnit.BeamHelpers.write_beam()
|
||||||
|
|
||||||
defmodule ExUnit.DocTestTest.PatternMatching do
|
defmodule ExUnit.DocTestTest.PatternMatching do
|
||||||
def starting_line, do: __ENV__.line + 2
|
def starting_line, do: __ENV__.line + 2
|
||||||
|
|
||||||
@@ -507,6 +516,7 @@ defmodule ExUnit.DocTestTest do
|
|||||||
doctest ExUnit.DocTestTest.IndentationHeredocs
|
doctest ExUnit.DocTestTest.IndentationHeredocs
|
||||||
doctest ExUnit.DocTestTest.FencedHeredocs
|
doctest ExUnit.DocTestTest.FencedHeredocs
|
||||||
doctest ExUnit.DocTestTest.Haiku
|
doctest ExUnit.DocTestTest.Haiku
|
||||||
|
doctest ExUnit.DocTestTest.VariableInExpectation
|
||||||
|
|
||||||
import ExUnit.CaptureIO
|
import ExUnit.CaptureIO
|
||||||
|
|
||||||
|
|||||||
@@ -491,28 +491,25 @@ defmodule ExUnit.FormatterTest do
|
|||||||
test "inspect failure" do
|
test "inspect failure" do
|
||||||
failure = [{:error, catch_assertion(assert :will_fail == %BadInspect{}), []}]
|
failure = [{:error, catch_assertion(assert :will_fail == %BadInspect{}), []}]
|
||||||
|
|
||||||
message = ~S'''
|
assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ ~s'''
|
||||||
got FunctionClauseError with message:
|
|
||||||
|
|
||||||
"""
|
|
||||||
no function clause matching in Inspect.ExUnit.FormatterTest.BadInspect.inspect/2
|
|
||||||
"""
|
|
||||||
|
|
||||||
while inspecting:
|
|
||||||
|
|
||||||
%{__struct__: ExUnit.FormatterTest.BadInspect, key: 0}
|
|
||||||
|
|
||||||
Stacktrace:
|
|
||||||
'''
|
|
||||||
|
|
||||||
assert format_test_failure(test(), failure, 1, 80, &formatter/2) =~ """
|
|
||||||
1) world (Hello)
|
1) world (Hello)
|
||||||
test/ex_unit/formatter_test.exs:1
|
test/ex_unit/formatter_test.exs:1
|
||||||
Assertion with == failed
|
Assertion with == failed
|
||||||
code: assert :will_fail == %BadInspect{}
|
code: assert :will_fail == %BadInspect{}
|
||||||
left: :will_fail
|
left: :will_fail
|
||||||
right: #Inspect.Error<\n#{message}\
|
right: #Inspect.Error<
|
||||||
"""
|
got FunctionClauseError with message:
|
||||||
|
|
||||||
|
"""
|
||||||
|
no function clause matching in Inspect.ExUnit.FormatterTest.BadInspect.inspect/2
|
||||||
|
"""
|
||||||
|
|
||||||
|
while inspecting:
|
||||||
|
|
||||||
|
#{inspect(%BadInspect{}, structs: false)}
|
||||||
|
|
||||||
|
Stacktrace:
|
||||||
|
'''
|
||||||
end
|
end
|
||||||
|
|
||||||
defmodule BadMessage do
|
defmodule BadMessage do
|
||||||
|
|||||||
@@ -73,19 +73,14 @@ defmodule ExUnit.SupervisedTest do
|
|||||||
|
|
||||||
test "starts a supervised process with ID checks" do
|
test "starts a supervised process with ID checks" do
|
||||||
{:ok, pid} = start_supervised({MyAgent, 0})
|
{:ok, pid} = start_supervised({MyAgent, 0})
|
||||||
|
assert is_pid(pid)
|
||||||
|
|
||||||
assert {:error, {:duplicate_child_name, ExUnit.SupervisedTest.MyAgent}} =
|
assert {:error, _} = start_supervised({MyAgent, 0})
|
||||||
start_supervised({MyAgent, 0})
|
assert {:error, _} = start_supervised({MyAgent, 0}, id: :another)
|
||||||
|
|
||||||
assert {:error, {{:already_started, ^pid}, _}} = start_supervised({MyAgent, 0}, id: :another)
|
|
||||||
|
|
||||||
assert_raise RuntimeError, ~r"Reason: bad child specification", fn ->
|
assert_raise RuntimeError, ~r"Reason: bad child specification", fn ->
|
||||||
start_supervised!(%{id: 1, start: :oops})
|
start_supervised!(%{id: 1, start: :oops})
|
||||||
end
|
end
|
||||||
|
|
||||||
assert_raise RuntimeError, ~r"Reason: already started", fn ->
|
|
||||||
start_supervised!({MyAgent, 0}, id: :another)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "stops a supervised process" do
|
test "stops a supervised process" do
|
||||||
|
|||||||
+28
-12
@@ -536,6 +536,9 @@ defmodule IEx do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns `true` if IEx was started, `false` otherwise.
|
Returns `true` if IEx was started, `false` otherwise.
|
||||||
|
|
||||||
|
This means the IEx application was started, but not
|
||||||
|
that its CLI interface is running.
|
||||||
"""
|
"""
|
||||||
@spec started?() :: boolean()
|
@spec started?() :: boolean()
|
||||||
def started? do
|
def started? do
|
||||||
@@ -579,9 +582,6 @@ defmodule IEx do
|
|||||||
@doc """
|
@doc """
|
||||||
Pries into the process environment.
|
Pries into the process environment.
|
||||||
|
|
||||||
When you start `iex`, IEx will set this function to be the
|
|
||||||
default `dbg/2` backend unless the `--no-pry` flag is given.
|
|
||||||
|
|
||||||
This function is useful for debugging a particular chunk of code
|
This function is useful for debugging a particular chunk of code
|
||||||
when executed by a particular process. The process becomes
|
when executed by a particular process. The process becomes
|
||||||
the evaluator of IEx commands and is temporarily changed to
|
the evaluator of IEx commands and is temporarily changed to
|
||||||
@@ -597,6 +597,11 @@ defmodule IEx do
|
|||||||
|
|
||||||
See also `break!/4` for others ways to pry.
|
See also `break!/4` for others ways to pry.
|
||||||
|
|
||||||
|
> #### `dbg/0` integration
|
||||||
|
>
|
||||||
|
> By calling `iex --dbg pry`, `iex` will set this function
|
||||||
|
> as the default backend for `dbg/0` calls.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Let's suppose you want to investigate what is happening
|
Let's suppose you want to investigate what is happening
|
||||||
@@ -857,6 +862,8 @@ defmodule IEx do
|
|||||||
|
|
||||||
# This is a callback invoked by Erlang shell utilities
|
# This is a callback invoked by Erlang shell utilities
|
||||||
# when someone presses Ctrl+G and adds `s 'Elixir.IEx'`.
|
# when someone presses Ctrl+G and adds `s 'Elixir.IEx'`.
|
||||||
|
# Let's consider exposing this as iex:start() and rename
|
||||||
|
# elixir:start_iex() to iex:cli().
|
||||||
@doc false
|
@doc false
|
||||||
def start(opts \\ [], mfa \\ {IEx, :dont_display_result, []}) do
|
def start(opts \\ [], mfa \\ {IEx, :dont_display_result, []}) do
|
||||||
# TODO: Keep only this branch, delete optional args and mfa,
|
# TODO: Keep only this branch, delete optional args and mfa,
|
||||||
@@ -864,15 +871,19 @@ defmodule IEx do
|
|||||||
if Code.ensure_loaded?(:prim_tty) do
|
if Code.ensure_loaded?(:prim_tty) do
|
||||||
spawn(fn ->
|
spawn(fn ->
|
||||||
{:ok, _} = Application.ensure_all_started(:iex)
|
{:ok, _} = Application.ensure_all_started(:iex)
|
||||||
|
:ok = :io.setopts(binary: true, encoding: :unicode)
|
||||||
_ = for fun <- Enum.reverse(after_spawn()), do: fun.()
|
_ = for fun <- Enum.reverse(after_spawn()), do: fun.()
|
||||||
IEx.Server.run([register: false] ++ opts)
|
IEx.Server.run([register: false] ++ opts)
|
||||||
end)
|
end)
|
||||||
else
|
else
|
||||||
spawn(fn ->
|
spawn(fn ->
|
||||||
{:ok, _} = Application.ensure_all_started(:elixir)
|
case :init.notify_when_started(self()) do
|
||||||
System.wait_until_booted()
|
:started -> :ok
|
||||||
:ok = :io.setopts(binary: true, encoding: :unicode)
|
_ -> :init.wait_until_started()
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, _} = Application.ensure_all_started(:iex)
|
{:ok, _} = Application.ensure_all_started(:iex)
|
||||||
|
:ok = :io.setopts(binary: true, encoding: :unicode)
|
||||||
_ = for fun <- Enum.reverse(after_spawn()), do: fun.()
|
_ = for fun <- Enum.reverse(after_spawn()), do: fun.()
|
||||||
IEx.Server.run_from_shell(opts, mfa)
|
IEx.Server.run_from_shell(opts, mfa)
|
||||||
end)
|
end)
|
||||||
@@ -920,13 +931,18 @@ defmodule IEx do
|
|||||||
mfa
|
mfa
|
||||||
end
|
end
|
||||||
|
|
||||||
:ok = :shell.start_interactive(shell)
|
case :shell.start_interactive(shell) do
|
||||||
|
:ok ->
|
||||||
|
receive do
|
||||||
|
{^ref, shell} -> shell
|
||||||
|
after
|
||||||
|
15_000 ->
|
||||||
|
IO.puts(:stderr, "Could not start IEx CLI due to reason: :boot_timeout")
|
||||||
|
System.halt(1)
|
||||||
|
end
|
||||||
|
|
||||||
receive do
|
{:error, reason} ->
|
||||||
{^ref, shell} -> shell
|
IO.puts(:stderr, "Could not start IEx CLI due to reason: #{inspect(reason)}")
|
||||||
after
|
|
||||||
15_000 ->
|
|
||||||
IO.puts(:stderr, "Could not start the shell after 15 seconds, aborting...")
|
|
||||||
System.halt(1)
|
System.halt(1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ defmodule IEx.Autocomplete do
|
|||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
def exports(mod) do
|
def exports(mod) do
|
||||||
if Code.ensure_loaded?(mod) and function_exported?(mod, :__info__, 1) do
|
if ensure_loaded?(mod) and function_exported?(mod, :__info__, 1) do
|
||||||
mod.__info__(:macros) ++ (mod.__info__(:functions) -- [__info__: 1])
|
mod.__info__(:macros) ++ (mod.__info__(:functions) -- [__info__: 1])
|
||||||
else
|
else
|
||||||
mod.module_info(:exports) -- [module_info: 0, module_info: 1]
|
mod.module_info(:exports) -- [module_info: 0, module_info: 1]
|
||||||
@@ -313,21 +313,23 @@ defmodule IEx.Autocomplete do
|
|||||||
for {alias, mod} <- aliases_from_env(shell),
|
for {alias, mod} <- aliases_from_env(shell),
|
||||||
[name] = Module.split(alias),
|
[name] = Module.split(alias),
|
||||||
String.starts_with?(name, hint),
|
String.starts_with?(name, hint),
|
||||||
struct?(mod) and not function_exported?(mod, :exception, 1),
|
do: {mod, name}
|
||||||
do: %{kind: :struct, name: name}
|
|
||||||
|
|
||||||
modules =
|
modules =
|
||||||
for "Elixir." <> name = full_name <- match_modules("Elixir." <> hint, true),
|
for "Elixir." <> name = full_name <- match_modules("Elixir." <> hint, true),
|
||||||
String.starts_with?(name, hint),
|
String.starts_with?(name, hint),
|
||||||
mod = String.to_atom(full_name),
|
mod = String.to_atom(full_name),
|
||||||
struct?(mod) and not function_exported?(mod, :exception, 1),
|
do: {mod, name}
|
||||||
|
|
||||||
|
all = aliases ++ modules
|
||||||
|
Code.ensure_all_loaded(Enum.map(all, &elem(&1, 0)))
|
||||||
|
|
||||||
|
refs =
|
||||||
|
for {mod, name} <- all,
|
||||||
|
function_exported?(mod, :__struct__, 1) and not function_exported?(mod, :exception, 1),
|
||||||
do: %{kind: :struct, name: name}
|
do: %{kind: :struct, name: name}
|
||||||
|
|
||||||
format_expansion(aliases ++ modules, hint)
|
format_expansion(refs, hint)
|
||||||
end
|
|
||||||
|
|
||||||
defp struct?(mod) do
|
|
||||||
Code.ensure_loaded?(mod) and function_exported?(mod, :__struct__, 1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp expand_container_context(code, context, hint, shell) do
|
defp expand_container_context(code, context, hint, shell) do
|
||||||
@@ -420,7 +422,9 @@ defmodule IEx.Autocomplete do
|
|||||||
defp container_context_struct(cursor, pairs, aliases, shell) do
|
defp container_context_struct(cursor, pairs, aliases, shell) do
|
||||||
with {pairs, [^cursor]} <- Enum.split(pairs, -1),
|
with {pairs, [^cursor]} <- Enum.split(pairs, -1),
|
||||||
alias = value_from_alias(aliases, shell),
|
alias = value_from_alias(aliases, shell),
|
||||||
true <- Keyword.keyword?(pairs) and struct?(alias) do
|
true <-
|
||||||
|
Keyword.keyword?(pairs) and ensure_loaded?(alias) and
|
||||||
|
function_exported?(alias, :__struct__, 1) do
|
||||||
{:struct, alias, pairs}
|
{:struct, alias, pairs}
|
||||||
else
|
else
|
||||||
_ -> nil
|
_ -> nil
|
||||||
|
|||||||
@@ -85,9 +85,13 @@ defmodule IEx.Broker do
|
|||||||
yes?(IO.gets(:stdio, interrupt))
|
yes?(IO.gets(:stdio, interrupt))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp yes?(string) do
|
defp yes?(string) when is_binary(string),
|
||||||
is_binary(string) and String.trim(string) in ["", "y", "Y", "yes", "YES", "Yes"]
|
do: String.trim(string) in ["", "y", "Y", "yes", "YES", "Yes"]
|
||||||
end
|
|
||||||
|
defp yes?(charlist) when is_list(charlist),
|
||||||
|
do: yes?(List.to_string(charlist))
|
||||||
|
|
||||||
|
defp yes?(_), do: false
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Client requests a takeover.
|
Client requests a takeover.
|
||||||
|
|||||||
+5
-10
@@ -6,7 +6,7 @@ defmodule IEx.CLI do
|
|||||||
|
|
||||||
def deprecated do
|
def deprecated do
|
||||||
if tty_works?() do
|
if tty_works?() do
|
||||||
:user_drv.start([:"tty_sl -c -e", old_tty_args()])
|
:user_drv.start([:"tty_sl -c -e", tty_args()])
|
||||||
else
|
else
|
||||||
if get_remsh(:init.get_plain_arguments()) do
|
if get_remsh(:init.get_plain_arguments()) do
|
||||||
IO.puts(
|
IO.puts(
|
||||||
@@ -20,14 +20,10 @@ defmodule IEx.CLI do
|
|||||||
# IEx.Broker is capable of considering all groups under user_drv but
|
# IEx.Broker is capable of considering all groups under user_drv but
|
||||||
# when we use :user.start(), we need to explicitly register it instead.
|
# when we use :user.start(), we need to explicitly register it instead.
|
||||||
# If we don't register, pry doesn't work.
|
# If we don't register, pry doesn't work.
|
||||||
IEx.start([register: true] ++ options())
|
IEx.start([register: true] ++ options(), {:elixir, :start_cli, []})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def prompt(_n) do
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Check if tty works. If it does not, we fall back to the
|
# Check if tty works. If it does not, we fall back to the
|
||||||
# simple/dumb terminal. This is starting the linked in
|
# simple/dumb terminal. This is starting the linked in
|
||||||
# driver twice, it would be nice and appropriate if we had
|
# driver twice, it would be nice and appropriate if we had
|
||||||
@@ -41,7 +37,7 @@ defmodule IEx.CLI do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp old_tty_args do
|
defp tty_args do
|
||||||
if remote = get_remsh(:init.get_plain_arguments()) do
|
if remote = get_remsh(:init.get_plain_arguments()) do
|
||||||
remote = List.to_atom(append_hostname(remote))
|
remote = List.to_atom(append_hostname(remote))
|
||||||
|
|
||||||
@@ -77,7 +73,7 @@ defmodule IEx.CLI do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp local_start_mfa do
|
defp local_start_mfa do
|
||||||
{IEx, :start, [options()]}
|
{IEx, :start, [options(), {:elixir, :start_cli, []}]}
|
||||||
end
|
end
|
||||||
|
|
||||||
def remote_start(parent, ref) do
|
def remote_start(parent, ref) do
|
||||||
@@ -93,8 +89,7 @@ defmodule IEx.CLI do
|
|||||||
spawn_link(fn ->
|
spawn_link(fn ->
|
||||||
receive do
|
receive do
|
||||||
{:begin, ^ref, other} ->
|
{:begin, ^ref, other} ->
|
||||||
{:ok, _} = Application.ensure_all_started(:elixir)
|
:elixir.start_cli()
|
||||||
System.wait_until_booted()
|
|
||||||
send(other, {:done, ref})
|
send(other, {:done, ref})
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ defmodule IEx.Helpers do
|
|||||||
reenable_tasks(config)
|
reenable_tasks(config)
|
||||||
|
|
||||||
force? = Keyword.get(options, :force, false)
|
force? = Keyword.get(options, :force, false)
|
||||||
args = ["--purge-consolidation-path-if-stale", consolidation]
|
args = ["--purge-consolidation-path-if-stale", "--return-errors", consolidation]
|
||||||
args = if force?, do: ["--force" | args], else: args
|
args = if force?, do: ["--force" | args], else: args
|
||||||
|
|
||||||
{result, _} = Mix.Task.run("compile", args)
|
{result, _} = Mix.Task.run("compile", args)
|
||||||
|
|||||||
@@ -293,6 +293,7 @@ defmodule IEx.Introspection do
|
|||||||
module.module_info(:exports)
|
module.module_info(:exports)
|
||||||
end
|
end
|
||||||
|> Enum.sort()
|
|> Enum.sort()
|
||||||
|
|> Enum.dedup()
|
||||||
|
|
||||||
result =
|
result =
|
||||||
for {^function, arity} <- exports,
|
for {^function, arity} <- exports,
|
||||||
|
|||||||
@@ -689,6 +689,8 @@ defmodule IEx.Pry do
|
|||||||
[asts_string, :faint, " #=> ", :reset, inspect(value, options), "\n\n"]
|
[asts_string, :faint, " #=> ", :reset, inspect(value, options), "\n\n"]
|
||||||
|> IO.ANSI.format()
|
|> IO.ANSI.format()
|
||||||
|> IO.write()
|
|> IO.write()
|
||||||
|
|
||||||
|
value
|
||||||
end
|
end
|
||||||
|
|
||||||
defp chunk_pipeline_asts_by_line(asts, %Macro.Env{line: env_line}) do
|
defp chunk_pipeline_asts_by_line(asts, %Macro.Env{line: env_line}) do
|
||||||
|
|||||||
+31
-21
@@ -210,7 +210,7 @@ defmodule Logger do
|
|||||||
metadata: [:error_code, :file]
|
metadata: [:error_code, :file]
|
||||||
|
|
||||||
Or to configure default handler, for instance, to log into a file with
|
Or to configure default handler, for instance, to log into a file with
|
||||||
built-in support for log rotation:
|
built-in support for log rotation and compression:
|
||||||
|
|
||||||
config :logger, :default_handler,
|
config :logger, :default_handler,
|
||||||
config: [
|
config: [
|
||||||
@@ -218,9 +218,18 @@ defmodule Logger do
|
|||||||
filesync_repeat_interval: 5000,
|
filesync_repeat_interval: 5000,
|
||||||
file_check: 5000,
|
file_check: 5000,
|
||||||
max_no_bytes: 10_000_000,
|
max_no_bytes: 10_000_000,
|
||||||
max_no_files: 5
|
max_no_files: 5,
|
||||||
|
compress_on_rotate: true
|
||||||
]
|
]
|
||||||
|
|
||||||
|
See [`:logger_std_h`](`:logger_std_h`) for all relevant configuration,
|
||||||
|
including overload protection. Or set `:default_handler` to false to
|
||||||
|
disable the default logging altogether:
|
||||||
|
|
||||||
|
config :logger, :default_handler, false
|
||||||
|
|
||||||
|
How to add new handlers is covered in later sections.
|
||||||
|
|
||||||
> #### Keywords or maps {: .tip}
|
> #### Keywords or maps {: .tip}
|
||||||
>
|
>
|
||||||
> While Erlang's logger expects `:config` to be a map, Elixir's Logger
|
> While Erlang's logger expects `:config` to be a map, Elixir's Logger
|
||||||
@@ -231,14 +240,6 @@ defmodule Logger do
|
|||||||
> When reading the handler configuration using Erlang's APIs,
|
> When reading the handler configuration using Erlang's APIs,
|
||||||
> the configuration will always be read (and written) as a map.
|
> the configuration will always be read (and written) as a map.
|
||||||
|
|
||||||
See [`:logger_std_h`](`:logger_std_h`) for all relevant configuration,
|
|
||||||
including overload protection. Or set `:default_handler` to false to
|
|
||||||
disable the default logging altogether:
|
|
||||||
|
|
||||||
config :logger, :default_handler, false
|
|
||||||
|
|
||||||
How to add new handlers is covered in later sections.
|
|
||||||
|
|
||||||
### Compile configuration
|
### Compile configuration
|
||||||
|
|
||||||
The following configuration must be set via config files (such as
|
The following configuration must be set via config files (such as
|
||||||
@@ -329,16 +330,19 @@ defmodule Logger do
|
|||||||
default handler, but you can use Erlang's [`:logger`](`:logger`) module
|
default handler, but you can use Erlang's [`:logger`](`:logger`) module
|
||||||
to add other handlers too.
|
to add other handlers too.
|
||||||
|
|
||||||
Erlang/OTP handlers must be listed under your own application. For example,
|
Erlang/OTP handlers must be listed under your own application.
|
||||||
to setup an additional handler that writes to disk:
|
For example, to setup an additional handler, so you write to
|
||||||
|
console and file:
|
||||||
|
|
||||||
config :my_app, :logger, [
|
config :my_app, :logger, [
|
||||||
{:handler, :disk_log, :logger_disk_log_h, %{
|
{:handler, :file_log, :logger_std_h, %{
|
||||||
config: %{
|
config: %{
|
||||||
file: 'system.log',
|
file: ~c"system.log",
|
||||||
filesync_repeat_interval: 5000,
|
filesync_repeat_interval: 5000,
|
||||||
|
file_check: 5000,
|
||||||
max_no_bytes: 10_000_000,
|
max_no_bytes: 10_000_000,
|
||||||
max_no_files: 5
|
max_no_files: 5,
|
||||||
|
compress_on_rotate: true
|
||||||
},
|
},
|
||||||
formatter: Logger.Formatter.new()
|
formatter: Logger.Formatter.new()
|
||||||
}}
|
}}
|
||||||
@@ -355,9 +359,12 @@ defmodule Logger do
|
|||||||
flexibility but they should avoid performing any long running action in
|
flexibility but they should avoid performing any long running action in
|
||||||
such handlers, as it may slow down the action being executed considerably.
|
such handlers, as it may slow down the action being executed considerably.
|
||||||
At the moment, there is no built-in overload protection for Erlang handlers,
|
At the moment, there is no built-in overload protection for Erlang handlers,
|
||||||
so it is your responsibility to implement it. Alternatively, you can use the
|
so it is your responsibility to implement it.
|
||||||
[`:logger_backends`](https://github.com/elixir-lang/logger_backends)
|
|
||||||
project.
|
Alternatively, you can use the
|
||||||
|
[`:logger_backends`](https://github.com/elixir-lang/logger_backends) project.
|
||||||
|
It sets up a log handler with overload protection and allows incoming events
|
||||||
|
to be dispatched to multiple backends.
|
||||||
|
|
||||||
## Backends and backwards compatibility
|
## Backends and backwards compatibility
|
||||||
|
|
||||||
@@ -824,9 +831,12 @@ defmodule Logger do
|
|||||||
|
|
||||||
defp update_translators(updater) do
|
defp update_translators(updater) do
|
||||||
:elixir_config.serial(fn ->
|
:elixir_config.serial(fn ->
|
||||||
|
translators = updater.(Application.fetch_env!(:logger, :translators))
|
||||||
|
Application.put_env(:logger, :translators, translators)
|
||||||
|
|
||||||
with %{filters: filters} <- :logger.get_primary_config(),
|
with %{filters: filters} <- :logger.get_primary_config(),
|
||||||
{{_, {fun, config}}, filters} <- List.keytake(filters, :logger_translator, 0) do
|
{{_, {fun, config}}, filters} <- List.keytake(filters, :logger_translator, 0) do
|
||||||
config = update_in(config.translators, updater)
|
config = %{config | translators: translators}
|
||||||
:ok = :logger.set_primary_config(:filters, filters ++ [logger_translator: {fun, config}])
|
:ok = :logger.set_primary_config(:filters, filters ++ [logger_translator: {fun, config}])
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
@@ -1002,7 +1012,7 @@ defmodule Logger do
|
|||||||
compile_time_purge_matching?(compile_level, compile_metadata) ->
|
compile_time_purge_matching?(compile_level, compile_metadata) ->
|
||||||
no_log(data, quoted_metadata)
|
no_log(data, quoted_metadata)
|
||||||
|
|
||||||
Application.fetch_env!(:logger, :always_evaluate_messages) ->
|
Application.get_env(:logger, :always_evaluate_messages, false) ->
|
||||||
quote do
|
quote do
|
||||||
data = Logger.__evaluate_log__(unquote(data))
|
data = Logger.__evaluate_log__(unquote(data))
|
||||||
metadata = unquote(quoted_metadata)
|
metadata = unquote(quoted_metadata)
|
||||||
@@ -1110,7 +1120,7 @@ defmodule Logger do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp no_log(data, metadata) do
|
defp no_log(data, metadata) do
|
||||||
if Application.fetch_env!(:logger, :always_evaluate_messages) do
|
if Application.get_env(:logger, :always_evaluate_messages, false) do
|
||||||
quote do
|
quote do
|
||||||
Logger.__evaluate_log__(unquote(data))
|
Logger.__evaluate_log__(unquote(data))
|
||||||
unquote(metadata)
|
unquote(metadata)
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ defmodule Logger.Formatter do
|
|||||||
|
|
||||||
def format(_event, _config) do
|
def format(_event, _config) do
|
||||||
raise "invalid configuration for Logger.Formatter. " <>
|
raise "invalid configuration for Logger.Formatter. " <>
|
||||||
"Use Logger.Formatter.init/1 to define a formatter"
|
"Use Logger.Formatter.new/1 to define a formatter"
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compute_meta(:module, %{mfa: {mod, _, _}}), do: mod
|
defp compute_meta(:module, %{mfa: {mod, _, _}}), do: mod
|
||||||
@@ -498,8 +498,6 @@ defmodule Logger.Formatter do
|
|||||||
rest
|
rest
|
||||||
end
|
end
|
||||||
|
|
||||||
defp metadata(:file, file) when is_list(file), do: file
|
|
||||||
|
|
||||||
defp metadata(:domain, [head | tail]) when is_atom(head) do
|
defp metadata(:domain, [head | tail]) when is_atom(head) do
|
||||||
Enum.map_intersperse([head | tail], ?., &Atom.to_string/1)
|
Enum.map_intersperse([head | tail], ?., &Atom.to_string/1)
|
||||||
end
|
end
|
||||||
@@ -514,6 +512,8 @@ defmodule Logger.Formatter do
|
|||||||
Exception.format_mfa(mod, fun, arity)
|
Exception.format_mfa(mod, fun, arity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp metadata(:function, function) when is_list(function), do: function
|
||||||
|
defp metadata(:file, file) when is_list(file), do: file
|
||||||
defp metadata(_, list) when is_list(list), do: nil
|
defp metadata(_, list) when is_list(list), do: nil
|
||||||
|
|
||||||
defp metadata(_, other) do
|
defp metadata(_, other) do
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ defmodule Logger.Backends.HandlerTest do
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "add_translator/1 and remove_translator/1 for logger formats" do
|
test "add_translator/1 and remove_translator/1 for logger formats" do
|
||||||
|
refute {CustomTranslator, :t} in Application.fetch_env!(:logger, :translators)
|
||||||
assert Logger.add_translator({CustomTranslator, :t})
|
assert Logger.add_translator({CustomTranslator, :t})
|
||||||
|
assert {CustomTranslator, :t} in Application.fetch_env!(:logger, :translators)
|
||||||
|
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
:logger.info(~c"hello: ~p", [:ok])
|
:logger.info(~c"hello: ~p", [:ok])
|
||||||
|
|||||||
@@ -54,6 +54,28 @@ defmodule Logger.FormatterTest do
|
|||||||
assert format_time(time) == ["12", ?:, "30", ?:, "10", ?., [?0, "10"]]
|
assert format_time(time) == ["12", ?:, "30", ?:, "10", ?., [?0, "10"]]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "log" do
|
||||||
|
test "handles :module and :function" do
|
||||||
|
{_, formatter} =
|
||||||
|
new(
|
||||||
|
format: "\n$time $metadata[$level] $message\n",
|
||||||
|
metadata: [:module, :function, :mfa],
|
||||||
|
colors: [enabled: false]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert %{
|
||||||
|
level: :warn,
|
||||||
|
msg: {:string, "foo"},
|
||||||
|
meta: %{
|
||||||
|
mfa: {Logger.Formatter, :compile, 1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> format(formatter)
|
||||||
|
|> IO.chardata_to_string() =~
|
||||||
|
"module=Logger.Formatter function=compile/1 mfa=Logger.Formatter.compile/1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "compile + format" do
|
describe "compile + format" do
|
||||||
defmodule CompileMod do
|
defmodule CompileMod do
|
||||||
def format(_level, _msg, _ts, _md) do
|
def format(_level, _msg, _ts, _md) do
|
||||||
@@ -66,7 +88,7 @@ defmodule Logger.FormatterTest do
|
|||||||
["\n", :time, " ", :metadata, "[", :level, "] ", :message, "\n"]
|
["\n", :time, " ", :metadata, "[", :level, "] ", :message, "\n"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "compile with str" do
|
test "compile with string" do
|
||||||
assert compile("$level $time $date $metadata $message $node") ==
|
assert compile("$level $time $date $metadata $message $node") ==
|
||||||
Enum.intersperse([:level, :time, :date, :metadata, :message, :node], " ")
|
Enum.intersperse([:level, :time, :date, :metadata, :message, :node], " ")
|
||||||
|
|
||||||
|
|||||||
+20
-11
@@ -600,26 +600,29 @@ defmodule Mix do
|
|||||||
"""
|
"""
|
||||||
@doc since: "1.15.0"
|
@doc since: "1.15.0"
|
||||||
def ensure_application!(app) when is_atom(app) do
|
def ensure_application!(app) when is_atom(app) do
|
||||||
ensure_application!(app, Mix.State.builtin_apps())
|
ensure_application!(app, Mix.State.builtin_apps(), [])
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp ensure_application!(app, builtin_apps) do
|
defp ensure_application!(app, builtin_apps, optional) do
|
||||||
case builtin_apps do
|
case builtin_apps do
|
||||||
%{^app => path} ->
|
%{^app => path} ->
|
||||||
Code.prepend_path(path, cache: true)
|
Code.prepend_path(path, cache: true)
|
||||||
Application.load(app)
|
Application.load(app)
|
||||||
|
optional = List.wrap(Application.spec(app, :optional_applications))
|
||||||
|
|
||||||
Application.spec(app, :applications)
|
Application.spec(app, :applications)
|
||||||
|> List.wrap()
|
|> List.wrap()
|
||||||
|> Enum.each(&ensure_application!(&1, builtin_apps))
|
|> Enum.each(&ensure_application!(&1, builtin_apps, optional))
|
||||||
|
|
||||||
%{} ->
|
%{} ->
|
||||||
Mix.raise(
|
unless app in optional do
|
||||||
"The application \"#{app}\" could not be found. This may happen if your " <>
|
Mix.raise(
|
||||||
"Operating System broke Erlang into multiple packages and may be fixed " <>
|
"The application \"#{app}\" could not be found. This may happen if your " <>
|
||||||
"by installing the missing \"erlang-dev\" and \"erlang-#{app}\" packages"
|
"Operating System broke Erlang into multiple packages and may be fixed " <>
|
||||||
)
|
"by installing the missing \"erlang-dev\" and \"erlang-#{app}\" packages"
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -674,6 +677,9 @@ defmodule Mix do
|
|||||||
* `:lockfile` (since v1.14.0) - path to a lockfile to be used as a basis of
|
* `:lockfile` (since v1.14.0) - path to a lockfile to be used as a basis of
|
||||||
dependency resolution.
|
dependency resolution.
|
||||||
|
|
||||||
|
* `:start_applications` (since v1.15.3) - if `true`, ensures that installed app
|
||||||
|
and its dependencies are started after install (Default: `true`)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Installing `:decimal` and `:jason`:
|
Installing `:decimal` and `:jason`:
|
||||||
@@ -794,6 +800,7 @@ defmodule Mix do
|
|||||||
config_path = expand_path(opts[:config_path], deps, :config_path, "config/config.exs")
|
config_path = expand_path(opts[:config_path], deps, :config_path, "config/config.exs")
|
||||||
system_env = Keyword.get(opts, :system_env, [])
|
system_env = Keyword.get(opts, :system_env, [])
|
||||||
consolidate_protocols? = Keyword.get(opts, :consolidate_protocols, true)
|
consolidate_protocols? = Keyword.get(opts, :consolidate_protocols, true)
|
||||||
|
start_applications? = Keyword.get(opts, :start_applications, true)
|
||||||
|
|
||||||
id =
|
id =
|
||||||
{deps, config, system_env, consolidate_protocols?}
|
{deps, config, system_env, consolidate_protocols?}
|
||||||
@@ -893,9 +900,11 @@ defmodule Mix do
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
for %{app: app, opts: opts} <- Mix.Dep.cached(),
|
if start_applications? do
|
||||||
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) do
|
for %{app: app, opts: opts} <- Mix.Dep.cached(),
|
||||||
Application.ensure_all_started(app)
|
Keyword.get(opts, :runtime, true) and Keyword.get(opts, :app, true) do
|
||||||
|
Application.ensure_all_started(app)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
Mix.State.put(:installed, id)
|
Mix.State.put(:installed, id)
|
||||||
|
|||||||
@@ -136,8 +136,12 @@ defmodule Mix.AppLoader do
|
|||||||
end
|
end
|
||||||
|
|
||||||
# We have processed all apps.
|
# We have processed all apps.
|
||||||
defp traverse_apps([], _seen, _deps_children, _builtin_paths, _lib_path) do
|
defp traverse_apps([], seen, _deps_children, builtin_paths, _lib_path) do
|
||||||
[]
|
# We want to keep erts in the load path but it doesn't require to be loaded.
|
||||||
|
case builtin_paths do
|
||||||
|
%{erts: path} when not is_map_key(seen, :erts) -> [{:erts, path}]
|
||||||
|
%{} -> []
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp app_children(app) do
|
defp app_children(app) do
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
defmodule Mix.Compilers.Elixir do
|
defmodule Mix.Compilers.Elixir do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
@manifest_vsn 19
|
@manifest_vsn 20
|
||||||
@checkpoint_vsn 2
|
@checkpoint_vsn 2
|
||||||
|
|
||||||
import Record
|
import Record
|
||||||
@@ -46,6 +46,11 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
{all_modules, all_sources, all_local_exports, old_parents, old_cache_key, old_deps_config} =
|
{all_modules, all_sources, all_local_exports, old_parents, old_cache_key, old_deps_config} =
|
||||||
parse_manifest(manifest, dest)
|
parse_manifest(manifest, dest)
|
||||||
|
|
||||||
|
# Prepend ourselves early because of __mix_recompile__? checks
|
||||||
|
# and also that, in case of nothing compiled, we already need
|
||||||
|
# ourselves available in the path.
|
||||||
|
Code.prepend_path(dest)
|
||||||
|
|
||||||
# If modules have been added or removed from the Erlang compiler,
|
# If modules have been added or removed from the Erlang compiler,
|
||||||
# we need to recompile all references to old and new modules.
|
# we need to recompile all references to old and new modules.
|
||||||
stale =
|
stale =
|
||||||
@@ -56,7 +61,7 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
|
|
||||||
local_deps = Enum.reject(Mix.Dep.cached(), & &1.scm.fetchable?)
|
local_deps = Enum.reject(Mix.Dep.cached(), & &1.scm.fetchable?())
|
||||||
|
|
||||||
# If mix.exs has changed, recompile anything that calls Mix.Project.
|
# If mix.exs has changed, recompile anything that calls Mix.Project.
|
||||||
stale =
|
stale =
|
||||||
@@ -393,7 +398,7 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
Enum.any?(modules, &Map.has_key?(modules_to_recompile, &1)) or
|
Enum.any?(modules, &Map.has_key?(modules_to_recompile, &1)) or
|
||||||
Enum.any?(external, &stale_external?(&1, modified, sources_stats)) or
|
Enum.any?(external, &stale_external?(&1, modified, sources_stats)) or
|
||||||
(last_mtime > modified and
|
(last_mtime > modified and
|
||||||
(missing_beam_file?(dest, modules) or digest != digest_file!(source))),
|
(missing_beam_file?(dest, modules) or digest_changed?(source, digest))),
|
||||||
do: source
|
do: source
|
||||||
|
|
||||||
changed = new_paths ++ changed
|
changed = new_paths ++ changed
|
||||||
@@ -423,7 +428,7 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
defp stale_external?({external, digest}, modified, sources_stats) do
|
defp stale_external?({external, digest}, modified, sources_stats) do
|
||||||
case sources_stats do
|
case sources_stats do
|
||||||
%{^external => {0, 0}} -> digest != nil
|
%{^external => {0, 0}} -> digest != nil
|
||||||
%{^external => {mtime, _}} -> mtime > modified and digest != digest_file!(external)
|
%{^external => {mtime, _}} -> mtime > modified and digest_changed?(external, digest)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -437,8 +442,11 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp digest_file!(file) do
|
defp digest_changed?(file, digest) do
|
||||||
file |> File.read!() |> digest_contents()
|
case File.read(file) do
|
||||||
|
{:ok, binary} -> digest != digest_contents(binary)
|
||||||
|
{:error, _} -> true
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp digest_contents(contents) do
|
defp digest_contents(contents) do
|
||||||
@@ -446,6 +454,9 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
8 -> :crypto.hash(:blake2b, contents)
|
8 -> :crypto.hash(:blake2b, contents)
|
||||||
_ -> :crypto.hash(:blake2s, contents)
|
_ -> :crypto.hash(:blake2s, contents)
|
||||||
end
|
end
|
||||||
|
rescue
|
||||||
|
# Blake may not be available on all OpenSSL distribution
|
||||||
|
_ -> :erlang.md5(contents)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_compiler_opts(opts) do
|
defp set_compiler_opts(opts) do
|
||||||
@@ -558,18 +569,18 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
Enum.any?(enumerable, &Map.has_key?(map, &1))
|
Enum.any?(enumerable, &Map.has_key?(map, &1))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp stale_local_deps(local_deps, manifest, stale_modules, modified, old_exports) do
|
defp stale_local_deps(local_deps, manifest, stale_modules, modified, deps_exports) do
|
||||||
base = Path.basename(manifest)
|
base = Path.basename(manifest)
|
||||||
|
|
||||||
# The stale modules so far will become both stale_modules and stale_exports,
|
# The stale modules so far will become both stale_modules and stale_exports,
|
||||||
# as any export from a dependency needs to be recompiled.
|
# as any export from a dependency needs to be recompiled.
|
||||||
stale_modules = Map.from_keys(stale_modules, true)
|
stale_modules = Map.from_keys(stale_modules, true)
|
||||||
|
|
||||||
for %{opts: opts} <- local_deps,
|
for %{app: app, opts: opts} <- local_deps,
|
||||||
manifest = Path.join([opts[:build], ".mix", base]),
|
manifest = Path.join([opts[:build], ".mix", base]),
|
||||||
Mix.Utils.last_modified(manifest) > modified,
|
Mix.Utils.last_modified(manifest) > modified,
|
||||||
reduce: {stale_modules, stale_modules, old_exports} do
|
reduce: {stale_modules, stale_modules, deps_exports} do
|
||||||
{modules, exports, new_exports} ->
|
{modules, exports, deps_exports} ->
|
||||||
{manifest_modules, manifest_sources} = read_manifest(manifest)
|
{manifest_modules, manifest_sources} = read_manifest(manifest)
|
||||||
|
|
||||||
dep_modules =
|
dep_modules =
|
||||||
@@ -584,9 +595,11 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
{dep_modules, _, _} =
|
{dep_modules, _, _} =
|
||||||
fixpoint_runtime_modules(manifest_sources, Map.from_keys(dep_modules, true))
|
fixpoint_runtime_modules(manifest_sources, Map.from_keys(dep_modules, true))
|
||||||
|
|
||||||
|
old_exports = Map.get(deps_exports, app, %{})
|
||||||
|
|
||||||
# Update exports
|
# Update exports
|
||||||
{exports, new_exports} =
|
{exports, new_exports} =
|
||||||
for {module, _} <- dep_modules, reduce: {exports, new_exports} do
|
for {module, _} <- dep_modules, reduce: {exports, []} do
|
||||||
{exports, new_exports} ->
|
{exports, new_exports} ->
|
||||||
export = exports_md5(module, false)
|
export = exports_md5(module, false)
|
||||||
|
|
||||||
@@ -600,17 +613,27 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
do: exports,
|
do: exports,
|
||||||
else: Map.put(exports, module, true)
|
else: Map.put(exports, module, true)
|
||||||
|
|
||||||
# In any case, we always store it as the most update export
|
# Then we store the new export if any
|
||||||
# that we have, otherwise we delete it.
|
|
||||||
new_exports =
|
new_exports =
|
||||||
if export,
|
if export,
|
||||||
do: Map.put(new_exports, module, export),
|
do: [{module, export} | new_exports],
|
||||||
else: Map.delete(new_exports, module)
|
else: new_exports
|
||||||
|
|
||||||
{exports, new_exports}
|
{exports, new_exports}
|
||||||
end
|
end
|
||||||
|
|
||||||
{Map.merge(modules, dep_modules), exports, new_exports}
|
new_exports = Map.new(new_exports)
|
||||||
|
|
||||||
|
removed =
|
||||||
|
for {module, _} <- old_exports,
|
||||||
|
not is_map_key(new_exports, module),
|
||||||
|
do: {module, true},
|
||||||
|
into: %{}
|
||||||
|
|
||||||
|
modules = modules |> Map.merge(dep_modules) |> Map.merge(removed)
|
||||||
|
exports = Map.merge(exports, removed)
|
||||||
|
deps_exports = Map.put(deps_exports, app, new_exports)
|
||||||
|
{modules, exports, deps_exports}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1081,7 +1104,7 @@ defmodule Mix.Compilers.Elixir do
|
|||||||
source(
|
source(
|
||||||
source,
|
source,
|
||||||
# We preserve the digest if the file is recompiled but not changed
|
# We preserve the digest if the file is recompiled but not changed
|
||||||
digest: source(source, :digest) || digest_file!(file),
|
digest: source(source, :digest) || file |> File.read!() |> digest_contents(),
|
||||||
compile_references: compile_references,
|
compile_references: compile_references,
|
||||||
export_references: export_references,
|
export_references: export_references,
|
||||||
runtime_references: runtime_references,
|
runtime_references: runtime_references,
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ defmodule Mix.Compilers.Erlang do
|
|||||||
@manifest_vsn 1
|
@manifest_vsn 1
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Compiles the files in `mappings` with given extensions into
|
Compiles the given `mappings`.
|
||||||
the destination, automatically invoking the callback for each
|
|
||||||
stale input and output pair (or for all if `force` is `true`) and
|
|
||||||
removing files that no longer have a source, while keeping the
|
|
||||||
`manifest` up to date.
|
|
||||||
|
|
||||||
`mappings` should be a list of tuples in the form of `{src, dest}` paths.
|
`mappings` is a list of `{src, dest}` pairs, where the source
|
||||||
|
extensions are compiled into the destination extension,
|
||||||
|
automatically invoking the callback for each stale pair (or for
|
||||||
|
all if `force` is `true`) and removing files that no longer have
|
||||||
|
a source, while keeping the `manifest` up to date.
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
@@ -18,6 +18,9 @@ defmodule Mix.Compilers.Erlang do
|
|||||||
|
|
||||||
* `:parallel` - a mapset of files to compile in parallel
|
* `:parallel` - a mapset of files to compile in parallel
|
||||||
|
|
||||||
|
* `:preload` - any code that must be preloaded if any pending
|
||||||
|
entry needs to be compiled
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
For example, a simple compiler for Lisp Flavored Erlang
|
For example, a simple compiler for Lisp Flavored Erlang
|
||||||
@@ -54,22 +57,25 @@ defmodule Mix.Compilers.Erlang do
|
|||||||
def compile(manifest, mappings, src_ext, dest_ext, opts, callback) when is_list(opts) do
|
def compile(manifest, mappings, src_ext, dest_ext, opts, callback) when is_list(opts) do
|
||||||
force = opts[:force]
|
force = opts[:force]
|
||||||
|
|
||||||
files =
|
entries =
|
||||||
for {src, dest} <- mappings,
|
for {src, dest} <- mappings,
|
||||||
target <- extract_targets(src, src_ext, dest, dest_ext, force),
|
target <- extract_entries(src, src_ext, dest, dest_ext, force),
|
||||||
do: target
|
do: target
|
||||||
|
|
||||||
compile(manifest, files, src_ext, opts, callback)
|
if preload = entries != [] && opts[:preload] do
|
||||||
|
preload.()
|
||||||
|
end
|
||||||
|
|
||||||
|
compile(manifest, entries, src_ext, opts, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Compiles the given `mappings`.
|
Compiles the given `entries`.
|
||||||
|
|
||||||
`mappings` should be a list of tuples in the form of `{src, dest}`.
|
`entries` are a list of `{:ok | :stale, src, dest}` tuples.
|
||||||
|
|
||||||
A `manifest` file and a `callback` to be invoked for each src/dest pair
|
A `manifest` file and a `callback` to be invoked for each stale
|
||||||
must be given. A src/dest pair where destination is `nil` is considered
|
src/dest pair must also be given.
|
||||||
to be up to date and won't be (re-)compiled.
|
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
@@ -78,8 +84,8 @@ defmodule Mix.Compilers.Erlang do
|
|||||||
* `:parallel` - a mapset of files to compile in parallel
|
* `:parallel` - a mapset of files to compile in parallel
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def compile(manifest, mappings, opts \\ [], callback) do
|
def compile(manifest, entries, opts \\ [], callback) do
|
||||||
compile(manifest, mappings, :erl, opts, callback)
|
compile(manifest, entries, :erl, opts, callback)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp compile(manifest, mappings, ext, opts, callback) do
|
defp compile(manifest, mappings, ext, opts, callback) do
|
||||||
@@ -102,7 +108,7 @@ defmodule Mix.Compilers.Erlang do
|
|||||||
# Clear stale and removed files from manifest
|
# Clear stale and removed files from manifest
|
||||||
entries =
|
entries =
|
||||||
Enum.reject(entries, fn {dest, _warnings} ->
|
Enum.reject(entries, fn {dest, _warnings} ->
|
||||||
dest in removed || Enum.any?(stale, fn {_, stale_dest} -> dest == stale_dest end)
|
dest in removed || List.keymember?(stale, dest, 1)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
if Keyword.get(opts, :all_warnings, true), do: show_warnings(entries)
|
if Keyword.get(opts, :all_warnings, true), do: show_warnings(entries)
|
||||||
@@ -156,9 +162,8 @@ defmodule Mix.Compilers.Erlang do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
# TODO: Deprecate this in favor of `Mix.ensure_application!/1` in Elixir v1.19.
|
||||||
Ensures the native OTP application is available.
|
@doc false
|
||||||
"""
|
|
||||||
def ensure_application!(app, _input) do
|
def ensure_application!(app, _input) do
|
||||||
Mix.ensure_application!(app)
|
Mix.ensure_application!(app)
|
||||||
{:ok, _} = Application.ensure_all_started(app)
|
{:ok, _} = Application.ensure_all_started(app)
|
||||||
@@ -202,7 +207,7 @@ defmodule Mix.Compilers.Erlang do
|
|||||||
manifest |> read_manifest() |> Enum.map(&elem(&1, 0))
|
manifest |> read_manifest() |> Enum.map(&elem(&1, 0))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp extract_targets(src_dir, src_ext, dest_dir, dest_ext, force) do
|
defp extract_entries(src_dir, src_ext, dest_dir, dest_ext, force) do
|
||||||
files = Mix.Utils.extract_files(List.wrap(src_dir), List.wrap(src_ext))
|
files = Mix.Utils.extract_files(List.wrap(src_dir), List.wrap(src_ext))
|
||||||
|
|
||||||
for file <- files do
|
for file <- files do
|
||||||
|
|||||||
+23
-21
@@ -114,12 +114,12 @@ defmodule Mix.Dep do
|
|||||||
write_cached_deps(top, {env, target}, load_and_cache(config, top, bottom, env, target))
|
write_cached_deps(top, {env, target}, load_and_cache(config, top, bottom, env, target))
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
converge(env: env, target: target)
|
converge_and_load(env: env, target: target)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp load_and_cache(_config, top, top, env, target) do
|
defp load_and_cache(_config, top, top, env, target) do
|
||||||
converge(env: env, target: target)
|
converge_and_load(env: env, target: target)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp load_and_cache(config, _top, bottom, _env, _target) do
|
defp load_and_cache(config, _top, bottom, _env, _target) do
|
||||||
@@ -152,6 +152,20 @@ defmodule Mix.Dep do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp converge_and_load(opts) do
|
||||||
|
for %{app: app, opts: opts} = dep <- Mix.Dep.Converger.converge(opts) do
|
||||||
|
case Keyword.pop(opts, :app_properties) do
|
||||||
|
{nil, _opts} ->
|
||||||
|
dep
|
||||||
|
|
||||||
|
{app_properties, opts} ->
|
||||||
|
# We don't raise because child dependencies may be missing if manually cleaned
|
||||||
|
:application.load({:application, app, app_properties})
|
||||||
|
%{dep | opts: opts}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp read_cached_deps(project, env_target) do
|
defp read_cached_deps(project, env_target) do
|
||||||
case Mix.State.read_cache({:cached_deps, project}) do
|
case Mix.State.read_cache({:cached_deps, project}) do
|
||||||
{^env_target, deps} -> deps
|
{^env_target, deps} -> deps
|
||||||
@@ -164,6 +178,13 @@ defmodule Mix.Dep do
|
|||||||
deps
|
deps
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Although private API, this is kept to avoid breaking code on patch releases.
|
||||||
|
"""
|
||||||
|
def load_on_environment(opts) do
|
||||||
|
Mix.Dep.Converger.converge(opts)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Clears loaded dependencies from the cache for the current environment.
|
Clears loaded dependencies from the cache for the current environment.
|
||||||
"""
|
"""
|
||||||
@@ -174,25 +195,6 @@ defmodule Mix.Dep do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
|
||||||
Returns loaded dependencies recursively on the given environment.
|
|
||||||
|
|
||||||
If no environment is passed, dependencies are loaded across all
|
|
||||||
environments. The result is not cached.
|
|
||||||
|
|
||||||
## Exceptions
|
|
||||||
|
|
||||||
This function raises an exception if any of the dependencies
|
|
||||||
provided in the project are in the wrong format.
|
|
||||||
"""
|
|
||||||
def load_on_environment(opts) do
|
|
||||||
converge(opts)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp converge(opts) do
|
|
||||||
Mix.Dep.Converger.converge(nil, nil, opts, &{&1, &2, &3}) |> elem(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Filters the given dependencies by name.
|
Filters the given dependencies by name.
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,18 @@ defmodule Mix.Dep.Converger do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Converge without lock and accumulator updates.
|
||||||
|
|
||||||
|
Note the dependencies returned from converge are not yet loaded.
|
||||||
|
The relevant app keys are found under `dep.opts[:app_properties]`.
|
||||||
|
|
||||||
|
See `Mix.Dep.Loader.children/1` for options.
|
||||||
|
"""
|
||||||
|
def converge(opts \\ []) do
|
||||||
|
converge(nil, nil, opts, &{&1, &2, &3}) |> elem(0)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Converges all dependencies from the current project,
|
Converges all dependencies from the current project,
|
||||||
including nested dependencies.
|
including nested dependencies.
|
||||||
@@ -68,11 +80,14 @@ defmodule Mix.Dep.Converger do
|
|||||||
must return an updated dependency in case some processing
|
must return an updated dependency in case some processing
|
||||||
is done.
|
is done.
|
||||||
|
|
||||||
|
Note the dependencies returned from converge are not yet loaded.
|
||||||
|
The relevant app keys are found under `dep.opts[:app_properties]`.
|
||||||
|
|
||||||
See `Mix.Dep.Loader.children/1` for options.
|
See `Mix.Dep.Loader.children/1` for options.
|
||||||
"""
|
"""
|
||||||
def converge(acc, lock, opts, callback) do
|
def converge(acc, lock, opts, callback) do
|
||||||
{deps, acc, lock} = all(acc, lock, opts, callback)
|
{deps, acc, lock} = all(acc, lock, opts, callback)
|
||||||
if remote = Mix.RemoteConverger.get(), do: remote.post_converge
|
if remote = Mix.RemoteConverger.get(), do: remote.post_converge()
|
||||||
{topological_sort(deps), acc, lock}
|
{topological_sort(deps), acc, lock}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -205,21 +220,20 @@ defmodule Mix.Dep.Converger do
|
|||||||
all(t, [dep | acc], upper, breadths, optional, rest, lock, state)
|
all(t, [dep | acc], upper, breadths, optional, rest, lock, state)
|
||||||
|
|
||||||
:nomatch ->
|
:nomatch ->
|
||||||
{%{app: app, deps: deps, opts: opts} = dep, app_properties, rest, lock} =
|
{%{app: app, deps: deps, opts: opts} = dep, rest, lock} =
|
||||||
case state.cache.(dep) do
|
case state.cache.(dep) do
|
||||||
{:loaded, cached_dep} ->
|
{:loaded, cached_dep} ->
|
||||||
{cached_dep, nil, rest, lock}
|
{cached_dep, rest, lock}
|
||||||
|
|
||||||
{:unloaded, dep, children} ->
|
{:unloaded, dep, children} ->
|
||||||
{dep, rest, lock} = state.callback.(put_lock(dep, lock), rest, lock)
|
{dep, rest, lock} = state.callback.(put_lock(dep, lock), rest, lock)
|
||||||
|
|
||||||
Mix.Dep.Loader.with_system_env(dep, fn ->
|
dep =
|
||||||
# After we invoke the callback (which may actually check out the
|
Mix.Dep.Loader.with_system_env(dep, fn ->
|
||||||
# dependency), we load the dependency including its latest info
|
Mix.Dep.Loader.load(dep, children, state.locked?)
|
||||||
# and children information.
|
end)
|
||||||
{dep, app_properties} = Mix.Dep.Loader.load(dep, children, state.locked?)
|
|
||||||
{dep, app_properties, rest, lock}
|
{dep, rest, lock}
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Something that we previously ruled out as an optional dependency is
|
# Something that we previously ruled out as an optional dependency is
|
||||||
@@ -234,24 +248,7 @@ defmodule Mix.Dep.Converger do
|
|||||||
split_non_fulfilled_optional(deps, Enum.map(acc, & &1.app), opts[:from_umbrella])
|
split_non_fulfilled_optional(deps, Enum.map(acc, & &1.app), opts[:from_umbrella])
|
||||||
|
|
||||||
new_breadths = Enum.map(deps, & &1.app) ++ breadths
|
new_breadths = Enum.map(deps, & &1.app) ++ breadths
|
||||||
res = all(deps, acc, breadths, new_breadths, discarded ++ optional, rest, lock, state)
|
all(deps, acc, breadths, new_breadths, discarded ++ optional, rest, lock, state)
|
||||||
|
|
||||||
# After we traverse all of our children, we can load ourselves.
|
|
||||||
# This is important in case of included application.
|
|
||||||
if app_properties do
|
|
||||||
case :application.load({:application, app, app_properties}) do
|
|
||||||
:ok ->
|
|
||||||
:ok
|
|
||||||
|
|
||||||
{:error, {:already_loaded, _}} ->
|
|
||||||
:ok
|
|
||||||
|
|
||||||
{:error, error} ->
|
|
||||||
Mix.raise("Could not start application #{inspect(app)}: #{inspect(error)}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
res
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -383,21 +383,21 @@ defmodule Mix.Dep.Loader do
|
|||||||
|
|
||||||
cond do
|
cond do
|
||||||
not ok?(dep) ->
|
not ok?(dep) ->
|
||||||
{dep, nil}
|
dep
|
||||||
|
|
||||||
recently_fetched?(dep) ->
|
recently_fetched?(dep) ->
|
||||||
{%{dep | status: :compile}, nil}
|
%{dep | status: :compile}
|
||||||
|
|
||||||
opts_app == false ->
|
opts_app == false ->
|
||||||
{dep, nil}
|
dep
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
path = if is_binary(opts_app), do: opts_app, else: "ebin/#{app}.app"
|
path = if is_binary(opts_app), do: opts_app, else: "ebin/#{app}.app"
|
||||||
path = Path.expand(path, opts[:build])
|
path = Path.expand(path, opts[:build])
|
||||||
|
|
||||||
case app_status(path, app, req) do
|
case app_status(path, app, req) do
|
||||||
{:ok, vsn, app} -> {%{dep | status: {:ok, vsn}}, app}
|
{:ok, vsn, app} -> %{dep | status: {:ok, vsn}, opts: [app_properties: app] ++ opts}
|
||||||
status -> {%{dep | status: status}, nil}
|
status -> %{dep | status: status}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ defmodule Mix.Dep.Umbrella do
|
|||||||
apps = Enum.map(deps, & &1.app)
|
apps = Enum.map(deps, & &1.app)
|
||||||
|
|
||||||
Enum.map(deps, fn umbrella_dep ->
|
Enum.map(deps, fn umbrella_dep ->
|
||||||
{umbrella_dep, _} = Mix.Dep.Loader.load(umbrella_dep, nil, false)
|
umbrella_dep = Mix.Dep.Loader.load(umbrella_dep, nil, false)
|
||||||
|
|
||||||
deps =
|
deps =
|
||||||
Enum.filter(umbrella_dep.deps, fn dep ->
|
Enum.filter(umbrella_dep.deps, fn dep ->
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ defmodule Mix.Release do
|
|||||||
@default_apps [kernel: :permanent, stdlib: :permanent, elixir: :permanent, sasl: :permanent]
|
@default_apps [kernel: :permanent, stdlib: :permanent, elixir: :permanent, sasl: :permanent]
|
||||||
@safe_modes [:permanent, :temporary, :transient]
|
@safe_modes [:permanent, :temporary, :transient]
|
||||||
@unsafe_modes [:load, :none]
|
@unsafe_modes [:load, :none]
|
||||||
@significant_chunks ~w(Atom AtU8 Attr Code StrT ImpT ExpT FunT LitT Line)c
|
@additional_chunks ~w(Attr)c
|
||||||
@copy_app_dirs ["priv"]
|
@copy_app_dirs ["priv"]
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
@@ -285,7 +285,8 @@ defmodule Mix.Release do
|
|||||||
|> Enum.map(&{&1, new_mode})
|
|> Enum.map(&{&1, new_mode})
|
||||||
|
|
||||||
seen = put_in(seen[app][:mode], new_mode)
|
seen = put_in(seen[app][:mode], new_mode)
|
||||||
load_apps(apps, deps_apps, seen, otp_root, [], overrides)
|
optional = Keyword.get(properties, :optional_applications, [])
|
||||||
|
load_apps(apps, deps_apps, seen, otp_root, optional, overrides)
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
seen
|
seen
|
||||||
@@ -878,7 +879,7 @@ defmodule Mix.Release do
|
|||||||
@spec strip_beam(binary(), keyword()) :: {:ok, binary()} | {:error, :beam_lib, term()}
|
@spec strip_beam(binary(), keyword()) :: {:ok, binary()} | {:error, :beam_lib, term()}
|
||||||
def strip_beam(binary, options \\ []) when is_list(options) do
|
def strip_beam(binary, options \\ []) when is_list(options) do
|
||||||
chunks_to_keep = options[:keep] |> List.wrap() |> Enum.map(&String.to_charlist/1)
|
chunks_to_keep = options[:keep] |> List.wrap() |> Enum.map(&String.to_charlist/1)
|
||||||
all_chunks = Enum.uniq(@significant_chunks ++ chunks_to_keep)
|
all_chunks = Enum.uniq(@additional_chunks ++ :beam_lib.significant_chunks() ++ chunks_to_keep)
|
||||||
compress? = Keyword.get(options, :compress, false)
|
compress? = Keyword.get(options, :compress, false)
|
||||||
|
|
||||||
case :beam_lib.chunks(binary, all_chunks, [:allow_missing_chunks]) do
|
case :beam_lib.chunks(binary, all_chunks, [:allow_missing_chunks]) do
|
||||||
|
|||||||
+14
-6
@@ -58,14 +58,22 @@ defmodule Mix.Task do
|
|||||||
|
|
||||||
@requirements ["app.config"]
|
@requirements ["app.config"]
|
||||||
|
|
||||||
Tasks typically depend on the `"app.config"` task, when they
|
A task will typically depend on one of the following tasks:
|
||||||
need to access code from the current project with all apps
|
|
||||||
already configured, or the "app.start" task, when they also
|
|
||||||
need those apps to be already started:
|
|
||||||
|
|
||||||
@requirements ["app.start"]
|
* "loadpaths" - this ensures dependencies are available
|
||||||
|
and compiled. If you are publishing a task as part of
|
||||||
|
a library to be used by others, and your task does not
|
||||||
|
need to interact with the user code in any way, this is
|
||||||
|
the recommended requirement
|
||||||
|
|
||||||
You can also run tasks directly with `run/2`.
|
* "app.config" - additionally compiles and loads the runtime
|
||||||
|
configuration for the current project. If you are creating
|
||||||
|
a task to be used within your application or as part of a
|
||||||
|
library, which must invoke or interact with the user code,
|
||||||
|
this is the minimum recommended requirement
|
||||||
|
|
||||||
|
* "app.start" - additionally starts the supervision tree of
|
||||||
|
the current project and its dependencies
|
||||||
|
|
||||||
Finally, set `@recursive true` if you want the task to run
|
Finally, set `@recursive true` if you want the task to run
|
||||||
on each umbrella child in an umbrella project.
|
on each umbrella child in an umbrella project.
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ defmodule Mix.Tasks.Archive.Build do
|
|||||||
project = Mix.Project.get()
|
project = Mix.Project.get()
|
||||||
|
|
||||||
if project && Keyword.get(opts, :compile, true) do
|
if project && Keyword.get(opts, :compile, true) do
|
||||||
Mix.Task.run(:compile, args)
|
Mix.Task.run(:compile, ["--no-protocol-consolidation" | args])
|
||||||
end
|
end
|
||||||
|
|
||||||
source =
|
source =
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ defmodule Mix.Tasks.Compile.All do
|
|||||||
{loaded_paths, loaded_modules} =
|
{loaded_paths, loaded_modules} =
|
||||||
Mix.AppLoader.load_apps(apps, deps, config, {[], []}, fn {app, path}, {paths, mods} ->
|
Mix.AppLoader.load_apps(apps, deps, config, {[], []}, fn {app, path}, {paths, mods} ->
|
||||||
paths = if path, do: [path | paths], else: paths
|
paths = if path, do: [path | paths], else: paths
|
||||||
mods = if app_cache, do: [{app, Application.spec(app, :modules)} | mods], else: mods
|
mods = if app_cache, do: [{app, Application.spec(app, :modules) || []} | mods], else: mods
|
||||||
{paths, mods}
|
{paths, mods}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@@ -43,7 +43,11 @@ defmodule Mix.Tasks.Compile.All do
|
|||||||
Code.delete_paths(current_paths -- loaded_paths)
|
Code.delete_paths(current_paths -- loaded_paths)
|
||||||
end
|
end
|
||||||
|
|
||||||
Code.prepend_paths(loaded_paths -- current_paths, cache: true)
|
# Add the current compilation path. compile.elixir and compile.erlang
|
||||||
|
# will also add this path, but only if they run, so we always add it
|
||||||
|
# here too. Furthermore, we don't cache it as we may still write to it.
|
||||||
|
compile_path = to_charlist(Mix.Project.compile_path())
|
||||||
|
Code.prepend_paths([compile_path | loaded_paths -- current_paths], cache: true)
|
||||||
|
|
||||||
result =
|
result =
|
||||||
if "--no-compile" in args do
|
if "--no-compile" in args do
|
||||||
@@ -64,12 +68,6 @@ defmodule Mix.Tasks.Compile.All do
|
|||||||
Mix.AppLoader.write_cache(app_cache, Map.new(loaded_modules))
|
Mix.AppLoader.write_cache(app_cache, Map.new(loaded_modules))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add the current compilation path. compile.elixir and compile.erlang
|
|
||||||
# will also add this path, but only if they run, so we always add it
|
|
||||||
# here too. Furthermore, we don't cache it as we may still write to it.
|
|
||||||
compile_path = to_charlist(Mix.Project.compile_path())
|
|
||||||
_ = Code.prepend_path(compile_path)
|
|
||||||
|
|
||||||
unless "--no-app-loading" in args do
|
unless "--no-app-loading" in args do
|
||||||
app = config[:app]
|
app = config[:app]
|
||||||
|
|
||||||
@@ -125,7 +123,7 @@ defmodule Mix.Tasks.Compile.All do
|
|||||||
Enum.reduce(Mix.ProjectStack.pop_after_compiler(compiler), result, & &1.(&2))
|
Enum.reduce(Mix.ProjectStack.pop_after_compiler(compiler), result, & &1.(&2))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp project_apps(config) do
|
def project_apps(config) do
|
||||||
project = Mix.Project.get!()
|
project = Mix.Project.get!()
|
||||||
|
|
||||||
properties =
|
properties =
|
||||||
|
|||||||
@@ -146,10 +146,9 @@ defmodule Mix.Tasks.Compile do
|
|||||||
config = Mix.Project.config()
|
config = Mix.Project.config()
|
||||||
|
|
||||||
# If we are in an umbrella project, now load paths from all children.
|
# If we are in an umbrella project, now load paths from all children.
|
||||||
if Mix.Project.umbrella?(config) do
|
if apps_paths = Mix.Project.apps_paths(config) do
|
||||||
loaded_paths =
|
loaded_paths =
|
||||||
Mix.Project.apps_paths(config)
|
(Mix.Tasks.Compile.All.project_apps(config) ++ Map.keys(apps_paths))
|
||||||
|> Map.keys()
|
|
||||||
|> Mix.AppLoader.load_apps(Mix.Dep.cached(), config, [], fn
|
|> Mix.AppLoader.load_apps(Mix.Dep.cached(), config, [], fn
|
||||||
{_app, path}, acc -> if path, do: [path | acc], else: acc
|
{_app, path}, acc -> if path, do: [path | acc], else: acc
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -53,15 +53,19 @@ defmodule Mix.Tasks.Compile.Leex do
|
|||||||
Mix.raise(":leex_options should be a list of options, got: #{inspect(options)}")
|
Mix.raise(":leex_options should be a list of options, got: #{inspect(options)}")
|
||||||
end
|
end
|
||||||
|
|
||||||
opts = [parallel: true] ++ opts
|
opts = [parallel: true, preload: &preload/0] ++ opts
|
||||||
|
|
||||||
Erlang.compile(manifest(), mappings, :xrl, :erl, opts, fn input, output ->
|
Erlang.compile(manifest(), mappings, :xrl, :erl, opts, fn input, output ->
|
||||||
Erlang.ensure_application!(:parsetools, input)
|
|
||||||
options = options ++ @forced_opts ++ [scannerfile: Erlang.to_erl_file(output)]
|
options = options ++ @forced_opts ++ [scannerfile: Erlang.to_erl_file(output)]
|
||||||
:leex.file(Erlang.to_erl_file(input), options)
|
:leex.file(Erlang.to_erl_file(input), options)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp preload do
|
||||||
|
Mix.ensure_application!(:parsetools)
|
||||||
|
{:ok, _} = Application.ensure_all_started(:parsetools)
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def manifests, do: [manifest()]
|
def manifests, do: [manifest()]
|
||||||
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
|
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
|
||||||
|
|||||||
@@ -53,15 +53,19 @@ defmodule Mix.Tasks.Compile.Yecc do
|
|||||||
Mix.raise(":yecc_options should be a list of options, got: #{inspect(options)}")
|
Mix.raise(":yecc_options should be a list of options, got: #{inspect(options)}")
|
||||||
end
|
end
|
||||||
|
|
||||||
opts = [parallel: true] ++ opts
|
opts = [parallel: true, preload: &preload/0] ++ opts
|
||||||
|
|
||||||
Erlang.compile(manifest(), mappings, :yrl, :erl, opts, fn input, output ->
|
Erlang.compile(manifest(), mappings, :yrl, :erl, opts, fn input, output ->
|
||||||
Erlang.ensure_application!(:parsetools, input)
|
|
||||||
options = options ++ @forced_opts ++ [parserfile: Erlang.to_erl_file(output)]
|
options = options ++ @forced_opts ++ [parserfile: Erlang.to_erl_file(output)]
|
||||||
:yecc.file(Erlang.to_erl_file(input), options)
|
:yecc.file(Erlang.to_erl_file(input), options)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp preload do
|
||||||
|
Mix.ensure_application!(:parsetools)
|
||||||
|
{:ok, _} = Application.ensure_all_started(:parsetools)
|
||||||
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def manifests, do: [manifest()]
|
def manifests, do: [manifest()]
|
||||||
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
|
defp manifest, do: Path.join(Mix.Project.manifest_path(), @manifest)
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ defmodule Mix.Tasks.Deps.Clean do
|
|||||||
value = opts[switch],
|
value = opts[switch],
|
||||||
do: {key, :"#{value}"}
|
do: {key, :"#{value}"}
|
||||||
|
|
||||||
loaded_deps = Mix.Dep.load_on_environment(loaded_opts)
|
loaded_deps = Mix.Dep.Converger.converge(loaded_opts)
|
||||||
|
|
||||||
apps_to_clean =
|
apps_to_clean =
|
||||||
cond do
|
cond do
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user