Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7df646d960 | ||
|
|
74fe558c17 | ||
|
|
7918d64700 | ||
|
|
81d6007410 | ||
|
|
be9fd499e6 | ||
|
|
a5958e31a1 | ||
|
|
29067a0a7d | ||
|
|
ce8f0ac7d0 | ||
|
|
2d71e7144b | ||
|
|
0c1395c215 | ||
|
|
1b3f9f8d5c | ||
|
|
dbfdedf9b3 | ||
|
|
6f96693b35 | ||
|
|
4e5a8d48ba | ||
|
|
acb5832c69 | ||
|
|
f026cb3756 | ||
|
|
73d58ec01b | ||
|
|
e9a6c9e3b3 | ||
|
|
237f3bc566 | ||
|
|
1a7545655c | ||
|
|
22fa810200 | ||
|
|
c3e0908c52 | ||
|
|
3d0273afb3 | ||
|
|
2b9d8b720e | ||
|
|
4687556f8f | ||
|
|
a8824bab09 | ||
|
|
476b4fdf6e | ||
|
|
2229ed7924 | ||
|
|
f9c053d60c | ||
|
|
1108a6c3d2 | ||
|
|
32c929b339 | ||
|
|
00e7245715 | ||
|
|
f596cf1629 | ||
|
|
12927b8840 | ||
|
|
130ed60f4d | ||
|
|
3eb94a482e | ||
|
|
6eca976752 | ||
|
|
e977f023a0 | ||
|
|
dc471cd876 | ||
|
|
6c31a6c8bb | ||
|
|
7535c0c403 | ||
|
|
9fd9bceeaa | ||
|
|
2ca0f1a99c | ||
|
|
6a1c5a449f | ||
|
|
c31d1b3eb0 | ||
|
|
6730d669fb | ||
|
|
fb4ccfa656 | ||
|
|
ff2ea69710 | ||
|
|
2146bb449f | ||
|
|
5a583c753b | ||
|
|
ec31cc2331 | ||
|
|
6549d209d9 | ||
|
|
71d27919ad | ||
|
|
a8aea1c763 | ||
|
|
a72f18b38b | ||
|
|
a1a37c67b6 | ||
|
|
ff73830103 | ||
|
|
950bcc0704 | ||
|
|
80564fc954 | ||
|
|
94e0e386ce | ||
|
|
bfa799b31f | ||
|
|
abe2af477b | ||
|
|
2a82c35e39 | ||
|
|
1f14211a5e | ||
|
|
1ae36fa079 | ||
|
|
e3fc947b92 | ||
|
|
0909940b04 | ||
|
|
d061cb96ef | ||
|
|
d1f7457907 | ||
|
|
123034569f | ||
|
|
bdea27c1e3 | ||
|
|
7f70f64a01 | ||
|
|
2acd0949dc | ||
|
|
7996c0a966 | ||
|
|
c01658b12d | ||
|
|
fdf96ad48a | ||
|
|
c80f8b6698 | ||
|
|
e65dae9f54 | ||
|
|
6c5d7bfe3f | ||
|
|
804d03431a | ||
|
|
29a2bb49d0 | ||
|
|
eb43a6a444 | ||
|
|
fcb7b69cb5 | ||
|
|
32c2a2db62 | ||
|
|
a285e5876b | ||
|
|
bc936f77c7 | ||
|
|
ee602cd33e | ||
|
|
b5941d2f6b | ||
|
|
5f20a94895 | ||
|
|
fd97c2f00f | ||
|
|
3d2c3101c3 | ||
|
|
278eba5767 | ||
|
|
b2130136fb | ||
|
|
7e76039434 | ||
|
|
375f2bd1cd | ||
|
|
f6f4f817f9 | ||
|
|
c2426ce98a | ||
|
|
e62a2d74ca | ||
|
|
61b2601488 | ||
|
|
87dc7929fd | ||
|
|
78d6b88e33 | ||
|
|
7f494e6293 | ||
|
|
41fa1db031 | ||
|
|
887e2182dc | ||
|
|
1c391417d4 | ||
|
|
3f2ded08a5 | ||
|
|
d60040636e | ||
|
|
d84f88954c | ||
|
|
d11d619c20 | ||
|
|
f0943e74a7 | ||
|
|
d1055330c2 | ||
|
|
efcb90bf10 | ||
|
|
1457728e54 | ||
|
|
bd63670765 | ||
|
|
b78b480587 | ||
|
|
62ec71a489 | ||
|
|
cfb51e3be9 |
@@ -0,0 +1,87 @@
|
||||
name: builds.hex.pm
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- v*.*
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
ELIXIR_OPTS: "--warnings-as-errors"
|
||||
ERLC_OPTS: "warnings_as_errors"
|
||||
LANG: C.UTF-8
|
||||
|
||||
concurrency: builds_txt
|
||||
|
||||
jobs:
|
||||
release_pre_built:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
include:
|
||||
- otp: 25
|
||||
otp_version: '25.3'
|
||||
build_docs: build_docs
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 50
|
||||
- name: Get tags
|
||||
run: git fetch --tags origin
|
||||
- uses: ./.github/workflows/release_pre_built
|
||||
with:
|
||||
otp_version: ${{ matrix.otp_version }}
|
||||
otp: ${{ matrix.otp }}
|
||||
build_docs: ${{ matrix.build_docs }}
|
||||
- name: Utils.sh
|
||||
run: |
|
||||
cat << 'EOF' > utils.sh
|
||||
function purge_key() {
|
||||
curl \
|
||||
-X POST \
|
||||
-H "Fastly-Key: ${FASTLY_KEY}" \
|
||||
-H "Accept: application/json" \
|
||||
-H "Content-Length: 0" \
|
||||
"https://api.fastly.com/service/$1/purge/$2"
|
||||
}
|
||||
function purge() {
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
sleep 2
|
||||
purge_key ${FASTLY_REPO_SERVICE_ID} $1
|
||||
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
|
||||
}
|
||||
EOF
|
||||
chmod +x utils.sh
|
||||
- name: Upload Docs to S3
|
||||
if: ${{ matrix.build_docs }}
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
run: |
|
||||
source utils.sh
|
||||
version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
|
||||
for f in doc/*; do
|
||||
if [ -d "$f" ]; then
|
||||
app=`echo $f | sed s/"doc\/"//`
|
||||
tarball="${app}-${version}.tar.gz"
|
||||
surrogate_key="docs/${app}-${version}"
|
||||
tar -czf "${tarball}" -C "doc/${app}" .
|
||||
aws s3 cp "${tarball}" "s3://${{ env.AWS_S3_BUCKET }}/docs/${tarball}" \
|
||||
--cache-control "public,max-age=3600" \
|
||||
--metadata "{\"surrogate-key\":\"${surrogate_key}\",\"surrogate-control\":\"public,max-age=604800\"}"
|
||||
purge "${surrogate_key}"
|
||||
fi
|
||||
done
|
||||
@@ -13,7 +13,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
test_linux:
|
||||
name: Linux, ${{ matrix.otp_release }}, Ubuntu 18.04
|
||||
name: Linux, ${{ matrix.otp_release }}, Ubuntu 20.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
development: true
|
||||
- otp_release: maint
|
||||
development: true
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Install Erlang/OTP
|
||||
run: |
|
||||
cd $RUNNER_TEMP
|
||||
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-18.04/${{ matrix.otp_release }}.tar.gz
|
||||
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-20.04/${{ matrix.otp_release }}.tar.gz
|
||||
mkdir -p otp
|
||||
tar zxf otp.tar.gz -C otp --strip-components=1
|
||||
otp/Install -minimal $(pwd)/otp
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
|
||||
check_posix_compliant:
|
||||
name: Check POSIX-compliant
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
name: Notify
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: 24.3
|
||||
elixir-version: 1.13.4
|
||||
otp-version: '25.0'
|
||||
elixir-version: '1.14.0'
|
||||
- name: Run Elixir script
|
||||
env:
|
||||
ELIXIR_FORUM_TOKEN: ${{ secrets.ELIXIR_FORUM_TOKEN }}
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
create_draft_release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- otp: 25
|
||||
otp_version: 25.0
|
||||
build_docs: build_docs
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: "Release pre built"
|
||||
description: "Builds elixir release, ExDoc and generates docs"
|
||||
inputs:
|
||||
otp:
|
||||
description: "The major OTP version"
|
||||
otp_version:
|
||||
description: "The exact OTP version (major.minor[.patch])"
|
||||
build_docs:
|
||||
description: "If docs have to be built or not"
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: ${{ inputs.otp_version }}
|
||||
version-type: strict
|
||||
- name: Build Elixir Release
|
||||
shell: bash
|
||||
run: |
|
||||
make Precompiled.zip
|
||||
mv Precompiled.zip elixir-otp-${{ inputs.otp }}.zip
|
||||
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
|
||||
echo "$PWD/bin" >> $GITHUB_PATH
|
||||
- name: Get latest stable ExDoc version
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
EX_DOC_LATEST_STABLE_VERSION=$(curl -s https://hex.pm/api/packages/ex_doc | jq --raw-output '.latest_stable_version')
|
||||
echo "EX_DOC_LATEST_STABLE_VERSION=${EX_DOC_LATEST_STABLE_VERSION}" >> $GITHUB_ENV
|
||||
- uses: actions/checkout@v3
|
||||
if: ${{ inputs.build_docs }}
|
||||
with:
|
||||
repository: elixir-lang/ex_doc
|
||||
ref: v${{ env.EX_DOC_LATEST_STABLE_VERSION }}
|
||||
path: ex_doc
|
||||
- name: Build ex_doc
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
mv ex_doc ../ex_doc
|
||||
cd ../ex_doc
|
||||
../elixir/bin/mix do local.rebar --force + local.hex --force + deps.get + compile
|
||||
cd ../elixir
|
||||
- name: Build Docs
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
run: |
|
||||
make Docs.zip
|
||||
shasum -a 1 Docs.zip > Docs.zip.sha1sum
|
||||
shasum -a 256 Docs.zip > Docs.zip.sha256sum
|
||||
+164
-3
@@ -241,7 +241,168 @@ protocol, giving developers more control over the struct representation.
|
||||
See the updated documentation for `Inspect` for a general rundown on
|
||||
the approaches and options available.
|
||||
|
||||
## v1.14.0 (2022-08-31)
|
||||
## v1.14.5 (2023-05-22)
|
||||
|
||||
This release contains fixes for Erlang/OTP 26.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Fix a bug where stdout would block when there was no attached terminal on Windows when running on Erlang/OTP 26
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Properly set SSL configuration for Mix downloads when running on Erlang/OTP 26
|
||||
|
||||
## v1.14.4 (2023-04-03)
|
||||
|
||||
This release adds basic support for Erlang/OTP 26. When migrating
|
||||
to Erlang/OTP 26, keep it mind it changes how maps are stored
|
||||
internally and they will be printed and traversed in a different
|
||||
order (note maps never provided a guarantee of their order).
|
||||
To aid migration, this release adds `:sort_maps` to `inspect`
|
||||
custom options, in case you want to sort them before inspection:
|
||||
|
||||
inspect(map, custom_options: [sort_maps: true])
|
||||
|
||||
### Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Inspect] Add `:sort_maps` to `Inspect.Opts.custom_options`
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Support shell history in Erlang/OTP 26+
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.elixir] Optimize application tracer
|
||||
|
||||
### Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Properly handle blocks with comments in all cases in `Code.quoted_to_string_with_comments/2`
|
||||
* [Kernel] Fix `debug_info/4` when returning core_v1
|
||||
* [Kernel] Store complete path on `quote keep: true` to avoid invalid stacktraces
|
||||
* [Kernel] Fix column count when tokenizing escaped interpolations
|
||||
* [Stream] Fix `Stream.zip/1` hanging on empty list
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix format] Don't call formatter on directories
|
||||
|
||||
## v1.14.3 (2023-01-14)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Speed up loading of runtime modules in the parallel compiler
|
||||
* [Range] Optimize range membership implementation
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Return values from running doctests and make their order consistent
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Calendar] Fix handling of negative years in `Calendar.strftime/2`
|
||||
* [DateTime] Consistently merge precision of subsecond operations (the bug was that subsecond precision was lost in functions like `DateTime.add/3`)
|
||||
* [Exception] Improve blaming of FunctionClauseError with `is_struct/2` guards
|
||||
* [Kernel] Fix invalid variable scoping in `defguard` expansion
|
||||
* [Kernel] Do not warn on captured underscored vars from `defmodule`
|
||||
* [Kernel] Do not crash for missing line info on type warnings
|
||||
* [NaiveDateTime] Consistently merge precision of subsecond operations (the bug was that subsecond precision was lost in functions like `NaiveDateTime.add/3`)
|
||||
* [Macro] Fix `Macro.to_string/1` for large negative integers
|
||||
* [Macro] Properly type and escape expansion of `__ENV__` in macros
|
||||
* [Path] Make sure `Path.wildcard/2` expands `..` symlinks accordingly
|
||||
* [Range] Address corner cases in `Range.disjoint?/2` implementation
|
||||
* [Time] Consistently merge precision of subsecond operations (the bug was that subsecond precision was lost in functions like `Time.add/3`)
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.DocTest] Remove unnecessary literal quotes from error message on reports
|
||||
|
||||
## v1.14.2 (2022-11-11)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Add `Code.eval_quoted_with_env/4` with support for the `:prune_binding` option
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Case] Allow test cases to not be registered on use
|
||||
* [ExUnit.DocTest] Include `:doctest` and `:doctest_line` as meta tags
|
||||
* [ExUnit.Formatter] Expose `ExUnit.Formatter.format_assertion_diff/4`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] `Mix.install/2` accepts atoms as paths
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code.Formatter] Fix `size*unit` shortcut in bitstring
|
||||
* [Kernel] Generate unique variables for macro expansion of `defguard`
|
||||
* [Protocol] Expand `:for` in protocols with the appropriate env
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Do not run duplicate cases on `ExUnit.run/1`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix test] Ensure proper error message when there is no test directory
|
||||
|
||||
## v1.14.1 (2022-10-10)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Perform partial expansion of literals in module attributes
|
||||
* [Kernel] Do not add compile-time dependencies for literals as defaults in `Application.compile_env/3` inside module attributes
|
||||
* [Macro] Add `Macro.expand_literals/2` and `Macro.expand_literals/3`
|
||||
* [System] Add `:close_stdin` to `System.shell/2`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix test] Accept `--all-warnings` option
|
||||
|
||||
## 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Fix misleading warning when `:uniq` is given in `for` comprehensions and the result is unused
|
||||
* [Kernel] Improve error message for when there is a conflicting struct and ignoring module conflict
|
||||
* [Kernel] Do not delete `@enforce_keys` attribute after `defstruct` declaration
|
||||
* [Kernel] Do not crash the checker on modules with missing `:debug_info` chunk
|
||||
* [Macro] Fix error in `Macro.to_string/2` when converting an AST with `:erlang.binary_to_atom/2`
|
||||
* [String] Fix `String.split/3` and `String.next_grapheme/1` returning invalid results on invalid UTF-8 encoding
|
||||
* [System] Do not close stdin by default in `System.shell/2`
|
||||
* [URI] Do not return `uri.port` as `:undefined` in certain cases in `URI.new/1`
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.DocTest] Do not crash when both `:moduledoc` and functions are specified in `:only`
|
||||
|
||||
#### IEx
|
||||
|
||||
* [CLI] Fix invalid argument handling when `--no-pry` is given
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix format] Do not cache inputs from `.formatter.exs` so they are properly re-evaluted on every call
|
||||
|
||||
## v1.14.0 (2022-09-01)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -287,7 +448,7 @@ the approaches and options available.
|
||||
* [Keyword] Add `Keyword.from_keys/2` and `Keyword.replace_lazy/3`
|
||||
* [List] Add `List.keysort/3` with support for a `sorter` function
|
||||
* [Macro] Add `Macro.classify_atom/1` and `Macro.inspect_atom/2`
|
||||
* [Macro] Add `Macro.expand_literal/2` and `Macro.path/2`
|
||||
* [Macro] Add `Macro.path/2`
|
||||
* [Macro.Env] Add `Macro.Env.prune_compile_info/1`
|
||||
* [Map] Add `Map.from_keys/2` and `Map.replace_lazy/3`
|
||||
* [MapSet] Add `MapSet.filter/2`, `MapSet.reject/2`, and `MapSet.symmetric_difference/2`
|
||||
@@ -361,7 +522,7 @@ the approaches and options available.
|
||||
* [Module] Fix loop while unifying type variables
|
||||
* [Protocol] Warn if a protocol has no definitions
|
||||
* [Regex] Show list options when inspecting a Regex manually defined with `Regex.compile/2`
|
||||
* [String] Allow slices to overflow on both starting and ending positions
|
||||
* [String] Allow slices to overflow on both starting and ending positions
|
||||
* [System] Raise non-generic exception on missing env in `System.fetch_env!/1` to mirror map operations
|
||||
|
||||
#### ExUnit
|
||||
|
||||
@@ -181,39 +181,46 @@ clean_residual_files:
|
||||
|
||||
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
|
||||
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}")
|
||||
COMPILE_DOCS = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
|
||||
DOCS_COMPILE = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
|
||||
DOCS_CONFIG = bin/elixir lib/elixir/scripts/docs_config.exs "$(1)"
|
||||
|
||||
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
|
||||
|
||||
docs_elixir: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (elixir)"
|
||||
$(Q) rm -rf doc/elixir
|
||||
$(call COMPILE_DOCS,Elixir,elixir,Kernel,--config "lib/elixir/docs.exs")
|
||||
$(call DOCS_COMPILE,Elixir,elixir,Kernel,--config "lib/elixir/scripts/elixir_docs.exs")
|
||||
$(call DOCS_CONFIG,elixir)
|
||||
|
||||
docs_eex: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (eex)"
|
||||
$(Q) rm -rf doc/eex
|
||||
$(call COMPILE_DOCS,EEx,eex,EEx,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,EEx,eex,EEx,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,eex)
|
||||
|
||||
docs_mix: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (mix)"
|
||||
$(Q) rm -rf doc/mix
|
||||
$(call COMPILE_DOCS,Mix,mix,Mix,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,Mix,mix,Mix,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,mix)
|
||||
|
||||
docs_iex: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (iex)"
|
||||
$(Q) rm -rf doc/iex
|
||||
$(call COMPILE_DOCS,IEx,iex,IEx,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,IEx,iex,IEx,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,iex)
|
||||
|
||||
docs_ex_unit: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (ex_unit)"
|
||||
$(Q) rm -rf doc/ex_unit
|
||||
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,ExUnit,ex_unit,ExUnit,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,ex_unit)
|
||||
|
||||
docs_logger: compile ../ex_doc/bin/ex_doc
|
||||
@ echo "==> ex_doc (logger)"
|
||||
$(Q) rm -rf doc/logger
|
||||
$(call COMPILE_DOCS,Logger,logger,Logger,--config "lib/mix/docs.exs")
|
||||
$(call DOCS_COMPILE,Logger,logger,Logger,--config "lib/elixir/scripts/mix_docs.exs")
|
||||
$(call DOCS_CONFIG,logger)
|
||||
|
||||
../ex_doc/bin/ex_doc:
|
||||
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
|
||||
@@ -228,7 +235,7 @@ Docs.zip: docs
|
||||
|
||||
Precompiled.zip: build_man compile
|
||||
rm -f Precompiled.zip
|
||||
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
|
||||
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE Makefile man NOTICE README.md VERSION
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled.zip"
|
||||
|
||||
#==> Test tasks
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.14.0
|
||||
ELIXIR_VERSION=1.14.5
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
@@ -112,10 +112,10 @@ while [ $I -le $LENGTH ]; do
|
||||
C=1
|
||||
MODE="elixirc"
|
||||
;;
|
||||
-v|--no-halt)
|
||||
-v|--no-halt|--no-pry)
|
||||
C=1
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--app|--eval|--remsh|--dot-iex|--no-pry)
|
||||
-e|-r|-pr|-pa|-pz|--app|--eval|--remsh|--dot-iex)
|
||||
C=2
|
||||
;;
|
||||
--rpc-eval)
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
|
||||
set ELIXIR_VERSION=1.14.0
|
||||
set ELIXIR_VERSION=1.14.5
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
|
||||
@@ -532,7 +532,7 @@ defmodule Application do
|
||||
raise "Application.compile_env/3 cannot be called inside functions, only in the module body"
|
||||
end
|
||||
|
||||
key_or_path = Macro.expand_literal(key_or_path, %{__CALLER__ | function: {:__info__, 1}})
|
||||
key_or_path = Macro.expand_literals(key_or_path, %{__CALLER__ | function: {:__info__, 1}})
|
||||
|
||||
quote do
|
||||
Application.compile_env(__ENV__, unquote(app), unquote(key_or_path), unquote(default))
|
||||
@@ -572,7 +572,7 @@ defmodule Application do
|
||||
raise "Application.compile_env!/2 cannot be called inside functions, only in the module body"
|
||||
end
|
||||
|
||||
key_or_path = Macro.expand_literal(key_or_path, %{__CALLER__ | function: {:__info__, 1}})
|
||||
key_or_path = Macro.expand_literals(key_or_path, %{__CALLER__ | function: {:__info__, 1}})
|
||||
|
||||
quote do
|
||||
Application.compile_env!(__ENV__, unquote(app), unquote(key_or_path))
|
||||
|
||||
@@ -791,7 +791,14 @@ defmodule Calendar do
|
||||
|
||||
# Year
|
||||
defp format_modifiers("Y" <> rest, width, pad, datetime, format_options, acc) do
|
||||
result = datetime.year |> Integer.to_string() |> pad_leading(width, pad)
|
||||
{sign, year} =
|
||||
if datetime.year < 0 do
|
||||
{?-, -datetime.year}
|
||||
else
|
||||
{[], datetime.year}
|
||||
end
|
||||
|
||||
result = [sign | year |> Integer.to_string() |> pad_leading(width, pad)]
|
||||
parse(rest, datetime, format_options, [result | acc])
|
||||
end
|
||||
|
||||
|
||||
@@ -1493,6 +1493,13 @@ defmodule DateTime do
|
||||
iex> dt |> DateTime.add(1, :day, FakeTimeZoneDatabase)
|
||||
#DateTime<2019-04-01 02:00:00+02:00 CEST Europe/Copenhagen>
|
||||
|
||||
This operation merges the precision of the naive date time with the given unit:
|
||||
|
||||
iex> result = DateTime.add(~U[2014-10-02 00:29:10Z], 21, :millisecond)
|
||||
~U[2014-10-02 00:29:10.021Z]
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
"""
|
||||
@doc since: "1.8.0"
|
||||
@spec add(
|
||||
@@ -1529,8 +1536,15 @@ defmodule DateTime do
|
||||
microsecond: {_, precision}
|
||||
} = datetime
|
||||
|
||||
if not is_integer(unit) and
|
||||
unit not in ~w(second millisecond microsecond nanosecond)a do
|
||||
raise ArgumentError,
|
||||
"unsupported time unit. Expected :day, :hour, :minute, :second, :millisecond, :microsecond, :nanosecond, or a positive integer, got #{inspect(unit)}"
|
||||
end
|
||||
|
||||
ppd = System.convert_time_unit(86400, :second, unit)
|
||||
total_offset = System.convert_time_unit(utc_offset + std_offset, :second, unit)
|
||||
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
|
||||
|
||||
result =
|
||||
datetime
|
||||
|
||||
@@ -247,6 +247,17 @@ defmodule Calendar.ISO do
|
||||
defguardp is_utc_offset(offset) when is_integer(offset)
|
||||
defguardp is_std_offset(offset) when is_integer(offset)
|
||||
|
||||
@doc """
|
||||
Converts a `t:System.time_unit/0` to precision.
|
||||
|
||||
Integer-based time units always get maximum precision.
|
||||
"""
|
||||
def time_unit_to_precision(:nanosecond), do: 6
|
||||
def time_unit_to_precision(:microsecond), do: 6
|
||||
def time_unit_to_precision(:millisecond), do: 3
|
||||
def time_unit_to_precision(:second), do: 0
|
||||
def time_unit_to_precision(_), do: 6
|
||||
|
||||
@doc """
|
||||
Parses a time `string` in the `:extended` format.
|
||||
|
||||
|
||||
@@ -361,8 +361,7 @@ defmodule NaiveDateTime do
|
||||
`t:System.time_unit/0`. It defaults to `:second`. Negative values
|
||||
will move backwards in time.
|
||||
|
||||
This function always consider the unit to be computed according
|
||||
to the `Calendar.ISO`.
|
||||
This function always considers the unit to be computed according to the `Calendar.ISO`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -379,7 +378,7 @@ defmodule NaiveDateTime do
|
||||
It can also work with subsecond precisions:
|
||||
|
||||
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10], 2_000, :millisecond)
|
||||
~N[2014-10-02 00:29:12]
|
||||
~N[2014-10-02 00:29:12.000]
|
||||
|
||||
As well as days/hours/minutes:
|
||||
|
||||
@@ -390,16 +389,12 @@ defmodule NaiveDateTime do
|
||||
iex> NaiveDateTime.add(~N[2015-02-28 00:29:10], 60, :minute)
|
||||
~N[2015-02-28 01:29:10]
|
||||
|
||||
This operation keeps the precision of the naive date time:
|
||||
This operation merges the precision of the naive date time with the given unit:
|
||||
|
||||
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10.021], 21, :second)
|
||||
~N[2014-10-02 00:29:31.021]
|
||||
|
||||
And ignores any changes below the precision:
|
||||
|
||||
iex> hidden = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
|
||||
iex> hidden.microsecond # ~N[2014-10-02 00:29:10]
|
||||
{21000, 0}
|
||||
iex> result = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
|
||||
~N[2014-10-02 00:29:10.021]
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
Operations on top of gregorian seconds or the Unix epoch are optimized:
|
||||
|
||||
@@ -440,6 +435,7 @@ defmodule NaiveDateTime do
|
||||
)
|
||||
when is_integer(amount_to_add) do
|
||||
ppd = System.convert_time_unit(86400, :second, unit)
|
||||
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
|
||||
|
||||
naive_datetime
|
||||
|> to_iso_days()
|
||||
|
||||
@@ -493,6 +493,13 @@ defmodule Time do
|
||||
iex> Time.add(~T[17:10:05], 30, :minute)
|
||||
~T[17:40:05]
|
||||
|
||||
This operation merges the precision of the time with the given unit:
|
||||
|
||||
iex> result = Time.add(~T[00:29:10], 21, :millisecond)
|
||||
~T[00:29:10.021]
|
||||
iex> result.microsecond
|
||||
{21000, 3}
|
||||
|
||||
"""
|
||||
@doc since: "1.6.0"
|
||||
@spec add(Calendar.time(), integer, :hour | :minute | System.time_unit()) :: t
|
||||
@@ -511,6 +518,7 @@ defmodule Time do
|
||||
amount_to_add = System.convert_time_unit(amount_to_add, unit, :microsecond)
|
||||
total = time_to_microseconds(time) + amount_to_add
|
||||
parts = Integer.mod(total, @parts_per_day)
|
||||
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
|
||||
|
||||
{hour, minute, second, {microsecond, _}} =
|
||||
calendar.time_from_day_fraction({parts, @parts_per_day})
|
||||
@@ -736,14 +744,14 @@ defmodule Time do
|
||||
hour: hour1,
|
||||
minute: minute1,
|
||||
second: second1,
|
||||
microsecond: {microsecond1, @parts_per_day}
|
||||
microsecond: {microsecond1, _}
|
||||
},
|
||||
%{
|
||||
calendar: Calendar.ISO,
|
||||
hour: hour2,
|
||||
minute: minute2,
|
||||
second: second2,
|
||||
microsecond: {microsecond2, @parts_per_day}
|
||||
microsecond: {microsecond2, _}
|
||||
},
|
||||
unit
|
||||
) do
|
||||
|
||||
+43
-24
@@ -100,10 +100,15 @@ defmodule Code do
|
||||
following events are available to tracers:
|
||||
|
||||
* `:start` - (since v1.11.0) invoked whenever the compiler starts to trace
|
||||
a new lexical context, such as a new file. Keep in mind the compiler runs
|
||||
in parallel, so multiple files may invoke `:start` and run at the same
|
||||
time. The value of the `lexical_tracker` of the macro environment, albeit
|
||||
opaque, can be used to uniquely identify the environment.
|
||||
a new lexical context. A lexical context is started when compiling a new
|
||||
file or when defining a module within a function. Note evaluated code
|
||||
does not start a new lexical context (because they don't track unused
|
||||
aliases, imports, etc) but defining a module inside evaluated code will.
|
||||
|
||||
Note this event may be emitted in parallel, where multiple files/modules
|
||||
invoke `:start` and run at the same time. The value of the `lexical_tracker`
|
||||
of the macro environment, albeit opaque, can be used to uniquely identify
|
||||
the environment.
|
||||
|
||||
* `:stop` - (since v1.11.0) invoked whenever the compiler stops tracing a
|
||||
new lexical context, such as a new file.
|
||||
@@ -181,7 +186,7 @@ defmodule Code do
|
||||
"""
|
||||
|
||||
@typedoc """
|
||||
A list with all variable bindings.
|
||||
A list with all variables and their values.
|
||||
|
||||
The binding keys are usually atoms, but they may be a tuple for variables
|
||||
defined in a different context.
|
||||
@@ -344,7 +349,7 @@ defmodule Code do
|
||||
@doc """
|
||||
Evaluates the contents given by `string`.
|
||||
|
||||
The `binding` argument is a list of variable bindings.
|
||||
The `binding` argument is a list of all variables and their values.
|
||||
The `opts` argument is a keyword list of environment options.
|
||||
|
||||
**Warning**: `string` can be any Elixir code and will be executed with
|
||||
@@ -362,17 +367,15 @@ defmodule Code do
|
||||
* `:line` - the line on which the script starts
|
||||
|
||||
Additionally, you may also pass an environment as second argument,
|
||||
so the evaluation happens within that environment. However, if the evaluated
|
||||
code requires or compiles another file, the environment given to this function
|
||||
will not apply to said files.
|
||||
so the evaluation happens within that environment.
|
||||
|
||||
Returns a tuple of the form `{value, binding}`, where `value` is the value
|
||||
returned from evaluating `string`. If an error occurs while evaluating
|
||||
`string` an exception will be raised.
|
||||
`string`, an exception will be raised.
|
||||
|
||||
`binding` is a list with all variable bindings after evaluating `string`.
|
||||
The binding keys are usually atoms, but they may be a tuple for variables
|
||||
defined in a different context.
|
||||
`binding` is a list with all variable names and their values after evaluating
|
||||
`string`. The binding keys are usually atoms, but they may be a tuple for variables
|
||||
defined in a different context. The names are in no particular order.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -419,13 +422,13 @@ defmodule Code do
|
||||
defp validated_eval_string(string, binding, opts_or_env) do
|
||||
%{line: line, file: file} = env = env_for_eval(opts_or_env)
|
||||
forms = :elixir.string_to_quoted!(to_charlist(string), line, 1, file, [])
|
||||
{value, binding, _env} = eval_verify(:eval_forms, forms, binding, env)
|
||||
{value, binding, _env} = eval_verify(:eval_forms, [forms, binding, env])
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
defp eval_verify(fun, forms, binding, env) do
|
||||
defp eval_verify(fun, args) do
|
||||
Module.ParallelChecker.verify(fn ->
|
||||
apply(:elixir, fun, [forms, binding, env])
|
||||
apply(:elixir, fun, args)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -822,7 +825,9 @@ defmodule Code do
|
||||
"""
|
||||
@spec eval_quoted(Macro.t(), binding, Macro.Env.t() | keyword) :: {term, binding}
|
||||
def eval_quoted(quoted, binding \\ [], env_or_opts \\ []) do
|
||||
{value, binding, _env} = eval_verify(:eval_quoted, quoted, binding, env_for_eval(env_or_opts))
|
||||
{value, binding, _env} =
|
||||
eval_verify(:eval_quoted, [quoted, binding, env_for_eval(env_or_opts)])
|
||||
|
||||
{value, binding}
|
||||
end
|
||||
|
||||
@@ -859,10 +864,20 @@ defmodule Code do
|
||||
Therefore, the first time you call this function, you must compute
|
||||
the initial environment with `env_for_eval/1`. The remaining calls
|
||||
must pass the environment that was returned by this function.
|
||||
|
||||
## Options
|
||||
|
||||
* `:prune_binding` - (since v1.14.2) prune binding to keep only
|
||||
variables read or written by the evaluated code. Note that
|
||||
variables used by modules are always pruned, even if later used
|
||||
by the modules. You can submit to the `:on_module` tracer event
|
||||
and access the variables used by the module from its environment.
|
||||
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
def eval_quoted_with_env(quoted, binding, %Macro.Env{} = env) when is_list(binding) do
|
||||
eval_verify(:eval_forms, quoted, binding, env)
|
||||
def eval_quoted_with_env(quoted, binding, %Macro.Env{} = env, opts \\ [])
|
||||
when is_list(binding) do
|
||||
eval_verify(:eval_quoted, [quoted, binding, env, opts])
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -1334,12 +1349,16 @@ defmodule Code do
|
||||
|
||||
Available options are:
|
||||
|
||||
* `:docs` - when `true`, retain documentation in the compiled module.
|
||||
* `:docs` - when `true`, retains documentation in the compiled module.
|
||||
Defaults to `true`.
|
||||
|
||||
* `:debug_info` - when `true`, retain debug information in the compiled
|
||||
module. This allows a developer to reconstruct the original source
|
||||
code. Defaults to `true`.
|
||||
* `:debug_info` - when `true`, retains debug information in the compiled
|
||||
module. This enables static analysis tools as it allows developers to
|
||||
partially reconstruct the original source code. Therefore, disabling
|
||||
`:debug_info` is not recommended as it removes the ability of the
|
||||
Elixir compiler and other tools to provide feedback. If you want to
|
||||
remove the `:debug_info` while deploying, tools like `mix release`
|
||||
already do such by default.
|
||||
|
||||
* `:ignore_already_consolidated` - when `true`, does not warn when a protocol
|
||||
has already been consolidated and a new implementation is added. Defaults
|
||||
@@ -1348,7 +1367,7 @@ defmodule Code do
|
||||
* `:ignore_module_conflict` - when `true`, does not warn when a module has
|
||||
already been defined. Defaults to `false`.
|
||||
|
||||
* `:relative_paths` - when `true`, use relative paths in quoted nodes,
|
||||
* `:relative_paths` - when `true`, uses relative paths in quoted nodes,
|
||||
warnings, and errors generated by the compiler. Note disabling this option
|
||||
won't affect runtime warnings and errors. Defaults to `true`.
|
||||
|
||||
|
||||
@@ -1411,7 +1411,7 @@ defmodule Code.Formatter do
|
||||
|
||||
defp bitstring_segment_to_algebra({{:"::", _, [segment, spec]}, i}, state, last) do
|
||||
{doc, state} = quoted_to_algebra(segment, :parens_arg, state)
|
||||
{spec, state} = bitstring_spec_to_algebra(spec, state)
|
||||
{spec, state} = bitstring_spec_to_algebra(spec, state, state.normalize_bitstring_modifiers)
|
||||
|
||||
spec = wrap_in_parens_if_inspected_atom(spec)
|
||||
spec = if i == last, do: bitstring_wrap_parens(spec, i, last), else: spec
|
||||
@@ -1430,26 +1430,29 @@ defmodule Code.Formatter do
|
||||
{bitstring_wrap_parens(doc, i, last), state}
|
||||
end
|
||||
|
||||
defp bitstring_spec_to_algebra({op, _, [left, right]}, state) when op in [:-, :*] do
|
||||
{left, state} = bitstring_spec_to_algebra(left, state)
|
||||
{right, state} = bitstring_spec_element_to_algebra(right, state)
|
||||
defp bitstring_spec_to_algebra({op, _, [left, right]}, state, normalize_modifiers)
|
||||
when op in [:-, :*] do
|
||||
normalize_modifiers = normalize_modifiers && op != :*
|
||||
{left, state} = bitstring_spec_to_algebra(left, state, normalize_modifiers)
|
||||
{right, state} = bitstring_spec_element_to_algebra(right, state, normalize_modifiers)
|
||||
{concat(concat(left, Atom.to_string(op)), right), state}
|
||||
end
|
||||
|
||||
defp bitstring_spec_to_algebra(spec, state) do
|
||||
bitstring_spec_element_to_algebra(spec, state)
|
||||
defp bitstring_spec_to_algebra(spec, state, normalize_modifiers) do
|
||||
bitstring_spec_element_to_algebra(spec, state, normalize_modifiers)
|
||||
end
|
||||
|
||||
defp bitstring_spec_element_to_algebra(
|
||||
{atom, meta, empty_args},
|
||||
state = %{normalize_bitstring_modifiers: true}
|
||||
state,
|
||||
_normalize_modifiers = true
|
||||
)
|
||||
when is_atom(atom) and empty_args in [nil, []] do
|
||||
empty_args = bitstring_spec_normalize_empty_args(atom)
|
||||
quoted_to_algebra_with_parens_if_operator({atom, meta, empty_args}, :parens_arg, state)
|
||||
end
|
||||
|
||||
defp bitstring_spec_element_to_algebra(spec_element, state) do
|
||||
defp bitstring_spec_element_to_algebra(spec_element, state, _normalize_modifiers) do
|
||||
quoted_to_algebra_with_parens_if_operator(spec_element, :parens_arg, state)
|
||||
end
|
||||
|
||||
@@ -1580,6 +1583,10 @@ defmodule Code.Formatter do
|
||||
insert_underscores(int_part) <> "." <> decimal_part
|
||||
end
|
||||
|
||||
defp insert_underscores("-" <> digits) do
|
||||
"-" <> insert_underscores(digits)
|
||||
end
|
||||
|
||||
defp insert_underscores(digits) do
|
||||
cond do
|
||||
digits =~ "_" ->
|
||||
|
||||
@@ -70,13 +70,21 @@ defmodule Code.Normalizer do
|
||||
# Atoms with interpolations
|
||||
defp do_normalize(
|
||||
{{:., dot_meta, [:erlang, :binary_to_atom]}, call_meta,
|
||||
[{:<<>>, _, args} = string, :utf8]},
|
||||
[{:<<>>, _, parts} = string, :utf8]},
|
||||
state
|
||||
)
|
||||
when is_list(args) do
|
||||
when is_list(parts) do
|
||||
dot_meta = patch_meta_line(dot_meta, state.parent_meta)
|
||||
call_meta = patch_meta_line(call_meta, dot_meta)
|
||||
|
||||
utf8 =
|
||||
if parts == [] or binary_interpolated?(parts) do
|
||||
# a non-normalized :utf8 atom signals an atom interpolation
|
||||
:utf8
|
||||
else
|
||||
normalize_literal(:utf8, [], state)
|
||||
end
|
||||
|
||||
string =
|
||||
if state.escape do
|
||||
normalize_bitstring(string, state, true)
|
||||
@@ -84,27 +92,31 @@ defmodule Code.Normalizer do
|
||||
normalize_bitstring(string, state)
|
||||
end
|
||||
|
||||
{{:., dot_meta, [:erlang, :binary_to_atom]}, call_meta, [string, :utf8]}
|
||||
{{:., dot_meta, [:erlang, :binary_to_atom]}, call_meta, [string, utf8]}
|
||||
end
|
||||
|
||||
# Charlists with interpolations
|
||||
defp do_normalize({{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}, state) do
|
||||
parts =
|
||||
Enum.map(parts, fn
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args} ->
|
||||
args = normalize_args(args, state)
|
||||
defp do_normalize({{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]} = quoted, state) do
|
||||
if list_interpolated?(parts) do
|
||||
parts =
|
||||
Enum.map(parts, fn
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args} ->
|
||||
args = normalize_args(args, state)
|
||||
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args}
|
||||
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args}
|
||||
|
||||
part ->
|
||||
if state.escape do
|
||||
maybe_escape_literal(part, state)
|
||||
else
|
||||
part
|
||||
end
|
||||
end)
|
||||
part when is_binary(part) ->
|
||||
if state.escape do
|
||||
maybe_escape_literal(part, state)
|
||||
else
|
||||
part
|
||||
end
|
||||
end)
|
||||
|
||||
{{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}
|
||||
{{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}
|
||||
else
|
||||
normalize_call(quoted, state)
|
||||
end
|
||||
end
|
||||
|
||||
# Don't normalize the `Access` atom in access syntax
|
||||
@@ -350,7 +362,7 @@ defmodule Code.Normalizer do
|
||||
normalize_kw_blocks(form, meta, args, state)
|
||||
|
||||
allow_keyword?(form, arity) ->
|
||||
args = normalize_args(args, %{state | parent_meta: state.parent_meta})
|
||||
args = normalize_args(args, %{state | parent_meta: meta})
|
||||
{last_arg, leading_args} = List.pop_at(args, -1, [])
|
||||
|
||||
last_args =
|
||||
@@ -372,7 +384,7 @@ defmodule Code.Normalizer do
|
||||
{form, meta, leading_args ++ last_args}
|
||||
|
||||
true ->
|
||||
args = normalize_args(args, %{state | parent_meta: state.parent_meta})
|
||||
args = normalize_args(args, %{state | parent_meta: meta})
|
||||
{form, meta, args}
|
||||
end
|
||||
end
|
||||
@@ -381,11 +393,11 @@ defmodule Code.Normalizer do
|
||||
defp allow_keyword?(:{}, _), do: false
|
||||
defp allow_keyword?(op, arity), do: not is_atom(op) or not Macro.operator?(op, arity)
|
||||
|
||||
defp normalize_bitstring({:<<>>, meta, parts} = quoted, state, escape_interpolation \\ false) do
|
||||
defp normalize_bitstring({:<<>>, meta, parts}, state, escape_interpolation \\ false) do
|
||||
meta = patch_meta_line(meta, state.parent_meta)
|
||||
|
||||
parts =
|
||||
if interpolated?(quoted) do
|
||||
if binary_interpolated?(parts) do
|
||||
normalize_interpolation_parts(parts, %{state | parent_meta: meta}, escape_interpolation)
|
||||
else
|
||||
state = %{state | parent_meta: meta}
|
||||
@@ -535,8 +547,7 @@ defmodule Code.Normalizer do
|
||||
term
|
||||
end
|
||||
|
||||
# Check if we have an interpolated string.
|
||||
defp interpolated?({:<<>>, _, [_ | _] = parts}) do
|
||||
defp binary_interpolated?(parts) do
|
||||
Enum.all?(parts, fn
|
||||
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
|
||||
binary when is_binary(binary) -> true
|
||||
@@ -544,8 +555,12 @@ defmodule Code.Normalizer do
|
||||
end)
|
||||
end
|
||||
|
||||
defp interpolated?(_) do
|
||||
false
|
||||
defp list_interpolated?(parts) do
|
||||
Enum.all?(parts, fn
|
||||
{{:., _, [Kernel, :to_string]}, _, [_]} -> true
|
||||
binary when is_binary(binary) -> true
|
||||
_ -> false
|
||||
end)
|
||||
end
|
||||
|
||||
defp patch_meta_line(meta, parent_meta) do
|
||||
|
||||
@@ -2276,13 +2276,13 @@ defmodule Enum do
|
||||
iex> Enum.split_with([5, 4, 3, 2, 1, 0], fn x -> rem(x, 2) == 0 end)
|
||||
{[4, 2, 0], [5, 3, 1]}
|
||||
|
||||
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v < 0 end)
|
||||
iex> Enum.split_with([a: 1, b: -2, c: 1, d: -3], fn {_k, v} -> v < 0 end)
|
||||
{[b: -2, d: -3], [a: 1, c: 1]}
|
||||
|
||||
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v > 50 end)
|
||||
iex> Enum.split_with([a: 1, b: -2, c: 1, d: -3], fn {_k, v} -> v > 50 end)
|
||||
{[], [a: 1, b: -2, c: 1, d: -3]}
|
||||
|
||||
iex> Enum.split_with(%{}, fn {_k, v} -> v > 50 end)
|
||||
iex> Enum.split_with([], fn {_k, v} -> v > 50 end)
|
||||
{[], []}
|
||||
|
||||
"""
|
||||
|
||||
@@ -294,13 +294,11 @@ defmodule Exception do
|
||||
end
|
||||
end
|
||||
|
||||
defp undo_is_struct_guard(
|
||||
{:and, meta, [_, %{node: {_, _, [{_, _, [_, {struct, _, _}]} | optional]}}]}
|
||||
) do
|
||||
defp undo_is_struct_guard({:and, meta, [_, %{node: {_, _, [{_, _, [_, arg]} | optional]}}]}) do
|
||||
args =
|
||||
case optional do
|
||||
[] -> [{struct, meta, nil}]
|
||||
[module] -> [{struct, meta, nil}, module]
|
||||
[] -> [arg]
|
||||
[module] -> [arg, module]
|
||||
end
|
||||
|
||||
%{match?: meta[:value], node: {:is_struct, meta, args}}
|
||||
|
||||
@@ -302,7 +302,12 @@ end
|
||||
|
||||
defimpl Inspect, for: Map do
|
||||
def inspect(map, opts) do
|
||||
list = Map.to_list(map)
|
||||
list =
|
||||
if Keyword.get(opts.custom_options, :sort_maps) do
|
||||
map |> Map.to_list() |> :lists.sort()
|
||||
else
|
||||
Map.to_list(map)
|
||||
end
|
||||
|
||||
fun =
|
||||
if Inspect.List.keyword?(list) do
|
||||
|
||||
@@ -31,6 +31,11 @@ defmodule Inspect.Opts do
|
||||
options. Useful when implementing the `Inspect` protocol for nested structs
|
||||
to pass the custom options through.
|
||||
|
||||
It supports some pre-defined keys:
|
||||
- `:sort_maps` (since v1.15.0) - if set to `true`, sorts key-value pairs in maps.
|
||||
This can be helpful to make map inspection deterministic for testing,
|
||||
especially since key order is random since OTP 26.
|
||||
|
||||
* `:inspect_fun` (since v1.9.0) - a function to build algebra documents.
|
||||
Defaults to `Inspect.Opts.default_inspect_fun/0`.
|
||||
|
||||
|
||||
@@ -78,7 +78,9 @@ defmodule IO.ANSI.Docs do
|
||||
@metadata_filter [:deprecated, :guard, :since]
|
||||
|
||||
defp print_each_metadata(metadata, options) do
|
||||
Enum.reduce(metadata, false, fn
|
||||
metadata
|
||||
|> Enum.sort()
|
||||
|> Enum.reduce(false, fn
|
||||
{key, value}, _printed when is_binary(value) and key in @metadata_filter ->
|
||||
label = metadata_label(key, options)
|
||||
indent = String.duplicate(" ", length_without_escape(label, 0) + 1)
|
||||
|
||||
+40
-26
@@ -3590,33 +3590,31 @@ defmodule Kernel do
|
||||
defp collect_traces(:after_verify, arg, _env), do: {arg, []}
|
||||
defp collect_traces(:on_definition, arg, _env), do: {arg, []}
|
||||
|
||||
defp collect_traces(_name, arg, env) do
|
||||
case is_pid(env.lexical_tracker) and Macro.quoted_literal?(arg) do
|
||||
true ->
|
||||
env = %{env | function: {:__info__, 1}}
|
||||
defp collect_traces(_name, arg, %{lexical_tracker: pid} = env) when is_pid(pid) do
|
||||
env = %{env | function: {:__info__, 1}}
|
||||
|
||||
{arg, aliases} =
|
||||
Macro.prewalk(arg, %{}, fn
|
||||
{:__aliases__, _, _} = alias, acc ->
|
||||
case Macro.expand(alias, env) do
|
||||
atom when is_atom(atom) -> {atom, Map.put(acc, atom, [])}
|
||||
_ -> {alias, acc}
|
||||
end
|
||||
{arg, aliases} =
|
||||
Macro.expand_literals(arg, %{}, fn
|
||||
{:__aliases__, _, _} = alias, acc ->
|
||||
case Macro.expand(alias, env) do
|
||||
atom when is_atom(atom) -> {atom, Map.put(acc, atom, [])}
|
||||
_ -> {alias, acc}
|
||||
end
|
||||
|
||||
node, acc ->
|
||||
{node, acc}
|
||||
end)
|
||||
node, acc ->
|
||||
{Macro.expand(node, env), acc}
|
||||
end)
|
||||
|
||||
case map_size(aliases) do
|
||||
0 -> {arg, []}
|
||||
_ -> {arg, [{env.line, env.lexical_tracker, env.tracers, :maps.keys(aliases)}]}
|
||||
end
|
||||
|
||||
false ->
|
||||
{arg, []}
|
||||
case map_size(aliases) do
|
||||
0 -> {arg, []}
|
||||
_ -> {arg, [{env.line, env.lexical_tracker, env.tracers, :maps.keys(aliases)}]}
|
||||
end
|
||||
end
|
||||
|
||||
defp collect_traces(_name, arg, _env) do
|
||||
{arg, []}
|
||||
end
|
||||
|
||||
defp typespec?(:type), do: true
|
||||
defp typespec?(:typep), do: true
|
||||
defp typespec?(:opaque), do: true
|
||||
@@ -4802,11 +4800,27 @@ defmodule Kernel do
|
||||
:elixir_quote.escape(block, :none, false)
|
||||
end
|
||||
|
||||
module_vars = :lists.map(&module_var/1, :maps.keys(env.versioned_vars))
|
||||
versioned_vars = env.versioned_vars
|
||||
prune = :erlang.is_map_key({:elixir, :prune_binding}, versioned_vars)
|
||||
|
||||
var_meta =
|
||||
case prune do
|
||||
true -> [generated: true, keep_unused: true]
|
||||
false -> [generated: true]
|
||||
end
|
||||
|
||||
module_vars = :lists.map(&module_var(&1, var_meta), :maps.keys(versioned_vars))
|
||||
|
||||
quote do
|
||||
unquote(with_alias)
|
||||
:elixir_module.compile(unquote(expanded), unquote(escaped), unquote(module_vars), __ENV__)
|
||||
|
||||
:elixir_module.compile(
|
||||
unquote(expanded),
|
||||
unquote(escaped),
|
||||
unquote(module_vars),
|
||||
unquote(prune),
|
||||
__ENV__
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4855,8 +4869,8 @@ defmodule Kernel do
|
||||
{module, module, nil}
|
||||
end
|
||||
|
||||
defp module_var({name, kind}) when is_atom(kind), do: {name, [generated: true], kind}
|
||||
defp module_var({name, kind}), do: {name, [counter: kind, generated: true], nil}
|
||||
defp module_var({name, kind}, meta) when is_atom(kind), do: {name, meta, kind}
|
||||
defp module_var({name, kind}, meta), do: {name, [counter: kind] ++ meta, nil}
|
||||
|
||||
@doc ~S"""
|
||||
Defines a public function with the given name and body.
|
||||
@@ -5746,7 +5760,7 @@ defmodule Kernel do
|
||||
"""
|
||||
defmacro defdelegate(funs, opts) do
|
||||
funs = Macro.escape(funs, unquote: true)
|
||||
opts = Macro.expand_literal(opts, %{__CALLER__ | function: {:__info__, 1}})
|
||||
opts = Macro.expand_literals(opts, %{__CALLER__ | function: {:__info__, 1}})
|
||||
|
||||
quote bind_quoted: [funs: funs, opts: opts] do
|
||||
target = Kernel.Utils.defdelegate_all(funs, opts, __ENV__)
|
||||
|
||||
@@ -271,12 +271,6 @@ defmodule Kernel.ParallelCompiler do
|
||||
for {{:module, module}, _} <- result,
|
||||
do: module
|
||||
|
||||
runtime_modules =
|
||||
for module <- runtime_modules,
|
||||
path = :code.which(module),
|
||||
is_list(path) and path != [],
|
||||
do: {module, path}
|
||||
|
||||
profile_checker(profile, compiled_modules, runtime_modules, fn ->
|
||||
Module.ParallelChecker.verify(checker, runtime_modules)
|
||||
end)
|
||||
|
||||
@@ -138,10 +138,17 @@ defmodule Kernel.Utils do
|
||||
fields = :lists.map(mapper, fields)
|
||||
|
||||
enforce_keys =
|
||||
case :ets.take(set, :enforce_keys) do
|
||||
[{_, enforce_keys, _, _}] when is_list(enforce_keys) -> enforce_keys
|
||||
[{_, enforce_key, _, _}] -> [enforce_key]
|
||||
[] -> []
|
||||
case :ets.lookup(set, :enforce_keys) do
|
||||
[{_, enforce_keys, _, _}] when is_list(enforce_keys) ->
|
||||
:ets.update_element(set, :enforce_keys, {3, :used})
|
||||
enforce_keys
|
||||
|
||||
[{_, enforce_key, _, _}] ->
|
||||
:ets.update_element(set, :enforce_keys, {3, :used})
|
||||
[enforce_key]
|
||||
|
||||
[] ->
|
||||
[]
|
||||
end
|
||||
|
||||
# TODO: Make it raise on v2.0
|
||||
@@ -324,7 +331,7 @@ defmodule Kernel.Utils do
|
||||
quote do
|
||||
case Macro.Env.in_guard?(__CALLER__) do
|
||||
true -> unquote(literal_quote(unquote_every_ref(expr, vars)))
|
||||
false -> unquote(literal_quote(unquote_refs_once(expr, vars)))
|
||||
false -> unquote(literal_quote(unquote_refs_once(expr, vars, env.module)))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -354,7 +361,7 @@ defmodule Kernel.Utils do
|
||||
end
|
||||
|
||||
# Prefaces `guard` with unquoted versions of `refs`.
|
||||
defp unquote_refs_once(guard, refs) do
|
||||
defp unquote_refs_once(guard, refs, module) do
|
||||
{guard, used_refs} =
|
||||
Macro.postwalk(guard, %{}, fn
|
||||
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
|
||||
@@ -368,7 +375,7 @@ defmodule Kernel.Utils do
|
||||
|
||||
%{} ->
|
||||
generated = String.to_atom("arg" <> Integer.to_string(map_size(acc) + 1))
|
||||
new_var = Macro.var(generated, Elixir)
|
||||
new_var = Macro.unique_var(generated, module)
|
||||
{new_var, Map.put(acc, pair, {new_var, var})}
|
||||
end
|
||||
|
||||
|
||||
+92
-24
@@ -1763,7 +1763,7 @@ defmodule Macro do
|
||||
end
|
||||
|
||||
"""
|
||||
@spec expand_once(t(), Macro.Env.t()) :: t()
|
||||
@spec expand_once(input(), Macro.Env.t()) :: output()
|
||||
def expand_once(ast, env) do
|
||||
elem(do_expand_once(ast, env), 0)
|
||||
end
|
||||
@@ -1795,13 +1795,15 @@ defmodule Macro do
|
||||
defp do_expand_once({:__DIR__, _, atom}, env) when is_atom(atom),
|
||||
do: {:filename.dirname(env.file), true}
|
||||
|
||||
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom),
|
||||
do: {{:%{}, [], Map.to_list(env)}, true}
|
||||
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom) do
|
||||
env = update_in(env.versioned_vars, &maybe_escape_map/1)
|
||||
{maybe_escape_map(env), true}
|
||||
end
|
||||
|
||||
defp do_expand_once({{:., _, [{:__ENV__, _, atom}, field]}, _, []} = original, env)
|
||||
when is_atom(atom) and is_atom(field) do
|
||||
if Map.has_key?(env, field) do
|
||||
{Map.get(env, field), true}
|
||||
{maybe_escape_map(Map.get(env, field)), true}
|
||||
else
|
||||
{original, false}
|
||||
end
|
||||
@@ -1884,6 +1886,9 @@ defmodule Macro do
|
||||
# Anything else is just returned
|
||||
defp do_expand_once(other, _env), do: {other, false}
|
||||
|
||||
defp maybe_escape_map(map) when is_map(map), do: {:%{}, [], Map.to_list(map)}
|
||||
defp maybe_escape_map(other), do: other
|
||||
|
||||
@doc """
|
||||
Returns `true` if the given name and arity is a special form.
|
||||
"""
|
||||
@@ -1966,42 +1971,105 @@ defmodule Macro do
|
||||
|
||||
def quoted_literal?({:%{}, _, args}), do: quoted_literal?(args)
|
||||
def quoted_literal?({:{}, _, args}), do: quoted_literal?(args)
|
||||
def quoted_literal?({:__MODULE__, _, ctx}) when is_atom(ctx), do: true
|
||||
def quoted_literal?({left, right}), do: quoted_literal?(left) and quoted_literal?(right)
|
||||
def quoted_literal?(list) when is_list(list), do: :lists.all("ed_literal?/1, list)
|
||||
def quoted_literal?(term), do: is_atom(term) or is_number(term) or is_binary(term)
|
||||
|
||||
@doc """
|
||||
Expands the `ast` representing a quoted literal within the given `env`.
|
||||
@doc false
|
||||
@deprecated "Use Macro.expand_literals/2 instead"
|
||||
def expand_literal(ast, env) do
|
||||
expand_literals(ast, env)
|
||||
end
|
||||
|
||||
This function checks if the given AST represents a quoted literal
|
||||
(using `quoted_literal?/1`) and then expands all relevant nodes.
|
||||
If a literal node is not given, then it returns the AST as is.
|
||||
At the moment, the only expandable literal nodes in an AST are
|
||||
aliases, so this function only expands aliases.
|
||||
@doc """
|
||||
Expands all literals in `ast` with the given `env`.
|
||||
|
||||
This function is mostly used to remove compile-time dependencies
|
||||
from AST nodes. In such cases, the given environment is usually
|
||||
manipulate to represent a function:
|
||||
manipulated to represent a function:
|
||||
|
||||
Macro.expand_literal(ast, %{env | function: {:my_code, 1}})
|
||||
Macro.expand_literals(ast, %{env | function: {:my_code, 1}})
|
||||
|
||||
At the moment, the only expandable literal nodes in an AST are
|
||||
aliases, so this function only expands aliases.
|
||||
|
||||
However, be careful when removing compile-time dependencies between
|
||||
modules. If you remove them but you still invoke the module at
|
||||
compile-time, you may end-up with broken behaviour.
|
||||
compile-time, Elixir will be unable to properly recompile modules
|
||||
when they change.
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
@spec expand_literal(t(), Macro.Env.t()) :: t()
|
||||
def expand_literal(ast, env) do
|
||||
if quoted_literal?(ast) do
|
||||
prewalk(ast, fn
|
||||
{:__aliases__, _, _} = alias -> expand(alias, env)
|
||||
other -> other
|
||||
end)
|
||||
@doc since: "1.14.1"
|
||||
@spec expand_literals(input(), Macro.Env.t()) :: output()
|
||||
def expand_literals(ast, env) do
|
||||
{ast, :ok} = expand_literals(ast, :ok, fn node, :ok -> {expand(node, env), :ok} end)
|
||||
ast
|
||||
end
|
||||
|
||||
@doc """
|
||||
Expands all literals in `ast` with the given `acc` and `fun`.
|
||||
|
||||
`fun` will be invoked with an expandable AST node and `acc` and
|
||||
must return a new node with `acc`. This is a general version of
|
||||
`expand_literals/2` which supports a custom expansion function.
|
||||
Please check `expand_literals/2` for use cases and pitfalls.
|
||||
"""
|
||||
@doc since: "1.14.1"
|
||||
@spec expand_literals(t(), acc, (t(), acc -> {t(), acc})) :: t() when acc: term()
|
||||
def expand_literals(ast, acc, fun)
|
||||
|
||||
def expand_literals({:__aliases__, meta, args}, acc, fun) do
|
||||
{args, acc} = expand_literals(args, acc, fun)
|
||||
|
||||
if :lists.all(&is_atom/1, args) do
|
||||
fun.({:__aliases__, meta, args}, acc)
|
||||
else
|
||||
ast
|
||||
{{:__aliases__, meta, args}, acc}
|
||||
end
|
||||
end
|
||||
|
||||
def expand_literals({:__MODULE__, _meta, ctx} = node, acc, fun) when is_atom(ctx) do
|
||||
fun.(node, acc)
|
||||
end
|
||||
|
||||
def expand_literals({:%, meta, [left, right]}, acc, fun) do
|
||||
{left, acc} = expand_literals(left, acc, fun)
|
||||
{right, acc} = expand_literals(right, acc, fun)
|
||||
{{:%, meta, [left, right]}, acc}
|
||||
end
|
||||
|
||||
def expand_literals({:%{}, meta, args}, acc, fun) do
|
||||
{args, acc} = expand_literals(args, acc, fun)
|
||||
{{:%{}, meta, args}, acc}
|
||||
end
|
||||
|
||||
def expand_literals({:{}, meta, args}, acc, fun) do
|
||||
{args, acc} = expand_literals(args, acc, fun)
|
||||
{{:{}, meta, args}, acc}
|
||||
end
|
||||
|
||||
def expand_literals({left, right}, acc, fun) do
|
||||
{left, acc} = expand_literals(left, acc, fun)
|
||||
{right, acc} = expand_literals(right, acc, fun)
|
||||
{{left, right}, acc}
|
||||
end
|
||||
|
||||
def expand_literals(list, acc, fun) when is_list(list) do
|
||||
:lists.mapfoldl(&expand_literals(&1, &2, fun), acc, list)
|
||||
end
|
||||
|
||||
def expand_literals(
|
||||
{{:., _, [{:__aliases__, _, [:Application]}, :compile_env]} = node, meta,
|
||||
[app, key, default]},
|
||||
acc,
|
||||
fun
|
||||
) do
|
||||
{default, acc} = expand_literals(default, acc, fun)
|
||||
{{node, meta, [app, key, default]}, acc}
|
||||
end
|
||||
|
||||
def expand_literals(term, acc, _fun), do: {term, acc}
|
||||
|
||||
@doc """
|
||||
Receives an AST node and expands it until it can no longer
|
||||
be expanded.
|
||||
@@ -2012,7 +2080,7 @@ defmodule Macro do
|
||||
This function uses `expand_once/2` under the hood. Check
|
||||
it out for more information and examples.
|
||||
"""
|
||||
@spec expand(t(), Macro.Env.t()) :: t()
|
||||
@spec expand(input(), Macro.Env.t()) :: output()
|
||||
def expand(ast, env) do
|
||||
expand_until({ast, true}, env)
|
||||
end
|
||||
|
||||
@@ -532,7 +532,11 @@ defmodule Module do
|
||||
corresponding setting in `Code.get_compiler_option/1`
|
||||
|
||||
* `@compile {:debug_info, false}` - disables `:debug_info` regardless
|
||||
of the corresponding setting in `Code.get_compiler_option/1`
|
||||
of the corresponding setting in `Code.get_compiler_option/1`. Note
|
||||
disabling `:debug_info` is not recommended as it removes the ability
|
||||
of the Elixir compiler and other tools to static analyse the code.
|
||||
If you want to remove the `:debug_info` while deploying, tools like
|
||||
`mix release` already do such by default.
|
||||
|
||||
* `@compile {:inline, some_fun: 2, other_fun: 3}` - inlines the given
|
||||
name/arity pairs. Inlining is applied locally, calls from another
|
||||
@@ -818,7 +822,7 @@ defmodule Module do
|
||||
next = :elixir_module.next_counter(nil)
|
||||
meta = Keyword.take(opts, [:line, :generated])
|
||||
quoted = :elixir_quote.linify_with_context_counter(meta, {module, next}, quoted)
|
||||
:elixir_module.compile(module, quoted, [], :elixir.env_for_eval(opts))
|
||||
:elixir_module.compile(module, quoted, [], false, :elixir.env_for_eval(opts))
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
||||
@@ -62,17 +62,27 @@ defmodule Module.ParallelChecker do
|
||||
if is_map(info) do
|
||||
info
|
||||
else
|
||||
info |> File.read!() |> fetch_module_map!(module)
|
||||
case File.read(info) do
|
||||
{:ok, binary} -> maybe_module_map(binary, module)
|
||||
{:error, _} -> nil
|
||||
end
|
||||
end
|
||||
|
||||
cache_from_module_map(ets, module_map)
|
||||
module_map && cache_from_module_map(ets, module_map)
|
||||
send(checker, {ref, :cached})
|
||||
|
||||
receive do
|
||||
{^ref, :check} ->
|
||||
# Set the compiler info so we can collect warnings
|
||||
:erlang.put(:elixir_compiler_info, {pid, self()})
|
||||
warnings = check_module(module_map, {checker, ets})
|
||||
|
||||
warnings =
|
||||
if module_map do
|
||||
check_module(module_map, {checker, ets})
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
send(pid, {__MODULE__, module, warnings})
|
||||
send(checker, {__MODULE__, :done})
|
||||
end
|
||||
@@ -129,13 +139,13 @@ defmodule Module.ParallelChecker do
|
||||
the modules and adds the ExCk chunk to the binaries. Returns the updated
|
||||
list of warnings from the verification.
|
||||
"""
|
||||
@spec verify(pid(), [{module(), binary()}]) :: [warning()]
|
||||
@spec verify(pid(), [{module(), Path.t()}]) :: [warning()]
|
||||
def verify(checker, runtime_files) do
|
||||
for {module, file} <- runtime_files do
|
||||
spawn({self(), checker}, module, file)
|
||||
end
|
||||
|
||||
modules = :gen_server.call(checker, :start)
|
||||
modules = :gen_server.call(checker, :start, :infinity)
|
||||
collect_results(modules, [])
|
||||
end
|
||||
|
||||
@@ -161,7 +171,7 @@ defmodule Module.ParallelChecker do
|
||||
"""
|
||||
def test_cache do
|
||||
{:ok, checker} = start_link()
|
||||
{checker, :gen_server.call(checker, :ets)}
|
||||
{checker, :gen_server.call(checker, :ets, :infinity)}
|
||||
end
|
||||
|
||||
@doc """
|
||||
@@ -317,8 +327,7 @@ defmodule Module.ParallelChecker do
|
||||
|
||||
defp cache_from_chunk(ets, module) do
|
||||
with {^module, binary, _filename} <- :code.get_object_code(module),
|
||||
{:ok, ^module} <- binary |> :beam_lib.info() |> Keyword.fetch(:module),
|
||||
{:ok, {_, [{'ExCk', chunk}]}} <- :beam_lib.chunks(binary, ['ExCk']),
|
||||
{:ok, {^module, [{~c"ExCk", chunk}]}} <- :beam_lib.chunks(binary, [~c"ExCk"]),
|
||||
{:elixir_checker_v1, contents} <- :erlang.binary_to_term(chunk) do
|
||||
cache_chunk(ets, module, contents.exports)
|
||||
true
|
||||
@@ -357,11 +366,16 @@ defmodule Module.ParallelChecker do
|
||||
_ -> %{}
|
||||
end
|
||||
|
||||
defp fetch_module_map!(binary, module) when is_binary(binary) do
|
||||
{:ok, {_, [debug_info: chunk]}} = :beam_lib.chunks(binary, [:debug_info])
|
||||
{:debug_info_v1, backend, data} = chunk
|
||||
{:ok, module_map} = backend.debug_info(:elixir_v1, module, data, [])
|
||||
module_map
|
||||
defp maybe_module_map(binary, module) when is_binary(binary) do
|
||||
# If a module was compiled without debug_info,
|
||||
# then there is no module_map for further verification.
|
||||
with {:ok, {_, [debug_info: chunk]}} <- :beam_lib.chunks(binary, [:debug_info]),
|
||||
{:debug_info_v1, backend, data} = chunk,
|
||||
{:ok, module_map} <- backend.debug_info(:elixir_v1, module, data, []) do
|
||||
module_map
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp cache_from_module_map(ets, map) do
|
||||
|
||||
@@ -135,8 +135,7 @@ defmodule Module.Types do
|
||||
# Collect relevant information from context and traces to report error
|
||||
def error_to_warning(:unable_apply, {mfa, args, expected, signature, stack}, context) do
|
||||
{fun, arity} = context.function
|
||||
line = get_meta(stack.last_expr)[:line]
|
||||
location = {context.file, line, {context.module, fun, arity}}
|
||||
location = {context.file, get_line(stack), {context.module, fun, arity}}
|
||||
|
||||
traces = type_traces(stack, context)
|
||||
{[signature | args], traces} = lift_all_types([signature | args], traces, context)
|
||||
@@ -146,8 +145,7 @@ defmodule Module.Types do
|
||||
|
||||
def error_to_warning(:unable_unify, {left, right, stack}, context) do
|
||||
{fun, arity} = context.function
|
||||
line = get_meta(stack.last_expr)[:line]
|
||||
location = {context.file, line, {context.module, fun, arity}}
|
||||
location = {context.file, get_line(stack), {context.module, fun, arity}}
|
||||
|
||||
traces = type_traces(stack, context)
|
||||
{[left, right], traces} = lift_all_types([left, right], traces, context)
|
||||
@@ -155,6 +153,8 @@ defmodule Module.Types do
|
||||
{Module.Types, error, location}
|
||||
end
|
||||
|
||||
defp get_line(stack), do: stack.last_expr |> get_meta() |> Keyword.get(:line, 0)
|
||||
|
||||
# Collect relevant traces from context.traces using stack.unify_stack
|
||||
defp type_traces(stack, context) do
|
||||
# TODO: Do we need the unify_stack or is enough to only get the last variable
|
||||
@@ -211,17 +211,21 @@ defmodule Module.Types do
|
||||
## FORMAT WARNINGS
|
||||
|
||||
def format_warning({:unable_apply, mfa, args, expected, signature, {location, expr, traces}}) do
|
||||
{module, function, arity} = mfa
|
||||
mfa_args = Macro.generate_arguments(arity, __MODULE__)
|
||||
{module, function, ^arity} = call_to_mfa(erl_to_ex(module, function, mfa_args, []))
|
||||
{original_module, original_function, arity} = mfa
|
||||
{_, _, args} = mfa_or_fa = erl_to_ex(original_module, original_function, args, [])
|
||||
{module, function, ^arity} = call_to_mfa(mfa_or_fa)
|
||||
format_mfa = Exception.format_mfa(module, function, arity)
|
||||
{traces, [] = _hints} = format_traces(traces, [], false)
|
||||
|
||||
clauses =
|
||||
Enum.map(
|
||||
signature,
|
||||
&String.slice(IO.iodata_to_binary(Unify.format_type({:fun, [&1]}, false)), 1..-2)
|
||||
)
|
||||
Enum.map(signature, fn {ins, out} ->
|
||||
{_, _, ins} = erl_to_ex(original_module, original_function, ins, [])
|
||||
|
||||
{:fun, [{ins, out}]}
|
||||
|> Unify.format_type(false)
|
||||
|> IO.iodata_to_binary()
|
||||
|> binary_slice(1..-2//1)
|
||||
end)
|
||||
|
||||
[
|
||||
"expected #{format_mfa} to have signature:\n\n ",
|
||||
@@ -248,7 +252,7 @@ defmodule Module.Types do
|
||||
"undefined field \"#{atom}\" ",
|
||||
format_expr(expr, location),
|
||||
"expected one of the following fields: ",
|
||||
Enum.map_join(Enum.sort(known_atoms), ", ", & &1),
|
||||
Enum.join(Enum.sort(known_atoms), ", "),
|
||||
"\n\n",
|
||||
traces,
|
||||
format_message_hints(hints),
|
||||
|
||||
@@ -153,14 +153,18 @@ defmodule Module.Types.Expr do
|
||||
args_type = {:map, dynamic_value_pairs ++ [{:optional, :dynamic, :dynamic}]},
|
||||
{:ok, type, context} <- unify(map_type, args_type, stack, context) do
|
||||
# Retrieve map type and overwrite with the new value types from the map update
|
||||
{:map, pairs} = resolve_var(type, context)
|
||||
case resolve_var(type, context) do
|
||||
{:map, pairs} ->
|
||||
updated_pairs =
|
||||
Enum.reduce(arg_pairs, pairs, fn {:required, key, value}, pairs ->
|
||||
List.keyreplace(pairs, key, 1, {:required, key, value})
|
||||
end)
|
||||
|
||||
updated_pairs =
|
||||
Enum.reduce(arg_pairs, pairs, fn {:required, key, value}, pairs ->
|
||||
List.keyreplace(pairs, key, 1, {:required, key, value})
|
||||
end)
|
||||
{:ok, {:map, updated_pairs}, context}
|
||||
|
||||
{:ok, {:map, updated_pairs}, context}
|
||||
_ ->
|
||||
{:ok, :dynamic, context}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -128,35 +128,10 @@ defmodule Module.Types.Unify do
|
||||
{:ok, {:var, var}, context}
|
||||
end
|
||||
|
||||
%{^var => {:var, new_var} = var_type} ->
|
||||
unify_result =
|
||||
cond do
|
||||
recursive_type?(var_type, [], context) ->
|
||||
{:ok, var_type, put_in(context.types[var], var_type)}
|
||||
|
||||
var_source? ->
|
||||
unify(var_type, type, stack, context)
|
||||
|
||||
true ->
|
||||
unify(type, var_type, stack, context)
|
||||
end
|
||||
|
||||
case unify_result do
|
||||
{:ok, type, context} ->
|
||||
{:ok, type, context}
|
||||
|
||||
{:error, {type, reason, %{traces: error_traces} = error_context}} ->
|
||||
old_var_traces = Map.get(context.traces, new_var, [])
|
||||
new_var_traces = Map.get(error_traces, new_var, [])
|
||||
add_var_traces = Enum.drop(new_var_traces, -length(old_var_traces))
|
||||
|
||||
error_traces =
|
||||
error_traces
|
||||
|> Map.update(var, add_var_traces, &(add_var_traces ++ &1))
|
||||
|> Map.put(new_var, old_var_traces)
|
||||
|
||||
{:error, {type, reason, %{error_context | traces: error_traces}}}
|
||||
end
|
||||
%{^var => {:var, _} = var_type} ->
|
||||
# Do not recursively traverse type vars for now
|
||||
# to avoid pathological cases related to performance.
|
||||
{:ok, var_type, context}
|
||||
|
||||
%{^var => var_type} ->
|
||||
# Only add trace if the variable wasn't already "expanded"
|
||||
@@ -384,11 +359,11 @@ defmodule Module.Types.Unify do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Resolves a variable raising if it is unbound.
|
||||
Maybe resolves a variable.
|
||||
"""
|
||||
def resolve_var({:var, var}, context) do
|
||||
case context.types do
|
||||
%{^var => :unbound} -> raise "cannot resolve unbound var"
|
||||
%{^var => :unbound} -> {:var, var}
|
||||
%{^var => type} -> resolve_var(type, context)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -603,6 +603,10 @@ defmodule Path do
|
||||
:file.read_link_info(file)
|
||||
end
|
||||
|
||||
def read_file_info(file) do
|
||||
:file.read_file_info(file)
|
||||
end
|
||||
|
||||
def list_dir(dir) do
|
||||
case :file.list_dir(dir) do
|
||||
{:ok, files} -> {:ok, for(file <- files, hd(file) != ?., do: file)}
|
||||
|
||||
@@ -931,7 +931,8 @@ defmodule Protocol do
|
||||
raise ArgumentError, "defimpl/3 expects a :for option when declared outside a module"
|
||||
end)
|
||||
|
||||
for = Macro.expand_literal(for, %{env | module: Kernel, function: {:defimpl, 3}})
|
||||
for =
|
||||
Macro.expand_literals(for, %{env | module: env.module || Elixir, function: {:__impl__, 1}})
|
||||
|
||||
case opts do
|
||||
[] -> raise ArgumentError, "defimpl expects a do-end block"
|
||||
|
||||
+32
-28
@@ -24,7 +24,7 @@ defmodule Range do
|
||||
|
||||
When defining a range without a step, the step will be
|
||||
defined based on the first and last position of the
|
||||
range, If `first >= last`, it will be an increasing range
|
||||
range, If `last >= first`, it will be an increasing range
|
||||
with a step of 1. Otherwise, it is a decreasing range.
|
||||
Note however implicit decreasing ranges are deprecated.
|
||||
Therefore, if you need a decreasing range from `3` to `1`,
|
||||
@@ -294,22 +294,24 @@ defmodule Range do
|
||||
iex> Range.disjoint?(1..10//2, 2..10//2)
|
||||
true
|
||||
|
||||
# First element in common in all below is 29
|
||||
iex> Range.disjoint?(2..100//3, 9..100//5)
|
||||
false
|
||||
iex> Range.disjoint?(101..2//-3, 99..9//-5)
|
||||
false
|
||||
# First element in common is 29
|
||||
iex> Range.disjoint?(1..100//14, 8..100//21)
|
||||
false
|
||||
iex> Range.disjoint?(57..-1//-14, 8..100//21)
|
||||
false
|
||||
iex> Range.disjoint?(1..100//14, 51..8//-21)
|
||||
iex> Range.disjoint?(1..100//14, 50..8//-21)
|
||||
false
|
||||
|
||||
# If 29 is out of range
|
||||
iex> Range.disjoint?(1..28//14, 8..28//21)
|
||||
true
|
||||
|
||||
# First element in common is 14
|
||||
iex> Range.disjoint?(2..28//3, 9..28//5)
|
||||
false
|
||||
iex> Range.disjoint?(26..2//-3, 29..9//-5)
|
||||
false
|
||||
|
||||
# Starting from the back without alignment
|
||||
iex> Range.disjoint?(27..11//-3, 30..0//-7)
|
||||
true
|
||||
|
||||
"""
|
||||
@@ -334,21 +336,28 @@ defmodule Range do
|
||||
# progressions and see if they belong within the ranges
|
||||
# https://math.stackexchange.com/questions/1656120/formula-to-find-the-first-intersection-of-two-arithmetic-progressions
|
||||
{gcd, u, v} = Integer.extended_gcd(-step1, step2)
|
||||
c = first1 - first2 + step2 - step1
|
||||
t1 = -c / step1 * u
|
||||
t2 = -c / step2 * v
|
||||
t = max(floor(t1) + 1, floor(t2) + 1)
|
||||
x = div(c * u + t * step2, gcd) - 1
|
||||
y = div(c * v + t * step1, gcd) - 1
|
||||
|
||||
x < 0 or first1 + x * step1 > last1 or
|
||||
y < 0 or first2 + y * step2 > last2
|
||||
if rem(first2 - first1, gcd) == 0 do
|
||||
c = first1 - first2 + step2 - step1
|
||||
t1 = -c / step2 * u
|
||||
t2 = -c / step1 * v
|
||||
t = max(floor(t1) + 1, floor(t2) + 1)
|
||||
x = div(c * u + t * step2, gcd) - 1
|
||||
y = div(c * v + t * step1, gcd) - 1
|
||||
|
||||
x < 0 or first1 + x * step1 > last1 or
|
||||
y < 0 or first2 + y * step2 > last2
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@compile inline: [normalize: 3]
|
||||
defp normalize(first, last, step) when first > last, do: {last, first, -step}
|
||||
defp normalize(first, last, step) when first > last,
|
||||
do: {first - abs(div(first - last, step) * step), first, -step}
|
||||
|
||||
defp normalize(first, last, step), do: {first, last, step}
|
||||
|
||||
@doc false
|
||||
@@ -387,16 +396,11 @@ defimpl Enumerable, for: Range do
|
||||
{:done, acc}
|
||||
end
|
||||
|
||||
def member?(first..last//step = range, value) when is_integer(value) do
|
||||
cond do
|
||||
Range.size(range) == 0 ->
|
||||
{:ok, false}
|
||||
|
||||
first <= last ->
|
||||
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
|
||||
|
||||
true ->
|
||||
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
|
||||
def member?(first..last//step, value) when is_integer(value) do
|
||||
if step > 0 do
|
||||
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
|
||||
else
|
||||
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1315,6 +1315,8 @@ defmodule Stream do
|
||||
{:suspended, acc, &zip_list(enumerables, &1, fun, zip_fun)}
|
||||
end
|
||||
|
||||
defp zip_list([], {:cont, acc}, _fun, _zip_fun), do: {:done, acc}
|
||||
|
||||
defp zip_list(enumerables, {:cont, acc}, fun, zip_fun) do
|
||||
case zip_list_heads_tails(enumerables, [], []) do
|
||||
{heads, tails} -> zip_list(tails, fun.(zip_fun.(heads), acc), fun, zip_fun)
|
||||
|
||||
@@ -524,11 +524,11 @@ defmodule String do
|
||||
defp parts_to_index(n) when is_integer(n) and n > 0, do: n
|
||||
|
||||
defp split_empty("", true, 1), do: []
|
||||
defp split_empty(string, _, 1), do: [string]
|
||||
defp split_empty(string, _, 1), do: [IO.iodata_to_binary(string)]
|
||||
|
||||
defp split_empty(string, trim, count) do
|
||||
case :unicode_util.gc(string) do
|
||||
[gc] -> [grapheme_to_binary(gc) | split_empty(<<>>, trim, count - 1)]
|
||||
[gc] -> [grapheme_to_binary(gc) | split_empty("", trim, 1)]
|
||||
[gc | rest] -> [grapheme_to_binary(gc) | split_empty(rest, trim, count - 1)]
|
||||
[] -> split_empty("", trim, 1)
|
||||
{:error, <<byte, rest::bits>>} -> [<<byte>> | split_empty(rest, trim, count - 1)]
|
||||
@@ -1930,6 +1930,7 @@ defmodule String do
|
||||
def next_grapheme(string) when is_binary(string) do
|
||||
case :unicode_util.gc(string) do
|
||||
[gc] -> {grapheme_to_binary(gc), <<>>}
|
||||
[gc, rest] -> {grapheme_to_binary(gc), rest}
|
||||
[gc | rest] -> {grapheme_to_binary(gc), rest}
|
||||
[] -> nil
|
||||
{:error, <<byte, rest::bits>>} -> {<<byte>>, rest}
|
||||
|
||||
@@ -366,6 +366,7 @@ defmodule StringIO do
|
||||
defp get_until(encoding, prompt, mod, fun, args, %{input: input} = state) do
|
||||
case get_until(input, encoding, mod, fun, args, [], 0) do
|
||||
{result, input, count} ->
|
||||
# Convert :eof to "" as they are both treated the same
|
||||
input =
|
||||
case input do
|
||||
:eof -> ""
|
||||
@@ -392,7 +393,7 @@ defmodule StringIO do
|
||||
defp get_until(chars, encoding, mod, fun, args, continuation, count) do
|
||||
case bytes_until_eol(chars, encoding, 0) do
|
||||
{kind, size} when kind in [:split, :replace_split] ->
|
||||
{line, rest} = :erlang.split_binary(chars, size)
|
||||
<<line::binary-size(size), rest::binary>> = chars
|
||||
|
||||
case apply(mod, fun, [continuation, binary_to_list(line, encoding) | args]) do
|
||||
{:done, result, :eof} ->
|
||||
|
||||
@@ -82,7 +82,7 @@ defmodule Supervisor do
|
||||
GenServer.call(Counter, :get)
|
||||
#=> 0
|
||||
|
||||
GenServer.cast(Counter, {:bump, 3})
|
||||
GenServer.call(Counter, {:bump, 3})
|
||||
#=> 0
|
||||
|
||||
GenServer.call(Counter, :get)
|
||||
|
||||
@@ -920,19 +920,26 @@ defmodule System do
|
||||
|
||||
## Options
|
||||
|
||||
It accepts the same options as `cmd/3`, except for `arg0`.
|
||||
It accepts the same options as `cmd/3` (except for `arg0`).
|
||||
It also accepts the following exclusive options:
|
||||
|
||||
* `:close_stdin` (since v1.14.1) - if the stdin should be closed
|
||||
on Unix systems, forcing any command that waits on stdin to
|
||||
immediately terminate. Defaults to false.
|
||||
"""
|
||||
@doc since: "1.12.0"
|
||||
@spec shell(binary, keyword) :: {Collectable.t(), exit_status :: non_neg_integer}
|
||||
def shell(command, opts \\ []) when is_binary(command) do
|
||||
assert_no_null_byte!(command, "System.shell/2")
|
||||
{close_stdin?, opts} = Keyword.pop(opts, :close_stdin, false)
|
||||
|
||||
# Finding shell command logic from :os.cmd in OTP
|
||||
# https://github.com/erlang/otp/blob/8deb96fb1d017307e22d2ab88968b9ef9f1b71d0/lib/kernel/src/os.erl#L184
|
||||
case :os.type() do
|
||||
{:unix, _} ->
|
||||
shell_path = :os.find_executable('sh') || :erlang.error(:enoent, [command, opts])
|
||||
command = "(#{command}\n) </dev/null"
|
||||
shell_path = :os.find_executable(~c"sh") || :erlang.error(:enoent, [command, opts])
|
||||
close_stdin = if close_stdin?, do: " </dev/null", else: ""
|
||||
command = IO.iodata_to_binary(["(", command, "\n)", close_stdin])
|
||||
do_cmd({:spawn_executable, shell_path}, [args: ["-c", command]], opts)
|
||||
|
||||
{:win32, osname} ->
|
||||
|
||||
@@ -648,7 +648,7 @@ defmodule URI do
|
||||
scheme = String.downcase(scheme, :ascii)
|
||||
|
||||
case map do
|
||||
%{port: _} ->
|
||||
%{port: port} when port != :undefined ->
|
||||
%{uri | scheme: scheme}
|
||||
|
||||
%{} ->
|
||||
|
||||
@@ -42,7 +42,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
|
||||
|
||||
Elixir version | Supported Erlang/OTP versions
|
||||
:------------- | :-------------------------------
|
||||
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.12 | 22 - 24
|
||||
1.11 | 21 - 23 (and Erlang/OTP 24 from v1.11.4)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generate docs_config.js for version chooser in ExDoc
|
||||
[app] = System.argv()
|
||||
|
||||
{text_tags, 0} = System.cmd("git", ["tag"])
|
||||
skipped = Version.parse!("1.0.3")
|
||||
|
||||
list_contents =
|
||||
text_tags
|
||||
|> String.split()
|
||||
|> Enum.map(fn "v" <> rest -> Version.parse!(rest) end)
|
||||
|> Enum.filter(&(Version.compare(&1, skipped) == :gt))
|
||||
|> Enum.sort({:desc, Version})
|
||||
|> Enum.map_intersperse(", ", fn version ->
|
||||
version_string = Version.to_string(version)
|
||||
~s[{"version":"v#{version_string}", "url":"https://hexdocs.pm/#{app}/#{version_string}"}]
|
||||
end)
|
||||
|
||||
File.mkdir_p!("doc/#{app}")
|
||||
File.write!("doc/#{app}/docs_config.js", ["var versionNodes = [", list_contents, "];\n"])
|
||||
+38
-23
@@ -4,7 +4,8 @@
|
||||
-behaviour(application).
|
||||
-export([start_cli/0,
|
||||
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]).
|
||||
-include("elixir.hrl").
|
||||
-define(system, 'Elixir.System').
|
||||
|
||||
@@ -24,9 +25,9 @@
|
||||
-export([start/2, stop/1, config_change/3]).
|
||||
|
||||
start(_Type, _Args) ->
|
||||
_ = parse_otp_release(),
|
||||
OTP = parse_otp_release(),
|
||||
preload_common_modules(),
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8(),
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8(OTP),
|
||||
check_file_encoding(file:native_name_encoding()),
|
||||
|
||||
case application:get_env(elixir, check_endianness, true) of
|
||||
@@ -86,7 +87,10 @@ stop(Tab) ->
|
||||
config_change(_Changed, _New, _Remove) ->
|
||||
ok.
|
||||
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8() ->
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8(OTP) when OTP >= 26 ->
|
||||
ok = io:setopts(standard_io, [binary]),
|
||||
ok;
|
||||
set_stdio_and_stderr_to_binary_and_maybe_utf8(_OTP) ->
|
||||
%% In case there is a shell, we can't really change its
|
||||
%% encoding, so we just set binary to true. Otherwise
|
||||
%% we must set the encoding as the user with no shell
|
||||
@@ -260,22 +264,27 @@ env_for_eval(Opts) when is_list(Opts) ->
|
||||
|
||||
%% Quoted evaluation
|
||||
|
||||
eval_quoted(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_quoted(Tree, Binding, env_for_eval(Opts));
|
||||
eval_quoted(Tree, Binding, #{line := Line} = E) ->
|
||||
eval_forms(elixir_quote:linify(Line, line, Tree), Binding, E).
|
||||
eval_quoted(Tree, Binding, E) ->
|
||||
eval_quoted(Tree, Binding, E, []).
|
||||
eval_quoted(Tree, Binding, #{line := Line} = E, Opts) ->
|
||||
eval_forms(elixir_quote:linify(Line, line, Tree), Binding, E, Opts).
|
||||
|
||||
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_forms(Tree, Binding, env_for_eval(Opts));
|
||||
eval_forms(Tree, Binding, OrigE) ->
|
||||
{ExVars, ErlVars, ErlBinding} = elixir_erl_var:load_binding(Binding),
|
||||
eval_forms(Tree, Binding, OrigE, []).
|
||||
eval_forms(Tree, Binding, OrigE, Opts) ->
|
||||
Prune = proplists:get_value(prune_binding, Opts, false),
|
||||
{ExVars, ErlVars, ErlBinding} = elixir_erl_var:load_binding(Binding, Prune),
|
||||
E = elixir_env:with_vars(OrigE, ExVars),
|
||||
S = elixir_erl_var:from_env(E, ErlVars),
|
||||
{Erl, NewErlS, NewExS, NewE} = quoted_to_erl(Tree, E, S),
|
||||
ExS = elixir_env:env_to_ex(E),
|
||||
ErlS = elixir_erl_var:from_env(E, ErlVars),
|
||||
{Erl, NewErlS, NewExS, NewE} = quoted_to_erl(Tree, ErlS, ExS, E),
|
||||
|
||||
case Erl of
|
||||
{atom, _, Atom} ->
|
||||
{Atom, Binding, NewE};
|
||||
{Literal, _, Value} when Literal == atom; Literal == float; Literal == integer ->
|
||||
if
|
||||
Prune -> {Value, [], NewE#{versioned_vars := #{}}};
|
||||
true -> {Value, Binding, NewE}
|
||||
end;
|
||||
|
||||
_ ->
|
||||
Exprs =
|
||||
@@ -286,7 +295,12 @@ eval_forms(Tree, Binding, OrigE) ->
|
||||
|
||||
ExternalHandler = eval_external_handler(NewE),
|
||||
{value, Value, NewBinding} = erl_eval:exprs(Exprs, ErlBinding, none, ExternalHandler),
|
||||
{Value, elixir_erl_var:dump_binding(NewBinding, NewExS, NewErlS), NewE}
|
||||
PruneBefore = if Prune -> length(Binding); true -> -1 end,
|
||||
|
||||
{DumpedBinding, DumpedVars} =
|
||||
elixir_erl_var:dump_binding(NewBinding, NewErlS, NewExS, PruneBefore),
|
||||
|
||||
{Value, DumpedBinding, NewE#{versioned_vars := DumpedVars}}
|
||||
end.
|
||||
|
||||
%% TODO: Remove conditional once we require Erlang/OTP 25+.
|
||||
@@ -356,14 +370,15 @@ eval_external_handler(_Env) ->
|
||||
%% Converts a quoted expression to Erlang abstract format
|
||||
|
||||
quoted_to_erl(Quoted, E) ->
|
||||
{_, S} = elixir_erl_var:from_env(E),
|
||||
quoted_to_erl(Quoted, E, S).
|
||||
{_, ErlS} = elixir_erl_var:from_env(E),
|
||||
ExS = elixir_env:env_to_ex(E),
|
||||
quoted_to_erl(Quoted, ErlS, ExS, E).
|
||||
|
||||
quoted_to_erl(Quoted, Env, Scope) ->
|
||||
{Expanded, #elixir_ex{vars={ReadVars, _}} = NewExS, NewEnv} =
|
||||
elixir_expand:expand(Quoted, elixir_env:env_to_ex(Env), Env),
|
||||
{Erl, NewErlS} = elixir_erl_pass:translate(Expanded, erl_anno:new(?key(Env, line)), Scope),
|
||||
{Erl, NewErlS, NewExS, NewEnv#{versioned_vars := ReadVars}}.
|
||||
quoted_to_erl(Quoted, ErlS, ExS, Env) ->
|
||||
{Expanded, NewExS, NewEnv} =
|
||||
elixir_expand:expand(Quoted, ExS, Env),
|
||||
{Erl, NewErlS} = elixir_erl_pass:translate(Expanded, erl_anno:new(?key(Env, line)), ErlS),
|
||||
{Erl, NewErlS, NewExS, NewEnv}.
|
||||
|
||||
%% Converts a given string (charlist) into quote expression
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
'MACRO-defmodule'(_Caller, Alias, [{do, Block}]) ->
|
||||
Escaped = elixir_quote:escape(Block, none, false),
|
||||
Args = [Alias, Escaped, [], env()],
|
||||
Args = [Alias, Escaped, [], false, env()],
|
||||
{{'.', [], [elixir_module, compile]}, [], Args}.
|
||||
|
||||
'__info__'(functions) ->
|
||||
|
||||
@@ -37,7 +37,8 @@ eval_or_compile(Forms, Args, E) ->
|
||||
(not elixir_config:is_bootstrap()) of
|
||||
true -> fast_compile(Forms, E);
|
||||
false -> compile(Forms, Args, E)
|
||||
end.
|
||||
end,
|
||||
ok.
|
||||
|
||||
compile(Quoted, ArgsList, E) ->
|
||||
{Expanded, SE, EE} = elixir_expand:expand(Quoted, elixir_env:env_to_ex(E), E),
|
||||
@@ -47,7 +48,7 @@ compile(Quoted, ArgsList, E) ->
|
||||
elixir_erl_compiler:spawn(fun() -> spawned_compile(Expanded, E) end),
|
||||
|
||||
Args = list_to_tuple(ArgsList),
|
||||
{dispatch(Module, Fun, Args, Purgeable), EE}.
|
||||
{dispatch(Module, Fun, Args, Purgeable), SE, EE}.
|
||||
|
||||
spawned_compile(ExExprs, #{line := Line, file := File} = E) ->
|
||||
{Vars, S} = elixir_erl_var:from_env(E),
|
||||
@@ -126,7 +127,7 @@ fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
|
||||
end,
|
||||
|
||||
ContextModules = [Expanded | ?key(E, context_modules)],
|
||||
elixir_module:compile(Expanded, Block, [], E#{context_modules := ContextModules}).
|
||||
elixir_module:compile(Expanded, Block, [], false, E#{context_modules := ContextModules}).
|
||||
|
||||
%% Bootstrapper
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ reset_unused_vars(#elixir_ex{unused={_Unused, Version}} = S) ->
|
||||
|
||||
check_unused_vars(#elixir_ex{unused={Unused, _Version}}, E) ->
|
||||
[elixir_errors:form_warn([{line, Line}], E, ?MODULE, {unused_var, Name, Overridden}) ||
|
||||
{{Name, _}, {Line, Overridden}} <- maps:to_list(Unused), is_unused_var(Name)],
|
||||
{{{Name, nil}, _}, {Line, Overridden}} <- maps:to_list(Unused), is_unused_var(Name)],
|
||||
E.
|
||||
|
||||
merge_and_check_unused_vars(S, #elixir_ex{vars={Read, Write}, unused={Unused, _Version}}, E) ->
|
||||
@@ -93,20 +93,18 @@ merge_and_check_unused_vars(S, #elixir_ex{vars={Read, Write}, unused={Unused, _V
|
||||
|
||||
merge_and_check_unused_vars(Current, Unused, ClauseUnused, E) ->
|
||||
maps:fold(fun
|
||||
({Name, Count} = Key, false, Acc) ->
|
||||
Var = {Name, nil},
|
||||
|
||||
%% The parent knows it, so we have to propagate it was used up.
|
||||
({Var, Count} = Key, false, Acc) ->
|
||||
case Current of
|
||||
#{Var := CurrentCount} when Count =< CurrentCount ->
|
||||
%% The parent knows it, so we have to propagate it was used up.
|
||||
Acc#{Key => false};
|
||||
|
||||
#{} ->
|
||||
Acc
|
||||
end;
|
||||
|
||||
({Name, _Count}, {Line, Overridden}, Acc) ->
|
||||
case is_unused_var(Name) of
|
||||
({{Name, Kind}, _Count}, {Line, Overridden}, Acc) ->
|
||||
case (Kind == nil) andalso is_unused_var(Name) of
|
||||
true ->
|
||||
Warn = {unused_var, Name, Overridden},
|
||||
elixir_errors:form_warn([{line, Line}], E, ?MODULE, Warn);
|
||||
|
||||
@@ -24,7 +24,7 @@ debug_info(core_v1, _Module, {elixir_v1, Map, Specs}, Opts) ->
|
||||
%% warnings nor the other functionality provided there.
|
||||
case elixir_erl_compiler:erl_to_core(Prefix ++ Specs ++ Forms, AllOpts) of
|
||||
{ok, CoreForms, _} ->
|
||||
try compile:noenv_forms(CoreForms, [no_spawn_compiler_process, from_core, core, return | AllOpts]) of
|
||||
try compile:noenv_forms(CoreForms, [no_spawn_compiler_process, from_core, to_core, return | AllOpts]) of
|
||||
{ok, _, Core, _} -> {ok, Core};
|
||||
_What -> {error, failed_conversion}
|
||||
catch
|
||||
@@ -65,7 +65,8 @@ elixir_to_erl([], Ann) ->
|
||||
elixir_to_erl(<<>>, Ann) ->
|
||||
{bin, Ann, []};
|
||||
elixir_to_erl(#{} = Map, Ann) ->
|
||||
Assocs = [{map_field_assoc, Ann, elixir_to_erl(K, Ann), elixir_to_erl(V, Ann)} || {K, V} <- maps:to_list(Map)],
|
||||
Assocs = [{map_field_assoc, Ann, elixir_to_erl(K, Ann), elixir_to_erl(V, Ann)}
|
||||
|| {K, V} <- lists:sort(maps:to_list(Map))],
|
||||
{map, Ann, Assocs};
|
||||
elixir_to_erl(Tree, Ann) when is_list(Tree) ->
|
||||
elixir_to_erl_cons(Tree, Ann);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
-module(elixir_erl_for).
|
||||
-export([translate/4]).
|
||||
-export([translate/3]).
|
||||
-include("elixir.hrl").
|
||||
|
||||
translate(Meta, Args, Return, S) ->
|
||||
translate(Meta, Args, S) ->
|
||||
{Cases, [{do, Expr} | Opts]} = elixir_utils:split_last(Args),
|
||||
|
||||
case lists:keyfind(reduce, 1, Opts) of
|
||||
{reduce, Reduce} -> translate_reduce(Meta, Cases, Expr, Reduce, S);
|
||||
false -> translate_into(Meta, Cases, Expr, Opts, Return, S)
|
||||
false -> translate_into(Meta, Cases, Expr, Opts, S)
|
||||
end.
|
||||
|
||||
translate_reduce(Meta, Cases, Expr, Reduce, S) ->
|
||||
@@ -23,13 +23,12 @@ translate_reduce(Meta, Cases, Expr, Reduce, S) ->
|
||||
|
||||
build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE).
|
||||
|
||||
translate_into(Meta, Cases, Expr, Opts, Return, S) ->
|
||||
translate_into(Meta, Cases, Expr, Opts, S) ->
|
||||
Ann = ?ann(Meta),
|
||||
|
||||
{TInto, SI} =
|
||||
case lists:keyfind(into, 1, Opts) of
|
||||
{into, Into} -> elixir_erl_pass:translate(Into, Ann, S);
|
||||
false when Return -> {{nil, Ann}, S};
|
||||
false -> {false, S}
|
||||
end,
|
||||
|
||||
|
||||
@@ -182,7 +182,7 @@ translate({'receive', Meta, [Opts]}, _Ann, S) ->
|
||||
%% Comprehensions and with
|
||||
|
||||
translate({for, Meta, [_ | _] = Args}, _Ann, S) ->
|
||||
elixir_erl_for:translate(Meta, Args, true, S);
|
||||
elixir_erl_for:translate(Meta, Args, S);
|
||||
|
||||
translate({with, Meta, [_ | _] = Args}, _Ann, S) ->
|
||||
{Exprs, [{do, Do} | Opts]} = elixir_utils:split_last(Args),
|
||||
@@ -348,12 +348,6 @@ translate_block([H], Ann, Acc, S) ->
|
||||
translate_block([{'__block__', Meta, Args} | T], Ann, Acc, S) when is_list(Args) ->
|
||||
{TAcc, SA} = translate_block(Args, ?ann(Meta), Acc, S),
|
||||
translate_block(T, Ann, TAcc, SA);
|
||||
translate_block([{for, Meta, [_ | _] = Args} | T], Ann, Acc, S) ->
|
||||
{TH, TS} = elixir_erl_for:translate(Meta, Args, false, S),
|
||||
translate_block(T, Ann, [TH | Acc], TS);
|
||||
translate_block([{'=', _, [{'_', _, Ctx}, {for, Meta, [_ | _] = Args}]} | T], Ann, Acc, S) when is_atom(Ctx) ->
|
||||
{TH, TS} = elixir_erl_for:translate(Meta, Args, false, S),
|
||||
translate_block(T, Ann, [TH | Acc], TS);
|
||||
translate_block([H | T], Ann, Acc, S) ->
|
||||
{TH, TS} = translate(H, Ann, S),
|
||||
translate_block(T, Ann, [TH | Acc], TS).
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-module(elixir_erl_var).
|
||||
-export([
|
||||
translate/4, assign/2, build/2,
|
||||
load_binding/1, dump_binding/3,
|
||||
load_binding/2, dump_binding/4,
|
||||
from_env/1, from_env/2
|
||||
]).
|
||||
-include("elixir.hrl").
|
||||
@@ -65,16 +65,16 @@ to_erl_vars([], _Counter) ->
|
||||
to_erl_var(Counter) ->
|
||||
list_to_atom("_@" ++ integer_to_list(Counter)).
|
||||
|
||||
load_binding(Binding) ->
|
||||
load_binding(Binding, #{}, [], [], 0).
|
||||
load_binding(Binding, Prune) ->
|
||||
load_binding(Binding, #{}, [], [], 0, Prune).
|
||||
|
||||
load_binding([Binding | NextBindings], ExVars, ErlVars, Normalized, Counter) ->
|
||||
load_binding([Binding | NextBindings], ExVars, ErlVars, Normalized, Counter, Prune) ->
|
||||
{Pair, Value} = load_pair(Binding),
|
||||
|
||||
case ExVars of
|
||||
#{Pair := VarCounter} ->
|
||||
ErlVar = to_erl_var(VarCounter),
|
||||
load_binding(NextBindings, ExVars, ErlVars, [{ErlVar, Value} | Normalized], Counter);
|
||||
load_binding(NextBindings, ExVars, ErlVars, [{ErlVar, Value} | Normalized], Counter, Prune);
|
||||
|
||||
#{} ->
|
||||
ErlVar = to_erl_var(Counter),
|
||||
@@ -84,10 +84,13 @@ load_binding([Binding | NextBindings], ExVars, ErlVars, Normalized, Counter) ->
|
||||
ExVars#{Pair => Counter},
|
||||
[{Counter, ErlVar} | ErlVars],
|
||||
[{ErlVar, Value} | Normalized],
|
||||
Counter + 1
|
||||
Counter + 1,
|
||||
Prune
|
||||
)
|
||||
end;
|
||||
load_binding([], ExVars, ErlVars, Normalized, _Counter) ->
|
||||
load_binding([], ExVars, ErlVars, Normalized, Counter, true) ->
|
||||
load_binding([{{elixir, prune_binding}, true}], ExVars, ErlVars, Normalized, Counter, false);
|
||||
load_binding([], ExVars, ErlVars, Normalized, _Counter, false) ->
|
||||
%% TODO: Remove me once we require Erlang/OTP 24+
|
||||
%% Also revisit dump_binding below and remove the vars field for simplicity.
|
||||
Mod =
|
||||
@@ -101,9 +104,16 @@ load_binding([], ExVars, ErlVars, Normalized, _Counter) ->
|
||||
load_pair({Key, Value}) when is_atom(Key) -> {{Key, nil}, Value};
|
||||
load_pair({Pair, Value}) -> {Pair, Value}.
|
||||
|
||||
dump_binding(Binding, #elixir_ex{vars={ExVars, _}}, #elixir_erl{var_names=ErlVars}) ->
|
||||
dump_binding(Binding, ErlS, ExS, PruneBefore) ->
|
||||
#elixir_erl{var_names=ErlVars} = ErlS,
|
||||
#elixir_ex{vars={ExVars, _}, unused={Unused, _}} = ExS,
|
||||
|
||||
maps:fold(fun
|
||||
({Var, Kind} = Pair, Version, Acc) when is_atom(Kind) ->
|
||||
({Var, Kind} = Pair, Version, {B, V})
|
||||
when is_atom(Kind),
|
||||
%% If the variable is part of the pruning (usually the input binding)
|
||||
%% and is unused, we removed it from vars.
|
||||
Version > PruneBefore orelse is_map_key({Pair, Version}, Unused) ->
|
||||
Key = case Kind of
|
||||
nil -> Var;
|
||||
_ -> Pair
|
||||
@@ -111,11 +121,14 @@ dump_binding(Binding, #elixir_ex{vars={ExVars, _}}, #elixir_erl{var_names=ErlVar
|
||||
|
||||
ErlName = maps:get(Version, ErlVars),
|
||||
Value = find_binding(ErlName, Binding),
|
||||
[{Key, Value} | Acc];
|
||||
{[{Key, Value} | B], V};
|
||||
|
||||
(Pair, _, {B, V}) when PruneBefore >= 0 ->
|
||||
{B, maps:remove(Pair, V)};
|
||||
|
||||
(_, _, Acc) ->
|
||||
Acc
|
||||
end, [], ExVars).
|
||||
end, {[], ExVars}, ExVars).
|
||||
|
||||
find_binding(ErlName, Binding = #{}) ->
|
||||
case Binding of
|
||||
|
||||
@@ -173,7 +173,7 @@ expand({quote, Meta, [Opts, Do]}, S, E) when is_list(Do) ->
|
||||
|
||||
{File, Line} = case lists:keyfind(location, 1, EOpts) of
|
||||
{location, keep} ->
|
||||
{elixir_utils:relative_to_cwd(?key(E, file)), false};
|
||||
{?key(E, file), false};
|
||||
false ->
|
||||
{proplists:get_value(file, EOpts, nil), proplists:get_value(line, EOpts, false)}
|
||||
end,
|
||||
@@ -253,45 +253,8 @@ expand({'try', Meta, [Opts]}, S, E) ->
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand({for, Meta, [_ | _] = Args}, S, E) ->
|
||||
assert_no_match_or_guard_scope(Meta, "for", S, E),
|
||||
|
||||
{Cases, Block} =
|
||||
case elixir_utils:split_last(Args) of
|
||||
{OuterCases, OuterOpts} when is_list(OuterOpts) ->
|
||||
case elixir_utils:split_last(OuterCases) of
|
||||
{InnerCases, InnerOpts} when is_list(InnerOpts) ->
|
||||
{InnerCases, InnerOpts ++ OuterOpts};
|
||||
_ ->
|
||||
{OuterCases, OuterOpts}
|
||||
end;
|
||||
_ ->
|
||||
{Args, []}
|
||||
end,
|
||||
|
||||
validate_opts(Meta, for, [do, into, uniq, reduce], Block, E),
|
||||
|
||||
{Expr, Opts} =
|
||||
case lists:keytake(do, 1, Block) of
|
||||
{value, {do, Do}, DoOpts} ->
|
||||
{Do, DoOpts};
|
||||
false ->
|
||||
form_error(Meta, E, ?MODULE, {missing_option, for, [do]})
|
||||
end,
|
||||
|
||||
{EOpts, SO, EO} = expand(Opts, elixir_env:reset_unused_vars(S), E),
|
||||
{ECases, SC, EC} = mapfold(fun expand_for/3, SO, EO, Cases),
|
||||
assert_generator_start(Meta, ECases, E),
|
||||
|
||||
{EExpr, SE, EE} =
|
||||
case validate_for_options(EOpts, false, false, false) of
|
||||
{ok, MaybeReduce} -> expand_for_do_block(Meta, Expr, SC, EC, MaybeReduce);
|
||||
{error, Error} -> form_error(Meta, E, ?MODULE, Error)
|
||||
end,
|
||||
|
||||
{{for, Meta, ECases ++ [[{do, EExpr} | EOpts]]},
|
||||
elixir_env:merge_and_check_unused_vars(SE, S, EE),
|
||||
E};
|
||||
expand({for, _, [_ | _] } = Expr, S, E) ->
|
||||
expand_for(Expr, S, E, true);
|
||||
|
||||
%% With
|
||||
|
||||
@@ -344,7 +307,7 @@ expand({Name, Meta, Kind}, S, #{context := match} = E) when is_atom(Name), is_at
|
||||
%% Variable was already overridden
|
||||
#{Pair := VarVersion} when VarVersion >= PrematchVersion ->
|
||||
maybe_warn_underscored_var_repeat(Meta, Name, Kind, E),
|
||||
NewUnused = var_used(Pair, VarVersion, Unused),
|
||||
NewUnused = var_used(Meta, Pair, VarVersion, Unused),
|
||||
Var = {Name, [{version, VarVersion} | Meta], Kind},
|
||||
{Var, S#elixir_ex{unused={NewUnused, Version}}, E};
|
||||
|
||||
@@ -391,7 +354,7 @@ expand({Name, Meta, Kind}, S, E) when is_atom(Name), is_atom(Kind) ->
|
||||
{ok, PairVersion} ->
|
||||
maybe_warn_underscored_var_access(Meta, Name, Kind, E),
|
||||
Var = {Name, [{version, PairVersion} | Meta], Kind},
|
||||
{Var, S#elixir_ex{unused={var_used(Pair, PairVersion, Unused), Version}}, E};
|
||||
{Var, S#elixir_ex{unused={var_used(Meta, Pair, PairVersion, Unused), Version}}, E};
|
||||
|
||||
Error ->
|
||||
case lists:keyfind(if_undefined, 1, Meta) of
|
||||
@@ -405,7 +368,7 @@ expand({Name, Meta, Kind}, S, E) when is_atom(Name), is_atom(Kind) ->
|
||||
%% TODO: Remove this clause on v2.0
|
||||
_ when Error == warn ->
|
||||
elixir_errors:form_warn(Meta, E, ?MODULE, {unknown_variable, Name}),
|
||||
expand({Name, Meta, []}, S, E);
|
||||
expand({Name, [{if_undefined, warn} | Meta], []}, S, E);
|
||||
|
||||
_ when Error == pin ->
|
||||
form_error(Meta, E, ?MODULE, {undefined_var_pin, Name, Kind});
|
||||
@@ -563,6 +526,12 @@ expand_block([], Acc, _Meta, S, E) ->
|
||||
expand_block([H], Acc, Meta, S, E) ->
|
||||
{EH, SE, EE} = expand(H, S, E),
|
||||
expand_block([], [EH | Acc], Meta, SE, EE);
|
||||
expand_block([{for, _, [_ | _]} = H | T], Acc, Meta, S, E) ->
|
||||
{EH, SE, EE} = expand_for(H, S, E, false),
|
||||
expand_block(T, [EH | Acc], Meta, SE, EE);
|
||||
expand_block([{'=', _, [{'_', _, Ctx}, {for, _, [_ | _]} = H]} | T], Acc, Meta, S, E) when is_atom(Ctx) ->
|
||||
{EH, SE, EE} = expand_for(H, S, E, false),
|
||||
expand_block(T, [EH | Acc], Meta, SE, EE);
|
||||
expand_block([H | T], Acc, Meta, S, E) ->
|
||||
{EH, SE, EE} = expand(H, S, E),
|
||||
|
||||
@@ -638,16 +607,19 @@ mapfold(_Fun, S, E, [], Acc) ->
|
||||
|
||||
%% Match/var helpers
|
||||
|
||||
var_unused({Name, Kind}, Meta, Version, Unused, Override) ->
|
||||
var_unused({_, Kind} = Pair, Meta, Version, Unused, Override) ->
|
||||
case (Kind == nil) andalso should_warn(Meta) of
|
||||
true -> Unused#{{Name, Version} => {?line(Meta), Override}};
|
||||
true -> Unused#{{Pair, Version} => {?line(Meta), Override}};
|
||||
false -> Unused
|
||||
end.
|
||||
|
||||
var_used({Name, Kind}, Version, Unused) ->
|
||||
case Kind of
|
||||
nil -> Unused#{{Name, Version} => false};
|
||||
_ -> Unused
|
||||
var_used(Meta, {_, Kind} = Pair, Version, Unused) ->
|
||||
KeepUnused = lists:keymember(keep_unused, 1, Meta),
|
||||
|
||||
if
|
||||
KeepUnused -> Unused;
|
||||
is_atom(Kind) -> Unused#{{Pair, Version} => false};
|
||||
true -> Unused
|
||||
end.
|
||||
|
||||
maybe_warn_underscored_var_repeat(Meta, Name, Kind, E) ->
|
||||
@@ -741,18 +713,65 @@ generated_case_clauses([{do, Clauses}]) ->
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
validate_for_options([{into, _} = Pair | Opts], _Into, Uniq, Reduce) ->
|
||||
validate_for_options(Opts, Pair, Uniq, Reduce);
|
||||
validate_for_options([{uniq, Boolean} = Pair | Opts], Into, _Uniq, Reduce) when is_boolean(Boolean) ->
|
||||
validate_for_options(Opts, Into, Pair, Reduce);
|
||||
validate_for_options([{uniq, Value} | _], _, _, _) ->
|
||||
expand_for({for, Meta, [_ | _] = Args}, S, E, Return) ->
|
||||
assert_no_match_or_guard_scope(Meta, "for", S, E),
|
||||
|
||||
{Cases, Block} =
|
||||
case elixir_utils:split_last(Args) of
|
||||
{OuterCases, OuterOpts} when is_list(OuterOpts) ->
|
||||
case elixir_utils:split_last(OuterCases) of
|
||||
{InnerCases, InnerOpts} when is_list(InnerOpts) ->
|
||||
{InnerCases, InnerOpts ++ OuterOpts};
|
||||
_ ->
|
||||
{OuterCases, OuterOpts}
|
||||
end;
|
||||
_ ->
|
||||
{Args, []}
|
||||
end,
|
||||
|
||||
validate_opts(Meta, for, [do, into, uniq, reduce], Block, E),
|
||||
|
||||
{Expr, Opts} =
|
||||
case lists:keytake(do, 1, Block) of
|
||||
{value, {do, Do}, DoOpts} ->
|
||||
{Do, DoOpts};
|
||||
false ->
|
||||
form_error(Meta, E, ?MODULE, {missing_option, for, [do]})
|
||||
end,
|
||||
|
||||
{EOpts, SO, EO} = expand(Opts, elixir_env:reset_unused_vars(S), E),
|
||||
{ECases, SC, EC} = mapfold(fun expand_for_generator/3, SO, EO, Cases),
|
||||
assert_generator_start(Meta, ECases, E),
|
||||
|
||||
{{EExpr, SE, EE}, NormalizedOpts} =
|
||||
case validate_for_options(EOpts, false, false, false, Return, Meta, E, []) of
|
||||
{ok, MaybeReduce, NOpts} -> {expand_for_do_block(Meta, Expr, SC, EC, MaybeReduce), NOpts};
|
||||
{error, Error} -> {form_error(Meta, E, ?MODULE, Error), EOpts}
|
||||
end,
|
||||
|
||||
{{for, Meta, ECases ++ [[{do, EExpr} | NormalizedOpts]]},
|
||||
elixir_env:merge_and_check_unused_vars(SE, S, EE),
|
||||
E}.
|
||||
|
||||
validate_for_options([{into, _} = Pair | Opts], _Into, Uniq, Reduce, Return, Meta, E, Acc) ->
|
||||
validate_for_options(Opts, Pair, Uniq, Reduce, Return, Meta, E, [Pair | Acc]);
|
||||
validate_for_options([{uniq, Boolean} = Pair | Opts], Into, _Uniq, Reduce, Return, Meta, E, Acc) when is_boolean(Boolean) ->
|
||||
validate_for_options(Opts, Into, Pair, Reduce, Return, Meta, E, [Pair | Acc]);
|
||||
validate_for_options([{uniq, Value} | _], _, _, _, _, _, _, _) ->
|
||||
{error, {for_invalid_uniq, Value}};
|
||||
validate_for_options([{reduce, _} = Pair | Opts], Into, Uniq, _Reduce) ->
|
||||
validate_for_options(Opts, Into, Uniq, Pair);
|
||||
validate_for_options([], Into, Uniq, {reduce, _}) when Into /= false; Uniq /= false ->
|
||||
validate_for_options([{reduce, _} = Pair | Opts], Into, Uniq, _Reduce, Return, Meta, E, Acc) ->
|
||||
validate_for_options(Opts, Into, Uniq, Pair, Return, Meta, E, [Pair | Acc]);
|
||||
validate_for_options([], Into, Uniq, {reduce, _}, _Return, _Meta, _E, _Acc) when Into /= false; Uniq /= false ->
|
||||
{error, for_conflicting_reduce_into_uniq};
|
||||
validate_for_options([], _Into, _Uniq, Reduce) ->
|
||||
{ok, Reduce}.
|
||||
validate_for_options([], _Into = false, Uniq, Reduce = false, Return = true, Meta, E, Acc) ->
|
||||
Pair = {into, []},
|
||||
validate_for_options([Pair], Pair, Uniq, Reduce, Return, Meta, E, Acc);
|
||||
validate_for_options([], Into = false, {uniq, true}, Reduce = false, Return = false, Meta, E, Acc) ->
|
||||
elixir_errors:form_warn(Meta, E, ?MODULE, for_with_unused_uniq),
|
||||
AccWithoutUniq = lists:keydelete(uniq, 1, Acc),
|
||||
validate_for_options([], Into, false, Reduce, Return, Meta, E, AccWithoutUniq);
|
||||
validate_for_options([], _Into, _Uniq, Reduce, _Return, _Meta, _E, Acc) ->
|
||||
{ok, Reduce, lists:reverse(Acc)}.
|
||||
|
||||
expand_for_do_block(Meta, [{'->', _, _} | _], _S, E, false) ->
|
||||
form_error(Meta, E, ?MODULE, for_without_reduce_bad_block);
|
||||
@@ -1031,12 +1050,12 @@ expand_aliases({'__aliases__', Meta, _} = Alias, S, E, Report) ->
|
||||
|
||||
%% Comprehensions
|
||||
|
||||
expand_for({'<-', Meta, [Left, Right]}, S, E) ->
|
||||
expand_for_generator({'<-', Meta, [Left, Right]}, S, E) ->
|
||||
{ERight, SR, ER} = expand(Right, S, E),
|
||||
SM = elixir_env:reset_read(SR, S),
|
||||
{[ELeft], SL, EL} = elixir_clauses:head([Left], SM, ER),
|
||||
{{'<-', Meta, [ELeft, ERight]}, SL, EL};
|
||||
expand_for({'<<>>', Meta, Args} = X, S, E) when is_list(Args) ->
|
||||
expand_for_generator({'<<>>', Meta, Args} = X, S, E) when is_list(Args) ->
|
||||
case elixir_utils:split_last(Args) of
|
||||
{LeftStart, {'<-', OpMeta, [LeftEnd, Right]}} ->
|
||||
{ERight, SR, ER} = expand(Right, S, E),
|
||||
@@ -1048,7 +1067,7 @@ expand_for({'<<>>', Meta, Args} = X, S, E) when is_list(Args) ->
|
||||
_ ->
|
||||
expand(X, S, E)
|
||||
end;
|
||||
expand_for(X, S, E) ->
|
||||
expand_for_generator(X, S, E) ->
|
||||
expand(X, S, E).
|
||||
|
||||
assert_generator_start(_, [{'<-', _, [_, _]} | _], _) ->
|
||||
@@ -1170,6 +1189,8 @@ format_error(for_without_reduce_bad_block) ->
|
||||
"the do block was written using acc -> expr clauses but the :reduce option was not given";
|
||||
format_error(for_generator_start) ->
|
||||
"for comprehensions must start with a generator";
|
||||
format_error(for_with_unused_uniq) ->
|
||||
"the :uniq option has no effect since the result of the for comprehension is not used";
|
||||
format_error(unhandled_arrow_op) ->
|
||||
"unhandled operator ->";
|
||||
format_error(as_in_multi_alias_call) ->
|
||||
|
||||
@@ -39,7 +39,7 @@ extract([$\\, Last, Last, Last | Rest], Buffer, Output, Line, Column, Scope, Int
|
||||
extract(Rest, [Last, Last, Last | Buffer], Output, Line, Column+4, Scope, Interpol, All);
|
||||
|
||||
extract([$\\, $#, ${ | Rest], Buffer, Output, Line, Column, Scope, true, Last) ->
|
||||
extract(Rest, [${, $#, $\\ | Buffer], Output, Line, Column+1, Scope, true, Last);
|
||||
extract(Rest, [${, $#, $\\ | Buffer], Output, Line, Column+3, Scope, true, Last);
|
||||
|
||||
extract([$#, ${ | Rest], Buffer, Output, Line, Column, Scope, true, Last) ->
|
||||
Output1 = build_string(Buffer, Output),
|
||||
|
||||
@@ -25,7 +25,7 @@ expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs}, S, #{context := Context} =
|
||||
AssocKeys = [K || {K, _} <- Assocs],
|
||||
Struct = load_struct(Meta, ELeft, [Assocs], AssocKeys, EE),
|
||||
Keys = ['__struct__'] ++ AssocKeys,
|
||||
WithoutKeys = maps:to_list(maps:without(Keys, Struct)),
|
||||
WithoutKeys = lists:sort(maps:to_list(maps:without(Keys, Struct))),
|
||||
StructAssocs = elixir_quote:escape(WithoutKeys, none, false),
|
||||
{{'%', Meta, [ELeft, {'%{}', MapMeta, StructAssocs ++ Assocs}]}, SE, EE};
|
||||
|
||||
@@ -138,13 +138,19 @@ load_struct(Meta, Name, Args, Keys, E) ->
|
||||
wrapped_load_struct(Meta, Name, Args, Keys, E) ->
|
||||
%% We also include the current module because it won't be present
|
||||
%% in context module in case the module name is defined dynamically.
|
||||
InContext = lists:member(Name, [?key(E, module) | ?key(E, context_modules)]),
|
||||
Module = ?key(E, module),
|
||||
InContext = lists:member(Name, [Module | ?key(E, context_modules)]),
|
||||
|
||||
Arity = length(Args),
|
||||
External = InContext orelse (not(ensure_loaded(Name)) andalso wait_for_struct(Name)),
|
||||
|
||||
try
|
||||
case External andalso elixir_def:external_for(Meta, Name, '__struct__', Arity, [def]) of
|
||||
%% If I am accessing myself and there is no __struct__ function,
|
||||
%% don't invoke the fallback to avoid calling loaded code.
|
||||
false when Module == Name ->
|
||||
error(undef);
|
||||
|
||||
false ->
|
||||
apply(Name, '__struct__', Args);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-module(elixir_module).
|
||||
-export([file/1, data_tables/1, is_open/1, mode/1, delete_definition_attributes/6,
|
||||
compile/4, expand_callback/6, format_error/1, compiler_modules/0,
|
||||
compile/5, expand_callback/6, format_error/1, compiler_modules/0,
|
||||
write_cache/3, read_cache/2, next_counter/1]).
|
||||
-include("elixir.hrl").
|
||||
-define(counter_attr, {elixir, counter}).
|
||||
@@ -64,7 +64,7 @@ next_counter(Module) ->
|
||||
|
||||
%% Compilation hook
|
||||
|
||||
compile(Module, Block, Vars, Env) when is_atom(Module) ->
|
||||
compile(Module, Block, Vars, Prune, Env) when is_atom(Module) ->
|
||||
#{line := Line, function := Function, versioned_vars := OldVerVars} = Env,
|
||||
|
||||
{VerVars, _} =
|
||||
@@ -82,16 +82,16 @@ compile(Module, Block, Vars, Env) when is_atom(Module) ->
|
||||
#{lexical_tracker := nil} ->
|
||||
elixir_lexical:run(
|
||||
MaybeLexEnv,
|
||||
fun(LexEnv) -> compile(Line, Module, Block, Vars, LexEnv) end,
|
||||
fun(LexEnv) -> compile(Line, Module, Block, Vars, Prune, LexEnv) end,
|
||||
fun(_LexEnv) -> ok end
|
||||
);
|
||||
_ ->
|
||||
compile(Line, Module, Block, Vars, MaybeLexEnv)
|
||||
compile(Line, Module, Block, Vars, Prune, MaybeLexEnv)
|
||||
end;
|
||||
compile(Module, _Block, _Vars, #{line := Line, file := File}) ->
|
||||
compile(Module, _Block, _Vars, _Prune, #{line := Line, file := File}) ->
|
||||
elixir_errors:form_error([{line, Line}], File, ?MODULE, {invalid_module, Module}).
|
||||
|
||||
compile(Line, Module, Block, Vars, E) ->
|
||||
compile(Line, Module, Block, Vars, Prune, E) ->
|
||||
File = ?key(E, file),
|
||||
check_module_availability(Line, File, Module),
|
||||
ModuleAsCharlist = validate_module_name(Line, File, Module),
|
||||
@@ -102,7 +102,7 @@ compile(Line, Module, Block, Vars, E) ->
|
||||
|
||||
try
|
||||
put_compiler_modules([Module | CompilerModules]),
|
||||
{Result, NE} = eval_form(Line, Module, DataBag, Block, Vars, E),
|
||||
{Result, ModuleE, CallbackE} = eval_form(Line, Module, DataBag, Block, Vars, Prune, E),
|
||||
CheckerInfo = checker_info(),
|
||||
|
||||
{Binary, PersistedAttributes, Autoload} =
|
||||
@@ -133,7 +133,7 @@ compile(Line, Module, Block, Vars, E) ->
|
||||
CompileOpts = validate_compile_opts(RawCompileOpts, AllDefinitions, Unreachable, File, Line),
|
||||
|
||||
AfterVerify = bag_lookup_element(DataBag, {accumulate, after_verify}, 2),
|
||||
[elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, E) ||
|
||||
[elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, CallbackE) ||
|
||||
{VerifyMod, VerifyFun} <- AfterVerify],
|
||||
|
||||
ModuleMap = #{
|
||||
@@ -158,8 +158,8 @@ compile(Line, Module, Block, Vars, E) ->
|
||||
end),
|
||||
|
||||
Autoload andalso code:load_binary(Module, beam_location(ModuleAsCharlist), Binary),
|
||||
eval_callbacks(Line, DataBag, after_compile, [NE, Binary], NE),
|
||||
elixir_env:trace({on_module, Binary, none}, E),
|
||||
eval_callbacks(Line, DataBag, after_compile, [CallbackE, Binary], CallbackE),
|
||||
elixir_env:trace({on_module, Binary, none}, ModuleE),
|
||||
warn_unused_attributes(File, DataSet, DataBag, PersistedAttributes),
|
||||
make_module_available(Module, Binary),
|
||||
(CheckerInfo == undefined) andalso
|
||||
@@ -375,13 +375,27 @@ build(Line, File, Module) ->
|
||||
|
||||
%% Handles module and callback evaluations.
|
||||
|
||||
eval_form(Line, Module, DataBag, Block, Vars, E) ->
|
||||
{Value, EE} = elixir_compiler:compile(Block, Vars, E),
|
||||
eval_form(Line, Module, DataBag, Block, Vars, Prune, E) ->
|
||||
{Value, ExS, EE} = elixir_compiler:compile(Block, Vars, E),
|
||||
elixir_overridable:store_not_overridden(Module),
|
||||
EV = (elixir_env:reset_vars(EE))#{line := Line},
|
||||
EC = eval_callbacks(Line, DataBag, before_compile, [EV], EV),
|
||||
elixir_overridable:store_not_overridden(Module),
|
||||
{Value, EC}.
|
||||
{Value, maybe_prune_versioned_vars(Prune, Vars, ExS, E), EC}.
|
||||
|
||||
maybe_prune_versioned_vars(false, _Vars, _Exs, E) ->
|
||||
E;
|
||||
maybe_prune_versioned_vars(true, Vars, ExS, E) ->
|
||||
PruneBefore = length(Vars),
|
||||
#elixir_ex{vars={ExVars, _}, unused={Unused, _}} = ExS,
|
||||
|
||||
VersionedVars =
|
||||
maps:filter(fun
|
||||
(Pair, Version) when Version < PruneBefore, not is_map_key({Pair, Version}, Unused) -> false;
|
||||
(_, _) -> true
|
||||
end, ExVars),
|
||||
|
||||
E#{versioned_vars := VersionedVars}.
|
||||
|
||||
eval_callbacks(Line, DataBag, Name, Args, E) ->
|
||||
Callbacks = bag_lookup_element(DataBag, {accumulate, Name}, 2),
|
||||
|
||||
@@ -958,6 +958,15 @@ defmodule DateTimeTest do
|
||||
end
|
||||
|
||||
describe "add" do
|
||||
test "add with invalid time unit" do
|
||||
dt = DateTime.utc_now()
|
||||
|
||||
message =
|
||||
~r/unsupported time unit\. Expected :day, :hour, :minute, :second, :millisecond, :microsecond, :nanosecond, or a positive integer, got "day"/
|
||||
|
||||
assert_raise ArgumentError, message, fn -> DateTime.add(dt, 1, "day") end
|
||||
end
|
||||
|
||||
test "add with non-struct map that conforms to Calendar.datetime" do
|
||||
dt_map = DateTime.from_naive!(~N[2018-08-28 00:00:00], "Etc/UTC") |> Map.from_struct()
|
||||
|
||||
|
||||
@@ -364,5 +364,12 @@ defmodule CalendarTest do
|
||||
Calendar.strftime(~D[2019-08-15], "%D", unknown: "option")
|
||||
end
|
||||
end
|
||||
|
||||
test "zero padding for negative year" do
|
||||
assert Calendar.strftime(Date.new!(-1, 1, 1), "%Y") == "-0001"
|
||||
assert Calendar.strftime(Date.new!(-11, 1, 1), "%Y") == "-0011"
|
||||
assert Calendar.strftime(Date.new!(-111, 1, 1), "%Y") == "-0111"
|
||||
assert Calendar.strftime(Date.new!(-1111, 1, 1), "%Y") == "-1111"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -299,6 +299,8 @@ defmodule Code.Formatter.ContainersTest do
|
||||
|
||||
assert_format "<<x::binary()-(13 * 6)-custom>>", "<<x::binary-(13 * 6)-custom()>>"
|
||||
assert_same "<<x::binary-(13 * 6)-custom()>>"
|
||||
assert_same "<<0::size*unit, bytes::binary>>"
|
||||
assert_format "<<0::size*unit, bytes::custom>>", "<<0::size*unit, bytes::custom()>>"
|
||||
|
||||
assert_format "<<0, 1::2-integer() <- x>>", "<<0, 1::2-integer <- x>>"
|
||||
assert_same "<<0, 1::2-integer <- x>>"
|
||||
|
||||
@@ -507,6 +507,28 @@ defmodule Code.Normalizer.FormatterASTTest do
|
||||
# after
|
||||
""",
|
||||
literal_encoder: fn literal, _ -> {:ok, literal} end
|
||||
|
||||
assert_same """
|
||||
block do
|
||||
# before 1
|
||||
1 + 1
|
||||
|
||||
# before 2
|
||||
2 + 2
|
||||
end
|
||||
""",
|
||||
literal_encoder: fn literal, _ -> {:ok, literal} end
|
||||
|
||||
assert_same """
|
||||
block do
|
||||
# before 1
|
||||
Mix.install([1 + 1])
|
||||
|
||||
# before 2
|
||||
Mix.install([2 + 2])
|
||||
end
|
||||
""",
|
||||
literal_encoder: fn literal, _ -> {:ok, literal} end
|
||||
end
|
||||
|
||||
test "before and after expressions with newlines" do
|
||||
|
||||
@@ -515,6 +515,17 @@ defmodule Code.Normalizer.QuotedASTTest do
|
||||
test "charlist" do
|
||||
assert quoted_to_string(quote(do: [])) == "[]"
|
||||
assert quoted_to_string(quote(do: 'abc')) == "'abc'"
|
||||
|
||||
# False positive
|
||||
assert quoted_to_string(
|
||||
quote do
|
||||
:"Elixir.List".to_charlist([
|
||||
case var do
|
||||
var -> var
|
||||
end
|
||||
])
|
||||
end
|
||||
) =~ "List.to_charlist([\n case var do\n var -> var\n end\n])"
|
||||
end
|
||||
|
||||
test "string" do
|
||||
@@ -656,6 +667,13 @@ defmodule Code.Normalizer.QuotedASTTest do
|
||||
|
||||
assert quoted_to_string(quote(do: :"one\n\"#{2}\"\nthree")) == ~S[:"one\n\"#{2}\"\nthree"]
|
||||
end
|
||||
|
||||
test ":erlang.binary_to_atom/2 edge cases" do
|
||||
assert quoted_to_string(quote(do: :erlang.binary_to_atom(<<>>, :utf8))) == ~S[:""]
|
||||
|
||||
assert quoted_to_string(quote(do: :erlang.binary_to_atom(<<1>>, :utf8))) ==
|
||||
~S":erlang.binary_to_atom(<<1>>, :utf8)"
|
||||
end
|
||||
end
|
||||
|
||||
describe "quoted_to_algebra/2 does not escape" do
|
||||
|
||||
@@ -48,6 +48,16 @@ defmodule CodeTest do
|
||||
assert Code.eval_string("var!(a, Sample) = 1") == {1, [{{:a, Sample}, 1}]}
|
||||
end
|
||||
|
||||
defmacro hygiene_var do
|
||||
quote do
|
||||
a = 1
|
||||
end
|
||||
end
|
||||
|
||||
test "does not return bindings from macro hygiene" do
|
||||
assert Code.eval_string("require CodeTest; CodeTest.hygiene_var()") == {1, []}
|
||||
end
|
||||
|
||||
test "does not raise on duplicate bindings" do
|
||||
# The order of which values win is not guaranteed, but it should evaluate successfully.
|
||||
assert Code.eval_string("b = String.Chars.to_string(a)", a: 0, a: 1) ==
|
||||
@@ -167,6 +177,72 @@ defmodule CodeTest do
|
||||
assert Macro.Env.vars(env) == [{:x, nil}]
|
||||
end
|
||||
|
||||
test "eval_quoted_with_env/3 with pruning" do
|
||||
env = Code.env_for_eval(__ENV__)
|
||||
|
||||
fun = fn quoted, binding ->
|
||||
{_, binding, env} = Code.eval_quoted_with_env(quoted, binding, env, prune_binding: true)
|
||||
{binding, Macro.Env.vars(env)}
|
||||
end
|
||||
|
||||
assert fun.(quote(do: 123), []) == {[], []}
|
||||
assert fun.(quote(do: 123), x: 2, y: 3) == {[], []}
|
||||
|
||||
assert fun.(quote(do: var!(x) = 1), []) == {[x: 1], [x: nil]}
|
||||
assert fun.(quote(do: var!(x) = 1), x: 2, y: 3) == {[x: 1], [x: nil]}
|
||||
|
||||
assert fun.(quote(do: var!(x, :foo) = 1), []) == {[{{:x, :foo}, 1}], [x: :foo]}
|
||||
assert fun.(quote(do: var!(x, :foo) = 1), x: 2, y: 3) == {[{{:x, :foo}, 1}], [x: :foo]}
|
||||
|
||||
assert fun.(quote(do: var!(x, :foo) = 1), [{{:x, :foo}, 2}, {{:y, :foo}, 3}]) ==
|
||||
{[{{:x, :foo}, 1}], [x: :foo]}
|
||||
|
||||
assert fun.(quote(do: fn -> var!(x, :foo) = 1 end), []) == {[], []}
|
||||
assert fun.(quote(do: fn -> var!(x, :foo) = 1 end), x: 1, y: 2) == {[], []}
|
||||
|
||||
assert fun.(quote(do: fn -> var!(x) end), x: 2, y: 3) == {[x: 2], [x: nil]}
|
||||
|
||||
assert fun.(quote(do: fn -> var!(x, :foo) end), [{{:x, :foo}, 2}, {{:y, :foo}, 3}]) ==
|
||||
{[{{:x, :foo}, 2}], [x: :foo]}
|
||||
end
|
||||
|
||||
defmodule Tracer do
|
||||
def trace(event, env) do
|
||||
send(self(), {:trace, event, env})
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
test "eval_quoted_with_env/3 with tracing and pruning" do
|
||||
env = %{Code.env_for_eval(__ENV__) | tracers: [Tracer], function: nil}
|
||||
binding = [x: 1, y: 2, z: 3]
|
||||
|
||||
quoted =
|
||||
quote do
|
||||
defmodule Elixir.CodeTest.TracingPruning do
|
||||
var!(y) = :updated
|
||||
var!(y)
|
||||
var!(x)
|
||||
end
|
||||
end
|
||||
|
||||
{_, binding, env} = Code.eval_quoted_with_env(quoted, binding, env, prune_binding: true)
|
||||
assert Enum.sort(binding) == []
|
||||
assert env.versioned_vars == %{}
|
||||
|
||||
assert_receive {:trace, {:on_module, _, _}, %{module: CodeTest.TracingPruning} = trace_env}
|
||||
assert trace_env.versioned_vars == %{{:result, Kernel} => 5, {:x, nil} => 1, {:y, nil} => 4}
|
||||
end
|
||||
|
||||
test "eval_quoted_with_env/3 with defguard" do
|
||||
require Integer
|
||||
env = Code.env_for_eval(__ENV__)
|
||||
quoted = quote do: Integer.is_even(1)
|
||||
{false, binding, env} = Code.eval_quoted_with_env(quoted, [], env, prune_binding: true)
|
||||
assert binding == []
|
||||
assert Macro.Env.vars(env) == []
|
||||
end
|
||||
|
||||
test "compile_file/1" do
|
||||
assert Code.compile_file(fixture_path("code_sample.exs")) == []
|
||||
refute fixture_path("code_sample.exs") in Code.required_files()
|
||||
|
||||
@@ -735,11 +735,11 @@ defmodule EnumTest do
|
||||
|
||||
assert Enum.split_with(%{}, fn x -> rem(x, 2) == 0 end) == {[], []}
|
||||
|
||||
assert Enum.split_with(%{a: 1, b: 2, c: 3}, fn {_k, v} -> rem(v, 2) == 0 end) ==
|
||||
{[b: 2], [a: 1, c: 3]}
|
||||
assert Enum.split_with(%{a: 1, b: 2}, fn {_k, v} -> rem(v, 2) == 0 end) ==
|
||||
{[b: 2], [a: 1]}
|
||||
|
||||
assert Enum.split_with(%{b: 2, d: 4, f: 6}, fn {_k, v} -> rem(v, 2) == 0 end) ==
|
||||
{[b: 2, d: 4, f: 6], []}
|
||||
{Map.to_list(%{b: 2, d: 4, f: 6}), []}
|
||||
end
|
||||
|
||||
test "random/1" do
|
||||
@@ -1503,14 +1503,11 @@ defmodule EnumTest do
|
||||
assert result == [2, 4, 6, 8, 10]
|
||||
|
||||
# Maps
|
||||
result = Enum.zip_with(%{a: 7, c: 9}, 3..4, fn {key, value}, b -> {key, value + b} end)
|
||||
assert result == [a: 10, c: 13]
|
||||
result = Enum.zip_with(%{a: 7}, 3..4, fn {key, value}, b -> {key, value + b} end)
|
||||
assert result == [a: 10]
|
||||
|
||||
colour_1 = %{r: 176, g: 175, b: 255}
|
||||
colour_2 = %{r: 12, g: 176, b: 176}
|
||||
|
||||
result = Enum.zip_with(colour_1, colour_2, fn {k, left}, {k, right} -> {k, left + right} end)
|
||||
assert result == [b: 431, g: 351, r: 188]
|
||||
result = Enum.zip_with(3..4, %{a: 7}, fn a, {key, value} -> {key, value + a} end)
|
||||
assert result == [a: 10]
|
||||
end
|
||||
|
||||
test "zip_with/2" do
|
||||
@@ -1518,8 +1515,10 @@ defmodule EnumTest do
|
||||
result = Enum.zip_with([[:a, :b], [1, 2], ["foo", "bar"]], zip_fun)
|
||||
assert result == [{:a, 1, "foo"}, {:b, 2, "bar"}]
|
||||
|
||||
lots = Enum.zip_with([[:a, :b], [1, 2], ["foo", "bar"], %{a: :b, c: :d}], zip_fun)
|
||||
assert lots == [{:a, 1, "foo", {:a, :b}}, {:b, 2, "bar", {:c, :d}}]
|
||||
map = %{a: :b, c: :d}
|
||||
[x1, x2] = Map.to_list(map)
|
||||
lots = Enum.zip_with([[:a, :b], [1, 2], ["foo", "bar"], map], zip_fun)
|
||||
assert lots == [{:a, 1, "foo", x1}, {:b, 2, "bar", x2}]
|
||||
|
||||
assert Enum.zip_with([[:a, :b], [1, 2, 3, 4], ["foo", "bar", "baz", "qux"]], zip_fun) ==
|
||||
[{:a, 1, "foo"}, {:b, 2, "bar"}]
|
||||
@@ -1556,21 +1555,11 @@ defmodule EnumTest do
|
||||
assert result == [2, 4, 6, 8, 10]
|
||||
|
||||
# Maps
|
||||
result = Enum.zip_with([%{a: 7, c: 9}, 3..4], fn [{key, value}, b] -> {key, value + b} end)
|
||||
assert result == [a: 10, c: 13]
|
||||
result = Enum.zip_with([%{a: 7}, 3..4], fn [{key, value}, b] -> {key, value + b} end)
|
||||
assert result == [a: 10]
|
||||
|
||||
colour_1 = %{r: 176, g: 175, b: 255}
|
||||
colour_2 = %{r: 12, g: 176, b: 176}
|
||||
|
||||
result =
|
||||
Enum.zip_with([colour_1, colour_2], fn [{k, left}, {k, right}] -> {k, left + right} end)
|
||||
|
||||
assert result == [b: 431, g: 351, r: 188]
|
||||
|
||||
assert Enum.zip_with([%{a: :b, c: :d}, %{e: :f, g: :h}], & &1) == [
|
||||
[a: :b, e: :f],
|
||||
[c: :d, g: :h]
|
||||
]
|
||||
result = Enum.zip_with([%{a: 7}, 3..4], fn [{key, value}, b] -> {key, value + b} end)
|
||||
assert result == [a: 10]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2396,16 +2385,17 @@ defmodule EnumTest.Map do
|
||||
|
||||
test "random/1" do
|
||||
map = %{a: 1, b: 2, c: 3}
|
||||
[x1, x2, x3] = Map.to_list(map)
|
||||
seed1 = {1406, 407_414, 139_258}
|
||||
seed2 = {1406, 421_106, 567_597}
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.random(map) == {:c, 3}
|
||||
assert Enum.random(map) == {:a, 1}
|
||||
assert Enum.random(map) == {:b, 2}
|
||||
assert Enum.random(map) == x3
|
||||
assert Enum.random(map) == x1
|
||||
assert Enum.random(map) == x2
|
||||
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.random(map) == {:c, 3}
|
||||
assert Enum.random(map) == {:b, 2}
|
||||
assert Enum.random(map) == x3
|
||||
assert Enum.random(map) == x2
|
||||
end
|
||||
|
||||
test "take_random/2" do
|
||||
@@ -2418,35 +2408,37 @@ defmodule EnumTest.Map do
|
||||
# set a fixed seed so the test can be deterministic
|
||||
# please note the order of following assertions is important
|
||||
map = %{a: 1, b: 2, c: 3}
|
||||
[x1, x2, x3] = Map.to_list(map)
|
||||
seed1 = {1406, 407_414, 139_258}
|
||||
seed2 = {1406, 421_106, 567_597}
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.take_random(map, 1) == [c: 3]
|
||||
assert Enum.take_random(map, 1) == [x3]
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.take_random(map, 2) == [c: 3, a: 1]
|
||||
assert Enum.take_random(map, 2) == [x3, x1]
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.take_random(map, 3) == [c: 3, a: 1, b: 2]
|
||||
assert Enum.take_random(map, 3) == [x3, x1, x2]
|
||||
:rand.seed(:exsss, seed1)
|
||||
assert Enum.take_random(map, 4) == [c: 3, a: 1, b: 2]
|
||||
assert Enum.take_random(map, 4) == [x3, x1, x2]
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.take_random(map, 1) == [a: 1]
|
||||
assert Enum.take_random(map, 1) == [x1]
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.take_random(map, 2) == [a: 1, c: 3]
|
||||
assert Enum.take_random(map, 2) == [x1, x3]
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.take_random(map, 3) == [a: 1, c: 3, b: 2]
|
||||
assert Enum.take_random(map, 3) == [x1, x3, x2]
|
||||
:rand.seed(:exsss, seed2)
|
||||
assert Enum.take_random(map, 4) == [a: 1, c: 3, b: 2]
|
||||
assert Enum.take_random(map, 4) == [x1, x3, x2]
|
||||
end
|
||||
|
||||
test "reverse/1" do
|
||||
assert Enum.reverse(%{}) == []
|
||||
assert Enum.reverse(MapSet.new()) == []
|
||||
assert Enum.reverse(%{a: 1, b: 2, c: 3}) == [c: 3, b: 2, a: 1]
|
||||
|
||||
map = %{a: 1, b: 2, c: 3}
|
||||
assert Enum.reverse(map) == Map.to_list(map) |> Enum.reverse()
|
||||
end
|
||||
|
||||
test "reverse/2" do
|
||||
assert Enum.reverse([a: 1, b: 2, c: 3, a: 1], %{x: 1, y: 2, z: 3}) ==
|
||||
[a: 1, c: 3, b: 2, a: 1, x: 1, y: 2, z: 3]
|
||||
assert Enum.reverse([a: 1, b: 2, c: 3, a: 1], %{x: 1}) == [a: 1, c: 3, b: 2, a: 1, x: 1]
|
||||
|
||||
assert Enum.reverse([], %{a: 1}) == [a: 1]
|
||||
assert Enum.reverse([], %{}) == []
|
||||
@@ -2456,44 +2448,49 @@ defmodule EnumTest.Map do
|
||||
|
||||
test "fetch/2" do
|
||||
map = %{a: 1, b: 2, c: 3, d: 4, e: 5}
|
||||
assert Enum.fetch(map, 0) == {:ok, {:a, 1}}
|
||||
assert Enum.fetch(map, -2) == {:ok, {:d, 4}}
|
||||
[x1, _x2, _x3, x4, x5] = Map.to_list(map)
|
||||
assert Enum.fetch(map, 0) == {:ok, x1}
|
||||
assert Enum.fetch(map, -2) == {:ok, x4}
|
||||
assert Enum.fetch(map, -6) == :error
|
||||
assert Enum.fetch(map, 5) == :error
|
||||
assert Enum.fetch(%{}, 0) == :error
|
||||
|
||||
assert Stream.take(map, 3) |> Enum.fetch(3) == :error
|
||||
assert Stream.take(map, 5) |> Enum.fetch(4) == {:ok, {:e, 5}}
|
||||
assert Stream.take(map, 5) |> Enum.fetch(4) == {:ok, x5}
|
||||
end
|
||||
|
||||
test "map_intersperse/3" do
|
||||
assert Enum.map_intersperse(%{}, :a, & &1) == []
|
||||
assert Enum.map_intersperse(%{foo: :bar}, :a, & &1) == [{:foo, :bar}]
|
||||
|
||||
assert Enum.map_intersperse(%{foo: :bar, baz: :bat}, :a, & &1) ==
|
||||
[{:baz, :bat}, :a, {:foo, :bar}]
|
||||
map = %{foo: :bar, baz: :bat}
|
||||
[x1, x2] = Map.to_list(map)
|
||||
|
||||
assert Enum.map_intersperse(map, :a, & &1) == [x1, :a, x2]
|
||||
end
|
||||
|
||||
test "slice/2" do
|
||||
map = %{a: 1, b: 2, c: 3, d: 4, e: 5}
|
||||
assert Enum.slice(map, 0..0) == [a: 1]
|
||||
assert Enum.slice(map, 0..1) == [a: 1, b: 2]
|
||||
assert Enum.slice(map, 0..2) == [a: 1, b: 2, c: 3]
|
||||
[x1, x2, x3 | _] = Map.to_list(map)
|
||||
assert Enum.slice(map, 0..0) == [x1]
|
||||
assert Enum.slice(map, 0..1) == [x1, x2]
|
||||
assert Enum.slice(map, 0..2) == [x1, x2, x3]
|
||||
end
|
||||
|
||||
test "slice/3" do
|
||||
map = %{a: 1, b: 2, c: 3, d: 4, e: 5}
|
||||
assert Enum.slice(map, 1, 2) == [b: 2, c: 3]
|
||||
[x1, x2, x3, x4, x5] = Map.to_list(map)
|
||||
assert Enum.slice(map, 1, 2) == [x2, x3]
|
||||
assert Enum.slice(map, 1, 0) == []
|
||||
assert Enum.slice(map, 2, 5) == [c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, 2, 6) == [c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, 2, 5) == [x3, x4, x5]
|
||||
assert Enum.slice(map, 2, 6) == [x3, x4, x5]
|
||||
assert Enum.slice(map, 5, 5) == []
|
||||
assert Enum.slice(map, 6, 5) == []
|
||||
assert Enum.slice(map, 6, 0) == []
|
||||
assert Enum.slice(map, -6, 0) == []
|
||||
assert Enum.slice(map, -6, 5) == [a: 1, b: 2, c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, -2, 5) == [d: 4, e: 5]
|
||||
assert Enum.slice(map, -3, 1) == [c: 3]
|
||||
assert Enum.slice(map, -6, 5) == [x1, x2, x3, x4, x5]
|
||||
assert Enum.slice(map, -2, 5) == [x4, x5]
|
||||
assert Enum.slice(map, -3, 1) == [x3]
|
||||
|
||||
assert_raise FunctionClauseError, fn ->
|
||||
Enum.slice(map, 0, -1)
|
||||
@@ -2508,19 +2505,19 @@ defmodule EnumTest.Map do
|
||||
end
|
||||
|
||||
assert Enum.slice(map, 0, 0) == []
|
||||
assert Enum.slice(map, 0, 1) == [a: 1]
|
||||
assert Enum.slice(map, 0, 2) == [a: 1, b: 2]
|
||||
assert Enum.slice(map, 1, 2) == [b: 2, c: 3]
|
||||
assert Enum.slice(map, 0, 1) == [x1]
|
||||
assert Enum.slice(map, 0, 2) == [x1, x2]
|
||||
assert Enum.slice(map, 1, 2) == [x2, x3]
|
||||
assert Enum.slice(map, 1, 0) == []
|
||||
assert Enum.slice(map, 2, 5) == [c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, 2, 6) == [c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, 2, 5) == [x3, x4, x5]
|
||||
assert Enum.slice(map, 2, 6) == [x3, x4, x5]
|
||||
assert Enum.slice(map, 5, 5) == []
|
||||
assert Enum.slice(map, 6, 5) == []
|
||||
assert Enum.slice(map, 6, 0) == []
|
||||
assert Enum.slice(map, -6, 0) == []
|
||||
assert Enum.slice(map, -6, 5) == [a: 1, b: 2, c: 3, d: 4, e: 5]
|
||||
assert Enum.slice(map, -2, 5) == [d: 4, e: 5]
|
||||
assert Enum.slice(map, -3, 1) == [c: 3]
|
||||
assert Enum.slice(map, -6, 5) == [x1, x2, x3, x4, x5]
|
||||
assert Enum.slice(map, -2, 5) == [x4, x5]
|
||||
assert Enum.slice(map, -3, 1) == [x3]
|
||||
|
||||
assert_raise FunctionClauseError, fn ->
|
||||
Enum.slice(map, 0, -1)
|
||||
|
||||
@@ -18,13 +18,20 @@ defmodule ExceptionTest do
|
||||
end
|
||||
end
|
||||
|
||||
assert Exception.message(%{__struct__: BadException, __exception__: true, raise: true}) =~
|
||||
"got RuntimeError with message \"oops\" while retrieving Exception.message/1 " <>
|
||||
"for %{__exception__: true, __struct__: ExceptionTest.BadException, raise: true}"
|
||||
assert "got RuntimeError with message \"oops\" while retrieving Exception.message/1 for %{" <>
|
||||
inspected =
|
||||
Exception.message(%{__struct__: BadException, __exception__: true, raise: true})
|
||||
|
||||
assert Exception.message(%{__struct__: BadException, __exception__: true, raise: false}) =~
|
||||
"got nil while retrieving Exception.message/1 " <>
|
||||
"for %{__exception__: true, __struct__: ExceptionTest.BadException, raise: false}"
|
||||
assert inspected =~ "raise: true"
|
||||
assert inspected =~ "__exception__: true"
|
||||
assert inspected =~ "__struct__: ExceptionTest.BadException"
|
||||
|
||||
assert "got nil while retrieving Exception.message/1 for %{" <> inspected =
|
||||
Exception.message(%{__struct__: BadException, __exception__: true, raise: false})
|
||||
|
||||
assert inspected =~ "raise: false"
|
||||
assert inspected =~ "__exception__: true"
|
||||
assert inspected =~ "__struct__: ExceptionTest.BadException"
|
||||
end
|
||||
|
||||
test "normalize/2" do
|
||||
@@ -426,6 +433,8 @@ defmodule ExceptionTest do
|
||||
when is_binary(url) or (is_struct(url) and is_struct(url, url_module) and false) do
|
||||
url
|
||||
end
|
||||
|
||||
def sub_get!(url) when is_struct(url.sub, URI), do: url.sub
|
||||
end
|
||||
)
|
||||
|
||||
@@ -475,6 +484,19 @@ defmodule ExceptionTest do
|
||||
|
||||
def get!(url) when -is_binary(url)- or is_struct(url) and is_struct(url, URI) and -false-
|
||||
"""
|
||||
|
||||
assert blame_message(Req, & &1.sub_get!(%{})) =~ """
|
||||
no function clause matching in ExceptionTest.Req.sub_get!/1
|
||||
|
||||
The following arguments were given to ExceptionTest.Req.sub_get!/1:
|
||||
|
||||
# 1
|
||||
%{}
|
||||
|
||||
Attempted function clauses (showing 1 out of 1):
|
||||
|
||||
def sub_get!(url) when -is_struct(url.sub, URI)-
|
||||
"""
|
||||
end
|
||||
|
||||
test "annotates badarg on apply" do
|
||||
|
||||
@@ -417,14 +417,20 @@ defmodule Inspect.MapTest do
|
||||
test "basic" do
|
||||
assert inspect(%{1 => "b"}) == "%{1 => \"b\"}"
|
||||
|
||||
assert inspect(%{1 => "b", 2 => "c"}, pretty: true, width: 1) ==
|
||||
assert inspect(%{1 => "b", 2 => "c"},
|
||||
pretty: true,
|
||||
width: 1,
|
||||
custom_options: [sort_maps: true]
|
||||
) ==
|
||||
"%{\n 1 => \"b\",\n 2 => \"c\"\n}"
|
||||
end
|
||||
|
||||
test "keyword" do
|
||||
assert inspect(%{a: 1}) == "%{a: 1}"
|
||||
assert inspect(%{a: 1, b: 2}) == "%{a: 1, b: 2}"
|
||||
assert inspect(%{a: 1, b: 2, c: 3}) == "%{a: 1, b: 2, c: 3}"
|
||||
assert inspect(%{a: 1, b: 2}, custom_options: [sort_maps: true]) == "%{a: 1, b: 2}"
|
||||
|
||||
assert inspect(%{a: 1, b: 2, c: 3}, custom_options: [sort_maps: true]) ==
|
||||
"%{a: 1, b: 2, c: 3}"
|
||||
end
|
||||
|
||||
test "with limit" do
|
||||
@@ -446,12 +452,12 @@ defmodule Inspect.MapTest do
|
||||
test "public modified struct" do
|
||||
public = %Public{key: 1}
|
||||
|
||||
assert inspect(Map.put(public, :foo, :bar)) ==
|
||||
assert inspect(Map.put(public, :foo, :bar), custom_options: [sort_maps: true]) ==
|
||||
"%{__struct__: Inspect.MapTest.Public, foo: :bar, key: 1}"
|
||||
end
|
||||
|
||||
test "private struct" do
|
||||
assert inspect(%{__struct__: Private, key: 1}) ==
|
||||
assert inspect(%{__struct__: Private, key: 1}, custom_options: [sort_maps: true]) ==
|
||||
"%{__struct__: Inspect.MapTest.Private, key: 1}"
|
||||
end
|
||||
|
||||
@@ -486,7 +492,7 @@ defmodule Inspect.MapTest do
|
||||
%{__struct__: Inspect.MapTest.Failing, name: "Foo"}
|
||||
'''
|
||||
|
||||
assert inspect(%Failing{name: "Foo"}) =~ message
|
||||
assert inspect(%Failing{name: "Foo"}, custom_options: [sort_maps: true]) =~ message
|
||||
end
|
||||
|
||||
test "safely inspect bad implementation disables colors" do
|
||||
@@ -505,7 +511,10 @@ defmodule Inspect.MapTest do
|
||||
%{__struct__: Inspect.MapTest.Failing, name: "Foo"}
|
||||
'''
|
||||
|
||||
assert inspect(%Failing{name: "Foo"}, syntax_colors: [atom: [:green]]) =~ message
|
||||
assert inspect(%Failing{name: "Foo"},
|
||||
syntax_colors: [atom: [:green]],
|
||||
custom_options: [sort_maps: true]
|
||||
) =~ message
|
||||
end
|
||||
|
||||
test "unsafely inspect bad implementation" do
|
||||
@@ -524,7 +533,7 @@ defmodule Inspect.MapTest do
|
||||
'''
|
||||
|
||||
try do
|
||||
inspect(%Failing{name: "Foo"}, safe: false)
|
||||
inspect(%Failing{name: "Foo"}, safe: false, custom_options: [sort_maps: true])
|
||||
rescue
|
||||
exception in Inspect.Error ->
|
||||
assert Exception.message(exception) =~ exception_message
|
||||
@@ -552,14 +561,16 @@ defmodule Inspect.MapTest do
|
||||
|
||||
while inspecting:
|
||||
|
||||
%{__struct__: Inspect.MapTest.Failing, name: "Foo"}
|
||||
'''
|
||||
|
||||
try do
|
||||
Enum.to_list(%Failing{name: "Foo"})
|
||||
rescue
|
||||
exception in Protocol.UndefinedError ->
|
||||
assert Exception.message(exception) =~ exception_message
|
||||
message = Exception.message(exception)
|
||||
assert message =~ exception_message
|
||||
assert message =~ "__struct__: Inspect.MapTest.Failing"
|
||||
assert message =~ "name: \"Foo\""
|
||||
|
||||
assert [
|
||||
{Enumerable, :impl_for!, 1, _} | _
|
||||
@@ -617,7 +628,7 @@ defmodule Inspect.MapTest do
|
||||
)
|
||||
)
|
||||
|
||||
assert inspect(%Failing{name: "Foo"}) =~ message
|
||||
assert inspect(%Failing{name: "Foo"}, custom_options: [sort_maps: true]) =~ message
|
||||
assert inspected =~ message
|
||||
end
|
||||
|
||||
|
||||
@@ -115,6 +115,36 @@ defmodule Kernel.ComprehensionTest do
|
||||
assert for(x <- 1..3, nilly(), do: x * 2) == []
|
||||
end
|
||||
|
||||
test "for comprehensions with unique option where value is not used" do
|
||||
assert capture_io(:stderr, fn ->
|
||||
assert capture_io(fn ->
|
||||
Code.eval_quoted(
|
||||
quote do
|
||||
for x <- [1, 2, 1, 2], uniq: true, do: IO.puts(x)
|
||||
nil
|
||||
end
|
||||
)
|
||||
end) ==
|
||||
"1\n2\n1\n2\n"
|
||||
end) =~
|
||||
"the :uniq option has no effect since the result of the for comprehension is not used"
|
||||
end
|
||||
|
||||
test "for comprehensions with unique option where value is assigned to _" do
|
||||
assert capture_io(:stderr, fn ->
|
||||
assert capture_io(fn ->
|
||||
Code.eval_quoted(
|
||||
quote do
|
||||
_ = for x <- [1, 2, 1, 2], uniq: true, do: IO.puts(x)
|
||||
nil
|
||||
end
|
||||
)
|
||||
end) ==
|
||||
"1\n2\n1\n2\n"
|
||||
end) =~
|
||||
"the :uniq option has no effect since the result of the for comprehension is not used"
|
||||
end
|
||||
|
||||
test "for comprehensions with errors on filters" do
|
||||
assert_raise ArgumentError, fn ->
|
||||
for x <- 1..3, hd(x), do: x * 2
|
||||
|
||||
@@ -22,7 +22,26 @@ defmodule Kernel.DialyzerTest do
|
||||
end
|
||||
|
||||
# Add a few key Elixir modules for types and macro functions
|
||||
mods = [:elixir, :elixir_env, Atom, Enum, Exception, Kernel, Macro, Macro.Env, String]
|
||||
mods = [
|
||||
:elixir,
|
||||
:elixir_env,
|
||||
ArgumentError,
|
||||
Atom,
|
||||
Enum,
|
||||
Exception,
|
||||
ExUnit.AssertionError,
|
||||
ExUnit.Assertions,
|
||||
IO,
|
||||
Kernel,
|
||||
Kernel.Utils,
|
||||
List,
|
||||
Macro,
|
||||
Macro.Env,
|
||||
Module,
|
||||
String,
|
||||
String.Chars
|
||||
]
|
||||
|
||||
files = Enum.map(mods, &:code.which/1)
|
||||
dialyzer_run(analysis_type: :plt_build, output_plt: plt, apps: [:erts], files: files)
|
||||
|
||||
@@ -102,6 +121,7 @@ defmodule Kernel.DialyzerTest do
|
||||
|
||||
copy_beam!(context, ProtocolOpaque)
|
||||
copy_beam!(context, ProtocolOpaque.Entity)
|
||||
copy_beam!(context, ProtocolOpaque.Entity.Any)
|
||||
copy_beam!(context, ProtocolOpaque.Duck)
|
||||
assert_dialyze_no_warnings!(context)
|
||||
|
||||
|
||||
@@ -204,6 +204,23 @@ defmodule Kernel.ErrorsTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "bad struct on module conflict" do
|
||||
Code.put_compiler_option(:ignore_module_conflict, true)
|
||||
|
||||
assert_eval_raise CompileError, ~r'MissingStructOnReload\.__struct__/1 is undefined', ~c'''
|
||||
defmodule MissingStructOnReload do
|
||||
defstruct [:title]
|
||||
def d(), do: %MissingStructOnReload{}
|
||||
end
|
||||
|
||||
defmodule MissingStructOnReload do
|
||||
def d(), do: %MissingStructOnReload{}
|
||||
end
|
||||
'''
|
||||
after
|
||||
Code.put_compiler_option(:ignore_module_conflict, false)
|
||||
end
|
||||
|
||||
test "missing struct key" do
|
||||
missing_struct_key_error =
|
||||
~r"expected Kernel.ErrorsTest.MissingStructKey.__struct__/(0|1) to return a map.*, got: %\{\}"
|
||||
|
||||
@@ -13,76 +13,6 @@ end
|
||||
defmodule Kernel.ExpansionTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
defmacrop var_ver(var, version) do
|
||||
quote do
|
||||
{unquote(var), [version: unquote(version)], __MODULE__}
|
||||
end
|
||||
end
|
||||
|
||||
test "tracks variable version" do
|
||||
assert {:__block__, _, [{:=, _, [var_ver(:x, 0), 0]}, {:=, _, [_, var_ver(:x, 0)]}]} =
|
||||
expand_with_version(
|
||||
quote do
|
||||
x = 0
|
||||
_ = x
|
||||
end
|
||||
)
|
||||
|
||||
assert {:__block__, _,
|
||||
[
|
||||
{:=, _, [var_ver(:x, 0), 0]},
|
||||
{:=, _, [_, var_ver(:x, 0)]},
|
||||
{:=, _, [var_ver(:x, 1), 1]},
|
||||
{:=, _, [_, var_ver(:x, 1)]}
|
||||
]} =
|
||||
expand_with_version(
|
||||
quote do
|
||||
x = 0
|
||||
_ = x
|
||||
x = 1
|
||||
_ = x
|
||||
end
|
||||
)
|
||||
|
||||
assert {:__block__, _,
|
||||
[
|
||||
{:=, _, [var_ver(:x, 0), 0]},
|
||||
{:fn, _, [{:->, _, [[var_ver(:x, 1)], {:=, _, [var_ver(:x, 2), 2]}]}]},
|
||||
{:=, _, [_, var_ver(:x, 0)]},
|
||||
{:=, _, [var_ver(:x, 3), 3]}
|
||||
]} =
|
||||
expand_with_version(
|
||||
quote do
|
||||
x = 0
|
||||
fn x -> x = 2 end
|
||||
_ = x
|
||||
x = 3
|
||||
end
|
||||
)
|
||||
|
||||
assert {:__block__, _,
|
||||
[
|
||||
{:=, _, [var_ver(:x, 0), 0]},
|
||||
{:case, _, [:foo, [do: [{:->, _, [[var_ver(:x, 1)], var_ver(:x, 1)]}]]]},
|
||||
{:=, _, [_, var_ver(:x, 0)]},
|
||||
{:=, _, [var_ver(:x, 2), 2]}
|
||||
]} =
|
||||
expand_with_version(
|
||||
quote do
|
||||
x = 0
|
||||
case(:foo, do: (x -> x))
|
||||
_ = x
|
||||
x = 2
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
defp expand_with_version(expr) do
|
||||
env = :elixir_env.reset_vars(__ENV__)
|
||||
{expr, _, _} = :elixir_expand.expand(expr, :elixir_env.env_to_ex(env), env)
|
||||
expr
|
||||
end
|
||||
|
||||
describe "__block__" do
|
||||
test "expands to nil when empty" do
|
||||
assert expand(quote(do: unquote(:__block__)())) == nil
|
||||
@@ -328,12 +258,30 @@ defmodule Kernel.ExpansionTest do
|
||||
end
|
||||
|
||||
describe "vars" do
|
||||
test "expand to local call" do
|
||||
{output, env} = expand_env(quote(do: a), __ENV__)
|
||||
assert output == quote(do: a())
|
||||
test "expands vars to local call" do
|
||||
{output, env} = expand_env({:a, [], nil}, __ENV__, [])
|
||||
assert output == {:a, [if_undefined: :warn], []}
|
||||
assert Macro.Env.vars(env) == []
|
||||
end
|
||||
|
||||
test "expands vars to local call without warning" do
|
||||
env = __ENV__
|
||||
|
||||
{output, _, env} =
|
||||
:elixir_expand.expand({:a, [if_undefined: :apply], nil}, :elixir_env.env_to_ex(env), env)
|
||||
|
||||
assert output == {:a, [if_undefined: :apply], []}
|
||||
assert Macro.Env.vars(env) == []
|
||||
end
|
||||
|
||||
test "raises when expanding var to local call" do
|
||||
env = __ENV__
|
||||
|
||||
assert_raise CompileError, ~r"undefined variable \"a\"", fn ->
|
||||
:elixir_expand.expand({:a, [if_undefined: :raise], nil}, :elixir_env.env_to_ex(env), env)
|
||||
end
|
||||
end
|
||||
|
||||
test "forces variable to exist" do
|
||||
code =
|
||||
quote do
|
||||
@@ -361,6 +309,76 @@ defmodule Kernel.ExpansionTest do
|
||||
expand(quote(do: {1, 2, _}))
|
||||
end
|
||||
end
|
||||
|
||||
defmacrop var_ver(var, version) do
|
||||
quote do
|
||||
{unquote(var), [version: unquote(version)], __MODULE__}
|
||||
end
|
||||
end
|
||||
|
||||
defp expand_with_version(expr) do
|
||||
env = :elixir_env.reset_vars(__ENV__)
|
||||
{expr, _, _} = :elixir_expand.expand(expr, :elixir_env.env_to_ex(env), env)
|
||||
expr
|
||||
end
|
||||
|
||||
test "tracks variable version" do
|
||||
assert {:__block__, _, [{:=, _, [var_ver(:x, 0), 0]}, {:=, _, [_, var_ver(:x, 0)]}]} =
|
||||
expand_with_version(
|
||||
quote do
|
||||
x = 0
|
||||
_ = x
|
||||
end
|
||||
)
|
||||
|
||||
assert {:__block__, _,
|
||||
[
|
||||
{:=, _, [var_ver(:x, 0), 0]},
|
||||
{:=, _, [_, var_ver(:x, 0)]},
|
||||
{:=, _, [var_ver(:x, 1), 1]},
|
||||
{:=, _, [_, var_ver(:x, 1)]}
|
||||
]} =
|
||||
expand_with_version(
|
||||
quote do
|
||||
x = 0
|
||||
_ = x
|
||||
x = 1
|
||||
_ = x
|
||||
end
|
||||
)
|
||||
|
||||
assert {:__block__, _,
|
||||
[
|
||||
{:=, _, [var_ver(:x, 0), 0]},
|
||||
{:fn, _, [{:->, _, [[var_ver(:x, 1)], {:=, _, [var_ver(:x, 2), 2]}]}]},
|
||||
{:=, _, [_, var_ver(:x, 0)]},
|
||||
{:=, _, [var_ver(:x, 3), 3]}
|
||||
]} =
|
||||
expand_with_version(
|
||||
quote do
|
||||
x = 0
|
||||
fn x -> x = 2 end
|
||||
_ = x
|
||||
x = 3
|
||||
end
|
||||
)
|
||||
|
||||
assert {:__block__, _,
|
||||
[
|
||||
{:=, _, [var_ver(:x, 0), 0]},
|
||||
{:case, _, [:foo, [do: [{:->, _, [[var_ver(:x, 1)], var_ver(:x, 1)]}]]]},
|
||||
{:=, _, [_, var_ver(:x, 0)]},
|
||||
{:=, _, [var_ver(:x, 2), 2]}
|
||||
]} =
|
||||
expand_with_version(
|
||||
quote do
|
||||
x = 0
|
||||
case(:foo, do: (x -> x))
|
||||
_ = x
|
||||
x = 2
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "^" do
|
||||
@@ -798,7 +816,8 @@ defmodule Kernel.ExpansionTest do
|
||||
c = 3
|
||||
[4]
|
||||
),
|
||||
do: {a(), b, c(), d}
|
||||
do: {a(), b, c(), d},
|
||||
into: []
|
||||
)
|
||||
end
|
||||
|
||||
@@ -807,7 +826,7 @@ defmodule Kernel.ExpansionTest do
|
||||
|
||||
test "variables inside filters are available in blocks" do
|
||||
assert expand(quote(do: for(a <- b, c = a, do: c))) ==
|
||||
quote(do: for(a <- b(), c = a, do: c))
|
||||
quote(do: for(a <- b(), c = a, do: c, into: []))
|
||||
end
|
||||
|
||||
test "variables inside options do not leak" do
|
||||
@@ -2890,15 +2909,13 @@ defmodule Kernel.ExpansionTest do
|
||||
expand_env(expr, __ENV__) |> elem(0)
|
||||
end
|
||||
|
||||
defp expand_env(expr, env) do
|
||||
ExUnit.CaptureIO.capture_io(:stderr, fn ->
|
||||
send(self(), {:expand_env, :elixir_expand.expand(expr, :elixir_env.env_to_ex(env), env)})
|
||||
end)
|
||||
defp expand_env(expr, env, to_clean \\ [:version, :inferred_bitstring_spec, :if_undefined]) do
|
||||
{{expr, scope, env}, _capture} =
|
||||
ExUnit.CaptureIO.with_io(:stderr, fn ->
|
||||
:elixir_expand.expand(expr, :elixir_env.env_to_ex(env), env)
|
||||
end)
|
||||
|
||||
receive do
|
||||
{:expand_env, {expr, scope, env}} ->
|
||||
env = :elixir_env.to_caller({env.line, scope, env})
|
||||
{clean_meta(expr, [:version, :inferred_bitstring_spec]), env}
|
||||
end
|
||||
env = :elixir_env.to_caller({env.line, scope, env})
|
||||
{clean_meta(expr, to_clean), env}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -162,7 +162,7 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
{{compile, _, _, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.Attribute1 do
|
||||
@example [String, Enum]
|
||||
@example [String, Enum, 3 + 10]
|
||||
def foo(atom) when atom in @example, do: atom
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
@@ -226,6 +226,18 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
|
||||
assert String in compile
|
||||
assert Enum in compile
|
||||
|
||||
{{compile, _, _, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.Attribute6 do
|
||||
@example %{foo: Application.compile_env(:elixir, Enum, String)}
|
||||
def foo(atom) when atom == @example.foo, do: atom
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
refute String in compile
|
||||
refute Enum in compile
|
||||
end
|
||||
|
||||
test "@compile adds a runtime dependency" do
|
||||
|
||||
@@ -21,7 +21,7 @@ defmodule Kernel.QuoteTest do
|
||||
line = __ENV__.line + 2
|
||||
|
||||
assert quote(location: :keep, do: bar(1, 2, 3)) ==
|
||||
{:bar, [keep: {Path.relative_to_cwd(__ENV__.file), line}], [1, 2, 3]}
|
||||
{:bar, [keep: {__ENV__.file, line}], [1, 2, 3]}
|
||||
end
|
||||
|
||||
test "fixed line" do
|
||||
|
||||
@@ -411,6 +411,7 @@ defmodule Kernel.RaiseTest do
|
||||
fun = BadFunction.Missing.fun()
|
||||
|
||||
:code.delete(BadFunction.Missing)
|
||||
:code.purge(BadFunction.Missing)
|
||||
|
||||
defmodule BadFunction.Missing do
|
||||
def fun, do: fn -> :another end
|
||||
|
||||
@@ -172,10 +172,18 @@ defmodule MacroTest do
|
||||
end
|
||||
|
||||
test "env" do
|
||||
env = %{__ENV__ | line: 0}
|
||||
assert Macro.expand_once(quote(do: __ENV__), env) == {:%{}, [], Map.to_list(env)}
|
||||
env = %{__ENV__ | line: 0, lexical_tracker: self()}
|
||||
|
||||
expanded = Macro.expand_once(quote(do: __ENV__), env)
|
||||
assert Macro.validate(expanded) == :ok
|
||||
assert Code.eval_quoted(expanded) == {env, []}
|
||||
|
||||
assert Macro.expand_once(quote(do: __ENV__.file), env) == env.file
|
||||
assert Macro.expand_once(quote(do: __ENV__.unknown), env) == quote(do: __ENV__.unknown)
|
||||
|
||||
expanded = Macro.expand_once(quote(do: __ENV__.versioned_vars), env)
|
||||
assert Macro.validate(expanded) == :ok
|
||||
assert Code.eval_quoted(expanded) == {env.versioned_vars, []}
|
||||
end
|
||||
|
||||
defmacro local_macro(), do: raise("ignored")
|
||||
@@ -269,9 +277,26 @@ defmodule MacroTest do
|
||||
assert expand_and_clean(quote(do: oror(1, false)), __ENV__) == quoted
|
||||
end
|
||||
|
||||
test "expand_literal" do
|
||||
assert Macro.expand_literal(quote(do: Foo), __ENV__) == Foo
|
||||
assert Macro.expand_literal(quote(do: Foo + Bar), __ENV__) == quote(do: Foo + Bar)
|
||||
test "expand_literals/2" do
|
||||
assert Macro.expand_literals(quote(do: Foo), __ENV__) == Foo
|
||||
assert Macro.expand_literals(quote(do: Foo + Bar), __ENV__) == quote(do: Foo + Bar)
|
||||
assert Macro.expand_literals(quote(do: __MODULE__), __ENV__) == __MODULE__
|
||||
assert Macro.expand_literals(quote(do: __MODULE__.Foo), __ENV__) == __MODULE__.Foo
|
||||
assert Macro.expand_literals(quote(do: [Foo, 1 + 2]), __ENV__) == [Foo, quote(do: 1 + 2)]
|
||||
end
|
||||
|
||||
test "expand_literals/3" do
|
||||
fun = fn node, acc ->
|
||||
expanded = Macro.expand(node, __ENV__)
|
||||
{expanded, [expanded | acc]}
|
||||
end
|
||||
|
||||
assert Macro.expand_literals(quote(do: Foo), [], fun) == {Foo, [Foo]}
|
||||
assert Macro.expand_literals(quote(do: Foo + Bar), [], fun) == {quote(do: Foo + Bar), []}
|
||||
assert Macro.expand_literals(quote(do: __MODULE__), [], fun) == {__MODULE__, [__MODULE__]}
|
||||
|
||||
assert Macro.expand_literals(quote(do: __MODULE__.Foo), [], fun) ==
|
||||
{__MODULE__.Foo, [__MODULE__.Foo, __MODULE__]}
|
||||
end
|
||||
|
||||
test "var/2" do
|
||||
@@ -404,6 +429,16 @@ defmodule MacroTest do
|
||||
test "converts quoted to string" do
|
||||
assert Macro.to_string(quote do: hello(world)) == "hello(world)"
|
||||
end
|
||||
|
||||
test "large number literals" do
|
||||
# with quote
|
||||
assert Macro.to_string(quote do: 576_460_752_303_423_455) == "576_460_752_303_423_455"
|
||||
assert Macro.to_string(quote do: -576_460_752_303_423_455) == "-576_460_752_303_423_455"
|
||||
|
||||
# without quote
|
||||
assert Macro.to_string(576_460_752_303_423_455) == "576_460_752_303_423_455"
|
||||
assert Macro.to_string(-576_460_752_303_423_455) == "-576_460_752_303_423_455"
|
||||
end
|
||||
end
|
||||
|
||||
describe "to_string/2" do
|
||||
|
||||
@@ -206,7 +206,7 @@ defmodule MapTest do
|
||||
assert Map.replace!(map, :b, 10) == %{c: 3, b: 10, a: 1}
|
||||
assert Map.replace!(map, :a, 1) == map
|
||||
|
||||
assert_raise KeyError, "key :x not found in: %{a: 1, b: 2, c: 3}", fn ->
|
||||
assert_raise KeyError, ~r/key :x not found in: %{.*a: 1.*}/, fn ->
|
||||
Map.replace!(map, :x, 10)
|
||||
end
|
||||
|
||||
@@ -393,6 +393,10 @@ defmodule MapTest do
|
||||
defmodule TestMod do
|
||||
@enforce_keys :foo
|
||||
defstruct [:foo]
|
||||
|
||||
# Verify it remain set afterwards
|
||||
:foo = @enforce_keys
|
||||
|
||||
def foo do
|
||||
%TestMod{}
|
||||
end
|
||||
|
||||
@@ -562,6 +562,22 @@ defmodule Module.Types.IntegrationTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "regressions" do
|
||||
test "handle missing location info from quoted" do
|
||||
assert capture_io(:stderr, fn ->
|
||||
quote do
|
||||
defmodule X do
|
||||
def f() do
|
||||
x = %{}
|
||||
%{x | key: :value}
|
||||
end
|
||||
end
|
||||
end
|
||||
|> Code.compile_quoted()
|
||||
end) =~ "warning:"
|
||||
end
|
||||
end
|
||||
|
||||
describe "after_verify" do
|
||||
test "reports functions" do
|
||||
files = %{
|
||||
|
||||
@@ -140,25 +140,25 @@ defmodule Module.Types.PatternTest do
|
||||
end
|
||||
|
||||
test "struct" do
|
||||
assert quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{}) ==
|
||||
{:ok,
|
||||
{:map,
|
||||
[
|
||||
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
|
||||
{:required, {:atom, :bar}, :dynamic},
|
||||
{:required, {:atom, :baz}, :dynamic},
|
||||
{:required, {:atom, :foo}, :dynamic}
|
||||
]}}
|
||||
assert {:ok, {:map, fields}} = quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{})
|
||||
|
||||
assert quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{foo: 123, bar: :atom}) ==
|
||||
{:ok,
|
||||
{:map,
|
||||
[
|
||||
{:required, {:atom, :foo}, :integer},
|
||||
{:required, {:atom, :bar}, {:atom, :atom}},
|
||||
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
|
||||
{:required, {:atom, :baz}, :dynamic}
|
||||
]}}
|
||||
assert Enum.sort(fields) == [
|
||||
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
|
||||
{:required, {:atom, :bar}, :dynamic},
|
||||
{:required, {:atom, :baz}, :dynamic},
|
||||
{:required, {:atom, :foo}, :dynamic}
|
||||
]
|
||||
|
||||
assert {:ok, {:map, fields}} =
|
||||
quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{foo: 123, bar: :atom})
|
||||
|
||||
assert Enum.sort(fields) ==
|
||||
[
|
||||
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
|
||||
{:required, {:atom, :bar}, {:atom, :atom}},
|
||||
{:required, {:atom, :baz}, :dynamic},
|
||||
{:required, {:atom, :foo}, :integer}
|
||||
]
|
||||
end
|
||||
|
||||
test "struct var" do
|
||||
@@ -222,7 +222,6 @@ defmodule Module.Types.PatternTest do
|
||||
|
||||
assert quoted_pattern(x = y = 123) == {:ok, :integer}
|
||||
assert quoted_pattern(x = 123 = y) == {:ok, :integer}
|
||||
assert quoted_pattern(123 = x = y) == {:ok, :integer}
|
||||
|
||||
assert {:error, {:unable_unify, {{:tuple, 1, [var: 0]}, {:var, 0}, _}}} =
|
||||
quoted_pattern({x} = x)
|
||||
@@ -296,15 +295,6 @@ defmodule Module.Types.PatternTest do
|
||||
|
||||
assert quoted_head([x], [is_atom(x) > :foo]) == {:ok, [var: 0]}
|
||||
|
||||
assert quoted_head([x, x = y, y = z], [is_atom(x)]) ==
|
||||
{:ok, [:atom, :atom, :atom]}
|
||||
|
||||
assert quoted_head([x = y, y, y = z], [is_atom(y)]) ==
|
||||
{:ok, [:atom, :atom, :atom]}
|
||||
|
||||
assert quoted_head([x = y, y = z, z], [is_atom(z)]) ==
|
||||
{:ok, [:atom, :atom, :atom]}
|
||||
|
||||
assert quoted_head([x, y], [is_atom(x) or is_integer(y)]) ==
|
||||
{:ok, [{:var, 0}, {:var, 1}]}
|
||||
|
||||
@@ -320,15 +310,6 @@ defmodule Module.Types.PatternTest do
|
||||
assert quoted_head([x, y], [is_atom(y) or is_integer(y)]) ==
|
||||
{:ok, [{:var, 0}, {:union, [:atom, :integer]}]}
|
||||
|
||||
assert quoted_head([x = y], [is_atom(y) or is_integer(y)]) ==
|
||||
{:ok, [{:union, [:atom, :integer]}]}
|
||||
|
||||
assert quoted_head([x = y], [is_atom(x) or is_integer(x)]) ==
|
||||
{:ok, [{:union, [:atom, :integer]}]}
|
||||
|
||||
assert quoted_head([x = y], [is_atom(x) or is_integer(x)]) ==
|
||||
{:ok, [{:union, [:atom, :integer]}]}
|
||||
|
||||
assert quoted_head([x], [true == false or is_integer(x)]) ==
|
||||
{:ok, [var: 0]}
|
||||
|
||||
|
||||
@@ -187,36 +187,6 @@ defmodule Module.Types.TypesTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "warns on guards with multiple variables" do
|
||||
string = warning([x = y], [is_integer(x) and is_binary(y)], {x, y})
|
||||
|
||||
assert string == """
|
||||
incompatible types:
|
||||
|
||||
integer() !~ binary()
|
||||
|
||||
in expression:
|
||||
|
||||
# types_test.ex:1
|
||||
is_binary(y)
|
||||
|
||||
where "x" was given the same type as "y" in:
|
||||
|
||||
# types_test.ex:1
|
||||
x = y
|
||||
|
||||
where "y" was given the type integer() in:
|
||||
|
||||
# types_test.ex:1
|
||||
is_integer(x)
|
||||
|
||||
where "y" was given the type binary() in:
|
||||
|
||||
# types_test.ex:1
|
||||
is_binary(y)
|
||||
"""
|
||||
end
|
||||
|
||||
test "warns on guards from cases unless generated" do
|
||||
string =
|
||||
warning(
|
||||
@@ -243,36 +213,6 @@ defmodule Module.Types.TypesTest do
|
||||
assert string == :none
|
||||
end
|
||||
|
||||
test "only show relevant traces in warning" do
|
||||
string = warning([x = y, z], [is_integer(x) and is_binary(y) and is_boolean(z)], {x, y, z})
|
||||
|
||||
assert string == """
|
||||
incompatible types:
|
||||
|
||||
integer() !~ binary()
|
||||
|
||||
in expression:
|
||||
|
||||
# types_test.ex:1
|
||||
is_binary(y)
|
||||
|
||||
where "x" was given the same type as "y" in:
|
||||
|
||||
# types_test.ex:1
|
||||
x = y
|
||||
|
||||
where "y" was given the type integer() in:
|
||||
|
||||
# types_test.ex:1
|
||||
is_integer(x)
|
||||
|
||||
where "y" was given the type binary() in:
|
||||
|
||||
# types_test.ex:1
|
||||
is_binary(y)
|
||||
"""
|
||||
end
|
||||
|
||||
test "check body" do
|
||||
string = warning([x], [is_integer(x)], :foo = x)
|
||||
|
||||
@@ -417,6 +357,25 @@ defmodule Module.Types.TypesTest do
|
||||
foo - :bar
|
||||
"""
|
||||
end
|
||||
|
||||
test "rewrite call" do
|
||||
string = warning([foo], [is_map_key(1, foo)], foo)
|
||||
|
||||
assert string == """
|
||||
expected Kernel.is_map_key/2 to have signature:
|
||||
|
||||
integer(), var1 -> dynamic()
|
||||
|
||||
but it has signature:
|
||||
|
||||
%{optional(dynamic()) => dynamic()}, dynamic() -> dynamic()
|
||||
|
||||
in expression:
|
||||
|
||||
# types_test.ex:1
|
||||
is_map_key(1, foo)
|
||||
"""
|
||||
end
|
||||
end
|
||||
|
||||
describe "map warnings" do
|
||||
|
||||
@@ -332,6 +332,13 @@ defmodule ModuleTest do
|
||||
assert Elixir.ModuleTest.NonAtomAlias.hello() == :world
|
||||
end
|
||||
|
||||
test "does not warn on captured underscored vars" do
|
||||
_unused = 123
|
||||
|
||||
defmodule __MODULE__.NoVarWarning do
|
||||
end
|
||||
end
|
||||
|
||||
@compile {:no_warn_undefined, ModuleCreateSample}
|
||||
|
||||
test "create" do
|
||||
|
||||
@@ -38,6 +38,21 @@ defmodule PathTest do
|
||||
File.rm_rf(Path.join(config.tmp_dir, "wildcard"))
|
||||
end
|
||||
|
||||
@tag :tmp_dir
|
||||
test "wildcard/2 follows ..", config do
|
||||
hello = Path.join(config.tmp_dir, "wildcard/hello")
|
||||
world = Path.join(config.tmp_dir, "wildcard/world")
|
||||
File.mkdir_p(hello)
|
||||
File.touch(world)
|
||||
|
||||
assert Path.wildcard(Path.join(config.tmp_dir, "wildcard/w*/../h*")) == []
|
||||
|
||||
assert Path.wildcard(Path.join(config.tmp_dir, "wildcard/h*/../w*")) ==
|
||||
[Path.join(config.tmp_dir, "wildcard/hello/../world")]
|
||||
after
|
||||
File.rm_rf(Path.join(config.tmp_dir, "wildcard"))
|
||||
end
|
||||
|
||||
test "wildcard/2 raises on null byte" do
|
||||
assert_raise ArgumentError, ~r/null byte/, fn -> Path.wildcard("foo\0bar") end
|
||||
end
|
||||
|
||||
@@ -74,6 +74,14 @@ defmodule ProtocolTest do
|
||||
end
|
||||
end
|
||||
|
||||
defmodule ImplStructExplicitFor do
|
||||
defstruct a: 0, b: 0
|
||||
|
||||
defimpl Sample, for: __MODULE__ do
|
||||
def ok(_struct), do: true
|
||||
end
|
||||
end
|
||||
|
||||
test "protocol implementations without any" do
|
||||
assert is_nil(Sample.impl_for(:foo))
|
||||
assert is_nil(Sample.impl_for(fn x -> x end))
|
||||
@@ -91,6 +99,7 @@ defmodule ProtocolTest do
|
||||
assert is_nil(Sample.impl_for(make_ref()))
|
||||
|
||||
assert Sample.impl_for(%ImplStruct{}) == Sample.ProtocolTest.ImplStruct
|
||||
assert Sample.impl_for(%ImplStructExplicitFor{}) == Sample.ProtocolTest.ImplStructExplicitFor
|
||||
assert Sample.impl_for(%NoImplStruct{}) == nil
|
||||
end
|
||||
|
||||
|
||||
@@ -1340,6 +1340,8 @@ defmodule StreamTest do
|
||||
assert Stream.chunk_every([0, 1, 2, 3], 2) |> Stream.zip() |> Enum.to_list() ==
|
||||
[{0, 2}, {1, 3}]
|
||||
|
||||
assert Stream.zip([]) |> Enum.to_list() == []
|
||||
|
||||
stream = %HaltAcc{acc: 1..3}
|
||||
assert Stream.zip([1..3, stream]) |> Enum.to_list() == [{1, 1}, {2, 2}, {3, 3}]
|
||||
|
||||
|
||||
@@ -107,6 +107,20 @@ defmodule StringTest do
|
||||
assert String.split("x-x-", pattern, parts: 3, trim: true) == ["x", "x"]
|
||||
end
|
||||
|
||||
test "split/2,3 with malformed" do
|
||||
assert String.split(<<225, 158, 128, 225, 158, 185, 225>>, "", parts: 1) ==
|
||||
[<<225, 158, 128, 225, 158, 185, 225>>]
|
||||
|
||||
assert String.split(<<225, 158, 128, 225, 158, 185, 225>>, "", parts: 2) ==
|
||||
["", <<225, 158, 128, 225, 158, 185, 225>>]
|
||||
|
||||
assert String.split(<<225, 158, 128, 225, 158, 185, 225>>, "", parts: 3) ==
|
||||
["", "កឹ", <<225>>]
|
||||
|
||||
assert String.split(<<225, 158, 128, 225, 158, 185, 225>>, "", parts: 4) ==
|
||||
["", "កឹ", <<225>>, ""]
|
||||
end
|
||||
|
||||
test "splitter/2,3" do
|
||||
assert String.splitter("a,b,c", ",") |> Enum.to_list() == ["a", "b", "c"]
|
||||
assert String.splitter("a,b", ".") |> Enum.to_list() == ["a,b"]
|
||||
@@ -155,9 +169,18 @@ defmodule StringTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "split_at/2 with invalid guard" do
|
||||
test "split_at/2 with malformed" do
|
||||
assert String.split_at(<<?a, 195, 10, ?a>>, 2) == {<<?a, 195>>, <<10, ?a>>}
|
||||
assert String.split_at(<<107, 205, 135, 184>>, 1) == {<<107, 205, 135>>, <<184>>}
|
||||
|
||||
assert String.split_at(<<225, 158, 128, 225, 158, 185, 225>>, 0) ==
|
||||
{"", <<225, 158, 128, 225, 158, 185, 225>>}
|
||||
|
||||
assert String.split_at(<<225, 158, 128, 225, 158, 185, 225>>, 1) ==
|
||||
{"កឹ", <<225>>}
|
||||
|
||||
assert String.split_at(<<225, 158, 128, 225, 158, 185, 225>>, 2) ==
|
||||
{<<225, 158, 128, 225, 158, 185, 225>>, ""}
|
||||
end
|
||||
|
||||
test "upcase/1" do
|
||||
@@ -457,6 +480,10 @@ defmodule StringTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "with empty string and string replacement with malformed" do
|
||||
assert String.replace(<<225, 158, 128, 225, 158, 185, 225>>, "", ".") == ".កឹ.\xE1."
|
||||
end
|
||||
|
||||
test "with empty pattern list" do
|
||||
assert String.replace("elixir", [], "anything") == "elixir"
|
||||
end
|
||||
@@ -587,6 +614,60 @@ defmodule StringTest do
|
||||
assert String.next_grapheme("") == nil
|
||||
end
|
||||
|
||||
describe "randomized" do
|
||||
test "next_grapheme" do
|
||||
for _ <- 1..10 do
|
||||
bin = :crypto.strong_rand_bytes(20)
|
||||
|
||||
try do
|
||||
bin |> Stream.unfold(&String.next_grapheme/1) |> Enum.to_list()
|
||||
rescue
|
||||
# Ignore malformed pictographic sequences
|
||||
_ -> :ok
|
||||
else
|
||||
list ->
|
||||
assert Enum.all?(list, &is_binary/1), "cannot build graphemes for #{inspect(bin)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "split empty" do
|
||||
for _ <- 1..10 do
|
||||
bin = :crypto.strong_rand_bytes(20)
|
||||
|
||||
try do
|
||||
String.split(bin, "")
|
||||
rescue
|
||||
# Ignore malformed pictographic sequences
|
||||
_ -> :ok
|
||||
else
|
||||
split ->
|
||||
assert Enum.all?(split, &is_binary/1), "cannot split #{inspect(bin)}"
|
||||
assert IO.iodata_to_binary(split) == bin
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "graphemes" do
|
||||
for _ <- 1..10 do
|
||||
bin = :crypto.strong_rand_bytes(20)
|
||||
|
||||
try do
|
||||
String.graphemes(bin)
|
||||
rescue
|
||||
# Ignore malformed pictographic sequences
|
||||
_ -> :ok
|
||||
else
|
||||
graphemes ->
|
||||
assert Enum.all?(graphemes, &is_binary/1),
|
||||
"cannot build graphemes for #{inspect(bin)}"
|
||||
|
||||
assert IO.iodata_to_binary(graphemes) == bin
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "first/1" do
|
||||
assert String.first("elixir") == "e"
|
||||
assert String.first("íelixr") == "í"
|
||||
|
||||
@@ -203,7 +203,7 @@ defmodule SystemTest do
|
||||
|
||||
@tag timeout: 1_000
|
||||
test "shell/1 returns when command awaits input" do
|
||||
assert {"", 0} = System.shell("cat")
|
||||
assert {"", 0} = System.shell("cat", close_stdin: true)
|
||||
end
|
||||
|
||||
test "shell/1 with comment" do
|
||||
|
||||
@@ -279,6 +279,31 @@ defmodule URITest do
|
||||
end
|
||||
end
|
||||
|
||||
test "http://http://http://@http://http://?http://#http://" do
|
||||
assert URI.parse("http://http://http://@http://http://?http://#http://") ==
|
||||
%URI{
|
||||
scheme: "http",
|
||||
authority: "http:",
|
||||
userinfo: nil,
|
||||
host: "http",
|
||||
port: 80,
|
||||
path: "//http://@http://http://",
|
||||
query: "http://",
|
||||
fragment: "http://"
|
||||
}
|
||||
|
||||
assert URI.new!("http://http://http://@http://http://?http://#http://") ==
|
||||
%URI{
|
||||
scheme: "http",
|
||||
userinfo: nil,
|
||||
host: "http",
|
||||
port: 80,
|
||||
path: "//http://@http://http://",
|
||||
query: "http://",
|
||||
fragment: "http://"
|
||||
}
|
||||
end
|
||||
|
||||
test "default_port/1,2" do
|
||||
assert URI.default_port("http") == 80
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
eval(Content) ->
|
||||
{Value, Binding, _} =
|
||||
elixir:eval_forms(elixir:'string_to_quoted!'(Content, 1, 1, <<"nofile">>, []), [], []),
|
||||
Quoted = elixir:'string_to_quoted!'(Content, 1, 1, <<"nofile">>, []),
|
||||
{Value, Binding, _} = elixir:eval_forms(Quoted, [], elixir:env_for_eval([])),
|
||||
{Value, Binding}.
|
||||
|
||||
kv([{Key, nil}]) -> Key.
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
eval(Content) ->
|
||||
{Value, Binding, _} =
|
||||
elixir:eval_forms(elixir:'string_to_quoted!'(Content, 1, 1, <<"nofile">>, []), [], []),
|
||||
Quoted = elixir:'string_to_quoted!'(Content, 1, 1, <<"nofile">>, []),
|
||||
{Value, Binding, _} = elixir:eval_forms(Quoted, [], elixir:env_for_eval([])),
|
||||
{Value, lists:sort(Binding)}.
|
||||
|
||||
function_arg_do_end_test() ->
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
|
||||
eval(Content) ->
|
||||
{Value, Binding, _} =
|
||||
elixir:eval_forms(elixir:'string_to_quoted!'(Content, 1, 1, <<"nofile">>, []), [], []),
|
||||
Quoted = elixir:'string_to_quoted!'(Content, 1, 1, <<"nofile">>, []),
|
||||
{Value, Binding, _} = elixir:eval_forms(Quoted, [], elixir:env_for_eval([])),
|
||||
{Value, Binding}.
|
||||
|
||||
extract_interpolations(String) ->
|
||||
@@ -22,7 +22,7 @@ extract_interpolations_without_interpolation_test() ->
|
||||
|
||||
extract_interpolations_with_escaped_interpolation_test() ->
|
||||
["f\\#{o}o"] = extract_interpolations("f\\#{o}o"),
|
||||
{1, 8, ["f\\#{o}o"], [], _} =
|
||||
{1, 10, ["f\\#{o}o"], [], _} =
|
||||
elixir_interpolation:extract(1, 2, #elixir_tokenizer{}, true, "f\\#{o}o\"", $").
|
||||
|
||||
extract_interpolations_with_interpolation_test() ->
|
||||
|
||||
@@ -199,6 +199,11 @@ interpolation_test() ->
|
||||
{concat_op, {1, 10, nil}, '<>'},
|
||||
{bin_string, {1, 13, nil}, [<<>>]}] = tokenize("\"f#{oo}\" <> \"\"").
|
||||
|
||||
escaped_interpolation_test() ->
|
||||
[{bin_string, {1, 1, nil}, [<<"f#{oo}">>]},
|
||||
{concat_op, {1, 11, nil}, '<>'},
|
||||
{bin_string, {1, 14, nil}, [<<>>]}] = tokenize("\"f\\#{oo}\" <> \"\"").
|
||||
|
||||
capture_test() ->
|
||||
% Parens precedence
|
||||
[{capture_op, {1, 1, nil}, '&'},
|
||||
|
||||
@@ -201,7 +201,7 @@ defmodule ExUnit do
|
||||
|
||||
System.at_exit(fn
|
||||
0 ->
|
||||
time = ExUnit.Server.modules_loaded()
|
||||
time = ExUnit.Server.modules_loaded(false)
|
||||
options = persist_defaults(configuration())
|
||||
%{failures: failures} = ExUnit.Runner.run(options, time)
|
||||
|
||||
@@ -373,7 +373,7 @@ defmodule ExUnit do
|
||||
end
|
||||
end
|
||||
|
||||
_ = ExUnit.Server.modules_loaded()
|
||||
_ = ExUnit.Server.modules_loaded(additional_modules != [])
|
||||
options = persist_defaults(configuration())
|
||||
ExUnit.Runner.run(options, nil)
|
||||
end
|
||||
@@ -399,7 +399,7 @@ defmodule ExUnit do
|
||||
@doc since: "1.12.0"
|
||||
@spec await_run(Task.t()) :: suite_result()
|
||||
def await_run(task) do
|
||||
ExUnit.Server.modules_loaded()
|
||||
ExUnit.Server.modules_loaded(false)
|
||||
Task.await(task, :infinity)
|
||||
end
|
||||
|
||||
|
||||
@@ -260,19 +260,21 @@ defmodule ExUnit.Assertions do
|
||||
quote do
|
||||
left = unquote(left)
|
||||
right = unquote(right)
|
||||
expr = unquote(expr)
|
||||
message = unquote(message)
|
||||
|
||||
if ExUnit.Assertions.__equal__?(left, right) do
|
||||
ExUnit.Assertions.assert(false,
|
||||
left: left,
|
||||
expr: unquote(expr),
|
||||
message: unquote(message <> ", both sides are exactly equal")
|
||||
expr: expr,
|
||||
message: message <> ", both sides are exactly equal"
|
||||
)
|
||||
else
|
||||
ExUnit.Assertions.assert(unquote(call),
|
||||
left: left,
|
||||
right: right,
|
||||
expr: unquote(expr),
|
||||
message: unquote(message),
|
||||
expr: expr,
|
||||
message: message,
|
||||
context: unquote(context)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -551,8 +551,11 @@ defmodule ExUnit.Callbacks do
|
||||
@doc """
|
||||
Same as `start_supervised!/2` but links the started process to the test process.
|
||||
|
||||
This means that if the process that was started crashes that crash is propagated to
|
||||
the test process, failing the test and printing the cause of the crash.
|
||||
If the process that was started crashes, the crash is propagated to the test process,
|
||||
failing the test and printing the cause of the crash.
|
||||
|
||||
Note that if the started terminates before it is linked to the test process,
|
||||
this function will exit with reason `:noproc`.
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
@spec start_link_supervised!(Supervisor.child_spec() | module | {module, term}, keyword) ::
|
||||
|
||||
@@ -20,6 +20,9 @@ defmodule ExUnit.Case do
|
||||
It should be enabled only if tests do not change any global state.
|
||||
Defaults to `false`.
|
||||
|
||||
* `:register` - when `false`, does not register this module within
|
||||
ExUnit server. This means the module won't run when ExUnit suite runs.
|
||||
|
||||
This module automatically includes all callbacks defined in
|
||||
`ExUnit.Callbacks`. See that module for more information on `setup`,
|
||||
`start_supervised`, `on_exit` and the test process life cycle.
|
||||
@@ -156,6 +159,10 @@ defmodule ExUnit.Case do
|
||||
|
||||
* `:describe_line` - the line the describe block begins on
|
||||
|
||||
* `:doctest` - the module or the file being doctested (if a doctest)
|
||||
|
||||
* `:doctest_line` - the line the doctest was defined (if a doctest)
|
||||
|
||||
The following tags customize how tests behave:
|
||||
|
||||
* `:capture_log` - see the "Log Capture" section below
|
||||
@@ -167,8 +174,8 @@ defmodule ExUnit.Case do
|
||||
|
||||
* `:tmp_dir` - (since v1.11.0) see the "Tmp Dir" section below
|
||||
|
||||
The `:test_type` tag is automatically set by ExUnit, but is **not** reserved.
|
||||
This tag is available for users to customize if they desire.
|
||||
* `: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
|
||||
|
||||
@@ -284,7 +291,7 @@ defmodule ExUnit.Case do
|
||||
raise "you must set @tag, @describetag, and @moduletag after the call to \"use ExUnit.Case\""
|
||||
end
|
||||
|
||||
attributes = [
|
||||
accumulate_attributes = [
|
||||
:ex_unit_tests,
|
||||
:tag,
|
||||
:describetag,
|
||||
@@ -294,24 +301,23 @@ defmodule ExUnit.Case do
|
||||
:ex_unit_registered_module_attributes
|
||||
]
|
||||
|
||||
Enum.each(attributes, &Module.register_attribute(module, &1, accumulate: true))
|
||||
Enum.each(accumulate_attributes, &Module.register_attribute(module, &1, accumulate: true))
|
||||
|
||||
persisted_attributes = [:ex_unit_async]
|
||||
|
||||
Enum.each(persisted_attributes, &Module.register_attribute(module, &1, persist: true))
|
||||
|
||||
attributes = [
|
||||
before_compile: ExUnit.Case,
|
||||
after_compile: ExUnit.Case,
|
||||
ex_unit_async: false
|
||||
]
|
||||
if Keyword.get(opts, :register, true) do
|
||||
Module.put_attribute(module, :after_compile, ExUnit.Case)
|
||||
end
|
||||
|
||||
Enum.each(attributes, fn {k, v} -> Module.put_attribute(module, k, v) end)
|
||||
Module.put_attribute(module, :before_compile, ExUnit.Case)
|
||||
end
|
||||
|
||||
async? = opts[:async]
|
||||
|
||||
if is_boolean(async?) do
|
||||
Module.put_attribute(module, :ex_unit_async, async?)
|
||||
if is_boolean(async?) or not registered? do
|
||||
Module.put_attribute(module, :ex_unit_async, async? || false)
|
||||
end
|
||||
|
||||
registered?
|
||||
@@ -477,10 +483,16 @@ defmodule ExUnit.Case do
|
||||
end
|
||||
|
||||
@doc false
|
||||
defmacro __before_compile__(_) do
|
||||
defmacro __before_compile__(env) do
|
||||
tests =
|
||||
env.module
|
||||
|> Module.get_attribute(:ex_unit_tests)
|
||||
|> Enum.reverse()
|
||||
|> Macro.escape()
|
||||
|
||||
quote do
|
||||
def __ex_unit__ do
|
||||
%ExUnit.TestModule{file: __ENV__.file, name: __MODULE__, tests: @ex_unit_tests}
|
||||
%ExUnit.TestModule{file: __ENV__.file, name: __MODULE__, tests: unquote(tests)}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -582,6 +594,7 @@ defmodule ExUnit.Case do
|
||||
This function is deprecated in favor of `register_test/6` which performs
|
||||
better under tight loops by avoiding `__ENV__`.
|
||||
"""
|
||||
# TODO: Deprecate on Elixir v1.17
|
||||
@doc deprecated: "Use register_test/6 instead"
|
||||
def register_test(%{module: mod, file: file, line: line}, test_type, name, tags) do
|
||||
register_test(mod, file, line, test_type, name, tags)
|
||||
@@ -759,8 +772,9 @@ defmodule ExUnit.Case do
|
||||
|
||||
defp normalize_tags(tags) do
|
||||
Enum.reduce(Enum.reverse(tags), %{}, fn
|
||||
{key, value}, acc -> Map.put(acc, key, value)
|
||||
tag, acc when is_atom(tag) -> Map.put(acc, tag, true)
|
||||
tag, acc when is_list(tag) -> tag |> Enum.into(acc)
|
||||
tag, acc when is_list(tag) -> Enum.into(tag, acc)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -337,7 +337,9 @@ defmodule ExUnit.CLIFormatter do
|
||||
end
|
||||
|
||||
defp format_test_type_counts(%{test_counter: test_counter} = _config) do
|
||||
Enum.map(test_counter, fn {test_type, count} ->
|
||||
test_counter
|
||||
|> Enum.sort()
|
||||
|> Enum.map(fn {test_type, count} ->
|
||||
type_pluralized = pluralize(count, test_type, ExUnit.plural_rule(test_type |> to_string()))
|
||||
"#{count} #{type_pluralized}, "
|
||||
end)
|
||||
|
||||
@@ -193,25 +193,27 @@ defmodule ExUnit.DocTest do
|
||||
This macro is auto-imported with every `ExUnit.Case`.
|
||||
"""
|
||||
defmacro doctest(module, opts \\ []) do
|
||||
caller = __CALLER__
|
||||
|
||||
require =
|
||||
if is_atom(Macro.expand(module, __CALLER__)) do
|
||||
if is_atom(Macro.expand(module, caller)) do
|
||||
quote do
|
||||
require unquote(module)
|
||||
end
|
||||
end
|
||||
|
||||
tests =
|
||||
quote bind_quoted: [module: module, opts: opts] do
|
||||
env = __ENV__
|
||||
quote bind_quoted: [
|
||||
module: module,
|
||||
opts: opts,
|
||||
env_line: caller.line,
|
||||
env_file: caller.file
|
||||
] do
|
||||
file = ExUnit.DocTest.__file__(module)
|
||||
|
||||
for {name, test} <- ExUnit.DocTest.__doctests__(module, opts) do
|
||||
if tags = Keyword.get(opts, :tags) do
|
||||
@tag tags
|
||||
end
|
||||
|
||||
for {name, test, tags} <- ExUnit.DocTest.__doctests__(module, opts) do
|
||||
@file file
|
||||
doc = ExUnit.Case.register_test(env, :doctest, name, [])
|
||||
doc = ExUnit.Case.register_test(__MODULE__, env_file, env_line, :doctest, name, tags)
|
||||
def unquote(doc)(_), do: unquote(test)
|
||||
end
|
||||
end
|
||||
@@ -230,13 +232,15 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
@doc false
|
||||
def __doctests__(module, opts) do
|
||||
do_import = Keyword.get(opts, :import, false)
|
||||
tags = [doctest: module] ++ Keyword.get(opts, :tags, [])
|
||||
import = Keyword.get(opts, :import, false)
|
||||
file = module.module_info(:compile)[:source] |> Path.relative_to_cwd()
|
||||
|
||||
extract(module)
|
||||
|> filter_by_opts(module, opts)
|
||||
|> Stream.with_index()
|
||||
|> Enum.map(fn {test, acc} ->
|
||||
compile_test(test, module, do_import, acc + 1)
|
||||
|> Enum.sort_by(& &1.line)
|
||||
|> Enum.with_index(fn test, index ->
|
||||
compile_test(test, module, import, index + 1, file, tags)
|
||||
end)
|
||||
end
|
||||
|
||||
@@ -244,14 +248,9 @@ defmodule ExUnit.DocTest do
|
||||
except = Keyword.get(opts, :except, [])
|
||||
|
||||
case Keyword.fetch(opts, :only) do
|
||||
{:ok, []} ->
|
||||
[]
|
||||
|
||||
{:ok, only} ->
|
||||
filter_tests(module, tests, except, only)
|
||||
|
||||
:error ->
|
||||
Stream.reject(tests, &(&1.fun_arity in except))
|
||||
{:ok, []} -> []
|
||||
{:ok, only} -> filter_tests(module, tests, except, only)
|
||||
:error -> Stream.reject(tests, &(&1.fun_arity in except))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -264,7 +263,7 @@ defmodule ExUnit.DocTest do
|
||||
{tests, fun_arities} -> {[test | tests], [test.fun_arity | fun_arities]}
|
||||
end
|
||||
|
||||
case only -- fun_arities do
|
||||
case only -- [:moduledoc | fun_arities] do
|
||||
[] ->
|
||||
filtered_tests
|
||||
|
||||
@@ -287,8 +286,9 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
## Compilation of extracted tests
|
||||
|
||||
defp compile_test(test, module, do_import, n) do
|
||||
{test_name(test, module, n), test_content(test, module, do_import)}
|
||||
defp compile_test(test, module, do_import, n, file, tags) do
|
||||
tags = [doctest_line: test.line] ++ tags
|
||||
{test_name(test, module, n), test_content(test, module, do_import, file), tags}
|
||||
end
|
||||
|
||||
defp test_name(%{fun_arity: :moduledoc}, m, n) do
|
||||
@@ -299,8 +299,7 @@ defmodule ExUnit.DocTest do
|
||||
"#{inspect(m)}.#{f}/#{a} (#{n})"
|
||||
end
|
||||
|
||||
defp test_content(%{exprs: exprs, line: line}, module, do_import) do
|
||||
file = module.module_info(:compile)[:source] |> Path.relative_to_cwd()
|
||||
defp test_content(%{exprs: exprs, line: line}, module, do_import, file) do
|
||||
location = [line: line, file: file]
|
||||
stack = Macro.escape([{module, :__MODULE__, 0, location}])
|
||||
|
||||
@@ -314,8 +313,8 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
tests =
|
||||
Enum.map(exprs, fn {expr, expected, formatted} ->
|
||||
test_case_content(expr, expected, location, stack, formatted)
|
||||
Enum.map(exprs, fn {expr, expected, doctest} ->
|
||||
test_case_content(expr, expected, location, stack, doctest)
|
||||
end)
|
||||
|
||||
{:__block__, [], test_import(module, do_import) ++ tests}
|
||||
@@ -328,12 +327,11 @@ defmodule ExUnit.DocTest do
|
||||
end) > 1
|
||||
end
|
||||
|
||||
defp test_case_content(expr, :test, location, stack, formatted) do
|
||||
string_to_quoted(location, stack, expr, "\n" <> formatted) |> insert_assertions()
|
||||
defp test_case_content(expr, :test, location, stack, doctest) do
|
||||
string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
|
||||
end
|
||||
|
||||
defp test_case_content(expr, {:test, expected}, location, stack, formatted) do
|
||||
doctest = "\n" <> formatted <> "\n" <> expected
|
||||
defp test_case_content(expr, {:test, expected}, location, stack, doctest) do
|
||||
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
|
||||
expected_ast = string_to_quoted(location, stack, expected, doctest)
|
||||
last_expr = Macro.to_string(last_expr(expr_ast))
|
||||
@@ -341,56 +339,53 @@ defmodule ExUnit.DocTest do
|
||||
quote do
|
||||
value = unquote(expr_ast)
|
||||
expected = unquote(expected_ast)
|
||||
formatted = unquote(formatted)
|
||||
doctest = unquote(doctest)
|
||||
last_expr = unquote(last_expr)
|
||||
expected_expr = unquote(expected)
|
||||
stack = unquote(stack)
|
||||
|
||||
ExUnit.DocTest.__test__(value, expected, formatted, last_expr, expected_expr, stack)
|
||||
ExUnit.DocTest.__test__(value, expected, doctest, last_expr, expected_expr, stack)
|
||||
end
|
||||
end
|
||||
|
||||
defp test_case_content(expr, {:inspect, expected}, location, stack, formatted) do
|
||||
doctest = "\n" <> formatted <> "\n" <> expected
|
||||
defp test_case_content(expr, {:inspect, expected}, location, stack, doctest) do
|
||||
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
|
||||
expected_ast = string_to_quoted(location, stack, expected, doctest)
|
||||
last_expr = Macro.to_string(last_expr(expr_ast))
|
||||
|
||||
quote do
|
||||
value = unquote(expr_ast)
|
||||
expected = unquote(expected_ast)
|
||||
formatted = unquote(formatted)
|
||||
expected = unquote(expected)
|
||||
doctest = unquote(doctest)
|
||||
last_expr = unquote(last_expr)
|
||||
expected_expr = unquote(expected)
|
||||
expected_expr = unquote(inspect(expected))
|
||||
stack = unquote(stack)
|
||||
|
||||
ExUnit.DocTest.__inspect__(value, expected, formatted, last_expr, expected_expr, stack)
|
||||
ExUnit.DocTest.__inspect__(value, expected, doctest, last_expr, expected_expr, stack)
|
||||
end
|
||||
end
|
||||
|
||||
defp test_case_content(expr, {:error, exception, message}, location, stack, formatted) do
|
||||
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
|
||||
defp test_case_content(expr, {:error, exception, message}, location, stack, doctest) do
|
||||
expr_ast = string_to_quoted(location, stack, expr, doctest)
|
||||
|
||||
quote do
|
||||
stack = unquote(stack)
|
||||
message = unquote(message)
|
||||
formatted = unquote(formatted)
|
||||
doctest = unquote(doctest)
|
||||
exception = unquote(exception)
|
||||
ExUnit.DocTest.__error__(fn -> unquote(expr_ast) end, message, exception, formatted, stack)
|
||||
ExUnit.DocTest.__error__(fn -> unquote(expr_ast) end, message, exception, doctest, stack)
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __test__(value, expected, formatted, last_expr, expected_expr, stack) do
|
||||
def __test__(value, expected, doctest, last_expr, expected_expr, stack) do
|
||||
case value do
|
||||
^expected ->
|
||||
:ok
|
||||
{:ok, value}
|
||||
|
||||
_ ->
|
||||
error = [
|
||||
message: "Doctest failed",
|
||||
doctest: "\n" <> formatted <> "\n" <> expected_expr,
|
||||
doctest: doctest,
|
||||
expr: "#{last_expr} === #{String.trim(expected_expr)}",
|
||||
left: value,
|
||||
right: expected
|
||||
@@ -401,7 +396,7 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __inspect__(value, expected, formatted, last_expr, expected_expr, parent_stack) do
|
||||
def __inspect__(value, expected, doctest, last_expr, expected_expr, parent_stack) do
|
||||
result =
|
||||
try do
|
||||
inspect(value, safe: false)
|
||||
@@ -416,10 +411,9 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
case result do
|
||||
:ok ->
|
||||
:ok
|
||||
{:ok, value}
|
||||
|
||||
{extra, stack} ->
|
||||
doctest = "\n" <> formatted <> "\n" <> expected_expr
|
||||
expr = "inspect(#{last_expr}) === #{String.trim(expected_expr)}"
|
||||
error = [doctest: doctest, expr: expr] ++ extra
|
||||
reraise ExUnit.AssertionError, error, stack ++ parent_stack
|
||||
@@ -427,7 +421,7 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __error__(fun, message, exception, formatted, stack) do
|
||||
def __error__(fun, message, exception, doctest, stack) do
|
||||
try do
|
||||
fun.()
|
||||
rescue
|
||||
@@ -452,12 +446,10 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
if failed do
|
||||
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
|
||||
reraise ExUnit.AssertionError, [message: failed, doctest: doctest], stack
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
|
||||
failed = "Doctest failed: expected exception #{inspect(exception)} but nothing was raised"
|
||||
error = [message: failed, doctest: doctest]
|
||||
reraise ExUnit.AssertionError, error, stack
|
||||
@@ -863,7 +855,8 @@ defmodule ExUnit.DocTest do
|
||||
end
|
||||
|
||||
defp add_expr(%{exprs: exprs} = test, expr, expected, formatted) do
|
||||
%{test | exprs: [{expr, tag_expected(expected), formatted} | exprs]}
|
||||
doctest = "\n" <> formatted <> "\n" <> expected
|
||||
%{test | exprs: [{expr, tag_expected(expected), doctest} | exprs]}
|
||||
end
|
||||
|
||||
defp tag_expected(string) do
|
||||
@@ -877,7 +870,7 @@ defmodule ExUnit.DocTest do
|
||||
|
||||
_ ->
|
||||
if inspectable?(string) do
|
||||
{:inspect, inspect(string)}
|
||||
{:inspect, string}
|
||||
else
|
||||
{:test, string}
|
||||
end
|
||||
|
||||
@@ -39,9 +39,9 @@ defmodule ExUnit.Formatter do
|
||||
* `{:case_finished, test_module}` -
|
||||
a test module has finished. See `ExUnit.TestModule` for details.
|
||||
|
||||
The full ExUnit configuration is passed as the argument to `c:GenServer.init/1` callback when the
|
||||
formatters are started. If you need to do runtime configuration of a
|
||||
formatter, you can add any configuration needed by using `ExUnit.configure/1`
|
||||
The full ExUnit configuration is passed as the argument to `c:GenServer.init/1`
|
||||
callback when the formatters are started. If you need to do runtime configuration
|
||||
of a formatter, you can add any configuration needed by using `ExUnit.configure/1`
|
||||
or `ExUnit.start/1`, and this will then be included in the options passed to
|
||||
the `c:GenServer.init/1` callback.
|
||||
"""
|
||||
@@ -126,7 +126,7 @@ defmodule ExUnit.Formatter do
|
||||
end
|
||||
end
|
||||
|
||||
# Deprecate me on Elixir v1.16
|
||||
# TODO: Deprecate me on Elixir v1.16
|
||||
@doc false
|
||||
def format_time(run, load) do
|
||||
format_times(%{run: run, load: load, async: nil})
|
||||
@@ -165,43 +165,6 @@ defmodule ExUnit.Formatter do
|
||||
end)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def format_assertion_error(%ExUnit.AssertionError{} = struct) do
|
||||
format_exception(%{}, struct, [], :infinity, fn _, msg -> msg end, "") |> elem(0)
|
||||
end
|
||||
|
||||
defp format_exception(test, %ExUnit.AssertionError{} = struct, stack, width, formatter, pad) do
|
||||
label_padding_size = if has_value?(struct.right), do: 7, else: 6
|
||||
padding_size = label_padding_size + byte_size(@counter_padding)
|
||||
|
||||
code_multiline =
|
||||
if struct.doctest != @no_value,
|
||||
do: &pad_multiline(&1, padding_size),
|
||||
else: &code_multiline(&1, padding_size)
|
||||
|
||||
formatted =
|
||||
[
|
||||
note: if_value(struct.message, &format_message(&1, formatter)),
|
||||
doctest: if_value(struct.doctest, &pad_multiline(&1, 2 + byte_size(@counter_padding))),
|
||||
code: if_value(struct.expr, code_multiline),
|
||||
code: unless_value(struct.expr, fn -> get_code(test, stack) || @no_value end),
|
||||
arguments: if_value(struct.args, &format_args(&1, width))
|
||||
]
|
||||
|> Kernel.++(format_context(struct, formatter, padding_size, width))
|
||||
|> format_meta(formatter, pad, label_padding_size)
|
||||
|> IO.iodata_to_binary()
|
||||
|
||||
{formatted, stack}
|
||||
end
|
||||
|
||||
defp format_exception(test, %FunctionClauseError{} = struct, stack, _width, formatter, _pad) do
|
||||
{blamed, stack} = Exception.blame(:error, struct, stack)
|
||||
banner = Exception.format_banner(:error, struct)
|
||||
blamed = FunctionClauseError.blame(blamed, &inspect/1, &blame_match(&1, formatter))
|
||||
message = error_info(banner, formatter) <> "\n" <> pad(String.trim_leading(blamed, "\n"))
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
end
|
||||
|
||||
@doc false
|
||||
@deprecated "Use ExUnit.Formatter.format_test_all_failure/5 instead"
|
||||
def format_test_case_failure(test_case, failures, counter, width, formatter) do
|
||||
@@ -221,6 +184,8 @@ defmodule ExUnit.Formatter do
|
||||
end)
|
||||
end
|
||||
|
||||
## kind/reason formatting
|
||||
|
||||
defp format_kind_reason(test, :error, %mod{} = struct, stack, width, formatter)
|
||||
when mod in @formatter_exceptions do
|
||||
format_exception(test, struct, stack, width, formatter, @counter_padding)
|
||||
@@ -263,129 +228,67 @@ defmodule ExUnit.Formatter do
|
||||
|
||||
defp linked_or_trapped_exit(_kind, _reason), do: :error
|
||||
|
||||
defp format_code(test, stack, formatter) do
|
||||
if snippet = get_code(test, stack) do
|
||||
" " <> formatter.(:extra_info, "code: ") <> snippet <> "\n"
|
||||
else
|
||||
""
|
||||
end
|
||||
defp format_exception(test, %ExUnit.AssertionError{} = struct, stack, width, formatter, pad) do
|
||||
label_padding_size = if has_value?(struct.right), do: 7, else: 6
|
||||
padding_size = label_padding_size + byte_size(@counter_padding)
|
||||
|
||||
code_multiline =
|
||||
if struct.doctest != @no_value,
|
||||
do: &pad_multiline(&1, padding_size),
|
||||
else: &code_multiline(&1, padding_size)
|
||||
|
||||
formatted =
|
||||
[
|
||||
message: if_value(struct.message, &format_message(&1, formatter)),
|
||||
doctest: if_value(struct.doctest, &pad_multiline(&1, 2 + byte_size(@counter_padding))),
|
||||
code: if_value(struct.expr, code_multiline, fn -> get_code(test, stack) || @no_value end),
|
||||
arguments: if_value(struct.args, &format_args(&1, width))
|
||||
]
|
||||
|> Kernel.++(format_assertion_diff(struct, padding_size, width, formatter))
|
||||
|> format_meta(formatter, pad, label_padding_size)
|
||||
|> IO.iodata_to_binary()
|
||||
|
||||
{formatted, stack}
|
||||
end
|
||||
|
||||
defp get_code(%{module: module, name: name}, stack) do
|
||||
info =
|
||||
Enum.find_value(stack, fn
|
||||
{^module, ^name, _, info} -> info
|
||||
_ -> nil
|
||||
end)
|
||||
|
||||
file = info[:file]
|
||||
line = info[:line]
|
||||
|
||||
if line > 0 && file && File.exists?(file) do
|
||||
file |> File.stream!() |> Enum.at(line - 1) |> String.trim()
|
||||
end
|
||||
rescue
|
||||
_ -> nil
|
||||
defp format_exception(test, %FunctionClauseError{} = struct, stack, _width, formatter, _pad) do
|
||||
{blamed, stack} = Exception.blame(:error, struct, stack)
|
||||
banner = Exception.format_banner(:error, struct)
|
||||
blamed = FunctionClauseError.blame(blamed, &inspect/1, &blame_match(&1, formatter))
|
||||
message = error_info(banner, formatter) <> "\n" <> pad(String.trim_leading(blamed, "\n"))
|
||||
{message <> format_code(test, stack, formatter), stack}
|
||||
end
|
||||
|
||||
defp get_code(%{}, _) do
|
||||
nil
|
||||
## Assertion error and diffing
|
||||
|
||||
@doc false
|
||||
def format_assertion_error(%ExUnit.AssertionError{} = struct) do
|
||||
format_exception(%{}, struct, [], :infinity, fn _, msg -> msg end, "") |> elem(0)
|
||||
end
|
||||
|
||||
defp blame_match(%{match?: true, node: node}, _formatter),
|
||||
do: Macro.to_string(node)
|
||||
@doc """
|
||||
Formats `ExUnit.AssertionError` diff.
|
||||
|
||||
defp blame_match(%{match?: false, node: node}, formatter),
|
||||
do: formatter.(:blame_diff, Macro.to_string(node))
|
||||
It returns a keyword list with diffing information
|
||||
from the left and right side of the assertion, if
|
||||
any exists.
|
||||
|
||||
defp format_meta(fields, formatter, padding, padding_size) do
|
||||
for {label, value} <- fields, has_value?(value) do
|
||||
[padding, format_label(label, formatter, padding_size), value, "\n"]
|
||||
end
|
||||
end
|
||||
It expects the assertion error, the `padding_size`
|
||||
for formatted content, the width (may be `:infinity`),
|
||||
and the formatter callback function.
|
||||
"""
|
||||
def format_assertion_diff(assert_error, padding_size, width, formatter)
|
||||
|
||||
defp if_value(value, fun) do
|
||||
if has_value?(value) do
|
||||
fun.(value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
defp unless_value(value, fun) do
|
||||
if has_value?(value) do
|
||||
@no_value
|
||||
else
|
||||
fun.()
|
||||
end
|
||||
end
|
||||
|
||||
defp has_value?(value) do
|
||||
value != @no_value
|
||||
end
|
||||
|
||||
defp format_label(:note, _formatter, _padding_size), do: ""
|
||||
|
||||
defp format_label(label, formatter, padding_size) do
|
||||
formatter.(:extra_info, String.pad_trailing("#{label}:", padding_size))
|
||||
end
|
||||
|
||||
defp format_message(value, formatter) do
|
||||
value = String.replace(value, "\n", "\n" <> @counter_padding)
|
||||
formatter.(:error_info, value)
|
||||
end
|
||||
|
||||
defp format_args(args, width) do
|
||||
entries =
|
||||
for {arg, i} <- Enum.with_index(args, 1) do
|
||||
"""
|
||||
|
||||
# #{i}
|
||||
#{inspect_multiline(arg, 9, width)}
|
||||
"""
|
||||
end
|
||||
|
||||
["\n" | entries]
|
||||
end
|
||||
|
||||
@assertions [
|
||||
:assert,
|
||||
:assert_raise,
|
||||
:assert_receive,
|
||||
:assert_received,
|
||||
:refute,
|
||||
:refute_receive,
|
||||
:refute_received
|
||||
]
|
||||
|
||||
defp code_multiline({fun, _, [expr]}, padding_size) when fun in @assertions do
|
||||
pad_multiline(Atom.to_string(fun) <> " " <> Macro.to_string(expr), padding_size)
|
||||
end
|
||||
|
||||
defp code_multiline(expr, padding_size) do
|
||||
pad_multiline(Macro.to_string(expr), padding_size)
|
||||
end
|
||||
|
||||
defp inspect_multiline(expr, padding_size, width) do
|
||||
width = if width == :infinity, do: width, else: width - padding_size
|
||||
|
||||
expr
|
||||
|> Algebra.to_doc(%Inspect.Opts{width: width})
|
||||
|> Algebra.group()
|
||||
|> Algebra.nest(padding_size)
|
||||
|> Algebra.format(width)
|
||||
end
|
||||
|
||||
defp format_context(%{context: {:mailbox, _pins, []}}, _, _, _) do
|
||||
def format_assertion_diff(%ExUnit.AssertionError{context: {:mailbox, _pins, []}}, _, _, _) do
|
||||
[]
|
||||
end
|
||||
|
||||
defp format_context(
|
||||
%{left: left, context: {:mailbox, pins, mailbox}},
|
||||
formatter,
|
||||
padding_size,
|
||||
width
|
||||
) do
|
||||
def format_assertion_diff(
|
||||
%ExUnit.AssertionError{left: left, context: {:mailbox, pins, mailbox}},
|
||||
padding_size,
|
||||
width,
|
||||
formatter
|
||||
) do
|
||||
formatted_mailbox =
|
||||
for message <- mailbox do
|
||||
{pattern, value, _warnings} =
|
||||
@@ -413,14 +316,14 @@ defmodule ExUnit.Formatter do
|
||||
[mailbox: Enum.join(formatted_mailbox, "\n")]
|
||||
end
|
||||
|
||||
defp format_context(
|
||||
%{left: left, right: right, context: context},
|
||||
formatter,
|
||||
padding_size,
|
||||
width
|
||||
) do
|
||||
def format_assertion_diff(
|
||||
%ExUnit.AssertionError{left: left, right: right, context: context},
|
||||
padding_size,
|
||||
width,
|
||||
formatter
|
||||
) do
|
||||
{left, right, extras} = format_sides(left, right, context, formatter, padding_size, width)
|
||||
[left: left, right: right] ++ extras
|
||||
for {k, v} <- [left: left, right: right] ++ extras, has_value?(v), do: {k, v}
|
||||
end
|
||||
|
||||
defp format_sides(left, right, context, formatter, padding_size, width) do
|
||||
@@ -488,6 +391,121 @@ defmodule ExUnit.Formatter do
|
||||
end
|
||||
end
|
||||
|
||||
## Helpers
|
||||
|
||||
defp format_code(test, stack, formatter) do
|
||||
if snippet = get_code(test, stack) do
|
||||
" " <> formatter.(:extra_info, "code: ") <> snippet <> "\n"
|
||||
else
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
defp get_code(%{module: module, name: name}, stack) do
|
||||
info =
|
||||
Enum.find_value(stack, fn
|
||||
{^module, ^name, _, info} -> info
|
||||
_ -> nil
|
||||
end)
|
||||
|
||||
file = info[:file]
|
||||
line = info[:line]
|
||||
|
||||
if line > 0 && file && File.exists?(file) do
|
||||
file |> File.stream!() |> Enum.at(line - 1) |> String.trim()
|
||||
end
|
||||
rescue
|
||||
_ -> nil
|
||||
end
|
||||
|
||||
defp get_code(%{}, _) do
|
||||
nil
|
||||
end
|
||||
|
||||
defp blame_match(%{match?: true, node: node}, _formatter),
|
||||
do: Macro.to_string(node)
|
||||
|
||||
defp blame_match(%{match?: false, node: node}, formatter),
|
||||
do: formatter.(:blame_diff, Macro.to_string(node))
|
||||
|
||||
defp format_meta(fields, formatter, padding, padding_size) do
|
||||
for {label, value} <- fields, has_value?(value) do
|
||||
[padding, format_label(label, formatter, padding_size), value, "\n"]
|
||||
end
|
||||
end
|
||||
|
||||
defp if_value(value, fun) do
|
||||
if has_value?(value) do
|
||||
fun.(value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
defp if_value(value, do_fun, else_fun) do
|
||||
if has_value?(value) do
|
||||
do_fun.(value)
|
||||
else
|
||||
else_fun.()
|
||||
end
|
||||
end
|
||||
|
||||
defp has_value?(value) do
|
||||
value != @no_value
|
||||
end
|
||||
|
||||
defp format_label(:message, _formatter, _padding_size), do: ""
|
||||
|
||||
defp format_label(label, formatter, padding_size) do
|
||||
formatter.(:extra_info, String.pad_trailing("#{label}:", padding_size))
|
||||
end
|
||||
|
||||
defp format_message(value, formatter) do
|
||||
value = String.replace(value, "\n", "\n" <> @counter_padding)
|
||||
formatter.(:error_info, value)
|
||||
end
|
||||
|
||||
defp format_args(args, width) do
|
||||
entries =
|
||||
for {arg, i} <- Enum.with_index(args, 1) do
|
||||
"""
|
||||
|
||||
# #{i}
|
||||
#{inspect_multiline(arg, 9, width)}
|
||||
"""
|
||||
end
|
||||
|
||||
["\n" | entries]
|
||||
end
|
||||
|
||||
@assertions [
|
||||
:assert,
|
||||
:assert_raise,
|
||||
:assert_receive,
|
||||
:assert_received,
|
||||
:refute,
|
||||
:refute_receive,
|
||||
:refute_received
|
||||
]
|
||||
|
||||
defp code_multiline({fun, _, [expr]}, padding_size) when fun in @assertions do
|
||||
pad_multiline(Atom.to_string(fun) <> " " <> Macro.to_string(expr), padding_size)
|
||||
end
|
||||
|
||||
defp code_multiline(expr, padding_size) do
|
||||
pad_multiline(Macro.to_string(expr), padding_size)
|
||||
end
|
||||
|
||||
defp inspect_multiline(expr, padding_size, width) do
|
||||
width = if width == :infinity, do: width, else: width - padding_size
|
||||
|
||||
expr
|
||||
|> Algebra.to_doc(%Inspect.Opts{width: width})
|
||||
|> Algebra.group()
|
||||
|> Algebra.nest(padding_size)
|
||||
|> Algebra.format(width)
|
||||
end
|
||||
|
||||
defp format_stacktrace([], _case, _test, _color) do
|
||||
""
|
||||
end
|
||||
|
||||
@@ -571,7 +571,7 @@ defmodule ExUnit.Runner do
|
||||
end
|
||||
|
||||
defp shuffle(%{seed: 0}, list) do
|
||||
Enum.reverse(list)
|
||||
list
|
||||
end
|
||||
|
||||
defp shuffle(%{seed: seed}, list) do
|
||||
|
||||
@@ -22,8 +22,8 @@ defmodule ExUnit.Server do
|
||||
end
|
||||
end
|
||||
|
||||
def modules_loaded do
|
||||
GenServer.call(@name, :modules_loaded, @timeout)
|
||||
def modules_loaded(uniq?) do
|
||||
GenServer.call(@name, {:modules_loaded, uniq?}, @timeout)
|
||||
end
|
||||
|
||||
def take_async_modules(count) do
|
||||
@@ -58,11 +58,23 @@ defmodule ExUnit.Server do
|
||||
{:reply, state.sync_modules, %{state | sync_modules: [], loaded: System.monotonic_time()}}
|
||||
end
|
||||
|
||||
def handle_call(:modules_loaded, _from, %{loaded: :done} = state) do
|
||||
def handle_call({:modules_loaded, _}, _from, %{loaded: :done} = state) do
|
||||
{:reply, 0, state}
|
||||
end
|
||||
|
||||
def handle_call(:modules_loaded, _from, %{loaded: loaded} = state) when is_integer(loaded) do
|
||||
def handle_call({:modules_loaded, uniq?}, _from, %{loaded: loaded} = state)
|
||||
when is_integer(loaded) do
|
||||
state =
|
||||
if uniq? do
|
||||
%{
|
||||
state
|
||||
| async_modules: Enum.uniq(state.async_modules),
|
||||
sync_modules: Enum.uniq(state.sync_modules)
|
||||
}
|
||||
else
|
||||
state
|
||||
end
|
||||
|
||||
diff = System.convert_time_unit(System.monotonic_time() - loaded, :native, :microsecond)
|
||||
{:reply, diff, take_modules(%{state | loaded: :done})}
|
||||
end
|
||||
|
||||
@@ -462,7 +462,10 @@ defmodule ExUnit.DocTestTest do
|
||||
# doctest ExUnit.DocTest
|
||||
|
||||
doctest ExUnit.DocTestTest.GoodModule, import: true
|
||||
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithOnly, only: [one: 0], import: true
|
||||
|
||||
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithOnly,
|
||||
only: [:moduledoc, one: 0],
|
||||
import: true
|
||||
|
||||
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithExcept,
|
||||
except: [:moduledoc, two: 0],
|
||||
@@ -574,7 +577,7 @@ defmodule ExUnit.DocTestTest do
|
||||
Doctest failed
|
||||
doctest:
|
||||
iex> :oops
|
||||
"#Inspect<[]>"
|
||||
#Inspect<[]>
|
||||
code: inspect(:oops) === "#Inspect<[]>"
|
||||
left: ":oops"
|
||||
right: "#Inspect<[]>"
|
||||
@@ -597,7 +600,7 @@ defmodule ExUnit.DocTestTest do
|
||||
Doctest failed: expected exception WhatIsThis but got RuntimeError with message "oops"
|
||||
doctest:
|
||||
iex> raise "oops"
|
||||
** (WhatIsThis) "oops"
|
||||
** (WhatIsThis) oops
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 18}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
@@ -612,7 +615,7 @@ defmodule ExUnit.DocTestTest do
|
||||
"oops"
|
||||
doctest:
|
||||
iex> raise "oops"
|
||||
** (RuntimeError) "hello"
|
||||
** (RuntimeError) hello
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 21}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
@@ -632,18 +635,17 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
9) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (9) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
9) doctest ExUnit.DocTestTest.Invalid.b/0 (9) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 57}:5: unexpected token: "`" (column 5, code point U+0060)
|
||||
#{line_placeholder(starting_line + 57)} |
|
||||
#{starting_line + 57} | ```
|
||||
#{line_placeholder(starting_line + 57)} | ^
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 33}:6: syntax error before: '*'
|
||||
#{line_placeholder(starting_line + 33)} |
|
||||
#{starting_line + 33} | 1 + * 1
|
||||
#{line_placeholder(starting_line + 33)} | ^
|
||||
doctest:
|
||||
iex> 1 + 2
|
||||
3
|
||||
```
|
||||
iex> 1 + * 1
|
||||
1
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 56}: ExUnit.DocTestTest.Invalid (module)
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 33}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
@@ -677,7 +679,22 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
12) doctest ExUnit.DocTestTest.Invalid.invalid_utf8/0 (12) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
12) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (12) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 57}:5: unexpected token: "`" (column 5, code point U+0060)
|
||||
#{line_placeholder(starting_line + 57)} |
|
||||
#{starting_line + 57} | ```
|
||||
#{line_placeholder(starting_line + 57)} | ^
|
||||
doctest:
|
||||
iex> 1 + 2
|
||||
3
|
||||
```
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 56}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
13) doctest ExUnit.DocTestTest.Invalid.invalid_utf8/0 (13) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (UnicodeConversionError) invalid encoding starting at <<255, 34, 41>>
|
||||
stacktrace:
|
||||
@@ -685,7 +702,7 @@ defmodule ExUnit.DocTestTest do
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
13) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (13) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
14) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (14) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (TokenMissingError) test/ex_unit/doc_test_test.exs:#{starting_line + 69}:7: missing terminator: } (for "{" starting at line #{starting_line + 69})
|
||||
#{line_placeholder(starting_line + 69)} |
|
||||
@@ -699,28 +716,8 @@ defmodule ExUnit.DocTestTest do
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 69}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ "14) doctest ExUnit.DocTestTest.Invalid.raising_inspect/0"
|
||||
assert output =~ "iex> ExUnit.DocTestTest.Haiku.new(:this, :is, {:not, :a, :haiku})"
|
||||
|
||||
assert output =~
|
||||
"test/ex_unit/doc_test_test.exs:#{starting_line + 82}: ExUnit.DocTestTest.Invalid (module)"
|
||||
|
||||
assert output =~ """
|
||||
15) doctest ExUnit.DocTestTest.Invalid.b/0 (15) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 33}:6: syntax error before: '*'
|
||||
#{line_placeholder(starting_line + 33)} |
|
||||
#{starting_line + 33} | 1 + * 1
|
||||
#{line_placeholder(starting_line + 33)} | ^
|
||||
doctest:
|
||||
iex> 1 + * 1
|
||||
1
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 33}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
16) doctest ExUnit.DocTestTest.Invalid.t/0 (16) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
15) doctest ExUnit.DocTestTest.Invalid.t/0 (15) (ExUnit.DocTestTest.ActuallyCompiled)
|
||||
test/ex_unit/doc_test_test.exs:#{doctest_line}
|
||||
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 75}:6: syntax error before: '*'
|
||||
#{line_placeholder(starting_line + 75)} |
|
||||
@@ -732,6 +729,12 @@ defmodule ExUnit.DocTestTest do
|
||||
stacktrace:
|
||||
test/ex_unit/doc_test_test.exs:#{starting_line + 75}: ExUnit.DocTestTest.Invalid (module)
|
||||
"""
|
||||
|
||||
assert output =~ "16) doctest ExUnit.DocTestTest.Invalid.raising_inspect/0"
|
||||
assert output =~ "iex> ExUnit.DocTestTest.Haiku.new(:this, :is, {:not, :a, :haiku})"
|
||||
|
||||
assert output =~
|
||||
"test/ex_unit/doc_test_test.exs:#{starting_line + 82}: ExUnit.DocTestTest.Invalid (module)"
|
||||
end
|
||||
|
||||
test "pattern matching assertions in doctests" do
|
||||
@@ -862,18 +865,17 @@ defmodule ExUnit.DocTestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "doctests type" do
|
||||
defmodule DoctestType do
|
||||
use ExUnit.Case
|
||||
doctest ExUnit.DocTestTest.NoImport
|
||||
test "doctests built-in tags" do
|
||||
alias ExUnit.DocTestTest.NoImport
|
||||
|
||||
setup test do
|
||||
assert test.test_type == :doctest
|
||||
:ok
|
||||
end
|
||||
defmodule DoctestTags do
|
||||
use ExUnit.Case, register: false
|
||||
doctest NoImport
|
||||
end
|
||||
|
||||
assert capture_io(fn -> ExUnit.run() end) =~ "2 doctests, 0 failures"
|
||||
assert %ExUnit.TestModule{tests: [test1, test2]} = DoctestTags.__ex_unit__()
|
||||
assert %{test_type: :doctest, doctest: NoImport, doctest_line: 129} = test1.tags
|
||||
assert %{test_type: :doctest, doctest: NoImport, doctest_line: 132} = test2.tags
|
||||
end
|
||||
|
||||
test "multiple exceptions in one test case is not supported" do
|
||||
@@ -898,24 +900,7 @@ defmodule ExUnit.DocTestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "fails when testing single function not found" do
|
||||
message = """
|
||||
test/ex_unit/doc_test_test\.exs: undefined or private function given to doctest:
|
||||
|
||||
ExUnit.DocTestTest.SomewhatGoodModuleWithOnly.three/0
|
||||
|
||||
"""
|
||||
|
||||
assert_raise ExUnit.DocTest.Error, message, fn ->
|
||||
defmodule NeverCompiled do
|
||||
import ExUnit.DocTest
|
||||
|
||||
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithOnly, only: [three: 0], import: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "fails when testing multiple functions not found" do
|
||||
test "fails when testing functions are not found" do
|
||||
message = """
|
||||
test/ex_unit/doc_test_test\.exs: undefined or private functions given to doctest:
|
||||
|
||||
@@ -987,6 +972,16 @@ defmodule ExUnit.DocTestTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "doctest direct invocation" do
|
||||
defmodule Direct do
|
||||
use ExUnit.Case, register: false
|
||||
doctest ExUnit.DocTestTest.GoodModule, import: true
|
||||
end
|
||||
|
||||
[head | _] = Direct.__ex_unit__().tests
|
||||
assert apply(Direct, head.name, [%{}]) == {:ok, 2}
|
||||
end
|
||||
|
||||
defp line_placeholder(line_number) do
|
||||
digits =
|
||||
line_number
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user