Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59bc1598a2 | ||
|
|
46dd1f2a54 | ||
|
|
e884060f03 | ||
|
|
80268ceee9 | ||
|
|
fb729784e5 | ||
|
|
7e4fbe657d | ||
|
|
d52f533761 | ||
|
|
e0c8b8af87 | ||
|
|
f950226e6c | ||
|
|
89e57882e1 | ||
|
|
67cf14c857 | ||
|
|
fe43e23a25 | ||
|
|
65681b83d7 | ||
|
|
63d682b3d7 | ||
|
|
c028c2d915 | ||
|
|
4b480cb7e0 | ||
|
|
f809d03370 | ||
|
|
ee6ed7558f | ||
|
|
bb17204741 | ||
|
|
1e4ed32eda | ||
|
|
bac5b1d800 | ||
|
|
39d8675ac1 | ||
|
|
b63f8f541e | ||
|
|
babbae7e47 | ||
|
|
0f0d4c0f6e | ||
|
|
5ee492af55 | ||
|
|
25e480f3d0 | ||
|
|
2dc6773d6c | ||
|
|
ff103b19b9 | ||
|
|
d99a16256b | ||
|
|
1601b309b8 | ||
|
|
421a77d508 | ||
|
|
cc9e9b29a7 | ||
|
|
67f7c6ba5b | ||
|
|
c99a17b8cd | ||
|
|
9ad182986c | ||
|
|
16292c65ba | ||
|
|
d25ce48d5d | ||
|
|
66b4ab19ae | ||
|
|
01a4889e39 | ||
|
|
d5e7446399 | ||
|
|
ab1da111bf | ||
|
|
6549b00eba | ||
|
|
4431ac63e2 | ||
|
|
280215c24f | ||
|
|
071285403d | ||
|
|
3d3453c1e2 | ||
|
|
2dc3fd26b8 | ||
|
|
e61ed47066 | ||
|
|
33f9d04851 | ||
|
|
8804ff5488 | ||
|
|
7e66a4c159 | ||
|
|
b085ad259d | ||
|
|
8afac93a05 | ||
|
|
b725b8c055 | ||
|
|
47171d934d | ||
|
|
edafcc4e9a | ||
|
|
233fc09e67 | ||
|
|
31e24b3a12 | ||
|
|
d3b38fb472 | ||
|
|
2efc1e5cac | ||
|
|
a0b77bd884 | ||
|
|
cbba61a386 | ||
|
|
ebb347a34e | ||
|
|
713633a939 | ||
|
|
d239d9991a |
@@ -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: 23
|
||||
otp_version: '23.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
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
otp_release: ['OTP-24.0', 'OTP-23.3', 'OTP-23.0', 'OTP-22.3', 'OTP-22.0']
|
||||
otp_release: ['OTP-24.3', 'OTP-24.0', 'OTP-23.3', 'OTP-23.0', 'OTP-22.3', 'OTP-22.0']
|
||||
development: [false]
|
||||
include:
|
||||
- otp_release: master
|
||||
|
||||
@@ -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
|
||||
+95
-1
@@ -128,6 +128,99 @@ Now any application can use your formatter as follows:
|
||||
|
||||
Finally, the `Code` module has also been augmented with two functions: `Code.string_to_quoted_with_comments/2` and `Code.quoted_to_algebra/2`. Those functions allow someone to retrieve the Elixir AST with their original source code comments, and then convert this AST to formatted code. In other words, those functions provide a wrapper around the Elixir Code Formatter, supporting developers who wish to create tools that directly manipulate and custom format Elixir source code.
|
||||
|
||||
## v1.13.4 (2022-04-07)
|
||||
|
||||
This release has been verified to work with Erlang/OTP 25 RC2.
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Allow iodata to be returned in sigil formatting functions
|
||||
* [Code] Pass opening delimiter information to sigil formatting functions
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Tweak type unification to fix infinite loop with recursive vars
|
||||
* [Kernel] Add compile-time dependencies on `require`
|
||||
* [Registry] Make `Registry` send work with named triplets
|
||||
|
||||
### 3. Deprecations
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix rebar] Deprecate Rebar 2 as it no longer works on Erlang/OTP 25
|
||||
|
||||
## v1.13.3 (2022-02-09)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix format] Supply file and line to formatter plugins
|
||||
* [mix format] Support embedded Elixir expressions inside formatter plugins
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Fix duplicate bindings causing errors during evaluation
|
||||
* [Kernel] Make sure signatures stored in the Documentation chunk does not contain newlines
|
||||
* [Kernel] Fix infinite loop when compiling guards with recursive map access
|
||||
* [Macro] Fix error on `Macro.to_string/1` when the plain alias `Elixir` is given
|
||||
* [String] Fix error for certain codepoint combinations in `String.split_at/2`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Recompile project files when exports from dependencies change
|
||||
* [mix test] Fix total coverage always showing in red even when above the threshold
|
||||
|
||||
## v1.13.2 (2022-01-13)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix format] Allow plugins to also format `.ex` and `.exs` files
|
||||
* [mix release] Allow bypassing application mode validation in release spec
|
||||
* [mix test] Print a message when the suite fails due to the coverage threshold
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Do not emit warnings on `Code.Fragment.container_cursor_to_quoted/2`
|
||||
* [Kernel] Fix a crash when a for-comprehension with `:uniq` was used inside another comprehension with `:uniq`
|
||||
* [Kernel] Ensure `env.context_modules` is properly set inside optimized `defmodule`
|
||||
* [Keyword] Deprecate the recently added `Keyword.map/2` as it is equivalent to `Keyword.new/2`
|
||||
* [Map] Deprecate the recently added `Map.map/2` as it is equivalent to `Map.new/2`
|
||||
* [Protocol] Warn on zero arity callbacks inside protocols
|
||||
|
||||
## v1.13.1 (2021-12-14)
|
||||
|
||||
### 1. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code] Do not show code snippets in `SyntaxError` and `TokenMissingError` if line is empty
|
||||
* [Exception] Do not fail blaming `ArgumentError` for improper lists on `apply/3`
|
||||
* [Macro] Set a max `line_length` for `Macro.to_string/1`
|
||||
* [Macro] Fix formatting of lists on module attributes for `Macro.to_string/1`
|
||||
* [String] Fix incorrect codepoint byte counting in `slice` with negative positions in ranges
|
||||
* [Task] Ensure async streams can be consumed from another process than the one that creates them
|
||||
* [URI] Undeprecate `URI.parse/1` as `URI.new/1` is too strict in many common cases
|
||||
* [URI] Make sure `URI.new/1` returns nil for empty paths
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx] Make sure the `--version` flag halts IEx
|
||||
|
||||
#### Mix
|
||||
|
||||
* [Mix] Make protocol consolidation part of the `Mix.install/2` cache
|
||||
|
||||
## v1.13.0 (2021-12-03)
|
||||
|
||||
### 1. Enhancements
|
||||
@@ -151,7 +244,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
|
||||
* [Inspect] Allow default inspect fun to be set globally with `Inspect.Opts.default_inspect_fun/1`
|
||||
* [IO] Allow `:eof` to be given as limit to `IO.getn/2`
|
||||
* [Kernel] Support the `:sigils` option in `import Mod, only: :sigils` and allow the sigil modifiers to be also digits
|
||||
* [Kernel] Make `get_in` consistently abort when `nil` values are found
|
||||
* [Kernel] Make `get_in` consistently abort and return `nil` when `nil` values are found (previously Elixir would raise an error in this case). This allows a user to use `get_in` as a safe navigation operator.
|
||||
* [Kernel] Improve compilation times by reducing the amount of copies of the AST across compiler processes
|
||||
* [Kernel] Raise if trying to define a module with a slash in its name
|
||||
* [Kernel] Warn when `?\` is used and there is no need for a escape character
|
||||
@@ -187,6 +280,7 @@ Finally, the `Code` module has also been augmented with two functions: `Code.str
|
||||
#### Logger
|
||||
|
||||
* [Logger] Add `Logger.put_application_level/2`
|
||||
* [Logger] Print all log levels in accordance to Erlang/OTP. This also means `[warn]` is now shown as `[warning]`
|
||||
|
||||
#### Mix
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@ clean_residual_files:
|
||||
rm -rf lib/mix/test/fixtures/git_rebar/
|
||||
rm -rf lib/mix/test/fixtures/git_repo/
|
||||
rm -rf lib/mix/test/fixtures/git_sparse_repo/
|
||||
rm -rf lib/mix/test/fixtures/archive/ebin/
|
||||
rm -f erl_crash.dump
|
||||
$(Q) $(MAKE) clean_man
|
||||
|
||||
@@ -178,40 +179,48 @@ 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}")
|
||||
|
||||
DOCS_FORMAT = html
|
||||
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."
|
||||
@@ -220,23 +229,23 @@ docs_logger: compile ../ex_doc/bin/ex_doc
|
||||
#==> Zip tasks
|
||||
|
||||
Docs.zip: docs
|
||||
rm -f Docs-v$(VERSION).zip
|
||||
zip -9 -r Docs-v$(VERSION).zip CHANGELOG.md doc NOTICE LICENSE README.md
|
||||
@ echo "Docs file created $(CURDIR)/Docs-v$(VERSION).zip"
|
||||
rm -f Docs.zip
|
||||
zip -9 -r Docs.zip CHANGELOG.md doc NOTICE LICENSE README.md
|
||||
@ echo "Docs file created $(CURDIR)/Docs.zip"
|
||||
|
||||
Precompiled.zip: build_man compile
|
||||
rm -f Precompiled-v$(VERSION).zip
|
||||
zip -9 -r Precompiled-v$(VERSION).zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled-v$(VERSION).zip"
|
||||
rm -f Precompiled.zip
|
||||
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
|
||||
@ echo "Precompiled file created $(CURDIR)/Precompiled.zip"
|
||||
|
||||
zips: Precompiled.zip Docs.zip
|
||||
@ echo ""
|
||||
@ echo "### Checksums"
|
||||
@ echo ""
|
||||
@ shasum -a 1 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
|
||||
@ shasum -a 512 < Precompiled-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
|
||||
@ shasum -a 1 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA1:"
|
||||
@ shasum -a 512 < Docs-v$(VERSION).zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA512:"
|
||||
@ shasum -a 1 < Precompiled.zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA1:"
|
||||
@ shasum -a 512 < Precompiled.zip | sed -e "s/-//" | xargs echo " * Precompiled.zip SHA512:"
|
||||
@ shasum -a 1 < Docs.zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA1:"
|
||||
@ shasum -a 512 < Docs.zip | sed -e "s/-//" | xargs echo " * Docs.zip SHA512:"
|
||||
@ echo ""
|
||||
|
||||
#==> Test tasks
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.13.0
|
||||
ELIXIR_VERSION=1.13.4
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
@@ -16,7 +16,7 @@ Usage: $(basename "$0") [options] [.exs file] [data]
|
||||
-pr "FILE" Requires the given files/patterns in parallel (*)
|
||||
-pa "PATH" Prepends the given path to Erlang code path (*)
|
||||
-pz "PATH" Appends the given path to Erlang code path (*)
|
||||
-v, --version Prints Erlang/OTP and Elixir versions
|
||||
-v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
|
||||
--app APP Starts the given app and its dependencies (*)
|
||||
--erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
|
||||
|
||||
set ELIXIR_VERSION=1.13.0
|
||||
set ELIXIR_VERSION=1.13.4
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
@@ -23,7 +23,7 @@ echo -S SCRIPT Finds and executes the given script in $PATH
|
||||
echo -pr "FILE" Requires the given files/patterns in parallel (*)
|
||||
echo -pa "PATH" Prepends the given path to Erlang code path (*)
|
||||
echo -pz "PATH" Appends the given path to Erlang code path (*)
|
||||
echo -v, --version Prints Erlang/OTP and Elixir versions
|
||||
echo -v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
echo.
|
||||
echo --app APP Starts the given app and its dependencies (*)
|
||||
echo --erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ Usage: $(basename "$0") [elixir switches] [compiler switches] [.ex files]
|
||||
|
||||
-h, --help Prints this message and exits
|
||||
-o The directory to output compiled files
|
||||
-v, --version Prints Elixir version and exits
|
||||
-v, --version Prints Elixir version and exits (standalone)
|
||||
|
||||
--ignore-module-conflict Does not emit warnings if a module was previously defined
|
||||
--no-debug-info Does not attach debug info to compiled modules
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ echo Usage: %~nx0 [elixir switches] [compiler switches] [.ex files]
|
||||
echo.
|
||||
echo -h, --help Prints this message and exits
|
||||
echo -o The directory to output compiled files
|
||||
echo -v, --version Prints Elixir version and exits
|
||||
echo -v, --version Prints Elixir version and exits (standalone)
|
||||
echo.
|
||||
echo --ignore-module-conflict Does not emit warnings if a module was previously defined
|
||||
echo --no-debug-info Does not attach debug info to compiled modules
|
||||
|
||||
+18
-15
@@ -92,6 +92,9 @@ defmodule Base do
|
||||
|
||||
"""
|
||||
|
||||
@type encode_case :: :upper | :lower
|
||||
@type decode_case :: :upper | :lower | :mixed
|
||||
|
||||
b16_alphabet = '0123456789ABCDEF'
|
||||
b64_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
||||
b64url_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
|
||||
@@ -267,7 +270,7 @@ defmodule Base do
|
||||
"666f6f626172"
|
||||
|
||||
"""
|
||||
@spec encode16(binary, keyword) :: binary
|
||||
@spec encode16(binary, case: encode_case) :: binary
|
||||
def encode16(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
do_encode16(case, data)
|
||||
@@ -300,7 +303,7 @@ defmodule Base do
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode16(binary, keyword) :: {:ok, binary} | :error
|
||||
@spec decode16(binary, case: decode_case) :: {:ok, binary} | :error
|
||||
def decode16(string, opts \\ []) do
|
||||
{:ok, decode16!(string, opts)}
|
||||
rescue
|
||||
@@ -337,7 +340,7 @@ defmodule Base do
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode16!(binary, keyword) :: binary
|
||||
@spec decode16!(binary, case: decode_case) :: binary
|
||||
def decode16!(string, opts \\ [])
|
||||
|
||||
def decode16!(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do
|
||||
@@ -367,7 +370,7 @@ defmodule Base do
|
||||
"Zm9vYg"
|
||||
|
||||
"""
|
||||
@spec encode64(binary, keyword) :: binary
|
||||
@spec encode64(binary, padding: boolean) :: binary
|
||||
def encode64(data, opts \\ []) when is_binary(data) do
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
do_encode64(data, pad?)
|
||||
@@ -397,7 +400,7 @@ defmodule Base do
|
||||
{:ok, "foob"}
|
||||
|
||||
"""
|
||||
@spec decode64(binary, keyword) :: {:ok, binary} | :error
|
||||
@spec decode64(binary, ignore: :whitespace, padding: boolean) :: {:ok, binary} | :error
|
||||
def decode64(string, opts \\ []) when is_binary(string) do
|
||||
{:ok, decode64!(string, opts)}
|
||||
rescue
|
||||
@@ -431,7 +434,7 @@ defmodule Base do
|
||||
"foob"
|
||||
|
||||
"""
|
||||
@spec decode64!(binary, keyword) :: binary
|
||||
@spec decode64!(binary, ignore: :whitespace, padding: boolean) :: binary
|
||||
def decode64!(string, opts \\ []) when is_binary(string) do
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
string |> remove_ignored(opts[:ignore]) |> do_decode64(pad?)
|
||||
@@ -453,7 +456,7 @@ defmodule Base do
|
||||
"_3_-_A"
|
||||
|
||||
"""
|
||||
@spec url_encode64(binary, keyword) :: binary
|
||||
@spec url_encode64(binary, padding: boolean) :: binary
|
||||
def url_encode64(data, opts \\ []) when is_binary(data) do
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
do_encode64url(data, pad?)
|
||||
@@ -481,7 +484,7 @@ defmodule Base do
|
||||
{:ok, <<255, 127, 254, 252>>}
|
||||
|
||||
"""
|
||||
@spec url_decode64(binary, keyword) :: {:ok, binary} | :error
|
||||
@spec url_decode64(binary, ignore: :whitespace, padding: boolean) :: {:ok, binary} | :error
|
||||
def url_decode64(string, opts \\ []) when is_binary(string) do
|
||||
{:ok, url_decode64!(string, opts)}
|
||||
rescue
|
||||
@@ -513,7 +516,7 @@ defmodule Base do
|
||||
<<255, 127, 254, 252>>
|
||||
|
||||
"""
|
||||
@spec url_decode64!(binary, keyword) :: binary
|
||||
@spec url_decode64!(binary, ignore: :whitespace, padding: boolean) :: binary
|
||||
def url_decode64!(string, opts \\ []) when is_binary(string) do
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
string |> remove_ignored(opts[:ignore]) |> do_decode64url(pad?)
|
||||
@@ -551,7 +554,7 @@ defmodule Base do
|
||||
"MZXW6YTBOI"
|
||||
|
||||
"""
|
||||
@spec encode32(binary, keyword) :: binary
|
||||
@spec encode32(binary, case: encode_case, padding: boolean) :: binary
|
||||
def encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
@@ -594,7 +597,7 @@ defmodule Base do
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec decode32(binary, keyword) :: {:ok, binary} | :error
|
||||
@spec decode32(binary, case: decode_case, padding: boolean) :: {:ok, binary} | :error
|
||||
def decode32(string, opts \\ []) do
|
||||
{:ok, decode32!(string, opts)}
|
||||
rescue
|
||||
@@ -640,7 +643,7 @@ defmodule Base do
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec decode32!(binary, keyword) :: binary
|
||||
@spec decode32!(binary, case: decode_case, padding: boolean) :: binary
|
||||
def decode32!(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
@@ -680,7 +683,7 @@ defmodule Base do
|
||||
"CPNMUOJ1E8"
|
||||
|
||||
"""
|
||||
@spec hex_encode32(binary, keyword) :: binary
|
||||
@spec hex_encode32(binary, case: encode_case, padding: boolean) :: binary
|
||||
def hex_encode32(data, opts \\ []) when is_binary(data) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
@@ -724,7 +727,7 @@ defmodule Base do
|
||||
{:ok, "foobar"}
|
||||
|
||||
"""
|
||||
@spec hex_decode32(binary, keyword) :: {:ok, binary} | :error
|
||||
@spec hex_decode32(binary, case: decode_case, padding: boolean) :: {:ok, binary} | :error
|
||||
def hex_decode32(string, opts \\ []) do
|
||||
{:ok, hex_decode32!(string, opts)}
|
||||
rescue
|
||||
@@ -771,7 +774,7 @@ defmodule Base do
|
||||
"foobar"
|
||||
|
||||
"""
|
||||
@spec hex_decode32!(binary, keyword) :: binary
|
||||
@spec hex_decode32!(binary, case: decode_case, padding: boolean) :: binary
|
||||
def hex_decode32!(string, opts \\ []) when is_binary(string) do
|
||||
case = Keyword.get(opts, :case, :upper)
|
||||
pad? = Keyword.get(opts, :padding, true)
|
||||
|
||||
@@ -2,11 +2,14 @@ defmodule DateTime do
|
||||
@moduledoc """
|
||||
A datetime implementation with a time zone.
|
||||
|
||||
This datetime can be seen as an ephemeral snapshot
|
||||
of a datetime at a given time zone. For such purposes,
|
||||
it also includes both UTC and Standard offsets, as
|
||||
well as the zone abbreviation field used exclusively
|
||||
for formatting purposes.
|
||||
This datetime can be seen as a snapshot of a date and time
|
||||
at a given time zone. For such purposes, it also includes both
|
||||
UTC and Standard offsets, as well as the zone abbreviation
|
||||
field used exclusively for formatting purposes. Note future
|
||||
datetimes are not necessarily guaranteed to exist, as time
|
||||
zones may change any time in the future due to geopolitical
|
||||
reasons. See the "Datetimes as snapshots" section for more
|
||||
information.
|
||||
|
||||
Remember, comparisons in Elixir using `==/2`, `>/2`, `</2` and friends
|
||||
are structural and based on the DateTime struct fields. For proper
|
||||
@@ -41,6 +44,56 @@ defmodule DateTime do
|
||||
Calendar.put_time_zone_database(Tzdata.TimeZoneDatabase)
|
||||
|
||||
See the proper names in the library installation instructions.
|
||||
|
||||
## Datetimes as snapshots
|
||||
|
||||
In the first section, we described datetimes as a "snapshot of
|
||||
a date and time at a given time zone". To understand precisely
|
||||
what we mean, let's see an example.
|
||||
|
||||
Imagine someone in Poland wants to schedule a meeting with someone
|
||||
in Brazil in the next year. The meeting will happen at 2:30 AM
|
||||
in the Polish time zone. At what time will the meeting happen in
|
||||
Brazil?
|
||||
|
||||
You can consult the time zone database today, one year before,
|
||||
using the API in this module and it will give you an answer that
|
||||
is valid right now. However, this answer may not be valid in the
|
||||
future. Why? Because both Brazil and Poland may change their timezone
|
||||
rules, ultimately affecting the result. For example, a country may
|
||||
choose to enter or abandon "Daylight Saving Time", which is a
|
||||
process where we adjust the clock one hour forward or one hour
|
||||
back once per year. Whenener the rules change, the exact instant
|
||||
that 2:30 AM in Polish time will be in Brazil may change.
|
||||
|
||||
In other words, whenever working with future DateTimes, there is
|
||||
no guarantee the results you get will always be correct, until
|
||||
the event actually happens. Therefore, when you ask for a future
|
||||
time, the answers you get are a snapshot that reflects the current
|
||||
state of the time zone rules. For datetimes in the past, this is
|
||||
not a problem, because time zone rules do not change for past
|
||||
events.
|
||||
|
||||
To make matters worse, it may be that the 2:30 AM in Polish time
|
||||
does not actually even exist or it is ambiguous. If a certain
|
||||
time zone observes "Daylight Saving Time", they will move their
|
||||
clock forward once a year. When this happens, there is a whole
|
||||
hour that does not exist. Then, when they move the clock back,
|
||||
there is a certain hour that will happen twice. So if you want
|
||||
to schedule a meeting when this shift back happens, you would
|
||||
need to explicitly say which of the 2:30 AM you precisely mean.
|
||||
Applications that are date and time sensitive, need to take
|
||||
these scenarios into account and correctly communicate them to
|
||||
users.
|
||||
|
||||
The good news is: Elixir contains all of the building blocks
|
||||
necessary to tackle those problems. The default timezone database
|
||||
used by Elixir, `Calendar.UTCOnlyTimeZoneDatabase`, only works
|
||||
with UTC, which does not observe those issues. Once you bring
|
||||
a proper time zone database, the functions in this module will
|
||||
query the database and return the relevant information. For
|
||||
example, look at how `DateTime.new/4` returns different results
|
||||
based on the scenarios described in this section.
|
||||
"""
|
||||
|
||||
@enforce_keys [:year, :month, :day, :hour, :minute, :second] ++
|
||||
|
||||
@@ -177,6 +177,7 @@ defmodule Code.Formatter do
|
||||
defp state(comments, opts) do
|
||||
force_do_end_blocks = Keyword.get(opts, :force_do_end_blocks, false)
|
||||
locals_without_parens = Keyword.get(opts, :locals_without_parens, [])
|
||||
file = Keyword.get(opts, :file, nil)
|
||||
sigils = Keyword.get(opts, :sigils, [])
|
||||
|
||||
sigils =
|
||||
@@ -199,7 +200,8 @@ defmodule Code.Formatter do
|
||||
operand_nesting: 2,
|
||||
skip_eol: false,
|
||||
comments: comments,
|
||||
sigils: sigils
|
||||
sigils: sigils,
|
||||
file: file
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1320,13 +1322,21 @@ defmodule Code.Formatter do
|
||||
entries =
|
||||
case state.sigils do
|
||||
%{^name => callback} ->
|
||||
case callback.(hd(entries), sigil: List.to_atom([name]), modifiers: modifiers) do
|
||||
binary when is_binary(binary) ->
|
||||
[binary]
|
||||
metadata = [
|
||||
file: state.file,
|
||||
line: meta[:line],
|
||||
sigil: List.to_atom([name]),
|
||||
modifiers: modifiers,
|
||||
opening_delimiter: opening_delimiter
|
||||
]
|
||||
|
||||
case callback.(hd(entries), metadata) do
|
||||
iodata when is_binary(iodata) or is_list(iodata) ->
|
||||
[IO.iodata_to_binary(iodata)]
|
||||
|
||||
other ->
|
||||
raise ArgumentError,
|
||||
"expected sigil callback to return a binary, got: #{inspect(other)}"
|
||||
"expected sigil callback to return iodata, got: #{inspect(other)}"
|
||||
end
|
||||
|
||||
%{} ->
|
||||
|
||||
@@ -525,7 +525,7 @@ defmodule Code.Fragment do
|
||||
|> position_surround_context(line, column, opts)
|
||||
end
|
||||
|
||||
def surround_context(other, position, opts) do
|
||||
def surround_context(other, {_, _} = position, opts) do
|
||||
surround_context(to_charlist(other), position, opts)
|
||||
end
|
||||
|
||||
@@ -827,7 +827,7 @@ defmodule Code.Fragment do
|
||||
|
||||
case :elixir_tokenizer.tokenize(fragment, line, column, tokenizer_opts) do
|
||||
{:ok, _, _, _warnings, tokens} ->
|
||||
:elixir.tokens_to_quoted(tokens, file, columns: columns, token_metadata: token_metadata)
|
||||
:elixir.tokens_to_quoted(tokens, nil, columns: columns, token_metadata: token_metadata)
|
||||
|
||||
{:error, {line, column, {prefix, suffix}, token}, _rest, _warnings, _so_far} ->
|
||||
location = [line: line, column: column]
|
||||
|
||||
@@ -200,10 +200,15 @@ defmodule Code.Normalizer do
|
||||
# Module attributes
|
||||
defp do_normalize({:@, meta, [{name, name_meta, [value]}]}, state) do
|
||||
value =
|
||||
if is_list(value) do
|
||||
normalize_kw_args(value, state, false)
|
||||
else
|
||||
do_normalize(value, state)
|
||||
cond do
|
||||
keyword?(value) ->
|
||||
normalize_kw_args(value, state, true)
|
||||
|
||||
is_list(value) ->
|
||||
normalize_literal(value, meta, state)
|
||||
|
||||
true ->
|
||||
do_normalize(value, state)
|
||||
end
|
||||
|
||||
{:@, meta, [{name, name_meta, [value]}]}
|
||||
@@ -246,12 +251,16 @@ defmodule Code.Normalizer do
|
||||
|
||||
if is_atom(literal) and Code.Identifier.classify(literal) == :alias and
|
||||
is_nil(meta[:delimiter]) do
|
||||
"Elixir." <> segments = Atom.to_string(literal)
|
||||
|
||||
segments =
|
||||
segments
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_atom/1)
|
||||
case Atom.to_string(literal) do
|
||||
"Elixir" ->
|
||||
[:"Elixir"]
|
||||
|
||||
"Elixir." <> segments ->
|
||||
segments
|
||||
|> String.split(".")
|
||||
|> Enum.map(&String.to_atom/1)
|
||||
end
|
||||
|
||||
{:__aliases__, meta, segments}
|
||||
else
|
||||
|
||||
@@ -2578,6 +2578,7 @@ defmodule Enum do
|
||||
[:a, :b, :c, :e, :f, :g, :d]
|
||||
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
def slide(enumerable, range_or_single_index, insertion_index)
|
||||
|
||||
def slide(enumerable, single_index, insertion_index) when is_integer(single_index) do
|
||||
|
||||
@@ -732,6 +732,10 @@ defmodule ArgumentError do
|
||||
) do
|
||||
message =
|
||||
cond do
|
||||
not proper_list?(args) ->
|
||||
"you attempted to apply a function named #{inspect(function)} on module #{inspect(module)} " <>
|
||||
"with arguments #{inspect(args)}. Arguments (the third argument of apply) must always be a proper list"
|
||||
|
||||
# Note that args may be an empty list even if they were supplied
|
||||
not is_atom(module) and is_atom(function) and args == [] ->
|
||||
"you attempted to apply a function named #{inspect(function)} on #{inspect(module)}. " <>
|
||||
@@ -747,10 +751,6 @@ defmodule ArgumentError do
|
||||
"you attempted to apply a function named #{inspect(function)} on module #{inspect(module)}. " <>
|
||||
"However #{inspect(function)} is not a valid function name. Function names (the second argument " <>
|
||||
"of apply) must always be an atom"
|
||||
|
||||
not is_list(args) ->
|
||||
"you attempted to apply a function named #{inspect(function)} on module #{inspect(module)} " <>
|
||||
"with arguments #{inspect(args)}. Arguments (the third argument of apply) must always be a list"
|
||||
end
|
||||
|
||||
{%{exception | message: message}, stacktrace}
|
||||
@@ -759,6 +759,9 @@ defmodule ArgumentError do
|
||||
def blame(exception, stacktrace) do
|
||||
{exception, stacktrace}
|
||||
end
|
||||
|
||||
defp proper_list?(list) when length(list) >= 0, do: true
|
||||
defp proper_list?(_), do: false
|
||||
end
|
||||
|
||||
defmodule ArithmeticError do
|
||||
|
||||
@@ -218,12 +218,16 @@ defmodule Kernel.CLI do
|
||||
|
||||
# Parse shared options
|
||||
|
||||
defp parse_shared([opt | _], _config) when opt in @standalone_opts do
|
||||
defp warn_standalone(opt) do
|
||||
IO.puts(:stderr, "#{opt} : Standalone options can't be combined with other options")
|
||||
end
|
||||
|
||||
defp parse_shared([opt | _], _config) when opt in @standalone_opts do
|
||||
warn_standalone(opt)
|
||||
System.halt(1)
|
||||
end
|
||||
|
||||
defp parse_shared([opt | t], config) when opt in ["-v", "--version"] do
|
||||
defp parse_shared([opt | t], _config) when opt in ["-v", "--version"] do
|
||||
if function_exported?(IEx, :started?, 0) and IEx.started?() do
|
||||
IO.puts("IEx " <> System.build_info()[:build])
|
||||
else
|
||||
@@ -231,7 +235,8 @@ defmodule Kernel.CLI do
|
||||
IO.puts("Elixir " <> System.build_info()[:build])
|
||||
end
|
||||
|
||||
parse_shared(t, config)
|
||||
t != [] && warn_standalone(opt)
|
||||
System.halt(0)
|
||||
end
|
||||
|
||||
defp parse_shared(["-pa", h | t], config) do
|
||||
|
||||
@@ -30,8 +30,8 @@ defmodule Kernel.LexicalTracker do
|
||||
end
|
||||
|
||||
@doc false
|
||||
def add_require(pid, module) when is_atom(module) do
|
||||
:gen_server.cast(pid, {:add_require, module})
|
||||
def add_export(pid, module) when is_atom(module) do
|
||||
:gen_server.cast(pid, {:add_export, module})
|
||||
end
|
||||
|
||||
@doc false
|
||||
@@ -168,15 +168,16 @@ defmodule Kernel.LexicalTracker do
|
||||
{:noreply, update_in(state.compile_env, &:ordsets.add_element({app, path, return}, &1))}
|
||||
end
|
||||
|
||||
def handle_cast({:add_require, module}, state) do
|
||||
def handle_cast({:add_export, module}, state) do
|
||||
{:noreply, put_in(state.exports[module], true)}
|
||||
end
|
||||
|
||||
def handle_cast({:add_import, module, fas, line, warn}, state) do
|
||||
to_remove = for {{:import, {^module, _, _}} = key, _} <- state.directives, do: key
|
||||
%{directives: directives, exports: exports} = state
|
||||
to_remove = for {{:import, {^module, _, _}} = key, _} <- directives, do: key
|
||||
|
||||
directives =
|
||||
state.directives
|
||||
directives
|
||||
|> Map.drop(to_remove)
|
||||
|> add_directive(module, line, warn, :import)
|
||||
|
||||
@@ -185,7 +186,7 @@ defmodule Kernel.LexicalTracker do
|
||||
add_directive(directives, {module, function, arity}, line, warn, :import)
|
||||
end)
|
||||
|
||||
{:noreply, %{state | directives: directives}}
|
||||
{:noreply, %{state | directives: directives, exports: Map.put(exports, module, true)}}
|
||||
end
|
||||
|
||||
def handle_cast({:add_alias, module, line, warn}, state) do
|
||||
@@ -221,9 +222,9 @@ defmodule Kernel.LexicalTracker do
|
||||
do: Map.put(references, module, :compile)
|
||||
|
||||
defp add_reference(references, module, :runtime) when is_atom(module) do
|
||||
case Map.fetch(references, module) do
|
||||
{:ok, _} -> references
|
||||
:error -> Map.put(references, module, :runtime)
|
||||
case references do
|
||||
%{^module => _} -> references
|
||||
_ -> Map.put(references, module, :runtime)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -554,7 +554,9 @@ defmodule Kernel.SpecialForms do
|
||||
defmacro alias(module, opts), do: error!([module, opts])
|
||||
|
||||
@doc """
|
||||
Requires a module in order to use its macros.
|
||||
Requires a module as a compile-time dependency.
|
||||
|
||||
Requiring a module is necessary in order to use its macros.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -576,7 +578,6 @@ defmodule Kernel.SpecialForms do
|
||||
|
||||
`require/2` also accepts `:as` as an option so it automatically sets
|
||||
up an alias. Please check `alias/2` for more information.
|
||||
|
||||
"""
|
||||
defmacro require(module, opts), do: error!([module, opts])
|
||||
|
||||
@@ -1611,7 +1612,7 @@ defmodule Kernel.SpecialForms do
|
||||
end
|
||||
|
||||
Note how the code above is better organized and clearer once we
|
||||
make sure each clause in `with` returns a normalize format.
|
||||
make sure each clause in `with` returns a normalized format.
|
||||
"""
|
||||
defmacro with(args), do: error!([args])
|
||||
|
||||
|
||||
@@ -1374,27 +1374,9 @@ defmodule Keyword do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Maps the function `fun` over all key-value pairs in `keywords`,
|
||||
returning a keyword list with all the values replaced with
|
||||
the result of the function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Keyword.map([one: 1, two: 2, three: 3], fn {_key, val} -> to_string(val) end)
|
||||
[one: "1", two: "2", three: "3"]
|
||||
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec map(t, ({key, value} -> value)) :: t
|
||||
def map(keywords, fun) when is_list(keywords) and is_function(fun, 1) do
|
||||
do_map(keywords, fun)
|
||||
end
|
||||
|
||||
defp do_map([], _fun), do: []
|
||||
|
||||
defp do_map([{key, value} | rest], fun) do
|
||||
new_value = fun.({key, value})
|
||||
[{key, new_value} | do_map(rest, fun)]
|
||||
@doc false
|
||||
@deprecated "Use Keyword.new/2 instead"
|
||||
def map(keywords, fun) when is_list(keywords) do
|
||||
Enum.map(keywords, fn {k, v} -> {k, fun.({k, v})} end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -945,7 +945,7 @@ defmodule Macro do
|
||||
"""
|
||||
@spec to_string(t()) :: String.t()
|
||||
def to_string(tree) do
|
||||
doc = Inspect.Algebra.format(Code.quoted_to_algebra(tree), :infinity)
|
||||
doc = Inspect.Algebra.format(Code.quoted_to_algebra(tree), 98)
|
||||
IO.iodata_to_binary(doc)
|
||||
end
|
||||
|
||||
|
||||
+29
-30
@@ -214,7 +214,24 @@ defmodule Map do
|
||||
|
||||
"""
|
||||
@spec new(Enumerable.t(), (term -> {key, value})) :: map
|
||||
def new(enumerable, transform) when is_function(transform, 1) do
|
||||
def new(enumerable, transform)
|
||||
def new(%_{} = enumerable, transform), do: new_from_enum(enumerable, transform)
|
||||
def new(%{} = map, transform), do: new_from_map(map, transform)
|
||||
def new(enumerable, transform), do: new_from_enum(enumerable, transform)
|
||||
|
||||
defp new_from_map(map, transform) when is_function(transform, 1) do
|
||||
iter = :maps.iterator(map)
|
||||
next = :maps.next(iter)
|
||||
:maps.from_list(do_map(next, transform))
|
||||
end
|
||||
|
||||
defp do_map(:none, _fun), do: []
|
||||
|
||||
defp do_map({key, value, iter}, transform) do
|
||||
[transform.({key, value}) | do_map(:maps.next(iter), transform)]
|
||||
end
|
||||
|
||||
defp new_from_enum(enumerable, transform) when is_function(transform, 1) do
|
||||
enumerable
|
||||
|> Enum.map(transform)
|
||||
|> :maps.from_list()
|
||||
@@ -979,12 +996,14 @@ defmodule Map do
|
||||
`fun` receives the key and value of each of the
|
||||
elements in the map as a key-value pair.
|
||||
|
||||
`Map.filter/2` is faster than using `map |> Enum.filter(fun) |> Enum.into(%{})`,
|
||||
as no intermediate list is being built.
|
||||
|
||||
See also `reject/2` which discards all elements where the
|
||||
function returns a truthy value.
|
||||
|
||||
> Note: if you find yourself doing multiple calls to `Map.filter/2`
|
||||
> and `Map.reject/2` in a pipeline, it is likely more efficient
|
||||
> to use `Enum.map/2` and `Enum.filter/2` instead and convert to
|
||||
> a map at the end using `Map.new/1`.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.filter(%{one: 1, two: 2, three: 3}, fn {_key, val} -> rem(val, 2) == 1 end)
|
||||
@@ -1010,9 +1029,8 @@ defmodule Map do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns map excluding the pairs from `map` for which `fun` returns a truthy value.
|
||||
`Map.reject/2` is faster than using `map |> Enum.reject(fun) |> Enum.into(%{})`,
|
||||
as no intermediate list is being built.
|
||||
Returns map excluding the pairs from `map` for which `fun` returns
|
||||
a truthy value.
|
||||
|
||||
See also `filter/2`.
|
||||
|
||||
@@ -1040,28 +1058,9 @@ defmodule Map do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Maps the function `fun` over all key-value pairs in `map`, returning a map
|
||||
with all the values replaced with the result of the function.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Map.map(%{1 => "joe", 2 => "mike", 3 => "robert"}, fn {_key, val} -> String.capitalize(val) end)
|
||||
%{1 => "Joe", 2 => "Mike", 3 => "Robert"}
|
||||
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
@spec map(map, ({key, value} -> value)) :: map
|
||||
def map(map, fun) when is_map(map) and is_function(fun, 1) do
|
||||
iter = :maps.iterator(map)
|
||||
next = :maps.next(iter)
|
||||
:maps.from_list(do_map(next, fun))
|
||||
end
|
||||
|
||||
defp do_map(:none, _fun), do: []
|
||||
|
||||
defp do_map({key, value, iter}, fun) do
|
||||
new_value = fun.({key, value})
|
||||
[{key, new_value} | do_map(:maps.next(iter), fun)]
|
||||
@doc false
|
||||
@deprecated "Use Map.new/2 instead"
|
||||
def map(map, fun) when is_map(map) do
|
||||
new(map, fn {k, v} -> {k, fun.({k, v})} end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1811,7 +1811,8 @@ defmodule Module do
|
||||
acc
|
||||
|
||||
true ->
|
||||
:elixir_env.trace({:require, [], behaviour, []}, env)
|
||||
event = {:remote_function, [], behaviour, :behaviour_info, 1}
|
||||
:elixir_env.trace(event, %{env | function: {:__info__, 1}})
|
||||
optional_callbacks = behaviour_info(behaviour, :optional_callbacks)
|
||||
callbacks = behaviour_info(behaviour, :callbacks)
|
||||
Enum.reduce(callbacks, acc, &add_callback(&1, behaviour, env, optional_callbacks, &2))
|
||||
|
||||
@@ -403,10 +403,8 @@ defmodule Module.Types.Pattern do
|
||||
# bar, {:ok, baz}
|
||||
# bar, {:ok, bat}
|
||||
|
||||
expanded_args =
|
||||
args
|
||||
|> Enum.map(&flatten_union(&1, context))
|
||||
|> cartesian_product()
|
||||
flatten_args = Enum.map(args, &flatten_union(&1, context))
|
||||
cartesian_args = cartesian_product(flatten_args)
|
||||
|
||||
# Remove clauses that do not match the expected type
|
||||
# Ignore type variables in parameters by changing them to dynamic
|
||||
@@ -425,11 +423,11 @@ defmodule Module.Types.Pattern do
|
||||
# the type contexts from unifying argument and parameter to
|
||||
# infer type variables in arguments
|
||||
result =
|
||||
flat_map_ok(expanded_args, fn expanded_args ->
|
||||
flat_map_ok(cartesian_args, fn cartesian_args ->
|
||||
result =
|
||||
Enum.flat_map(clauses, fn {params, return} ->
|
||||
result =
|
||||
map_ok(Enum.zip(expanded_args, params), fn {arg, param} ->
|
||||
map_ok(Enum.zip(cartesian_args, params), fn {arg, param} ->
|
||||
case unify(arg, param, stack, context) do
|
||||
{:ok, _type, context} -> {:ok, context}
|
||||
{:error, reason} -> {:error, reason}
|
||||
@@ -453,7 +451,13 @@ defmodule Module.Types.Pattern do
|
||||
{:ok, returns_contexts} ->
|
||||
{success_returns, contexts} = Enum.unzip(returns_contexts)
|
||||
contexts = Enum.concat(contexts)
|
||||
indexes = Enum.uniq(Enum.flat_map(args, &collect_var_indexes_from_type/1))
|
||||
|
||||
indexes =
|
||||
for types <- flatten_args,
|
||||
type <- types,
|
||||
index <- collect_var_indexes_from_type(type),
|
||||
do: index,
|
||||
uniq: true
|
||||
|
||||
# Build unions from collected type contexts to unify with
|
||||
# type variables from arguments
|
||||
|
||||
@@ -43,14 +43,14 @@ defmodule Module.Types.Unify do
|
||||
{:ok, same, context}
|
||||
end
|
||||
|
||||
def unify(type, {:var, var}, stack, context) do
|
||||
unify_var(var, type, stack, context, _var_source = false)
|
||||
end
|
||||
|
||||
def unify({:var, var}, type, stack, context) do
|
||||
unify_var(var, type, stack, context, _var_source = true)
|
||||
end
|
||||
|
||||
def unify(type, {:var, var}, stack, context) do
|
||||
unify_var(var, type, stack, context, _var_source = false)
|
||||
end
|
||||
|
||||
def unify({:tuple, n, sources}, {:tuple, n, targets}, stack, context) do
|
||||
result =
|
||||
map_reduce_ok(Enum.zip(sources, targets), context, fn {source, target}, context ->
|
||||
|
||||
@@ -383,7 +383,7 @@ defmodule Process do
|
||||
@type spawn_opt ::
|
||||
:link
|
||||
| :monitor
|
||||
| {:monitor, :erlang.monitor_option()}
|
||||
| {:monitor, monitor_option()}
|
||||
| {:priority, :low | :normal | :high}
|
||||
| {:fullsweep_after, non_neg_integer}
|
||||
| {:min_heap_size, non_neg_integer}
|
||||
@@ -392,6 +392,10 @@ defmodule Process do
|
||||
| {:message_queue_data, :off_heap | :on_heap}
|
||||
@type spawn_opts :: [spawn_opt]
|
||||
|
||||
# TODO: Use :erlang.monitor_option() on Erlang/OTP 24+
|
||||
@typep monitor_option ::
|
||||
[alias: :explicit_unalias | :demonitor | :reply_demonitor, tag: term()]
|
||||
|
||||
@doc """
|
||||
Spawns the given function according to the given options.
|
||||
|
||||
|
||||
@@ -723,14 +723,14 @@ defmodule Protocol do
|
||||
|
||||
defp callback_ast_to_fa({kind, {:"::", meta, [{name, _, args}, _return]}, _pos})
|
||||
when kind in [:callback, :macrocallback] do
|
||||
[{{name, length(args)}, meta}]
|
||||
[{{name, length(List.wrap(args))}, meta}]
|
||||
end
|
||||
|
||||
defp callback_ast_to_fa(
|
||||
{kind, {:when, _, [{:"::", meta, [{name, _, args}, _return]}, _vars]}, _pos}
|
||||
)
|
||||
when kind in [:callback, :macrocallback] do
|
||||
[{{name, length(args)}, meta}]
|
||||
[{{name, length(List.wrap(args))}, meta}]
|
||||
end
|
||||
|
||||
defp callback_ast_to_fa({kind, _, _pos}) when kind in [:callback, :macrocallback] do
|
||||
|
||||
@@ -260,6 +260,10 @@ defmodule Registry do
|
||||
end
|
||||
end
|
||||
|
||||
def send({registry, key, _value}, msg) do
|
||||
Registry.send({registry, key}, msg)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def unregister_name({registry, key}), do: unregister(registry, key)
|
||||
def unregister_name({registry, key, _value}), do: unregister(registry, key)
|
||||
|
||||
@@ -2723,12 +2723,16 @@ defmodule String do
|
||||
graphemes_and_length: 1,
|
||||
reverse_characters_to_binary: 1}
|
||||
|
||||
defp byte_size_remaining_at(binary, 0) do
|
||||
byte_size(binary)
|
||||
defp byte_size_unicode(binary) when is_binary(binary), do: byte_size(binary)
|
||||
defp byte_size_unicode([head]), do: byte_size_unicode(head)
|
||||
defp byte_size_unicode([head | tail]), do: byte_size_unicode(head) + byte_size_unicode(tail)
|
||||
|
||||
defp byte_size_remaining_at(unicode, 0) do
|
||||
byte_size_unicode(unicode)
|
||||
end
|
||||
|
||||
defp byte_size_remaining_at(binary, n) do
|
||||
case :unicode_util.gc(binary) do
|
||||
defp byte_size_remaining_at(unicode, n) do
|
||||
case :unicode_util.gc(unicode) do
|
||||
[_] -> 0
|
||||
[_ | rest] -> byte_size_remaining_at(rest, n - 1)
|
||||
[] -> 0
|
||||
@@ -2736,7 +2740,7 @@ defmodule String do
|
||||
end
|
||||
end
|
||||
|
||||
defp codepoint_byte_size(cp) when cp <= 0x00FF, do: 1
|
||||
defp codepoint_byte_size(cp) when cp <= 0x007F, do: 1
|
||||
defp codepoint_byte_size(cp) when cp <= 0x07FF, do: 2
|
||||
defp codepoint_byte_size(cp) when cp <= 0xFFFF, do: 3
|
||||
defp codepoint_byte_size(_), do: 4
|
||||
|
||||
@@ -657,12 +657,14 @@ defmodule Task do
|
||||
end
|
||||
|
||||
defp build_stream(enumerable, fun, options) do
|
||||
owner = self()
|
||||
fn acc, acc_fun ->
|
||||
owner = get_owner(self())
|
||||
|
||||
&Task.Supervised.stream(enumerable, &1, &2, get_callers(owner), fun, options, fn ->
|
||||
{:ok, pid} = Task.Supervised.start_link(get_owner(owner), :nomonitor)
|
||||
{:ok, :link, pid}
|
||||
end)
|
||||
Task.Supervised.stream(enumerable, acc, acc_fun, get_callers(self()), fun, options, fn ->
|
||||
{:ok, pid} = Task.Supervised.start_link(owner, :nomonitor)
|
||||
{:ok, :link, pid}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a tuple with the node where this is executed and either the
|
||||
|
||||
@@ -484,20 +484,22 @@ defmodule Task.Supervisor do
|
||||
end
|
||||
|
||||
defp build_stream(supervisor, link_type, enumerable, fun, options) do
|
||||
shutdown = options[:shutdown]
|
||||
owner = self()
|
||||
fn acc, acc_fun ->
|
||||
shutdown = options[:shutdown]
|
||||
owner = get_owner(self())
|
||||
|
||||
&Task.Supervised.stream(enumerable, &1, &2, get_callers(owner), fun, options, fn ->
|
||||
args = [get_owner(owner), :monitor]
|
||||
Task.Supervised.stream(enumerable, acc, acc_fun, get_callers(self()), fun, options, fn ->
|
||||
args = [owner, :monitor]
|
||||
|
||||
case start_child_with_spec(supervisor, args, :temporary, shutdown) do
|
||||
{:ok, pid} ->
|
||||
if link_type == :link, do: Process.link(pid)
|
||||
{:ok, link_type, pid}
|
||||
case start_child_with_spec(supervisor, args, :temporary, shutdown) do
|
||||
{:ok, pid} ->
|
||||
if link_type == :link, do: Process.link(pid)
|
||||
{:ok, link_type, pid}
|
||||
|
||||
{:error, :max_children} ->
|
||||
{:error, :max_children}
|
||||
end
|
||||
end)
|
||||
{:error, :max_children} ->
|
||||
{:error, :max_children}
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+105
-25
@@ -12,7 +12,7 @@ defmodule URI do
|
||||
"""
|
||||
|
||||
defstruct scheme: nil,
|
||||
path: "",
|
||||
path: nil,
|
||||
query: nil,
|
||||
fragment: nil,
|
||||
authority: nil,
|
||||
@@ -20,7 +20,6 @@ defmodule URI do
|
||||
host: nil,
|
||||
port: nil
|
||||
|
||||
# TODO: Remove nil from path when we fully deprecate URI.parse on Elixir v1.17
|
||||
@type t :: %__MODULE__{
|
||||
authority: authority,
|
||||
fragment: nil | binary,
|
||||
@@ -38,6 +37,7 @@ defmodule URI do
|
||||
defmodule Error do
|
||||
defexception [:action, :reason, :part]
|
||||
|
||||
@doc false
|
||||
def message(%Error{action: action, reason: reason, part: part}) do
|
||||
"cannot #{action} due to reason #{reason}: #{inspect(part)}"
|
||||
end
|
||||
@@ -480,12 +480,13 @@ defmodule URI do
|
||||
Creates a new URI struct from a URI or a string.
|
||||
|
||||
If a `%URI{}` struct is given, it returns `{:ok, uri}`. If a string is
|
||||
given, it will parse it and returns `{:ok, uri}`. If the string is
|
||||
invalid, it returns `{:error, part}` instead, with the invalid part of the URI.
|
||||
given, it will parse and validate it. If the string is valid, it returns
|
||||
`{:ok, uri}`, otherwise it returns `{:error, part}` with the invalid part
|
||||
of the URI. For parsing URIs without further validation, see `parse/1`.
|
||||
|
||||
This function can parse both absolute and relative URLs. You can check
|
||||
if a URI is absolute or relative by checking if the `scheme` field is
|
||||
`nil` or not. All fields may be `nil`, except for the `path`.
|
||||
`nil` or not.
|
||||
|
||||
When a URI is given without a port, the value returned by `URI.default_port/1`
|
||||
for the URI's scheme is used for the `:port` field. The scheme is also
|
||||
@@ -552,7 +553,7 @@ defmodule URI do
|
||||
{:ok, %URI{
|
||||
fragment: nil,
|
||||
host: nil,
|
||||
path: "",
|
||||
path: nil,
|
||||
port: 443,
|
||||
query: "query",
|
||||
scheme: "https",
|
||||
@@ -634,6 +635,8 @@ defmodule URI do
|
||||
end
|
||||
end
|
||||
|
||||
defp uri_from_map(%{path: ""} = map), do: uri_from_map(%{map | path: nil})
|
||||
|
||||
defp uri_from_map(map) do
|
||||
uri = Map.merge(%URI{}, map)
|
||||
|
||||
@@ -658,29 +661,106 @@ defmodule URI do
|
||||
end
|
||||
|
||||
@doc """
|
||||
Parses a well-formed URI into its components.
|
||||
Parses a URI into its components, without further validation.
|
||||
|
||||
This function is deprecated as it fails to raise in case of invalid URIs.
|
||||
Use `URI.new!/1` or `URI.new/1` instead. In case you want to mimic the
|
||||
behaviour of this function, you can do:
|
||||
This function can parse both absolute and relative URLs. You can check
|
||||
if a URI is absolute or relative by checking if the `scheme` field is
|
||||
nil or not. Furthermore, this function expects both absolute and
|
||||
relative URIs to be well-formed and does not perform any validation.
|
||||
See the "Examples" section below. Use `new/1` if you want more strict
|
||||
validation.
|
||||
|
||||
case URI.new(path) do
|
||||
{:ok, uri} -> uri
|
||||
{:error, _} -> %URI{path: path}
|
||||
end
|
||||
When a URI is given without a port, the value returned by `URI.default_port/1`
|
||||
for the URI's scheme is used for the `:port` field. The scheme is also
|
||||
normalized to lowercase.
|
||||
|
||||
There are two differencws in the behaviour of this function compared to
|
||||
`URI.new/1`:
|
||||
If a `%URI{}` struct is given to this function, this function returns it
|
||||
unmodified.
|
||||
|
||||
* This function sets the deprecated authority field
|
||||
> Note: this function sets the field :authority for backwards
|
||||
> compatibility reasons but it is deprecated.
|
||||
|
||||
* This function sets the path to `nil` when it is empty,
|
||||
while `new/1` consider the path always exists and sets it
|
||||
to an empty string
|
||||
## Examples
|
||||
|
||||
iex> URI.parse("https://elixir-lang.org/")
|
||||
%URI{
|
||||
authority: "elixir-lang.org",
|
||||
fragment: nil,
|
||||
host: "elixir-lang.org",
|
||||
path: "/",
|
||||
port: 443,
|
||||
query: nil,
|
||||
scheme: "https",
|
||||
userinfo: nil
|
||||
}
|
||||
|
||||
iex> URI.parse("//elixir-lang.org/")
|
||||
%URI{
|
||||
authority: "elixir-lang.org",
|
||||
fragment: nil,
|
||||
host: "elixir-lang.org",
|
||||
path: "/",
|
||||
port: nil,
|
||||
query: nil,
|
||||
scheme: nil,
|
||||
userinfo: nil
|
||||
}
|
||||
|
||||
iex> URI.parse("/foo/bar")
|
||||
%URI{
|
||||
authority: nil,
|
||||
fragment: nil,
|
||||
host: nil,
|
||||
path: "/foo/bar",
|
||||
port: nil,
|
||||
query: nil,
|
||||
scheme: nil,
|
||||
userinfo: nil
|
||||
}
|
||||
|
||||
iex> URI.parse("foo/bar")
|
||||
%URI{
|
||||
authority: nil,
|
||||
fragment: nil,
|
||||
host: nil,
|
||||
path: "foo/bar",
|
||||
port: nil,
|
||||
query: nil,
|
||||
scheme: nil,
|
||||
userinfo: nil
|
||||
}
|
||||
|
||||
In contrast to `URI.new/1`, this function will parse poorly-formed
|
||||
URIs, for example:
|
||||
|
||||
iex> URI.parse("/invalid_greater_than_in_path/>")
|
||||
%URI{
|
||||
authority: nil,
|
||||
fragment: nil,
|
||||
host: nil,
|
||||
path: "/invalid_greater_than_in_path/>",
|
||||
port: nil,
|
||||
query: nil,
|
||||
scheme: nil,
|
||||
userinfo: nil
|
||||
}
|
||||
|
||||
Another example is a URI with brackets in query strings. It is accepted
|
||||
by `parse/1` but it will be refused by `new/1`:
|
||||
|
||||
iex> URI.parse("/?foo[bar]=baz")
|
||||
%URI{
|
||||
authority: nil,
|
||||
fragment: nil,
|
||||
host: nil,
|
||||
path: "/",
|
||||
port: nil,
|
||||
query: "foo[bar]=baz",
|
||||
scheme: nil,
|
||||
userinfo: nil
|
||||
}
|
||||
|
||||
"""
|
||||
# TODO: Deprecate me at least on v1.17
|
||||
@doc deprecated: "Use URI.new/1 or URI.new!/1 instead"
|
||||
@spec parse(t | binary) :: t
|
||||
def parse(%URI{} = uri), do: uri
|
||||
|
||||
@@ -813,6 +893,7 @@ defmodule URI do
|
||||
%{rel | scheme: base.scheme, path: remove_dot_segments_from_path(rel.path)}
|
||||
end
|
||||
|
||||
# TODO: Check only for nils in future versions
|
||||
def merge(%URI{} = base, %URI{path: rel_path} = rel) when rel_path in ["", nil] do
|
||||
%{base | query: rel.query || base.query, fragment: rel.fragment}
|
||||
end
|
||||
@@ -826,9 +907,7 @@ defmodule URI do
|
||||
merge(parse(base), parse(rel))
|
||||
end
|
||||
|
||||
# TODO: Deprecate me on Elixir v1.19
|
||||
defp merge_paths(nil, rel_path), do: merge_paths("/", rel_path)
|
||||
defp merge_paths("", rel_path), do: merge_paths("/", rel_path)
|
||||
defp merge_paths(_, "/" <> _ = rel_path), do: remove_dot_segments_from_path(rel_path)
|
||||
|
||||
defp merge_paths(base_path, rel_path) do
|
||||
@@ -868,7 +947,8 @@ end
|
||||
|
||||
defimpl String.Chars, for: URI do
|
||||
def to_string(%{host: host, path: path} = uri)
|
||||
when host != nil and path != "" and binary_part(path, 0, 1) != "/" do
|
||||
when host != nil and is_binary(path) and
|
||||
path != "" and binary_part(path, 0, 1) != "/" do
|
||||
raise ArgumentError,
|
||||
":path in URI must be empty or an absolute path if URL has a :host, got: #{inspect(uri)}"
|
||||
end
|
||||
|
||||
@@ -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 = 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"])
|
||||
+19
-12
@@ -254,7 +254,7 @@ eval_quoted(Tree, Binding, #{line := Line} = E) ->
|
||||
eval_forms(Tree, Binding, Opts) when is_list(Opts) ->
|
||||
eval_forms(Tree, Binding, env_for_eval(Opts));
|
||||
eval_forms(Tree, RawBinding, OrigE) ->
|
||||
{Vars, Binding} = normalize_binding(RawBinding, [], [], 0),
|
||||
{Vars, Binding} = normalize_binding(RawBinding, #{}, [], 0),
|
||||
E = elixir_env:with_vars(OrigE, Vars),
|
||||
{_, S} = elixir_env:env_to_erl(E),
|
||||
{Erl, NewErlS, NewExS, NewE} = quoted_to_erl(Tree, E, S),
|
||||
@@ -275,12 +275,19 @@ eval_forms(Tree, RawBinding, OrigE) ->
|
||||
{Value, elixir_erl_var:dump_binding(NewBinding, NewExS, NewErlS), NewE}
|
||||
end.
|
||||
|
||||
normalize_binding([{Key, Value} | Binding], Vars, Acc, I) when is_atom(Key) ->
|
||||
normalize_binding(Binding, [{{Key, nil}, I} | Vars], [{{Key, nil}, Value} | Acc], I + 1);
|
||||
normalize_binding([{Pair, Value} | Binding], Vars, Acc, I) ->
|
||||
normalize_binding(Binding, [{Pair, I} | Vars], [{Pair, Value} | Acc], I + 1);
|
||||
normalize_binding([], Vars, Acc, _I) ->
|
||||
{maps:from_list(Vars), Acc}.
|
||||
normalize_binding([Binding | NextBindings], VarsMap, Normalized, Counter) ->
|
||||
{Pair, Value} = normalize_pair(Binding),
|
||||
case VarsMap of
|
||||
#{Pair := _} ->
|
||||
normalize_binding(NextBindings, VarsMap, [{Pair, Value} | Normalized], Counter);
|
||||
#{} ->
|
||||
normalize_binding(NextBindings, VarsMap#{Pair => Counter}, [{Pair, Value} | Normalized], Counter + 1)
|
||||
end;
|
||||
normalize_binding([], VarsMap, Normalized, _Counter) ->
|
||||
{VarsMap, Normalized}.
|
||||
|
||||
normalize_pair({Key, Value}) when is_atom(Key) -> {{Key, nil}, Value};
|
||||
normalize_pair({Pair, Value}) -> {Pair, Value}.
|
||||
|
||||
recur_eval([Expr | Exprs], Binding, Env) ->
|
||||
{value, Value, NewBinding} =
|
||||
@@ -359,8 +366,8 @@ string_to_tokens(String, StartLine, StartColumn, File, Opts) when is_integer(Sta
|
||||
{error, {Location, to_binary(Error), to_binary(Token)}}
|
||||
end.
|
||||
|
||||
tokens_to_quoted(Tokens, File, Opts) ->
|
||||
handle_parsing_opts(File, Opts),
|
||||
tokens_to_quoted(Tokens, WarningFile, Opts) ->
|
||||
handle_parsing_opts(WarningFile, Opts),
|
||||
|
||||
try elixir_parser:parse(Tokens) of
|
||||
{ok, Forms} ->
|
||||
@@ -370,7 +377,7 @@ tokens_to_quoted(Tokens, File, Opts) ->
|
||||
{error, {Line, _, [Error, Token]}} ->
|
||||
{error, {parser_location(Line), to_binary(Error), to_binary(Token)}}
|
||||
after
|
||||
erase(elixir_parser_file),
|
||||
erase(elixir_parser_warning_file),
|
||||
erase(elixir_parser_columns),
|
||||
erase(elixir_token_metadata),
|
||||
erase(elixir_literal_encoder)
|
||||
@@ -406,7 +413,7 @@ parser_location(Meta) ->
|
||||
to_binary(List) when is_list(List) -> elixir_utils:characters_to_binary(List);
|
||||
to_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, utf8).
|
||||
|
||||
handle_parsing_opts(File, Opts) ->
|
||||
handle_parsing_opts(WarningFile, Opts) ->
|
||||
LiteralEncoder =
|
||||
case lists:keyfind(literal_encoder, 1, Opts) of
|
||||
{literal_encoder, Fun} -> Fun;
|
||||
@@ -414,7 +421,7 @@ handle_parsing_opts(File, Opts) ->
|
||||
end,
|
||||
TokenMetadata = lists:keyfind(token_metadata, 1, Opts) == {token_metadata, true},
|
||||
Columns = lists:keyfind(columns, 1, Opts) == {columns, true},
|
||||
put(elixir_parser_file, File),
|
||||
put(elixir_parser_warning_file, WarningFile),
|
||||
put(elixir_parser_columns, Columns),
|
||||
put(elixir_token_metadata, TokenMetadata),
|
||||
put(elixir_literal_encoder, LiteralEncoder).
|
||||
|
||||
@@ -133,7 +133,8 @@ fast_compile({defmodule, Meta, [Mod, [{do, TailBlock}]]}, NoLineE) ->
|
||||
'Elixir.Macro':expand(Mod, E)
|
||||
end,
|
||||
|
||||
elixir_module:compile(Expanded, Block, [], E).
|
||||
ContextModules = [Expanded | ?key(E, context_modules)],
|
||||
elixir_module:compile(Expanded, Block, [], E#{context_modules := ContextModules}).
|
||||
|
||||
%% Bootstrapper
|
||||
|
||||
|
||||
@@ -552,7 +552,9 @@ signature_to_binary(Module, '__struct__', []) ->
|
||||
<<"%", ('Elixir.Kernel':inspect(Module))/binary, "{}">>;
|
||||
|
||||
signature_to_binary(_, Name, Signature) ->
|
||||
'Elixir.Macro':to_string({Name, [{closing, []}], Signature}).
|
||||
Quoted = {Name, [{closing, []}], Signature},
|
||||
Doc = 'Elixir.Inspect.Algebra':format('Elixir.Code':quoted_to_algebra(Quoted), infinity),
|
||||
'Elixir.IO':iodata_to_binary(Doc).
|
||||
|
||||
checker_chunk(#{definitions := Definitions, deprecated := Deprecated, is_behaviour := IsBehaviour}) ->
|
||||
DeprecatedMap = maps:from_list(Deprecated),
|
||||
|
||||
@@ -21,7 +21,7 @@ translate_reduce(Meta, Cases, Expr, Reduce, S) ->
|
||||
({'case', CaseAnn, _, CaseBlock}, InnerAcc) -> {'case', CaseAnn, InnerAcc, CaseBlock}
|
||||
end,
|
||||
|
||||
{build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE), SE}.
|
||||
build_reduce(Ann, TCases, InnerFun, TExpr, TReduce, false, SE).
|
||||
|
||||
translate_into(Meta, Cases, Expr, Opts, Return, S) ->
|
||||
Ann = ?ann(Meta),
|
||||
@@ -115,11 +115,11 @@ build_inline(Ann, Clauses, Expr, Into, Uniq, S) ->
|
||||
|
||||
build_inline_each(Ann, Clauses, Expr, false, Uniq, S) ->
|
||||
InnerFun = fun(InnerExpr, _InnerAcc) -> InnerExpr end,
|
||||
{build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S), S};
|
||||
build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, S);
|
||||
build_inline_each(Ann, Clauses, Expr, {nil, _} = Into, Uniq, S) ->
|
||||
InnerFun = fun(InnerExpr, InnerAcc) -> {cons, Ann, InnerExpr, InnerAcc} end,
|
||||
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
|
||||
{?remote(Ann, lists, reverse, [ReduceExpr]), S};
|
||||
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, Into, Uniq, S),
|
||||
{?remote(Ann, lists, reverse, [ReduceExpr]), SR};
|
||||
build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
|
||||
{InnerValue, SV} = build_var(Ann, S),
|
||||
Generated = erl_anno:set_generated(true, Ann),
|
||||
@@ -138,8 +138,8 @@ build_inline_each(Ann, Clauses, Expr, {bin, _, []}, Uniq, S) ->
|
||||
]}
|
||||
end,
|
||||
|
||||
ReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
|
||||
{?remote(Ann, erlang, list_to_bitstring, [ReduceExpr]), SV}.
|
||||
{ReduceExpr, SR} = build_reduce(Ann, Clauses, InnerFun, Expr, {nil, Ann}, Uniq, SV),
|
||||
{?remote(Ann, erlang, list_to_bitstring, [ReduceExpr]), SR}.
|
||||
|
||||
build_into(Ann, Clauses, Expr, {map, _, []}, Uniq, S) ->
|
||||
{ReduceExpr, SR} = build_inline_each(Ann, Clauses, Expr, {nil, Ann}, Uniq, S),
|
||||
@@ -161,7 +161,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
|
||||
?remote(Ann, 'Elixir.Collectable', into, [Into])
|
||||
},
|
||||
|
||||
IntoReduceExpr = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
|
||||
{IntoReduceExpr, SN} = build_reduce(Ann, Clauses, InnerFun, Expr, Acc, Uniq, SD),
|
||||
|
||||
TryExpr =
|
||||
{'try', Ann,
|
||||
@@ -173,7 +173,7 @@ build_into(Ann, Clauses, Expr, Into, Uniq, S) ->
|
||||
[stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack)],
|
||||
[]},
|
||||
|
||||
{{block, Ann, [MatchExpr, TryExpr]}, SD}.
|
||||
{{block, Ann, [MatchExpr, TryExpr]}, SN}.
|
||||
|
||||
stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack) ->
|
||||
{clause, Ann,
|
||||
@@ -186,7 +186,7 @@ stacktrace_clause(Ann, Fun, Acc, Kind, Reason, Stack) ->
|
||||
|
||||
build_reduce(Ann, Clauses, InnerFun, Expr, Into, false, S) ->
|
||||
{Acc, SA} = build_var(Ann, S),
|
||||
build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA);
|
||||
{build_reduce_each(Clauses, InnerFun(Expr, Acc), Into, Acc, SA), SA};
|
||||
build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
|
||||
%% Those variables are used only inside the anonymous function
|
||||
%% so we don't need to worry about returning the scope.
|
||||
@@ -209,7 +209,7 @@ build_reduce(Ann, Clauses, InnerFun, Expr, Into, true, S) ->
|
||||
]},
|
||||
|
||||
EnumReduceCall = build_reduce_each(Clauses, InnerExpr, NewInto, Acc, SU),
|
||||
?remote(Ann, erlang, element, [{integer, Ann, 1}, EnumReduceCall]).
|
||||
{?remote(Ann, erlang, element, [{integer, Ann, 1}, EnumReduceCall]), SU}.
|
||||
|
||||
build_reduce_each([{enum, Meta, Left, Right, Filters} | T], Expr, Arg, Acc, S) ->
|
||||
Ann = ?ann(Meta),
|
||||
|
||||
@@ -87,9 +87,12 @@ snippet(InputString, Location, StartLine, StartColumn) ->
|
||||
case lists:keyfind(column, 1, Location) of
|
||||
{column, Column} ->
|
||||
Lines = string:split(InputString, "\n", all),
|
||||
Snippet = elixir_utils:characters_to_binary(lists:nth(Line - StartLine + 1, Lines)),
|
||||
Snippet = (lists:nth(Line - StartLine + 1, Lines)),
|
||||
Offset = if Line == StartLine -> Column - StartColumn; true -> Column - 1 end,
|
||||
#{content => Snippet, offset => Offset};
|
||||
case string:trim(Snippet, leading) of
|
||||
[] -> nil;
|
||||
_ -> #{content => elixir_utils:characters_to_binary(Snippet), offset => Offset}
|
||||
end;
|
||||
|
||||
false ->
|
||||
nil
|
||||
|
||||
@@ -85,6 +85,7 @@ expand({require, Meta, [Ref, Opts]}, S, E) ->
|
||||
if
|
||||
is_atom(ERef) ->
|
||||
elixir_aliases:ensure_loaded(Meta, ERef, ET),
|
||||
elixir_env:trace({require, Meta, ERef, EOpts}, ET),
|
||||
{ERef, ST, expand_require(Meta, ERef, EOpts, ET)};
|
||||
true ->
|
||||
form_error(Meta, E, ?MODULE, {expected_compile_time_module, require, Ref})
|
||||
@@ -931,7 +932,6 @@ no_alias_expansion(Other) ->
|
||||
Other.
|
||||
|
||||
expand_require(Meta, Ref, Opts, E) ->
|
||||
elixir_env:trace({require, Meta, Ref, Opts}, E),
|
||||
RE = E#{requires := ordsets:add_element(Ref, ?key(E, requires))},
|
||||
expand_alias(Meta, false, Ref, Opts, RE).
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@ trace({alias_expansion, _Meta, Lookup, _Result}, #{lexical_tracker := Pid}) ->
|
||||
?tracker:alias_dispatch(Pid, Lookup),
|
||||
ok;
|
||||
trace({require, _Meta, Module, _Opts}, #{lexical_tracker := Pid}) ->
|
||||
?tracker:add_require(Pid, Module),
|
||||
?tracker:remote_dispatch(Pid, Module, compile),
|
||||
ok;
|
||||
trace({struct_expansion, _Meta, Module, _Keys}, #{lexical_tracker := Pid}) ->
|
||||
?tracker:add_require(Pid, Module),
|
||||
?tracker:add_export(Pid, Module),
|
||||
ok;
|
||||
trace({alias_reference, _Meta, Module}, #{lexical_tracker := Pid} = E) ->
|
||||
?tracker:remote_dispatch(Pid, Module, mode(E)),
|
||||
|
||||
@@ -640,7 +640,6 @@ map -> struct_op struct_expr eol map_args : {'%', meta_from_token('$1'), ['$2',
|
||||
|
||||
Erlang code.
|
||||
|
||||
-define(file(), get(elixir_parser_file)).
|
||||
-define(columns(), get(elixir_parser_columns)).
|
||||
-define(token_metadata(), get(elixir_token_metadata)).
|
||||
|
||||
@@ -729,10 +728,12 @@ build_op(AST, {_Kind, Location, '//'}, Right) ->
|
||||
|
||||
build_op({UOp, _, [Left]}, {_Kind, {Line, Column, _} = Location, 'in'}, Right) when ?rearrange_uop(UOp) ->
|
||||
%% TODO: Remove "not left in right" rearrangement on v2.0
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
warn(
|
||||
{Line, Column},
|
||||
"\"not expr1 in expr2\" is deprecated. "
|
||||
"Instead use \"expr1 not in expr2\" if you require Elixir v1.5+, "
|
||||
"or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"),
|
||||
"or \"not(expr1 in expr2)\" if you have to support earlier Elixir versions"
|
||||
),
|
||||
Meta = meta_from_location(Location),
|
||||
{UOp, Meta, [{'in', Meta, [Left, Right]}]};
|
||||
|
||||
@@ -1169,21 +1170,22 @@ error_invalid_kw_identifier({_, Location, KW}) ->
|
||||
|
||||
%% TODO: Make this an error on v2.0
|
||||
warn_trailing_comma({',', {Line, Column, _}}) ->
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
"trailing commas are not allowed inside function/macro call arguments"
|
||||
).
|
||||
warn({Line, Column}, "trailing commas are not allowed inside function/macro call arguments").
|
||||
|
||||
%% TODO: Make this an error on v2.0
|
||||
warn_empty_paren({_, {Line, Column, _}}) ->
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
warn(
|
||||
{Line, Column},
|
||||
"invalid expression (). "
|
||||
"If you want to invoke or define a function, make sure there are "
|
||||
"no spaces between the function name and its arguments. If you wanted "
|
||||
"to pass an empty block or code, pass a value instead, such as a nil or an atom").
|
||||
"to pass an empty block or code, pass a value instead, such as a nil or an atom"
|
||||
).
|
||||
|
||||
%% TODO: Make this an error on v2.0
|
||||
warn_pipe({arrow_op, {Line, Column, _}, Op}, {_, [_ | _], [_ | _]}) ->
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
warn(
|
||||
{Line, Column},
|
||||
io_lib:format(
|
||||
"parentheses are required when piping into a function call. For example:\n\n"
|
||||
" foo 1 ~ts bar 2 ~ts baz 3\n\n"
|
||||
@@ -1197,6 +1199,14 @@ warn_pipe(_Token, _) ->
|
||||
ok.
|
||||
|
||||
warn_empty_stab_clause({stab_op, {Line, Column, _}, '->'}) ->
|
||||
elixir_errors:erl_warn({Line, Column}, ?file(),
|
||||
warn(
|
||||
{Line, Column},
|
||||
"an expression is always required on the right side of ->. "
|
||||
"Please provide a value after ->").
|
||||
"Please provide a value after ->"
|
||||
).
|
||||
|
||||
warn(LineColumn, Message) ->
|
||||
case get(elixir_parser_warning_file) of
|
||||
nil -> ok;
|
||||
File -> elixir_errors:erl_warn(LineColumn, File, Message)
|
||||
end.
|
||||
@@ -799,10 +799,21 @@ defmodule BaseTest do
|
||||
|> Enum.shuffle()
|
||||
|> IO.iodata_to_binary()
|
||||
|
||||
allowed_opts =
|
||||
encode
|
||||
|> Function.info()
|
||||
|> Keyword.fetch!(:name)
|
||||
|> case do
|
||||
:encode16 -> [:case]
|
||||
:encode64 -> [:padding]
|
||||
:url_encode64 -> [:padding]
|
||||
_ -> [:case, :padding]
|
||||
end
|
||||
|
||||
expected =
|
||||
data
|
||||
|> encode.(case: encode_case, pad: pad?)
|
||||
|> decode.(case: decode_case, pad: pad?)
|
||||
|> encode.(Keyword.take([case: encode_case, padding: pad?], allowed_opts))
|
||||
|> decode.(Keyword.take([case: decode_case, padding: pad?], allowed_opts))
|
||||
|
||||
assert data == expected,
|
||||
"identity did not match for #{inspect(data)} when #{inspect(encode)} (#{encode_case})"
|
||||
|
||||
@@ -126,23 +126,23 @@ defmodule Code.Formatter.GeneralTest do
|
||||
"""
|
||||
|
||||
formatter = fn content, opts ->
|
||||
assert opts == [sigil: :W, modifiers: []]
|
||||
assert opts == [file: nil, line: 1, sigil: :W, modifiers: [], opening_delimiter: "/"]
|
||||
content |> String.split(~r/ +/) |> Enum.join(" ")
|
||||
end
|
||||
|
||||
assert_format bad, good, sigils: [W: formatter]
|
||||
|
||||
bad = """
|
||||
var = ~W/foo bar baz/abc
|
||||
var = ~W<foo bar baz>abc
|
||||
"""
|
||||
|
||||
good = """
|
||||
var = ~W/foo bar baz/abc
|
||||
var = ~W<foo bar baz>abc
|
||||
"""
|
||||
|
||||
formatter = fn content, opts ->
|
||||
assert opts == [sigil: :W, modifiers: 'abc']
|
||||
content |> String.split(~r/ +/) |> Enum.join(" ")
|
||||
assert opts == [file: nil, line: 1, sigil: :W, modifiers: 'abc', opening_delimiter: "<"]
|
||||
content |> String.split(~r/ +/) |> Enum.intersperse(" ")
|
||||
end
|
||||
|
||||
assert_format bad, good, sigils: [W: formatter]
|
||||
@@ -162,34 +162,41 @@ defmodule Code.Formatter.GeneralTest do
|
||||
"""
|
||||
|
||||
formatter = fn content, opts ->
|
||||
assert opts == [sigil: :W, modifiers: []]
|
||||
assert opts == [file: nil, line: 1, sigil: :W, modifiers: [], opening_delimiter: "'''"]
|
||||
content |> String.split(~r/ +/) |> Enum.join(" ")
|
||||
end
|
||||
|
||||
assert_format bad, good, sigils: [W: formatter]
|
||||
|
||||
bad = """
|
||||
bad = ~S'''
|
||||
if true do
|
||||
~W'''
|
||||
~W"""
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
'''abc
|
||||
"""abc
|
||||
end
|
||||
"""
|
||||
'''
|
||||
|
||||
good = """
|
||||
good = ~S'''
|
||||
if true do
|
||||
~W'''
|
||||
~W"""
|
||||
foo
|
||||
bar
|
||||
baz
|
||||
'''abc
|
||||
"""abc
|
||||
end
|
||||
"""
|
||||
'''
|
||||
|
||||
formatter = fn content, opts ->
|
||||
assert opts == [sigil: :W, modifiers: 'abc']
|
||||
assert opts == [
|
||||
file: nil,
|
||||
line: 2,
|
||||
sigil: :W,
|
||||
modifiers: 'abc',
|
||||
opening_delimiter: ~S/"""/
|
||||
]
|
||||
|
||||
content |> String.split(~r/ +/) |> Enum.join("\n")
|
||||
end
|
||||
|
||||
|
||||
@@ -915,6 +915,11 @@ defmodule CodeFragmentTest do
|
||||
assert cc2q("foo(123, ~r/") == s2q("foo(123, __cursor__())")
|
||||
end
|
||||
|
||||
test "no warnings" do
|
||||
assert cc2q(~s"?\\ ") == s2q("__cursor__()")
|
||||
assert cc2q(~s"{fn -> end, ") == s2q("{fn -> nil end, __cursor__()}")
|
||||
end
|
||||
|
||||
test "options" do
|
||||
opts = [columns: true]
|
||||
assert cc2q("foo(", opts) == s2q("foo(__cursor__())", opts)
|
||||
|
||||
@@ -115,6 +115,7 @@ defmodule Code.Normalizer.FormatterASTTest do
|
||||
|
||||
test "does not reformat aliases" do
|
||||
assert_same ~S[:"Elixir.String"]
|
||||
assert_same ~S[:"Elixir"]
|
||||
end
|
||||
|
||||
test "quoted operators" do
|
||||
@@ -164,6 +165,12 @@ defmodule Code.Normalizer.FormatterASTTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "lists" do
|
||||
test "on module attribute" do
|
||||
assert_same ~S"@foo [1]"
|
||||
end
|
||||
end
|
||||
|
||||
describe "charlists" do
|
||||
test "without escapes" do
|
||||
assert_same ~S['']
|
||||
|
||||
@@ -542,6 +542,26 @@ defmodule Code.Normalizer.QuotedASTTest do
|
||||
assert quoted_to_string(quote(do: foo(catch: a))) == "foo(catch: a)"
|
||||
assert quoted_to_string(quote(do: foo |> [bar: :baz])) == "foo |> [bar: :baz]"
|
||||
end
|
||||
|
||||
test "list in module attribute" do
|
||||
assert quoted_to_string(
|
||||
quote do
|
||||
@foo [1]
|
||||
end
|
||||
) == "@foo [1]"
|
||||
|
||||
assert quoted_to_string(
|
||||
quote do
|
||||
@foo [foo: :bar]
|
||||
end
|
||||
) == "@foo foo: :bar"
|
||||
|
||||
assert quoted_to_string(
|
||||
quote do
|
||||
@foo [1, foo: :bar]
|
||||
end
|
||||
) == "@foo [1, foo: :bar]"
|
||||
end
|
||||
end
|
||||
|
||||
describe "quoted_to_algebra/2 escapes" do
|
||||
@@ -600,6 +620,7 @@ defmodule Code.Normalizer.QuotedASTTest do
|
||||
assert quoted_to_string({:__block__, [], [:"a\nb\tc"]}, escape: false) == ~s/:"a\nb\tc"/
|
||||
assert quoted_to_string({:__block__, [], [:"a\nb\tc"]}) == ~S/:"a\nb\tc"/
|
||||
|
||||
assert quoted_to_string(quote(do: :"Elixir")) == "Elixir"
|
||||
assert quoted_to_string(quote(do: :"Elixir.Foo")) == "Foo"
|
||||
assert quoted_to_string(quote(do: :"Elixir.Foo.Bar")) == "Foo.Bar"
|
||||
assert quoted_to_string(quote(do: :"Elixir.foobar")) == ~S/:"Elixir.foobar"/
|
||||
|
||||
@@ -52,6 +52,9 @@ defmodule CodeTest 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) ==
|
||||
{"1", [{:b, "1"}, {:a, 1}]}
|
||||
|
||||
assert Code.eval_string("b = String.Chars.to_string(a)", a: 0, a: 1, c: 2) ==
|
||||
{"1", [{:c, 2}, {:b, "1"}, {:a, 1}]}
|
||||
end
|
||||
|
||||
test "with many options" do
|
||||
@@ -75,18 +78,20 @@ defmodule CodeTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "raises streamlined argument errors" do
|
||||
assert_raise ArgumentError,
|
||||
~r"argument error while evaluating at line 1",
|
||||
fn -> Code.eval_string("a <> b", a: :a, b: :b) end
|
||||
if System.otp_release() < "25" do
|
||||
test "raises streamlined argument errors" do
|
||||
assert_raise ArgumentError,
|
||||
~r"argument error while evaluating at line 1",
|
||||
fn -> Code.eval_string("a <> b", a: :a, b: :b) end
|
||||
|
||||
assert_raise ArgumentError,
|
||||
~r"argument error while evaluating example.ex at line 1",
|
||||
fn -> Code.eval_string("a <> b", [a: :a, b: :b], file: "example.ex") end
|
||||
assert_raise ArgumentError,
|
||||
~r"argument error while evaluating example.ex at line 1",
|
||||
fn -> Code.eval_string("a <> b", [a: :a, b: :b], file: "example.ex") end
|
||||
|
||||
assert_raise ArgumentError,
|
||||
~r"argument error while evaluating example.ex between lines 1 and 2",
|
||||
fn -> Code.eval_string("a <>\nb", [a: :a, b: :b], file: "example.ex") end
|
||||
assert_raise ArgumentError,
|
||||
~r"argument error while evaluating example.ex between lines 1 and 2",
|
||||
fn -> Code.eval_string("a <>\nb", [a: :a, b: :b], file: "example.ex") end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -423,7 +423,11 @@ defmodule ExceptionTest do
|
||||
|
||||
assert blame_message(123, &apply(Kernel, :+, &1)) ==
|
||||
"you attempted to apply a function named :+ on module Kernel with arguments 123. " <>
|
||||
"Arguments (the third argument of apply) must always be a list"
|
||||
"Arguments (the third argument of apply) must always be a proper list"
|
||||
|
||||
assert blame_message(123, &apply(Kernel, :+, [&1 | 456])) ==
|
||||
"you attempted to apply a function named :+ on module Kernel with arguments [123 | 456]. " <>
|
||||
"Arguments (the third argument of apply) must always be a proper list"
|
||||
end
|
||||
|
||||
test "annotates function clause errors" do
|
||||
|
||||
@@ -71,10 +71,14 @@ defmodule Kernel.CLITest do
|
||||
assert output =~ "Erlang/OTP #{System.otp_release()}"
|
||||
assert output =~ "Elixir #{System.version()}"
|
||||
|
||||
output = iex('--version')
|
||||
assert output =~ "Erlang/OTP #{System.otp_release()}"
|
||||
assert output =~ "IEx #{System.version()}"
|
||||
|
||||
output = elixir('--version -e "IO.puts(:test_output)"')
|
||||
assert output =~ "Erlang/OTP #{System.otp_release()}"
|
||||
assert output =~ "Elixir #{System.version()}"
|
||||
assert output =~ "test_output"
|
||||
assert output =~ "Standalone options can't be combined with other options"
|
||||
end
|
||||
|
||||
test "--short-version smoke test" do
|
||||
|
||||
@@ -98,6 +98,19 @@ defmodule Kernel.ComprehensionTest do
|
||||
assert Process.get(:into_halt)
|
||||
end
|
||||
|
||||
test "nested for comprehensions with unique values" do
|
||||
assert for(x <- [1, 1, 2], uniq: true, do: for(y <- [3, 3], uniq: true, do: x * y)) == [
|
||||
[3],
|
||||
[6]
|
||||
]
|
||||
|
||||
assert for(<<x <- "abcabc">>,
|
||||
uniq: true,
|
||||
into: "",
|
||||
do: for(<<y <- "zz">>, uniq: true, into: "", do: to_bin(x) <> to_bin(y))
|
||||
) == "azbzcz"
|
||||
end
|
||||
|
||||
test "for comprehensions with nilly filters" do
|
||||
assert for(x <- 1..3, nilly(), do: x * 2) == []
|
||||
end
|
||||
|
||||
@@ -119,6 +119,14 @@ defmodule Kernel.DocsTest do
|
||||
|
||||
def two_good_names(first, :ok), do: first
|
||||
def two_good_names(second, :error), do: second
|
||||
|
||||
def really_long_signature(
|
||||
really_long_var_named_one,
|
||||
really_long_var_named_two,
|
||||
really_long_var_named_three
|
||||
) do
|
||||
{really_long_var_named_one, really_long_var_named_two, really_long_var_named_three}
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
@@ -128,6 +136,7 @@ defmodule Kernel.DocsTest do
|
||||
assert [
|
||||
arg_names,
|
||||
only_underscore,
|
||||
really_long_signature,
|
||||
two_good_names,
|
||||
with_defaults,
|
||||
with_map_and_default,
|
||||
@@ -141,6 +150,12 @@ defmodule Kernel.DocsTest do
|
||||
# only_underscore/1
|
||||
assert {{:only_underscore, 1}, ["only_underscore(_)"]} = only_underscore
|
||||
|
||||
# really_long_signature/3
|
||||
assert {{:really_long_signature, 3},
|
||||
[
|
||||
"really_long_signature(really_long_var_named_one, really_long_var_named_two, really_long_var_named_three)"
|
||||
]} = really_long_signature
|
||||
|
||||
# two_good_names/2
|
||||
assert {{:two_good_names, 2}, ["two_good_names(first, atom)"]} = two_good_names
|
||||
|
||||
|
||||
@@ -491,6 +491,21 @@ defmodule Kernel.ErrorsTest do
|
||||
assert_eval_raise TokenMissingError, ~r/nofile:1:3: invalid escape \\ at end of file/, '1 \\'
|
||||
end
|
||||
|
||||
test "show snippet on missing tokens" do
|
||||
assert_eval_raise TokenMissingError,
|
||||
"nofile:1:25: missing terminator: end (for \"do\" starting at line 1)\n" <>
|
||||
" |\n" <>
|
||||
" 1 | defmodule ShowSnippet do\n" <>
|
||||
" | ^",
|
||||
'defmodule ShowSnippet do'
|
||||
end
|
||||
|
||||
test "don't show snippet when error line is empty" do
|
||||
assert_eval_raise TokenMissingError,
|
||||
"nofile:3:1: missing terminator: end (for \"do\" starting at line 1)",
|
||||
'defmodule ShowSnippet do\n\n'
|
||||
end
|
||||
|
||||
test "function local conflict" do
|
||||
assert_eval_raise CompileError,
|
||||
"nofile:3: imported Kernel.&&/2 conflicts with local function",
|
||||
|
||||
@@ -21,8 +21,8 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
assert D.references(config[:pid]) == {[String], [], [], []}
|
||||
end
|
||||
|
||||
test "can add requires", config do
|
||||
D.add_require(config[:pid], URI)
|
||||
test "can add module requires and exports", config do
|
||||
D.add_export(config[:pid], URI)
|
||||
assert D.references(config[:pid]) == {[], [URI], [], []}
|
||||
|
||||
D.remote_dispatch(config[:pid], URI, :runtime)
|
||||
@@ -33,7 +33,6 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
end
|
||||
|
||||
test "can add module imports", config do
|
||||
D.add_require(config[:pid], String)
|
||||
D.add_import(config[:pid], String, [], 1, true)
|
||||
|
||||
D.import_dispatch(config[:pid], String, {:upcase, 1}, :runtime)
|
||||
@@ -44,7 +43,7 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
end
|
||||
|
||||
test "can add module with {function, arity} imports", config do
|
||||
D.add_require(config[:pid], String)
|
||||
D.add_export(config[:pid], String)
|
||||
D.add_import(config[:pid], String, [upcase: 1], 1, true)
|
||||
|
||||
D.import_dispatch(config[:pid], String, {:upcase, 1}, :compile)
|
||||
@@ -181,18 +180,18 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
{{compile, _exports, runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.Defdelegate do
|
||||
defdelegate a, to: A
|
||||
defdelegate parse(arg), to: URI
|
||||
|
||||
opts = [to: B]
|
||||
defdelegate b, opts
|
||||
opts = [to: Range]
|
||||
defdelegate disjoint?(left, right), opts
|
||||
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
refute A in compile
|
||||
assert B in compile
|
||||
assert A in runtime
|
||||
refute URI in compile
|
||||
assert Range in compile
|
||||
assert URI in runtime
|
||||
end
|
||||
|
||||
test "imports adds an export dependency" do
|
||||
@@ -209,6 +208,20 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
refute String in runtime
|
||||
end
|
||||
|
||||
test "requires adds a compile dependency" do
|
||||
{{compile, exports, runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.Requires do
|
||||
require String
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
assert String in compile
|
||||
refute String in exports
|
||||
refute String in runtime
|
||||
end
|
||||
|
||||
test "structs are exports or compile time" do
|
||||
{{compile, exports, runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
|
||||
@@ -521,6 +521,9 @@ defmodule Kernel.QuoteTest.AliasHygieneTest do
|
||||
assert {:__aliases__, [alias: false], [:Foo, :Bar]} = quote(do: Foo.Bar)
|
||||
assert {:__aliases__, [alias: false], [:Dict, :Bar]} = quote(do: Dict.Bar)
|
||||
assert {:__aliases__, [alias: Dict.Bar], [:SuperDict, :Bar]} = quote(do: SuperDict.Bar)
|
||||
|
||||
# Edge-case
|
||||
assert {:__aliases__, _, [Elixir]} = quote(do: Elixir)
|
||||
end
|
||||
|
||||
test "expand aliases" do
|
||||
|
||||
@@ -1163,9 +1163,11 @@ defmodule Kernel.WarningTest do
|
||||
|
||||
def without_specs(term, options \\ [])
|
||||
|
||||
@callback foo :: {:ok, term}
|
||||
@callback foo(term) :: {:ok, term}
|
||||
@callback foo(term, keyword) :: {:ok, term, keyword}
|
||||
|
||||
@callback foo_when :: {:ok, x} when x: term
|
||||
@callback foo_when(x) :: {:ok, x} when x: term
|
||||
@callback foo_when(x, opts) :: {:ok, x, opts} when x: term, opts: keyword
|
||||
|
||||
@@ -1178,12 +1180,18 @@ defmodule Kernel.WarningTest do
|
||||
""")
|
||||
end)
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo/2 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo_when/0 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
assert message =~
|
||||
"cannot define @callback foo_when/1 inside protocol, use def/1 to outline your protocol definition\n nofile:1"
|
||||
|
||||
|
||||
@@ -723,8 +723,12 @@ defmodule KernelTest do
|
||||
result = expand_to_string(quote(do: rand() in 1..2))
|
||||
assert result =~ "var = rand()"
|
||||
|
||||
assert result =~
|
||||
":erlang.andalso(:erlang.is_integer(var), :erlang.andalso(:erlang.>=(var, 1), :erlang.\"=<\"(var, 2)))"
|
||||
assert result =~ """
|
||||
:erlang.andalso(
|
||||
:erlang.is_integer(var),
|
||||
:erlang.andalso(:erlang.>=(var, 1), :erlang.\"=<\"(var, 2))
|
||||
)\
|
||||
"""
|
||||
|
||||
# Empty list
|
||||
assert expand_to_string(quote(do: :x in [])) =~ "_ = :x\nfalse"
|
||||
@@ -754,14 +758,26 @@ defmodule KernelTest do
|
||||
assert expand_to_string(quote(do: foo in [])) ==
|
||||
"_ = foo\nfalse"
|
||||
|
||||
assert expand_to_string(quote(do: foo in [1, 2, 3])) ==
|
||||
":erlang.orelse(:erlang.orelse(:erlang.\"=:=\"(foo, 1), :erlang.\"=:=\"(foo, 2)), :erlang.\"=:=\"(foo, 3))"
|
||||
assert expand_to_string(quote(do: foo in [1, 2, 3])) == """
|
||||
:erlang.orelse(
|
||||
:erlang.orelse(:erlang.\"=:=\"(foo, 1), :erlang.\"=:=\"(foo, 2)),
|
||||
:erlang.\"=:=\"(foo, 3)
|
||||
)\
|
||||
"""
|
||||
|
||||
assert expand_to_string(quote(do: foo in 0..1)) ==
|
||||
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, 0), :erlang.\"=<\"(foo, 1)))"
|
||||
assert expand_to_string(quote(do: foo in 0..1)) == """
|
||||
:erlang.andalso(
|
||||
:erlang.is_integer(foo),
|
||||
:erlang.andalso(:erlang.>=(foo, 0), :erlang.\"=<\"(foo, 1))
|
||||
)\
|
||||
"""
|
||||
|
||||
assert expand_to_string(quote(do: foo in -1..0)) ==
|
||||
":erlang.andalso(:erlang.is_integer(foo), :erlang.andalso(:erlang.>=(foo, -1), :erlang.\"=<\"(foo, 0)))"
|
||||
assert expand_to_string(quote(do: foo in -1..0)) == """
|
||||
:erlang.andalso(
|
||||
:erlang.is_integer(foo),
|
||||
:erlang.andalso(:erlang.>=(foo, -1), :erlang.\"=<\"(foo, 0))
|
||||
)\
|
||||
"""
|
||||
|
||||
assert expand_to_string(quote(do: foo in 1..1)) ==
|
||||
":erlang.\"=:=\"(foo, 1)"
|
||||
|
||||
@@ -326,6 +326,8 @@ defmodule MacroTest do
|
||||
end
|
||||
|
||||
test "aliases call" do
|
||||
assert macro_to_string(quote(do: Elixir)) == "Elixir"
|
||||
assert macro_to_string(quote(do: Foo)) == "Foo"
|
||||
assert macro_to_string(quote(do: Foo.Bar.baz(1, 2, 3))) == "Foo.Bar.baz(1, 2, 3)"
|
||||
assert macro_to_string(quote(do: Foo.Bar.baz([1, 2, 3]))) == "Foo.Bar.baz([1, 2, 3])"
|
||||
assert macro_to_string(quote(do: Foo.bar(<<>>, []))) == "Foo.bar(<<>>, [])"
|
||||
@@ -810,6 +812,14 @@ defmodule MacroTest do
|
||||
defmodule Foo.Bar do
|
||||
assert __MODULE__ in __ENV__.context_modules
|
||||
end
|
||||
|
||||
assert Foo.Bar in __ENV__.context_modules
|
||||
|
||||
Code.compile_string("""
|
||||
defmodule Foo.Bar.Compiled do
|
||||
true = __MODULE__ in __ENV__.context_modules
|
||||
end
|
||||
""")
|
||||
end
|
||||
|
||||
test "to_match/1" do
|
||||
|
||||
@@ -119,7 +119,6 @@ defmodule Module.LocalsTrackerTest do
|
||||
end
|
||||
|
||||
test "does not include unreachable locals" do
|
||||
assert NoPrivate.module_info(:functions) ==
|
||||
[__info__: 1, baz: 0, module_info: 0, module_info: 1]
|
||||
assert NoPrivate.module_info(:functions) |> Keyword.take([:foo, :bar, :"MACRO-foo"]) == []
|
||||
end
|
||||
end
|
||||
|
||||
@@ -200,20 +200,20 @@ defmodule Module.Types.TypesTest do
|
||||
# types_test.ex:1
|
||||
is_binary(y)
|
||||
|
||||
where "y" was given the same type as "x" in:
|
||||
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)
|
||||
|
||||
where "x" was given the type integer() in:
|
||||
|
||||
# types_test.ex:1
|
||||
is_integer(x)
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -256,20 +256,20 @@ defmodule Module.Types.TypesTest do
|
||||
# types_test.ex:1
|
||||
is_binary(y)
|
||||
|
||||
where "y" was given the same type as "x" in:
|
||||
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)
|
||||
|
||||
where "x" was given the type integer() in:
|
||||
|
||||
# types_test.ex:1
|
||||
is_integer(x)
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -443,7 +443,17 @@ defmodule Module.Types.TypesTest do
|
||||
# types_test.ex:5
|
||||
%{"id" => user_id} = user
|
||||
|
||||
where "user" was given the same type as "amount" in:
|
||||
where "amount" was given the type binary() in:
|
||||
|
||||
# types_test.ex:3
|
||||
%{"amount" => amount} = event
|
||||
|
||||
where "amount" was given the same type as "user" in:
|
||||
|
||||
# types_test.ex:4
|
||||
%{"user" => user} = event
|
||||
|
||||
where "user" was given the type binary() in:
|
||||
|
||||
# types_test.ex:4
|
||||
%{"user" => user} = event
|
||||
@@ -452,11 +462,6 @@ defmodule Module.Types.TypesTest do
|
||||
|
||||
# types_test.ex:5
|
||||
%{"id" => user_id} = user
|
||||
|
||||
where "amount" was given the type binary() in:
|
||||
|
||||
# types_test.ex:3
|
||||
%{"amount" => amount} = event
|
||||
"""
|
||||
end
|
||||
|
||||
@@ -687,5 +692,45 @@ defmodule Module.Types.TypesTest do
|
||||
end
|
||||
) == :none
|
||||
end
|
||||
|
||||
test "no-recursion on guards with map fields" do
|
||||
assert warning(
|
||||
[assigns],
|
||||
(
|
||||
variable_enum = assigns.variable_enum
|
||||
|
||||
case true do
|
||||
_ when variable_enum != nil -> assigns.variable_enum
|
||||
end
|
||||
)
|
||||
) == :none
|
||||
end
|
||||
|
||||
test "other recursive" do
|
||||
assert warning(
|
||||
[x, y],
|
||||
(
|
||||
key_var = y
|
||||
%{^key_var => _value} = x
|
||||
key_var2 = y
|
||||
%{^key_var2 => _value2} = x
|
||||
y.z
|
||||
)
|
||||
) == :none
|
||||
end
|
||||
|
||||
test "other recursive2" do
|
||||
assert warning(
|
||||
[x, y],
|
||||
(
|
||||
key_var = y
|
||||
%{^key_var => _value} = x
|
||||
key_var2 = y
|
||||
%{^key_var2 => _value2} = x
|
||||
key_var3 = y
|
||||
%{^key_var3 => _value3} = x
|
||||
)
|
||||
) == :none
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -502,25 +502,25 @@ defmodule Module.Types.UnifyTest do
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, :tuple, var_context)
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 0}, context)
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, context)
|
||||
assert context.types[0] == {:var, 1}
|
||||
assert context.types[1] == :tuple
|
||||
assert context.types[0] == :tuple
|
||||
assert context.types[1] == {:var, 0}
|
||||
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 2}, context)
|
||||
assert {:ok, {:var, _}, _context} = unify({:var, 2}, {:var, 0}, context)
|
||||
assert context.types[0] == :unbound
|
||||
assert context.types[1] == {:var, 0}
|
||||
assert context.types[2] == {:var, 1}
|
||||
assert context.types[0] == {:var, 1}
|
||||
assert context.types[1] == {:var, 2}
|
||||
assert context.types[2] == :unbound
|
||||
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
|
||||
|
||||
assert {:error, {:unable_unify, {{:var, 0}, {:tuple, 1, [{:var, 0}]}, _}}} =
|
||||
assert {:error, {:unable_unify, {{:var, 1}, {:tuple, 1, [{:var, 0}]}, _}}} =
|
||||
unify_lift({:var, 1}, {:tuple, 1, [{:var, 0}]}, context)
|
||||
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 0}, {:var, 1}, var_context)
|
||||
assert {:ok, {:var, _}, context} = unify({:var, 1}, {:var, 2}, context)
|
||||
|
||||
assert {:error, {:unable_unify, {{:var, 0}, {:tuple, 1, [{:var, 0}]}, _}}} =
|
||||
assert {:error, {:unable_unify, {{:var, 2}, {:tuple, 1, [{:var, 0}]}, _}}} =
|
||||
unify_lift({:var, 2}, {:tuple, 1, [{:var, 0}]}, context)
|
||||
end
|
||||
|
||||
@@ -707,9 +707,9 @@ defmodule Module.Types.UnifyTest do
|
||||
|
||||
{{:var, 0}, var_context} = new_var({:foo, [version: 0], nil}, new_context())
|
||||
{{:var, 1}, var_context} = new_var({:bar, [version: 1], nil}, var_context)
|
||||
{:ok, {:var, 1}, var_context} = unify({:var, 0}, {:var, 1}, var_context)
|
||||
assert flatten_union({:var, 0}, var_context) == [{:var, 0}]
|
||||
assert flatten_union({:var, 1}, var_context) == [{:var, 0}]
|
||||
{:ok, {:var, 0}, var_context} = unify({:var, 0}, {:var, 1}, var_context)
|
||||
assert flatten_union({:var, 0}, var_context) == [{:var, 1}]
|
||||
assert flatten_union({:var, 1}, var_context) == [{:var, 1}]
|
||||
end
|
||||
|
||||
test "format_type/1" do
|
||||
|
||||
@@ -896,6 +896,20 @@ defmodule RegistryTest do
|
||||
assert {%RuntimeError{message: "some error"}, _stacktrace} = error
|
||||
end
|
||||
|
||||
test "send works", %{registry: registry} do
|
||||
name = {registry, "self"}
|
||||
Registry.register_name(name, self())
|
||||
GenServer.cast({:via, Registry, name}, :message)
|
||||
assert_received {:"$gen_cast", :message}
|
||||
end
|
||||
|
||||
test "send works with value", %{registry: registry} do
|
||||
name = {registry, "self", "value"}
|
||||
Registry.register_name(name, self())
|
||||
GenServer.cast({:via, Registry, name}, :message)
|
||||
assert_received {:"$gen_cast", :message}
|
||||
end
|
||||
|
||||
defp register_task(registry, key, value) do
|
||||
parent = self()
|
||||
|
||||
|
||||
@@ -131,6 +131,11 @@ defmodule StringTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "split_at/2 with invalid guard" 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>>}
|
||||
end
|
||||
|
||||
test "upcase/1" do
|
||||
assert String.upcase("123 abcd 456 efg hij ( %$#) kl mnop @ qrst = -_ uvwxyz") ==
|
||||
"123 ABCD 456 EFG HIJ ( %$#) KL MNOP @ QRST = -_ UVWXYZ"
|
||||
@@ -652,6 +657,7 @@ defmodule StringTest do
|
||||
assert String.slice("あいうえお", -10..-15) == ""
|
||||
assert String.slice("hello あいうえお Unicode", 8..-1) == "うえお Unicode"
|
||||
assert String.slice("abc", -1..14) == "c"
|
||||
assert String.slice("a·̀ͯ‿.⁀:", 0..-2) == "a·̀ͯ‿.⁀"
|
||||
|
||||
assert_raise FunctionClauseError, fn ->
|
||||
String.slice(nil, 0..1)
|
||||
|
||||
@@ -450,6 +450,15 @@ defmodule Task.SupervisorTest do
|
||||
|
||||
assert_receive :done
|
||||
end
|
||||
|
||||
test "consuming from another process", config do
|
||||
parent = self()
|
||||
stream = Task.Supervisor.async_stream(config[:supervisor], [1, 2, 3], &send(parent, &1))
|
||||
Task.start(Stream, :run, [stream])
|
||||
assert_receive 1
|
||||
assert_receive 2
|
||||
assert_receive 3
|
||||
end
|
||||
end
|
||||
|
||||
describe "async_stream_nolink" do
|
||||
|
||||
@@ -829,6 +829,15 @@ defmodule TaskTest do
|
||||
|
||||
assert_receive :done
|
||||
end
|
||||
|
||||
test "consuming from another process" do
|
||||
parent = self()
|
||||
stream = Task.async_stream([1, 2, 3], &send(parent, &1))
|
||||
Task.start(Stream, :run, [stream])
|
||||
assert_receive 1
|
||||
assert_receive 2
|
||||
assert_receive 3
|
||||
end
|
||||
end
|
||||
|
||||
for {desc, concurrency} <- [==: 4, <: 2, >: 8] do
|
||||
|
||||
@@ -39,6 +39,14 @@ defmodule PathHelpers do
|
||||
executable_path("elixirc")
|
||||
end
|
||||
|
||||
def iex(args) do
|
||||
run_cmd(iex_executable(), args)
|
||||
end
|
||||
|
||||
def iex_executable do
|
||||
executable_path("iex")
|
||||
end
|
||||
|
||||
def write_beam({:module, name, bin, _} = res) do
|
||||
File.mkdir_p!(unquote(path))
|
||||
beam_path = Path.join(unquote(path), Atom.to_string(name) <> ".beam")
|
||||
|
||||
@@ -94,7 +94,7 @@ defmodule URITest do
|
||||
|
||||
describe "new/1" do
|
||||
test "empty" do
|
||||
assert URI.new("") == {:ok, %URI{path: ""}}
|
||||
assert URI.new("") == {:ok, %URI{}}
|
||||
end
|
||||
|
||||
test "errors on bad URIs" do
|
||||
@@ -131,7 +131,7 @@ defmodule URITest do
|
||||
query: nil,
|
||||
fragment: nil,
|
||||
port: 443,
|
||||
path: "",
|
||||
path: nil,
|
||||
userinfo: nil
|
||||
}
|
||||
|
||||
@@ -349,9 +349,9 @@ defmodule URITest do
|
||||
assert URI.merge(base, "") |> to_string == "http://example.com/foo/bar"
|
||||
assert URI.merge(base, "#fragment") |> to_string == "http://example.com/foo/bar#fragment"
|
||||
assert URI.merge(base, "?query") |> to_string == "http://example.com/foo/bar?query"
|
||||
assert URI.merge(base, %URI{path: ""}) |> to_string == "http://example.com/foo/bar"
|
||||
assert URI.merge(base, %URI{}) |> to_string == "http://example.com/foo/bar"
|
||||
|
||||
assert URI.merge(base, %URI{path: "", fragment: "fragment"})
|
||||
assert URI.merge(base, %URI{fragment: "fragment"})
|
||||
|> to_string == "http://example.com/foo/bar#fragment"
|
||||
|
||||
base = URI.new!("http://example.com")
|
||||
|
||||
+3
-2
@@ -663,9 +663,10 @@ defmodule Mix do
|
||||
|
||||
config = Keyword.get(opts, :config, [])
|
||||
system_env = Keyword.get(opts, :system_env, [])
|
||||
consolidate_protocols? = Keyword.get(opts, :consolidate_protocols, true)
|
||||
|
||||
id =
|
||||
{deps, config, system_env}
|
||||
{deps, config, system_env, consolidate_protocols?}
|
||||
|> :erlang.term_to_binary()
|
||||
|> :erlang.md5()
|
||||
|> Base.encode16(case: :lower)
|
||||
@@ -703,7 +704,7 @@ defmodule Mix do
|
||||
erlc_paths: ["src"],
|
||||
elixirc_paths: ["lib"],
|
||||
compilers: [],
|
||||
consolidate_protocols: Keyword.get(opts, :consolidate_protocols, true)
|
||||
consolidate_protocols: consolidate_protocols?
|
||||
]
|
||||
|
||||
started_apps = Application.started_applications()
|
||||
|
||||
@@ -2,6 +2,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
@moduledoc false
|
||||
|
||||
@manifest_vsn 13
|
||||
@checkpoint_vsn 2
|
||||
|
||||
import Record
|
||||
|
||||
@@ -102,7 +103,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
{false, stale, old_lock, old_config}
|
||||
end
|
||||
|
||||
{stale_local_mods, stale_local_exports, all_local_exports} =
|
||||
{stale_modules, stale_exports, all_local_exports} =
|
||||
stale_local_deps(manifest, stale, modified, all_local_exports)
|
||||
|
||||
prev_paths = for source(source: source) <- all_sources, do: source
|
||||
@@ -120,8 +121,8 @@ defmodule Mix.Compilers.Elixir do
|
||||
all_modules,
|
||||
all_sources,
|
||||
removed,
|
||||
stale_local_mods,
|
||||
Map.merge(stale_local_exports, removed_modules),
|
||||
Map.merge(stale_modules, removed_modules),
|
||||
Map.merge(stale_exports, removed_modules),
|
||||
dest
|
||||
)
|
||||
end
|
||||
@@ -180,7 +181,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
delete_compiler_info()
|
||||
end
|
||||
else
|
||||
# We need to return ok if deps_changed? or stale_local_mods changed,
|
||||
# We need to return ok if deps_changed? or stale_modules changed,
|
||||
# even if no code was compiled, because we need to propagate the changed
|
||||
# status to compile.protocols. This will be the case whenever:
|
||||
#
|
||||
@@ -193,7 +194,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
# will only compute the diff with current protocols. In fact, there is no
|
||||
# need to reconsolidate if an Erlang file changes and it doesn't trigger
|
||||
# any other change, but the diff check should be reasonably fast anyway.
|
||||
status = if removed != [] or deps_changed? or stale_local_mods != %{}, do: :ok, else: :noop
|
||||
status = if removed != [] or deps_changed? or stale_modules != %{}, do: :ok, else: :noop
|
||||
|
||||
# If nothing changed but there is one more recent mtime, bump the manifest
|
||||
if status != :noop or Enum.any?(Map.values(sources_stats), &(elem(&1, 0) > modified)) do
|
||||
@@ -283,8 +284,8 @@ defmodule Mix.Compilers.Elixir do
|
||||
all_modules,
|
||||
all_sources,
|
||||
removed,
|
||||
stale_local_mods,
|
||||
stale_local_exports,
|
||||
stale_modules,
|
||||
stale_exports,
|
||||
dest
|
||||
) do
|
||||
# TODO: Use :maps.from_keys/2 on Erlang/OTP 24+
|
||||
@@ -294,13 +295,17 @@ defmodule Mix.Compilers.Elixir do
|
||||
into: %{},
|
||||
do: {module, []}
|
||||
|
||||
{checkpoint_stale, checkpoint_modules} = parse_checkpoint(manifest)
|
||||
modules_to_recompile = Map.merge(checkpoint_modules, modules_to_recompile)
|
||||
stale_local_mods = Map.merge(checkpoint_stale, stale_local_mods)
|
||||
{checkpoint_stale_modules, checkpoint_stale_exports, checkpoint_modules} =
|
||||
parse_checkpoint(manifest)
|
||||
|
||||
if map_size(stale_local_mods) != map_size(checkpoint_stale) or
|
||||
modules_to_recompile = Map.merge(checkpoint_modules, modules_to_recompile)
|
||||
stale_modules = Map.merge(checkpoint_stale_modules, stale_modules)
|
||||
stale_exports = Map.merge(checkpoint_stale_exports, stale_exports)
|
||||
|
||||
if map_size(stale_modules) != map_size(checkpoint_stale_modules) or
|
||||
map_size(stale_exports) != map_size(checkpoint_stale_exports) or
|
||||
map_size(modules_to_recompile) != map_size(checkpoint_modules) do
|
||||
write_checkpoint(manifest, stale_local_mods, modules_to_recompile)
|
||||
write_checkpoint(manifest, stale_modules, stale_exports, modules_to_recompile)
|
||||
end
|
||||
|
||||
sources_stats =
|
||||
@@ -332,8 +337,8 @@ defmodule Mix.Compilers.Elixir do
|
||||
all_modules,
|
||||
all_sources,
|
||||
removed ++ changed,
|
||||
stale_local_mods,
|
||||
stale_local_exports,
|
||||
stale_modules,
|
||||
stale_exports,
|
||||
dest
|
||||
)
|
||||
|
||||
@@ -654,16 +659,16 @@ defmodule Mix.Compilers.Elixir do
|
||||
# files that have changed. Then it recursively figures out
|
||||
# all the files that changed (via the module dependencies) and
|
||||
# return the non-changed entries and the removed sources.
|
||||
defp update_stale_entries(modules, _sources, [], stale_mods, stale_exports, _compile_path)
|
||||
when stale_mods == %{} and stale_exports == %{} do
|
||||
defp update_stale_entries(modules, _sources, [], stale_modules, stale_exports, _compile_path)
|
||||
when stale_modules == %{} and stale_exports == %{} do
|
||||
{modules, %{}, []}
|
||||
end
|
||||
|
||||
defp update_stale_entries(modules, sources, changed, stale_mods, stale_exports, compile_path) do
|
||||
defp update_stale_entries(modules, sources, changed, stale_modules, stale_exports, compile_path) do
|
||||
# TODO: Use :maps.from_keys/2 on Erlang/OTP 24+
|
||||
changed = Enum.into(changed, %{}, &{&1, []})
|
||||
reducer = &remove_stale_entry(&1, &2, sources, stale_exports, compile_path)
|
||||
remove_stale_entries(modules, %{}, changed, stale_mods, reducer)
|
||||
remove_stale_entries(modules, %{}, changed, stale_modules, reducer)
|
||||
end
|
||||
|
||||
defp remove_stale_entries(modules, exports, old_changed, old_stale, reducer) do
|
||||
@@ -720,6 +725,8 @@ defmodule Mix.Compilers.Elixir do
|
||||
defp stale_local_deps(manifest, stale_modules, modified, old_exports) do
|
||||
base = Path.basename(manifest)
|
||||
|
||||
# The stale modules so far will become both stale_modules and stale_exports,
|
||||
# as any export from a dependency needs to be recompiled.
|
||||
# TODO: Use :maps.from_keys/2 on Erlang/OTP 24+
|
||||
stale_modules = for module <- stale_modules, do: {module, []}, into: %{}
|
||||
|
||||
@@ -727,7 +734,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
not scm.fetchable?,
|
||||
manifest = Path.join([opts[:build], ".mix", base]),
|
||||
Mix.Utils.last_modified(manifest) > modified,
|
||||
reduce: {stale_modules, %{}, old_exports} do
|
||||
reduce: {stale_modules, stale_modules, old_exports} do
|
||||
{modules, exports, new_exports} ->
|
||||
{_manifest_modules, dep_sources} = read_manifest(manifest)
|
||||
|
||||
@@ -1017,19 +1024,19 @@ defmodule Mix.Compilers.Elixir do
|
||||
(manifest <> ".checkpoint") |> File.read!() |> :erlang.binary_to_term()
|
||||
rescue
|
||||
_ ->
|
||||
{%{}, %{}}
|
||||
{%{}, %{}, %{}}
|
||||
else
|
||||
{@manifest_vsn, stale, recompile_modules} ->
|
||||
{stale, recompile_modules}
|
||||
{@checkpoint_vsn, stale_modules, stale_exports, recompile_modules} ->
|
||||
{stale_modules, stale_exports, recompile_modules}
|
||||
|
||||
_ ->
|
||||
{%{}, %{}}
|
||||
{%{}, %{}, %{}}
|
||||
end
|
||||
end
|
||||
|
||||
defp write_checkpoint(manifest, stale, recompile_modules) do
|
||||
defp write_checkpoint(manifest, stale_modules, stale_exports, recompile_modules) do
|
||||
File.mkdir_p!(Path.dirname(manifest))
|
||||
term = {@manifest_vsn, stale, recompile_modules}
|
||||
term = {@checkpoint_vsn, stale_modules, stale_exports, recompile_modules}
|
||||
checkpoint_data = :erlang.term_to_binary(term, [:compressed])
|
||||
File.write!(manifest <> ".checkpoint", checkpoint_data)
|
||||
end
|
||||
|
||||
@@ -90,6 +90,15 @@ defmodule Mix.Dep.Loader do
|
||||
# 4. Inferred from files in dependency (mix.exs, rebar.config, Makefile)
|
||||
manager = opts[:manager] || scm_manager(scm, opts) || manager || infer_manager(opts[:dest])
|
||||
|
||||
# TODO: Remove rebar2 support on Elixir v1.15+.
|
||||
if manager == :rebar do
|
||||
Mix.shell().error(
|
||||
"dependency #{inspect(dep.app)} is using Rebar 2, " <>
|
||||
"which is no longer maintained and no longer works in recent Erlang/OTP versions. " <>
|
||||
"Remove the :manager option or set it to :rebar3 instead"
|
||||
)
|
||||
end
|
||||
|
||||
dep = %{dep | manager: manager, status: scm_status(scm, opts)}
|
||||
|
||||
{dep, children} =
|
||||
|
||||
@@ -609,13 +609,24 @@ defmodule Mix.Release do
|
||||
end
|
||||
|
||||
defp build_release_spec(release, modes) do
|
||||
%{name: name, version: version, erts_version: erts_version, applications: apps} = release
|
||||
%{
|
||||
name: name,
|
||||
version: version,
|
||||
erts_version: erts_version,
|
||||
applications: apps,
|
||||
options: options
|
||||
} = release
|
||||
|
||||
skip_mode_validation_for =
|
||||
options
|
||||
|> Keyword.get(:skip_mode_validation_for, [])
|
||||
|> MapSet.new()
|
||||
|
||||
rel_apps =
|
||||
for {app, mode} <- modes do
|
||||
properties = Map.get(apps, app) || throw({:error, "Unknown application #{inspect(app)}"})
|
||||
children = Keyword.get(properties, :applications, [])
|
||||
validate_mode!(app, mode, modes, children)
|
||||
app in skip_mode_validation_for || validate_mode!(app, mode, modes, children)
|
||||
build_app_for_release(app, mode, properties)
|
||||
end
|
||||
|
||||
|
||||
@@ -104,11 +104,11 @@ defmodule Mix.Tasks.Format do
|
||||
|
||||
The `opts` passed to `format/2` contains all the formatting options and either:
|
||||
|
||||
* `:sigil` (atom) - the sigil being formatted, e.g. `:M`.
|
||||
* `:sigil` (atom) - the sigil being formatted, e.g. `:M`.
|
||||
|
||||
* `:modifiers` (charlist) - list of sigil modifiers.
|
||||
* `:modifiers` (charlist) - list of sigil modifiers.
|
||||
|
||||
* `:extension` (string) - the extension of the file being formatted, e.g. `".md"`.
|
||||
* `:extension` (string) - the extension of the file being formatted, e.g. `".md"`.
|
||||
|
||||
Now any application can use your formatter as follows:
|
||||
|
||||
@@ -213,6 +213,7 @@ defmodule Mix.Tasks.Format do
|
||||
to be formatted. The options are returned for reflection
|
||||
purposes.
|
||||
"""
|
||||
@doc since: "1.13.0"
|
||||
def formatter_for_file(file, opts \\ []) do
|
||||
{dot_formatter, formatter_opts} = eval_dot_formatter(opts)
|
||||
|
||||
@@ -287,7 +288,15 @@ defmodule Mix.Tasks.Format do
|
||||
end
|
||||
end
|
||||
|
||||
formatter_opts = Keyword.put(formatter_opts, :plugins, plugins)
|
||||
sigils =
|
||||
for plugin <- plugins,
|
||||
sigil <- find_sigils_from_plugins(plugin, formatter_opts),
|
||||
do: {sigil, &plugin.format(&1, &2 ++ formatter_opts)}
|
||||
|
||||
formatter_opts =
|
||||
formatter_opts
|
||||
|> Keyword.put(:plugins, plugins)
|
||||
|> Keyword.put(:sigils, sigils)
|
||||
|
||||
if deps == [] and subs == [] do
|
||||
{{formatter_opts, []}, sources}
|
||||
@@ -494,12 +503,12 @@ defmodule Mix.Tasks.Format do
|
||||
ext = Path.extname(file)
|
||||
|
||||
cond do
|
||||
plugin = find_plugin_for_extension(formatter_opts, ext) ->
|
||||
&plugin.format(&1, [extension: ext, file: file] ++ formatter_opts)
|
||||
|
||||
ext in ~w(.ex .exs) ->
|
||||
&elixir_format(&1, [file: file] ++ formatter_opts)
|
||||
|
||||
plugin = find_plugin_for_extension(formatter_opts, ext) ->
|
||||
&plugin.format(&1, [extension: ext] ++ formatter_opts)
|
||||
|
||||
true ->
|
||||
& &1
|
||||
end
|
||||
@@ -536,12 +545,7 @@ defmodule Mix.Tasks.Format do
|
||||
defp stdin_or_wildcard(path), do: path |> Path.expand() |> Path.wildcard(match_dot: true)
|
||||
|
||||
defp elixir_format(content, formatter_opts) do
|
||||
sigils =
|
||||
for plugin <- Keyword.fetch!(formatter_opts, :plugins),
|
||||
sigil <- find_sigils_from_plugins(plugin, formatter_opts),
|
||||
do: {sigil, &plugin.format(&1, &2 ++ formatter_opts)}
|
||||
|
||||
IO.iodata_to_binary([Code.format_string!(content, [sigils: sigils] ++ formatter_opts), ?\n])
|
||||
IO.iodata_to_binary([Code.format_string!(content, formatter_opts), ?\n])
|
||||
end
|
||||
|
||||
defp find_sigils_from_plugins(plugin, formatter_opts) do
|
||||
|
||||
@@ -455,6 +455,14 @@ defmodule Mix.Tasks.Release do
|
||||
* `:steps` - a list of steps to execute when assembling the release. See
|
||||
the "Steps" section for more information.
|
||||
|
||||
* `:skip_mode_validation_for` - a list of application names
|
||||
(atoms) specifying applications to skip strict validation of
|
||||
"unsafe" modes. An "unsafe" case is when a parent application
|
||||
mode is `:permanent` but one of the applications it depends on
|
||||
is set to `:load`. Use this with care, as a release with
|
||||
invalid modes may no longer boot without additional tweaks.
|
||||
Defaults to `[]`.
|
||||
|
||||
Note each release definition can be given as an anonymous function. This
|
||||
is useful if some release attributes are expensive to compute:
|
||||
|
||||
|
||||
@@ -299,6 +299,7 @@ defmodule Mix.Tasks.Test.Coverage do
|
||||
print_summary(module_results, totals, summary_opts)
|
||||
|
||||
if totals < get_threshold(summary_opts) do
|
||||
print_failed_threshold(totals, get_threshold(summary_opts))
|
||||
System.at_exit(fn _ -> exit({:shutdown, 3}) end)
|
||||
end
|
||||
|
||||
@@ -349,7 +350,14 @@ defmodule Mix.Tasks.Test.Coverage do
|
||||
Mix.shell().info("-----------|--------------------------")
|
||||
results |> Enum.sort() |> Enum.each(&display(&1, threshold))
|
||||
Mix.shell().info("-----------|--------------------------")
|
||||
display({totals, "Total"}, opts)
|
||||
display({totals, "Total"}, threshold)
|
||||
Mix.shell().info("")
|
||||
end
|
||||
|
||||
defp print_failed_threshold(totals, threshold) do
|
||||
Mix.shell().info("Coverage test failed, threshold not met:\n")
|
||||
Mix.shell().info(" Coverage: #{format_number(totals, 6)}%")
|
||||
Mix.shell().info(" Threshold: #{format_number(threshold, 6)}%")
|
||||
Mix.shell().info("")
|
||||
end
|
||||
|
||||
@@ -369,6 +377,9 @@ defmodule Mix.Tasks.Test.Coverage do
|
||||
defp color(percentage, threshold) when percentage >= threshold, do: :green
|
||||
defp color(_, _), do: :red
|
||||
|
||||
defp format_number(number, length) when is_integer(number),
|
||||
do: format_number(number / 1, length)
|
||||
|
||||
defp format_number(number, length), do: :io_lib.format("~#{length}.2f", [number])
|
||||
|
||||
defp format_name(name) when is_binary(name), do: name
|
||||
|
||||
@@ -510,13 +510,16 @@ defmodule Mix.Tasks.Xref do
|
||||
|
||||
@doc false
|
||||
def trace({:require, meta, module, _opts}, env),
|
||||
do: add_trace(:export, :require, module, module, meta, env)
|
||||
do: add_trace(:compile, :require, module, module, meta, env)
|
||||
|
||||
def trace({:import, meta, module, _opts}, env),
|
||||
do: add_trace(:export, :import, module, module, meta, env)
|
||||
|
||||
def trace({:struct_expansion, meta, module, _keys}, env),
|
||||
do: add_trace(:export, :struct, module, module, meta, env)
|
||||
|
||||
def trace({:alias_reference, meta, module}, env) when env.module != module,
|
||||
do: add_trace(mode(env), :alias, module, module, meta, env)
|
||||
do: add_trace(mode(env), :module, module, module, meta, env)
|
||||
|
||||
def trace({:remote_function, meta, module, function, arity}, env),
|
||||
do: add_trace(mode(env), :call, module, {module, function, arity}, meta, env)
|
||||
@@ -551,14 +554,14 @@ defmodule Mix.Tasks.Xref do
|
||||
# We don't want to show aliases if there is an entry of the same type
|
||||
non_aliases =
|
||||
for {_file, _line, module_or_mfa, mode, type} <- entries,
|
||||
type != :alias,
|
||||
type != :module,
|
||||
into: %{},
|
||||
do: {{trace_module(module_or_mfa), mode}, []}
|
||||
|
||||
shell = Mix.shell()
|
||||
|
||||
for {file, line, module_or_mfa, mode, type} <- entries,
|
||||
type != :alias or not Map.has_key?(non_aliases, {module_or_mfa, mode}) do
|
||||
type != :module or not Map.has_key?(non_aliases, {module_or_mfa, mode}) do
|
||||
shell.info([
|
||||
Exception.format_file_line(Path.relative_to_cwd(file), line),
|
||||
?\s,
|
||||
|
||||
@@ -4,3 +4,4 @@
|
||||
/git_rebar/
|
||||
/git_repo/
|
||||
/git_sparse_repo/
|
||||
/archive/ebin/
|
||||
|
||||
Binary file not shown.
@@ -1,3 +0,0 @@
|
||||
{application,local_sample,
|
||||
[{modules,['Elixir.Mix.Tasks.Local.Sample']},
|
||||
{applications,[kernel,stdlib,elixir]}]}.
|
||||
Vendored
BIN
Binary file not shown.
@@ -496,8 +496,11 @@ defmodule Mix.DepTest do
|
||||
|
||||
with_deps(deps, fn ->
|
||||
in_fixture("deps_status", fn ->
|
||||
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) ==
|
||||
[:git_repo, :abc_repo, :deps_repo]
|
||||
# Both orders below are valid after topological sort
|
||||
assert Enum.map(Mix.Dep.load_on_environment([]), & &1.app) in [
|
||||
[:git_repo, :abc_repo, :deps_repo],
|
||||
[:abc_repo, :git_repo, :deps_repo]
|
||||
]
|
||||
|
||||
assert Map.keys(Mix.Project.deps_paths()) == [:abc_repo, :deps_repo, :git_repo]
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ defmodule Mix.RebarTest do
|
||||
|
||||
# We run only on Unix because Windows has a hard time
|
||||
# removing the Rebar executable after executed.
|
||||
@tag [unix: true]
|
||||
@tag :unix
|
||||
test "gets and compiles dependencies" do
|
||||
in_tmp("get and compile dependencies", fn ->
|
||||
Mix.Project.push(RebarAsDep)
|
||||
@@ -256,7 +256,7 @@ defmodule Mix.RebarTest do
|
||||
|
||||
# We run only on Unix because Windows has a hard time
|
||||
# removing the Rebar executable after executed.
|
||||
@tag [unix: true]
|
||||
@tag :unix
|
||||
test "applies variables from :system_env option when compiling dependencies" do
|
||||
in_tmp("applies variables from system_env", fn ->
|
||||
Mix.Project.push(RebarAsDepWithEnv)
|
||||
@@ -295,6 +295,7 @@ defmodule Mix.RebarTest do
|
||||
File.rm(MixTest.Case.tmp_path("rebar_dep/mix.exs"))
|
||||
end
|
||||
|
||||
@tag :rebar
|
||||
test "gets and compiles dependencies with Rebar2" do
|
||||
in_tmp("get and compile dependencies for Rebar2", fn ->
|
||||
Mix.Project.push(Rebar2AsDep)
|
||||
@@ -328,7 +329,8 @@ defmodule Mix.RebarTest do
|
||||
|
||||
# We run only on Unix because Windows has a hard time
|
||||
# removing the Rebar executable after executed.
|
||||
@tag [unix: true]
|
||||
@tag :unix
|
||||
@tag :rebar
|
||||
test "applies variables from :system_env option when compiling dependencies for Rebar2" do
|
||||
in_tmp("applies variables from system_env for Rebar2", fn ->
|
||||
Mix.Project.push(Rebar2AsDep)
|
||||
|
||||
@@ -479,6 +479,13 @@ defmodule Mix.ReleaseTest do
|
||||
assert message =~
|
||||
"Application :mix has mode :permanent but it depends on :elixir which is set to :none"
|
||||
end
|
||||
|
||||
test "does not raise on child unsafe mode if parent is in `skip_mode_validation_for`" do
|
||||
# `:mix` is a parent that depends on `:elixir`
|
||||
release = release(applications: [elixir: :load], skip_mode_validation_for: [:mix])
|
||||
assert make_boot_script(release, @boot_script_path, release.boot_scripts.start) == :ok
|
||||
assert make_boot_script(release, @boot_script_path, release.boot_scripts.start_clean) == :ok
|
||||
end
|
||||
end
|
||||
|
||||
describe "make_cookie/1" do
|
||||
|
||||
@@ -182,6 +182,11 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
Process.put({MixTest.Case.Sample, :application}, extra_applications: [:logger])
|
||||
File.mkdir_p!("config")
|
||||
|
||||
File.write!("config/config.exs", """
|
||||
import Config
|
||||
config :logger, :level, :debug
|
||||
""")
|
||||
|
||||
File.write!("lib/a.ex", """
|
||||
defmodule A do
|
||||
_ = Logger.metadata()
|
||||
@@ -234,6 +239,12 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
|
||||
assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time
|
||||
|
||||
# No-op does not recompile
|
||||
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
|
||||
assert recompile.() == {:ok, []}
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
|
||||
|
||||
# Changing self fully recompiles
|
||||
File.write!("config/config.exs", """
|
||||
import Config
|
||||
@@ -263,6 +274,76 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
Application.delete_env(:sample, :foo, persistent: true)
|
||||
end
|
||||
|
||||
test "recompiles files when config changes export dependencies" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
Mix.Project.push(MixTest.Case.Sample)
|
||||
Process.put({MixTest.Case.Sample, :application}, extra_applications: [:ex_unit])
|
||||
File.mkdir_p!("config")
|
||||
|
||||
File.write!("lib/a.ex", """
|
||||
defmodule A do
|
||||
def test_struct do
|
||||
%ExUnit.Test{}
|
||||
end
|
||||
end
|
||||
""")
|
||||
|
||||
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
|
||||
|
||||
recompile = fn ->
|
||||
Mix.ProjectStack.pop()
|
||||
Mix.Project.push(MixTest.Case.Sample)
|
||||
Mix.Tasks.Loadconfig.load_compile("config/config.exs")
|
||||
Mix.Tasks.Compile.Elixir.run(["--verbose"])
|
||||
end
|
||||
|
||||
# Adding config recompiles
|
||||
File.write!("config/config.exs", """
|
||||
import Config
|
||||
config :ex_unit, :some, :config
|
||||
""")
|
||||
|
||||
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
|
||||
assert recompile.() == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
|
||||
assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time
|
||||
|
||||
# Changing config recompiles
|
||||
File.write!("config/config.exs", """
|
||||
import Config
|
||||
config :ex_unit, :some, :another
|
||||
""")
|
||||
|
||||
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
|
||||
assert recompile.() == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
|
||||
assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time
|
||||
|
||||
# Removing config recompiles
|
||||
File.write!("config/config.exs", """
|
||||
import Config
|
||||
""")
|
||||
|
||||
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
|
||||
assert recompile.() == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
|
||||
assert File.stat!("_build/dev/lib/sample/.mix/compile.elixir").mtime > @old_time
|
||||
|
||||
# No-op does not recompile
|
||||
File.touch!("_build/dev/lib/sample/.mix/compile.elixir", @old_time)
|
||||
assert recompile.() == {:ok, []}
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/a.ex"]}
|
||||
refute_received {:mix_shell, :info, ["Compiled lib/b.ex"]}
|
||||
end)
|
||||
after
|
||||
Application.delete_env(:ex_unit, :some, persistent: true)
|
||||
end
|
||||
|
||||
test "recompiles files when config changes with crashes" do
|
||||
in_fixture("no_mixfile", fn ->
|
||||
Mix.Project.push(MixTest.Case.Sample)
|
||||
|
||||
@@ -206,6 +206,8 @@ defmodule Mix.Tasks.FormatTest do
|
||||
assert opts[:from_formatter_exs] == :yes
|
||||
assert opts[:sigil] == :W
|
||||
assert opts[:modifiers] == 'abc'
|
||||
assert opts[:line] == 2
|
||||
assert opts[:file] =~ ~r/\/a\.ex$/
|
||||
contents |> String.split(~r/\s/) |> Enum.join("\n")
|
||||
end
|
||||
end
|
||||
@@ -247,12 +249,15 @@ defmodule Mix.Tasks.FormatTest do
|
||||
|
||||
def features(opts) do
|
||||
assert opts[:from_formatter_exs] == :yes
|
||||
[extensions: ~w(.w)]
|
||||
[extensions: ~w(.w), sigils: [:W]]
|
||||
end
|
||||
|
||||
def format(contents, opts) do
|
||||
assert opts[:from_formatter_exs] == :yes
|
||||
assert opts[:extension] == ".w"
|
||||
assert opts[:file] =~ ~r/\/a\.w$/
|
||||
assert [W: sigil_fun] = opts[:sigils]
|
||||
assert is_function(sigil_fun, 2)
|
||||
contents |> String.split(~r/\s/) |> Enum.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -168,7 +168,14 @@ defmodule Mix.Tasks.TestTest do
|
||||
|
||||
# We skip a test in bar to force coverage below the default threshold
|
||||
# which should result in an exit status of 1.
|
||||
assert {_, code} = mix_code(["test", "--cover", "--exclude", "maybe_skip"])
|
||||
assert {output, code} = mix_code(["test", "--cover", "--exclude", "maybe_skip"])
|
||||
|
||||
assert output =~ """
|
||||
Coverage test failed, threshold not met:
|
||||
|
||||
Coverage: 0.00%
|
||||
Threshold: 90.00%
|
||||
"""
|
||||
|
||||
unless windows?() do
|
||||
assert code == 3
|
||||
@@ -426,9 +433,14 @@ defmodule Mix.Tasks.TestTest do
|
||||
in_fixture("umbrella_test", fn ->
|
||||
# Run false positive test first so at least the code is compiled
|
||||
# and we can perform more aggressive assertions later
|
||||
assert mix(["test", "apps/unknown_app/test"]) =~ """
|
||||
output = mix(["test", "apps/unknown_app/test"])
|
||||
|
||||
assert output =~ """
|
||||
==> bar
|
||||
Paths given to "mix test" did not match any directory/file: apps/unknown_app/test
|
||||
"""
|
||||
|
||||
assert output =~ """
|
||||
==> foo
|
||||
Paths given to "mix test" did not match any directory/file: apps/unknown_app/test
|
||||
"""
|
||||
|
||||
@@ -253,7 +253,7 @@ defmodule Mix.Tasks.XrefTest do
|
||||
output = """
|
||||
Compiling 2 files (.ex)
|
||||
Generated sample app
|
||||
lib/b.ex:2: require A (export)
|
||||
lib/b.ex:2: import A (export)
|
||||
lib/b.ex:3: call A.macro/0 (compile)
|
||||
lib/b.ex:4: import A.macro/0 (compile)
|
||||
lib/b.ex:5: call A.fun/0 (compile)
|
||||
@@ -287,6 +287,7 @@ defmodule Mix.Tasks.XrefTest do
|
||||
output = """
|
||||
Compiling 2 files (.ex)
|
||||
Generated sample app
|
||||
lib/b.ex:2: require A (compile)
|
||||
lib/b.ex:3: call A.macro/0 (compile)
|
||||
"""
|
||||
|
||||
@@ -313,10 +314,11 @@ defmodule Mix.Tasks.XrefTest do
|
||||
output = """
|
||||
Compiling 2 files (.ex)
|
||||
Generated sample app
|
||||
lib/b.ex:3: require A (compile)
|
||||
lib/b.ex:3: call A.macro/0 (compile)
|
||||
"""
|
||||
|
||||
message = "Too many traces (found: 1, permitted: 0)"
|
||||
message = "Too many traces (found: 2, permitted: 0)"
|
||||
|
||||
assert_raise Mix.Error, message, fn ->
|
||||
assert_trace(~w[--label compile --fail-above 0], "lib/b.ex", files, output)
|
||||
@@ -759,10 +761,7 @@ defmodule Mix.Tasks.XrefTest do
|
||||
defmodule B do
|
||||
# Let's also test that we track literal atom behaviours
|
||||
@behaviour :"Elixir.A.Behaviour"
|
||||
|
||||
def foo do
|
||||
A.foo()
|
||||
end
|
||||
def foo, do: :foo
|
||||
end
|
||||
""")
|
||||
|
||||
@@ -772,7 +771,7 @@ defmodule Mix.Tasks.XrefTest do
|
||||
digraph "xref graph" {
|
||||
"lib/a.ex"
|
||||
"lib/a.ex" -> "lib/b.ex" [label="(compile)"]
|
||||
"lib/b.ex" -> "lib/a.ex" [label="(export)"]
|
||||
"lib/b.ex" -> "lib/a.ex"
|
||||
"lib/b.ex"
|
||||
}
|
||||
"""
|
||||
|
||||
@@ -391,7 +391,7 @@ defmodule Mix.UmbrellaTest do
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
|
||||
# But exports dependencies are not recompiled
|
||||
File.write!("lib/bar.ex", "defmodule Bar, do: (require Foo)")
|
||||
File.write!("lib/bar.ex", "defmodule Bar, do: (import Foo, warn: false)")
|
||||
|
||||
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
|
||||
assert_received {:mix_shell, :info, ["Compiled lib/bar.ex"]}
|
||||
|
||||
@@ -7,12 +7,13 @@ Application.put_env(:logger, :backends, [])
|
||||
|
||||
os_exclude = if match?({:win32, _}, :os.type()), do: [unix: true], else: [windows: true]
|
||||
epmd_exclude = if match?({:win32, _}, :os.type()), do: [epmd: true], else: []
|
||||
ExUnit.start(trace: "--trace" in System.argv(), exclude: epmd_exclude ++ os_exclude)
|
||||
git_exclude = if Mix.SCM.Git.git_version() <= {1, 7, 4}, do: [git_sparse: true], else: []
|
||||
rebar_exclude = if System.otp_release() >= "25", do: [rebar: true], else: []
|
||||
|
||||
unless {1, 7, 4} <= Mix.SCM.Git.git_version() do
|
||||
IO.puts(:stderr, "Skipping tests with git sparse checkouts...")
|
||||
ExUnit.configure(exclude: :git_sparse)
|
||||
end
|
||||
ExUnit.start(
|
||||
trace: "--trace" in System.argv(),
|
||||
exclude: epmd_exclude ++ os_exclude ++ git_exclude ++ rebar_exclude
|
||||
)
|
||||
|
||||
# Clear environment variables that may affect tests
|
||||
System.delete_env("http_proxy")
|
||||
@@ -412,6 +413,26 @@ Enum.each([:invalidapp, :invalidvsn, :noappfile, :nosemver, :ok], fn dep ->
|
||||
File.mkdir_p!(Path.expand("fixtures/deps_status/deps/#{dep}/.git", __DIR__))
|
||||
end)
|
||||
|
||||
# Archive ebin
|
||||
target = Path.expand("fixtures/archive", __DIR__)
|
||||
|
||||
unless File.dir?(Path.join(target, "ebin")) do
|
||||
File.mkdir_p!(Path.join(target, "ebin"))
|
||||
|
||||
File.write!(Path.join([target, "ebin", "local_sample.app"]), """
|
||||
{application,local_sample,
|
||||
[
|
||||
{modules,['Elixir.Mix.Tasks.Local.Sample']},
|
||||
{applications,[kernel,stdlib,elixir]}
|
||||
]
|
||||
}.
|
||||
""")
|
||||
|
||||
[{name, bin}] = Code.compile_file("lib/local.sample.ex", target)
|
||||
|
||||
File.write!(Path.join([target, "ebin", Atom.to_string(name) <> ".beam"]), bin)
|
||||
end
|
||||
|
||||
## Generate helper modules
|
||||
|
||||
path = MixTest.Case.tmp_path("beams")
|
||||
|
||||
Reference in New Issue
Block a user