Compare commits

...
51 Commits
Author SHA1 Message Date
José Valim 524528b973 Add branch docs publishing 2023-05-30 11:23:49 +02:00
jendis a64d42f5d3 Fix elixir test assertion for erlang >=OTP-24.1 (#11309) 2021-10-13 23:48:14 +02:00
Tobias Pfeiffer 7636046bd9 Add missing --warnings-as-errors on mix test to Changelog (#11303)
Sources:
* https://dashbit.co/blog/tests-with-warnings-as-errors
* https://hexdocs.pm/mix/master/Mix.Tasks.Test.html
2021-10-08 13:30:42 +02:00
Justin Morrow 552b5abed8 docs(code.ex): fix ensure_loaded typos in Code docs (#11253)
Fixes two documentation typos in the code.ex `@moduledoc`
`ensure_lodead!` => `ensure_loaded!`
`ensure_lodead?` => `ensure_loaded?`
2021-09-17 08:50:38 +02:00
José Valim 74bfab8ee2 Release v1.12.3 2021-09-05 10:02:59 +02:00
José Valim e00cbe16cd Revert content tracking instead of mtime. 2021-08-24 08:31:52 +02:00
Marc-André Lafortune 9df7a548ed Make sure .beam files still exist for restored files (#11187) 2021-08-18 07:51:50 +02:00
José Valim 9137fd1cb2 Do not return context tuples on optimized eval 2021-07-22 08:10:38 +02:00
José Valim 5189b292ac Purge v8 manifests properly 2021-07-16 21:37:57 +02:00
Marc-André Lafortune 74f9a299ea Rely on modification time and hash to determine modified sources (#11080) 2021-07-16 21:37:57 +02:00
José Valim 2ce9d25552 Do not error autocompletion with module attribute, closes #11120 2021-07-16 19:21:36 +02:00
José Valim b558142819 Rename exit_code to exit_status for consistency 2021-07-15 20:52:20 +02:00
Łukasz Samson d7f96d8332 Keep backwards compatibility on Range and Date.Range (#11117) 2021-07-14 13:32:03 +02:00
José Valim 010efd8ec6 Fix regex 2021-07-13 11:29:26 +02:00
José Valim c0174044bc Fix regex 2021-07-13 11:12:08 +02:00
José Valim 616e0d8ccc Do not crash when handling ambiguity errors, closes #11111 2021-07-13 10:36:14 +02:00
José Valim 13ba96be3c Keep backwards compatibility on range usage, closes #11110 2021-07-12 12:32:22 +02:00
José Valim edb5e7e052 Document backwards incompatible change, closes #11105 2021-07-06 15:01:41 +02:00
José Valim 58ca6524f8 Release v1.12.2 2021-07-01 18:15:34 +02:00
José Valim afcefb3e21 Run formatter 2021-06-26 09:33:24 +02:00
José Valim fb5aabf05a Remove warning and improve coverage on Enum.into/2 tests 2021-06-26 09:31:02 +02:00
José Valim d9a81101dd Optimize Enum.into and Map.new 2021-06-26 09:26:45 +02:00
José Valim 8fca53ad31 Ensure deprecated macros emit warnings 2021-06-23 22:15:43 +02:00
José Valim 76dfedb2e3 Compile most recently changed files first 2021-06-22 20:07:38 +02:00
Christopher Keele 8bec593f5d Ensure unconstrained rebar deps generate valid mix specifications (#11063) 2021-06-15 10:25:33 +02:00
TORIFUKUKaiou 03859fb92e Add @doc since: "1.12.0" and comments (#11046) 2021-06-08 09:07:35 +02:00
José Valim 53e49f33f1 Update CHANGELOG 2021-06-08 09:03:06 +02:00
Marc Riera c0bdd6101f Improve docs for sigil_r (#11051) 2021-06-08 08:59:24 +02:00
José Valim e7cb210712 Do not add compile time deps on args to Application.compile_env/2, closes #11052 2021-06-08 08:59:21 +02:00
TORIFUKUKaiou 4a4657e413 Add @doc since: "1.12.0" (#11039) 2021-06-05 22:21:21 +02:00
José Valim fcada17819 Align profiling times for better readability
Before we would write:

    lib/foo.ex compiled in 10ms (plus 100ms waiting)

However, because the file name is variable, it was
hard to see which file took the longest. Now we will
write:

        10ms compiling +    100ms waiting for lib/foo.ex

By right aligning all times, we can easily look in
the first row and see which file took the longest.
We can also provide a summary option in the future,
if necessary.
2021-06-05 22:21:15 +02:00
José Valim 6be8bddc02 Also annotate temp var for remote call in map.foo as generated
Closes #11030.
2021-06-01 15:04:23 +02:00
José Valim 9feda914dc Simplify reading of apps from deps 2021-06-01 11:53:12 +02:00
José Valim 62e1d71377 Avoid central bottleneck when loading apps if we know their source 2021-06-01 10:24:18 +02:00
José Valim d6ed6d18ef Remove duplication across branches 2021-06-01 09:37:47 +02:00
José Valim e024a12c50 Load apps concurrently 2021-06-01 09:36:25 +02:00
José Valim 42807d288e Release v1.12.1 2021-05-28 17:34:14 +02:00
José Valim 3b8d7fa15d Update CHANGELOG 2021-05-28 11:41:32 +02:00
José Valim d1d5e04319 Revert "Run all available tests if there are no pending --failed tests"
This reverts commit ae48325991.
2021-05-27 15:53:28 +02:00
José Valim ba1fd1abb8 Improve error message on invalid config 2021-05-26 19:14:28 +02:00
Wojtek Mach 8fd6c8f845 Ensure Mix.install/2 can be called after errors with deps (#11011) 2021-05-26 19:14:04 +02:00
Wojtek Mach 95dcbe9fe0 Mix.install/2: Expand paths for path deps (#11012) 2021-05-26 19:14:04 +02:00
José Valim 98a79dc839 Fix spec for which_children, closes #11005 2021-05-25 09:46:59 +02:00
José Valim 29444793b7 Fix formatter integration test 2021-05-22 21:24:58 +02:00
José Valim ff3674ddef Format multiline inside interpolation on first run, closes #10996 2021-05-22 20:46:05 +02:00
José Valim 0460017c5c Revert keeping of underscores between digits in camelize, closes #10995 2021-05-21 14:11:13 +02:00
José Valim f3b870857a Split on \r\n and \n accordingly during unicode compilation, closes #10991 2021-05-20 12:12:55 +02:00
José Valim faa2f35d8e Fix bootstrapping issue 2021-05-19 23:23:55 +02:00
José Valim 3339168330 Silence httpc warnings 2021-05-19 23:13:56 +02:00
José Valim 1c3ee4ed72 Remove unecessary copying of priv/include for Rebar 3 deps 2021-05-19 19:30:40 +02:00
José Valim 926455ca24 Add date to CHANGELOG 2021-05-19 14:36:49 +02:00
58 changed files with 909 additions and 236 deletions
+87
View File
@@ -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
@@ -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
+68 -1
View File
@@ -90,7 +90,65 @@ As of Elixir v1.12, implicitly decreasing ranges are soft-deprecated and warning
Elixir v1.12 has the additional of many functions across the standard library. The `Enum` module received additions such as `Enum.count_until/2`, `Enum.product/1`, `Enum.zip_with/2`, and more. The `Integer` module now includes `Integer.pow/2` and `Integer.extended_gcd/2`. Finally, the `Kernel` module got two new functions, `Kernel.then/2` and `Kernel.tap/2`, which are specially useful in `|>` pipelines.
## v1.12.0
## v1.12.3 (2021-09-05)
### 1. Bug fixes
#### Elixir
* [Code] Make sure that bindings in the default context returned by `Code.eval_*` functions are not returned as tagged tuples
* [Kernel] Do not crash when handling ambiguity errors
* [Range] Still match on old range patterns throughout the stdlib
#### IEx
* [IEx.Autocomplete] Do not error autocompletion with module attribute
#### Mix
* [Mix] Rename inconsistent `:exit_code` option to `:exit_status` on `Mix.raise/2`
## v1.12.2 (2021-07-01)
### 1. Bug fixes
#### Elixir
* [Kernel] Ensure deprecated macros emit warnings
#### Mix
* [mix deps] Ensure unconstrained rebar deps generate valid mix specifications
### 2. Enhancements
#### Elixir
* [elixirc] Change the output of `--profile time` to make it easier to detect outliers
* [Application] Do not add compile time deps on args to `Application.compile_env/2` and `Application.compile_env!/2`
* [Enum] Optimize `Enum.into/3` and `Map.new/2`
#### Mix
* [mix compile] Compile most recently changed files first
* [mix compile, mix run, mix test] Speed up the time taken to load dependencies. This should make the usage of Mix inside projects quite more responsive
## v1.12.1 (2021-05-28)
### 1. Bug fixes
#### Elixir
* [Code] Make sure `Code.format_string!/2` formats multiline expression inside interpolation on the first run
* [Macro] Revert keeping of underscores between digits in camelize
#### Mix
* [Mix] Make sure `Mix.install/2` expand paths for deps
* [mix deps.get] Silence false positives on `httpc` warnings
* [mix test] Do not run the whole suite when there are no --failed tests as it won't behave as expected inside umbrellas
## v1.12.0 (2021-05-19)
### 1. Enhancements
@@ -169,6 +227,7 @@ Elixir v1.12 has the additional of many functions across the standard library. T
* [mix release] Add `RELEASE_PROG` to releases with the name of the executable starting the release
* [mix release] Support `remote.vm.args` to customize how the connecting VM boots
* [mix test] Run all available tests if there are no pending `--failed` tests. This provides a better workflow as you no longer need to toggle the `--failed` flag between runs
* [mix test] Support `mix test --warnings-as-errors`, treating warnings as errors and returning a non-zero exit status (only applies to test files, not compilation)
### 2. Bug fixes
@@ -237,6 +296,14 @@ Elixir v1.12 has the additional of many functions across the standard library. T
* [mix compile] The `:xref` compiler is deprecated and it has no effect. Please remove it from your mix.exs file.
### 5. Backwards incompatible changes
A backward incompatible change was found on Elixir v1.12.0 after the release. This was not planned and it is documented below:
#### ExUnit
* [ExUnit.Formatter] `{:suite_finished, load_time, run_time}` message has been updated to `{:suite_finished, %{load: load_time, run: run_time, async: async_time}}`
## v1.11
The CHANGELOG for v1.11 releases can be found [in the v1.11 branch](https://github.com/elixir-lang/elixir/blob/v1.11/CHANGELOG.md).
+25 -17
View File
@@ -179,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."
@@ -221,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
+1 -1
View File
@@ -1 +1 @@
1.12.0
1.12.3
+1 -1
View File
@@ -14,7 +14,7 @@ Usage: $(basename "$0") [elixir switches] [compiler switches] [.ex files]
--no-docs Does not attach documentation to compiled modules
--profile time Profile the time to compile modules
--verbose Prints compilation status
--warnings-as-errors Treats warnings as errors and return non-zero exit code
--warnings-as-errors Treats warnings as errors and return non-zero exit status
Options given after -- are passed down to the executed code.
Options can be passed to the Erlang runtime using \$ELIXIR_ERL_OPTIONS.
+1 -1
View File
@@ -23,7 +23,7 @@ echo --no-debug-info Does not attach debug info to compiled modules
echo --no-docs Does not attach documentation to compiled modules
echo --profile time Profile the time to compile modules
echo --verbose Prints compilation status
echo --warnings-as-errors Treats warnings as errors and returns non-zero exit code
echo --warnings-as-errors Treats warnings as errors and returns non-zero exit status
echo.
echo ** Options given after -- are passed down to the executed code
echo ** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS
+13
View File
@@ -507,11 +507,22 @@ defmodule Application do
raise "Application.compile_env/3 cannot be called inside functions, only in the module body"
end
key_or_path = expand_key_or_path(key_or_path, __CALLER__)
quote do
Application.__compile_env__(unquote(app), unquote(key_or_path), unquote(default), __ENV__)
end
end
defp expand_key_or_path({:__aliases__, _, _} = alias, env),
do: Macro.expand(alias, %{env | function: {:__info__, 1}})
defp expand_key_or_path(list, env) when is_list(list),
do: Enum.map(list, &expand_key_or_path(&1, env))
defp expand_key_or_path(other, _env),
do: other
@doc false
def __compile_env__(app, key_or_path, default, env) do
case fetch_compile_env(app, key_or_path, env) do
@@ -533,6 +544,8 @@ defmodule Application do
raise "Application.compile_env!/2 cannot be called inside functions, only in the module body"
end
key_or_path = expand_key_or_path(key_or_path, __CALLER__)
quote do
Application.__compile_env__!(unquote(app), unquote(key_or_path), __ENV__)
end
+84 -24
View File
@@ -26,13 +26,15 @@ defmodule Date.Range do
defstruct [:first, :last, :first_in_iso_days, :last_in_iso_days, :step]
defimpl Enumerable do
def member?(%{first: %{calendar: calendar}} = range, %Date{calendar: calendar} = date) do
%{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
step: step
} = range
def member?(
%Date.Range{
first: %{calendar: calendar},
first_in_iso_days: first_days,
last_in_iso_days: last_days,
step: step
} = range,
%Date{calendar: calendar} = date
) do
{days, _} = Date.to_iso_days(date)
cond do
@@ -47,24 +49,43 @@ defmodule Date.Range do
end
end
def member?(_, _) do
def member?(%Date.Range{step: _}, _) do
{:ok, false}
end
# TODO: Remove me on v2.0
def member?(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range,
date
) do
step = if first_days <= last_days, do: 1, else: -1
member?(Map.put(date_range, :step, step), date)
end
def count(range) do
{:ok, size(range)}
end
def slice(range) do
%{
first_in_iso_days: first,
first: %{calendar: calendar},
step: step
} = range
def slice(
%Date.Range{
first_in_iso_days: first,
first: %{calendar: calendar},
step: step
} = range
) do
{:ok, size(range), &slice(first + &1 * step, step, &2, calendar)}
end
# TODO: Remove me on v2.0
def slice(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range
) do
step = if first_days <= last_days, do: 1, else: -1
slice(Map.put(date_range, :step, step))
end
defp slice(current, _step, 1, calendar) do
[date_from_iso_days(current, calendar)]
end
@@ -76,17 +97,30 @@ defmodule Date.Range do
]
end
def reduce(range, acc, fun) do
%{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
first: %{calendar: calendar},
step: step
} = range
def reduce(
%Date.Range{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
first: %{calendar: calendar},
step: step
},
acc,
fun
) do
reduce(first_days, last_days, acc, fun, step, calendar)
end
# TODO: Remove me on v2.0
def reduce(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range,
acc,
fun
) do
step = if first_days <= last_days, do: 1, else: -1
reduce(Map.put(date_range, :step, step), acc, fun)
end
defp reduce(_first_days, _last_days, {:halt, acc}, _fun, _step, _calendar) do
{:halted, acc}
end
@@ -135,6 +169,15 @@ defmodule Date.Range do
defp size(%Date.Range{first_in_iso_days: first_days, last_in_iso_days: last_days, step: step}),
do: abs(div(last_days - first_days, step)) + 1
# TODO: Remove me on v2.0
defp size(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range
) do
step = if first_days <= last_days, do: 1, else: -1
size(Map.put(date_range, :step, step))
end
defp empty?(%Date.Range{
first_in_iso_days: first_days,
last_in_iso_days: last_days,
@@ -151,10 +194,21 @@ defmodule Date.Range do
when step < 0 and first_days < last_days,
do: true
defp empty?(%Date.Range{}), do: false
defp empty?(%Date.Range{step: _}), do: false
# TODO: Remove me on v2.0
defp empty?(
%{__struct__: Date.Range, first_in_iso_days: first_days, last_in_iso_days: last_days} =
date_range
) do
step = if first_days <= last_days, do: 1, else: -1
empty?(Map.put(date_range, :step, step))
end
end
defimpl Inspect do
import Kernel, except: [inspect: 2]
def inspect(%Date.Range{first: first, last: last, step: 1}, _) do
"#DateRange<" <> inspect(first) <> ", " <> inspect(last) <> ">"
end
@@ -162,5 +216,11 @@ defmodule Date.Range do
def inspect(%Date.Range{first: first, last: last, step: step}, _) do
"#DateRange<" <> inspect(first) <> ", " <> inspect(last) <> ", #{step}>"
end
# TODO: Remove me on v2.0
def inspect(%{__struct__: Date.Range, first: first, last: last} = date_range, opts) do
step = if first <= last, do: 1, else: -1
inspect(Map.put(date_range, :step, step), opts)
end
end
end
+3 -2
View File
@@ -38,8 +38,8 @@ defmodule Code do
are loaded as needed. In embedded mode the opposite happens, as all
modules need to be loaded upfront or explicitly.
You can use `ensure_loaded/1` (as well as `ensure_lodead?/1` and
`ensure_lodead!/1`) to check if a module is loaded before using it and
You can use `ensure_loaded/1` (as well as `ensure_loaded?/1` and
`ensure_loaded!/1`) to check if a module is loaded before using it and
act.
## `ensure_compiled/1` and `ensure_compiled!/1`
@@ -1550,6 +1550,7 @@ defmodule Code do
@doc """
Same as `ensure_loaded/1` but raises if the module cannot be loaded.
"""
@doc since: "1.12.0"
@spec ensure_loaded!(module) :: module
def ensure_loaded!(module) do
case ensure_loaded(module) do
+29 -25
View File
@@ -257,6 +257,7 @@ defmodule Code.Formatter do
force_do_end_blocks: force_do_end_blocks,
locals_without_parens: locals_without_parens,
operand_nesting: 2,
skip_eol: false,
comments: comments
}
end
@@ -528,7 +529,7 @@ defmodule Code.Formatter do
end
defp quoted_to_algebra({:fn, meta, [_ | _] = clauses}, _context, state) do
anon_fun_to_algebra(clauses, line(meta), closing_line(meta), state, eol?(meta))
anon_fun_to_algebra(clauses, line(meta), closing_line(meta), state, eol?(meta, state))
end
defp quoted_to_algebra({fun, meta, args}, context, state) when is_atom(fun) and is_list(args) do
@@ -777,7 +778,7 @@ defmodule Code.Formatter do
concat(concat(group(left), op_string), group(right))
true ->
eol? = eol?(meta)
eol? = eol?(meta, state)
next_break_fits? =
op in @next_break_fits_operators and next_break_fits?(right_arg, state) and not eol?
@@ -919,7 +920,7 @@ defmodule Code.Formatter do
{docs, comments?, state} =
quoted_to_algebra_with_comments(operands, acc, min_line, max_line, state, fun)
if comments? or eol?(meta) do
if comments? or eol?(meta, state) do
{docs |> Enum.reduce(&line(&2, &1)) |> force_unfit(), state}
else
{docs |> Enum.reduce(&glue(&2, &1)), state}
@@ -1155,7 +1156,7 @@ defmodule Code.Formatter do
end
args = if keyword?, do: left ++ right, else: left ++ [right]
many_eol? = match?([_, _ | _], args) and eol?(meta)
many_eol? = match?([_, _ | _], args) and eol?(meta, state)
no_generators? = no_generators?(args)
to_algebra_fun = &quoted_to_algebra(&1, context, &2)
@@ -1346,8 +1347,7 @@ defmodule Code.Formatter do
defp list_interpolation_to_algebra([entry | entries], escape, state, acc, last) do
{{:., _, [Kernel, :to_string]}, _meta, [quoted]} = entry
{doc, state} = block_to_algebra(quoted, @max_line, @min_line, state)
doc = surround("\#{", doc, "}") |> interpolation_to_string()
{doc, state} = interpolation_to_string(quoted, state)
list_interpolation_to_algebra(entries, escape, state, concat(acc, doc), last)
end
@@ -1363,8 +1363,7 @@ defmodule Code.Formatter do
defp interpolation_to_algebra([entry | entries], escape, state, acc, last) do
{:"::", _, [{{:., _, [Kernel, :to_string]}, _meta, [quoted]}, {:binary, _, _}]} = entry
{doc, state} = block_to_algebra(quoted, @max_line, @min_line, state)
doc = surround("\#{", doc, "}") |> interpolation_to_string()
{doc, state} = interpolation_to_string(quoted, state)
interpolation_to_algebra(entries, escape, state, concat(acc, doc), last)
end
@@ -1372,6 +1371,12 @@ defmodule Code.Formatter do
{concat(acc, last), state}
end
defp interpolation_to_string(quoted, %{skip_eol: skip_eol} = state) do
{doc, state} = block_to_algebra(quoted, @max_line, @min_line, %{state | skip_eol: true})
doc = interpolation_to_string(surround("\#{", doc, "}"))
{doc, %{state | skip_eol: skip_eol}}
end
defp interpolation_to_string(doc) do
[head | tail] =
doc
@@ -1421,7 +1426,7 @@ defmodule Code.Formatter do
defp bitstring_to_algebra(meta, args, state) do
last = length(args) - 1
join = if eol?(meta), do: :line, else: :flex_break
join = if eol?(meta, state), do: :line, else: :flex_break
to_algebra_fun = &bitstring_segment_to_algebra(&1, &2, last)
{args_doc, join, state} =
@@ -1489,7 +1494,7 @@ defmodule Code.Formatter do
## Literals
defp list_to_algebra(meta, args, state) do
join = if eol?(meta), do: :line, else: :break
join = if eol?(meta, state), do: :line, else: :break
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, _join, state} =
@@ -1499,7 +1504,7 @@ defmodule Code.Formatter do
end
defp map_to_algebra(meta, name_doc, [{:|, _, [left, right]}], state) do
join = if eol?(meta), do: :line, else: :break
join = if eol?(meta, state), do: :line, else: :break
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{left_doc, state} = fun.(left, state)
@@ -1516,7 +1521,7 @@ defmodule Code.Formatter do
end
defp map_to_algebra(meta, name_doc, args, state) do
join = if eol?(meta), do: :line, else: :break
join = if eol?(meta, state), do: :line, else: :break
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, _join, state} =
@@ -1527,7 +1532,7 @@ defmodule Code.Formatter do
end
defp tuple_to_algebra(meta, args, join, state) do
join = if eol?(meta), do: :line, else: join
join = if eol?(meta, state), do: :line, else: join
fun = &quoted_to_algebra(&1, :parens_arg, &2)
{args_doc, join, state} =
@@ -1709,7 +1714,7 @@ defmodule Code.Formatter do
|> glue(body_doc)
|> nest(2)
|> glue("end")
|> maybe_force_clauses(clauses)
|> maybe_force_clauses(clauses, state)
|> group()
{doc, state}
@@ -1743,7 +1748,7 @@ defmodule Code.Formatter do
|> glue(body_doc)
|> nest(2)
|> glue("end")
|> maybe_force_clauses(clauses)
|> maybe_force_clauses(clauses, state)
|> group()
{doc, state}
@@ -1771,7 +1776,7 @@ defmodule Code.Formatter do
"(() -> "
|> concat(nest(body_doc, :cursor))
|> concat(")")
|> maybe_force_clauses(clauses)
|> maybe_force_clauses(clauses, state)
|> group()
{doc, state}
@@ -1792,7 +1797,7 @@ defmodule Code.Formatter do
|> group()
|> concat(break() |> concat(body_doc) |> nest(2))
|> wrap_in_parens()
|> maybe_force_clauses(clauses)
|> maybe_force_clauses(clauses, state)
|> group()
{doc, state}
@@ -1811,8 +1816,8 @@ defmodule Code.Formatter do
## Clauses
defp maybe_force_clauses(doc, clauses) do
if Enum.any?(clauses, fn {:->, meta, _} -> eol?(meta) end) do
defp maybe_force_clauses(doc, clauses, state) do
if Enum.any?(clauses, fn {:->, meta, _} -> eol?(meta, state) end) do
force_unfit(doc)
else
doc
@@ -1835,7 +1840,7 @@ defmodule Code.Formatter do
{doc_acc, state_acc}
end)
{clauses_doc |> maybe_force_clauses([clause | clauses]) |> group(), state}
{clauses_doc |> maybe_force_clauses([clause | clauses], state) |> group(), state}
end
defp clauses_to_algebra(other, min_line, max_line, state) do
@@ -2176,8 +2181,8 @@ defmodule Code.Formatter do
false
end
defp eol_or_comments?(meta, %{comments: comments}) do
eol?(meta) or
defp eol_or_comments?(meta, %{comments: comments} = state) do
eol?(meta, state) or
(
min_line = line(meta)
max_line = closing_line(meta)
@@ -2264,9 +2269,8 @@ defmodule Code.Formatter do
defp keyword_key?(_),
do: false
defp eol?(meta) do
Keyword.get(meta, :newlines, 0) > 0
end
defp eol?(_meta, %{skip_eol: true}), do: false
defp eol?(meta, _state), do: Keyword.get(meta, :newlines, 0) > 0
defp meta?(meta, key) do
is_list(meta[key])
+1 -1
View File
@@ -419,7 +419,7 @@ defmodule DynamicSupervisor do
@doc since: "1.6.0"
@spec which_children(Supervisor.supervisor()) :: [
# module() | :dynamic here because :supervisor.modules() is not exported
{:undefined, pid | :restarting, :worker | :supervisor, module() | :dynamic}
{:undefined, pid | :restarting, :worker | :supervisor, [module()] | :dynamic}
]
def which_children(supervisor) do
call(supervisor, :which_children)
+50 -15
View File
@@ -1421,28 +1421,35 @@ defmodule Enum do
into_protocol(enumerable, collectable)
end
def into(%{} = enumerable, %{} = collectable) do
Map.merge(collectable, enumerable)
end
def into(enumerable, %{} = collectable) when is_list(enumerable) do
Map.merge(collectable, :maps.from_list(enumerable))
end
def into(enumerable, %{} = collectable) do
reduce(enumerable, collectable, fn {key, val}, acc ->
Map.put(acc, key, val)
end)
if map_size(collectable) == 0 do
into_map(enumerable)
else
into_map(enumerable, collectable)
end
end
def into(enumerable, collectable) do
into_protocol(enumerable, collectable)
end
defp into_map(%{} = enumerable), do: enumerable
defp into_map(enumerable) when is_list(enumerable), do: :maps.from_list(enumerable)
defp into_map(enumerable), do: enumerable |> Enum.to_list() |> :maps.from_list()
defp into_map(%{} = enumerable, collectable),
do: Map.merge(collectable, enumerable)
defp into_map(enumerable, collectable) when is_list(enumerable),
do: Map.merge(collectable, :maps.from_list(enumerable))
defp into_map(enumerable, collectable),
do: Enum.reduce(enumerable, collectable, fn {key, val}, acc -> Map.put(acc, key, val) end)
defp into_protocol(enumerable, collectable) do
{initial, fun} = Collectable.into(collectable)
into(enumerable, initial, fun, fn entry, acc ->
into_protocol(enumerable, initial, fun, fn entry, acc ->
fun.(acc, {:cont, entry})
end)
end
@@ -1461,20 +1468,42 @@ defmodule Enum do
"""
@spec into(Enumerable.t(), Collectable.t(), (term -> term)) :: Collectable.t()
def into(enumerable, collectable, transform) when is_list(collectable) do
collectable ++ map(enumerable, transform)
end
def into(%_{} = enumerable, collectable, transform) do
into_protocol(enumerable, collectable, transform)
end
def into(enumerable, %_{} = collectable, transform) do
into_protocol(enumerable, collectable, transform)
end
def into(enumerable, %{} = collectable, transform) do
if map_size(collectable) == 0 do
enumerable |> Enum.map(transform) |> :maps.from_list()
else
Enum.reduce(enumerable, collectable, fn entry, acc ->
{key, val} = transform.(entry)
Map.put(acc, key, val)
end)
end
end
def into(enumerable, collectable, transform) do
into_protocol(enumerable, collectable, transform)
end
defp into_protocol(enumerable, collectable, transform) do
{initial, fun} = Collectable.into(collectable)
into(enumerable, initial, fun, fn entry, acc ->
into_protocol(enumerable, initial, fun, fn entry, acc ->
fun.(acc, {:cont, transform.(entry)})
end)
end
defp into(enumerable, initial, fun, callback) do
defp into_protocol(enumerable, initial, fun, callback) do
try do
reduce(enumerable, initial, callback)
catch
@@ -2632,6 +2661,12 @@ defmodule Enum do
end
end
# TODO: Remove me on v2.0
def slice(enumerable, %{__struct__: Range, first: first, last: last} = index_range) do
step = if first <= last, do: 1, else: -1
slice(enumerable, Map.put(index_range, :step, step))
end
defp slice_range(enumerable, first, last) when last >= first and last >= 0 and first >= 0 do
slice_any(enumerable, first, last - first + 1)
end
+8
View File
@@ -516,6 +516,7 @@ defmodule IO do
IO.stream(:stdio, :line)
"""
@doc since: "1.12.0"
def stream, do: stream(:stdio, :line)
@doc """
@@ -534,6 +535,9 @@ defmodule IO do
Note that an IO stream has side effects and every time
you go over the stream you may get different results.
`stream/1` has been introduced in Elixir v1.12.0,
while `stream/2` has been available since v1.0.0.
## Examples
Here is an example on how we mimic an echo server
@@ -557,6 +561,7 @@ defmodule IO do
IO.binstream(:stdio, :line)
"""
@doc since: "1.12.0"
def binstream, do: binstream(:stdio, :line)
@doc """
@@ -574,6 +579,9 @@ defmodule IO do
Finally, do not use this function on IO devices in Unicode
mode as it will return the wrong result.
`binstream/1` has been introduced in Elixir v1.12.0,
while `binstream/2` has been available since v1.0.0.
"""
@spec binstream(device, :line | pos_integer) :: Enumerable.t()
def binstream(device \\ :stdio, line_or_bytes)
+5 -2
View File
@@ -5554,7 +5554,7 @@ defmodule Kernel do
quote(do: List.to_charlist(unquote(unescape_list_tokens(pieces))))
end
@doc """
@doc ~S"""
Handles the sigil `~r` for regular expressions.
It returns a regular expression pattern, unescaping characters and replacing
@@ -5564,12 +5564,15 @@ defmodule Kernel do
## Examples
iex> Regex.match?(~r(foo), "foo")
iex> Regex.match?(~r/foo/, "foo")
true
iex> Regex.match?(~r/a#{:b}c/, "abc")
true
While the `~r` sigil allows parens and brackets to be used as delimiters,
it is preferred to use `"` or `/` to avoid escaping conflicts with reserved
regex characters.
"""
defmacro sigil_r(term, modifiers)
+11 -4
View File
@@ -623,13 +623,13 @@ defmodule Kernel.ParallelCompiler do
data = maybe_warn_long_compilation(data, state)
if state.profile != :none do
compiling = System.convert_time_unit(data.compiling, :native, :millisecond)
waiting = System.convert_time_unit(data.waiting, :native, :millisecond)
extra = if waiting > 0, do: " (plus #{waiting}ms waiting)", else: ""
compiling = to_padded_ms(data.compiling)
waiting = to_padded_ms(data.waiting)
relative = Path.relative_to_cwd(data.file)
IO.puts(
:stderr,
"[profile] #{Path.relative_to_cwd(data.file)} compiled in #{compiling}ms" <> extra
"[profile] #{compiling}ms compiling + #{waiting}ms waiting for #{relative}"
)
end
@@ -640,6 +640,13 @@ defmodule Kernel.ParallelCompiler do
end)
end
defp to_padded_ms(time) do
time
|> System.convert_time_unit(:native, :millisecond)
|> Integer.to_string()
|> String.pad_leading(6, " ")
end
defp discard_down(pid) do
receive do
{:DOWN, _, :process, ^pid, _} -> :ok
-3
View File
@@ -1735,9 +1735,6 @@ defmodule Macro do
defp do_camelize(<<?_, h, t::binary>>) when h >= ?a and h <= ?z,
do: <<to_upper_char(h)>> <> do_camelize(t)
defp do_camelize(<<p, ?_, h, t::binary>>) when p >= ?0 and p <= ?9 and h >= ?0 and h <= ?9,
do: <<p, ?_, h>> <> do_camelize(t)
defp do_camelize(<<?_, h, t::binary>>) when h >= ?0 and h <= ?9, do: <<h>> <> do_camelize(t)
defp do_camelize(<<?_>>), do: <<>>
defp do_camelize(<<?/, t::binary>>), do: <<?.>> <> camelize(t)
+3
View File
@@ -105,7 +105,9 @@ defmodule Macro.Env do
vars: vars
}
# Define the __struct__ callbacks by hand for bootstrap reasons.
# TODO: Remove :vars field on v2.0
@doc false
def __struct__ do
%{
__struct__: __MODULE__,
@@ -130,6 +132,7 @@ defmodule Macro.Env do
}
end
@doc false
def __struct__(kv) do
Enum.reduce(kv, __struct__(), fn {k, v}, acc -> :maps.update(k, v, acc) end)
end
+1 -11
View File
@@ -216,20 +216,10 @@ defmodule Map do
@spec new(Enumerable.t(), (term -> {key, value})) :: map
def new(enumerable, transform) when is_function(transform, 1) do
enumerable
|> Enum.to_list()
|> new_transform(transform, [])
end
defp new_transform([], _fun, acc) do
acc
|> :lists.reverse()
|> Enum.map(transform)
|> :maps.from_list()
end
defp new_transform([element | rest], fun, acc) do
new_transform(rest, fun, [fun.(element) | acc])
end
@doc """
Returns whether the given `key` exists in the given `map`.
+32
View File
@@ -185,6 +185,12 @@ defmodule Range do
def size(first..last//step) when step < 0 and first < last, do: 0
def size(first..last//step), do: abs(div(last - first, step)) + 1
# TODO: Remove me on v2.0
def size(%{__struct__: Range, first: first, last: last} = range) do
step = if first <= last, do: 1, else: -1
size(Map.put(range, :step, step))
end
@doc """
Checks if two ranges are disjoint.
@@ -273,6 +279,12 @@ defimpl Enumerable, for: Range do
reduce(first, last, acc, fun, step)
end
# TODO: Remove me on v2.0
def reduce(%{__struct__: Range, first: first, last: last} = range, acc, fun) do
step = if first <= last, do: 1, else: -1
reduce(Map.put(range, :step, step), acc, fun)
end
defp reduce(_first, _last, {:halt, acc}, _fun, _step) do
{:halted, acc}
end
@@ -304,6 +316,13 @@ defimpl Enumerable, for: Range do
end
end
# TODO: Remove me on v2.0
def member?(%{__struct__: Range, first: first, last: last} = range, value)
when is_integer(value) do
step = if first <= last, do: 1, else: -1
member?(Map.put(range, :step, step), value)
end
def member?(_, _value) do
{:ok, false}
end
@@ -316,12 +335,19 @@ defimpl Enumerable, for: Range do
{:ok, Range.size(range), &slice(first + &1 * step, step, &2)}
end
# TODO: Remove me on v2.0
def slice(%{__struct__: Range, first: first, last: last} = range) do
step = if first <= last, do: 1, else: -1
slice(Map.put(range, :step, step))
end
defp slice(current, _step, 1), do: [current]
defp slice(current, step, remaining), do: [current | slice(current + step, step, remaining - 1)]
end
defimpl Inspect, for: Range do
import Inspect.Algebra
import Kernel, except: [inspect: 2]
def inspect(first..last//1, opts) do
concat([to_doc(first, opts), "..", to_doc(last, opts)])
@@ -330,4 +356,10 @@ defimpl Inspect, for: Range do
def inspect(first..last//step, opts) do
concat([to_doc(first, opts), "..", to_doc(last, opts), "//", to_doc(step, opts)])
end
# TODO: Remove me on v2.0
def inspect(%{__struct__: Range, first: first, last: last} = range, opts) do
step = if first <= last, do: 1, else: -1
inspect(Map.put(range, :step, step), opts)
end
end
+6
View File
@@ -2098,6 +2098,12 @@ defmodule String do
end
end
# TODO: Remove me on v2.0
def slice(string, %{__struct__: Range, first: first, last: last} = range) do
step = if first <= last, do: 1, else: -1
slice(string, Map.put(range, :step, step))
end
defp slice_range("", _, _), do: ""
defp slice_range(string, first, -1) when first >= 0 do
+1 -1
View File
@@ -930,7 +930,7 @@ defmodule Supervisor do
"""
@spec which_children(supervisor) :: [
# inlining module() | :dynamic here because :supervisor.modules() is not exported
{term() | :undefined, child | :restarting, :worker | :supervisor, module() | :dynamic}
{term() | :undefined, child | :restarting, :worker | :supervisor, [module()] | :dynamic}
]
def which_children(supervisor) do
call(supervisor, :which_children)
+19
View File
@@ -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"])
+1 -1
View File
@@ -252,7 +252,7 @@ eval_forms(Tree, RawBinding, OE) ->
case Erl of
{atom, _, Atom} ->
{Atom, Binding, NewE};
{Atom, RawBinding, NewE};
_ ->
Exprs =
+11 -9
View File
@@ -77,7 +77,7 @@ require_function(Meta, Receiver, Name, Arity, E) ->
end.
remote_function(Meta, Receiver, Name, Arity, E) ->
check_deprecated(Meta, Receiver, Name, Arity, E),
check_deprecated(Meta, function, Receiver, Name, Arity, E),
case elixir_rewrite:inline(Receiver, Name, Arity) of
{AR, AN} -> {remote, AR, AN, Arity};
@@ -161,7 +161,7 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
elixir_locals:record_import(Tuple, Receiver, Module, ?key(E, function)),
{ok, Receiver, Name, Args};
{macro, Receiver} ->
check_deprecated(Meta, Receiver, Name, Arity, E),
check_deprecated(Meta, macro, Receiver, Name, Arity, E),
elixir_env:trace({imported_macro, Meta, Receiver, Name, Arity}, E),
elixir_locals:record_import(Tuple, Receiver, Module, ?key(E, function)),
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
@@ -180,17 +180,18 @@ do_expand_import(Meta, {Name, Arity} = Tuple, Args, Module, E, Result) ->
end.
expand_require(Meta, Receiver, {Name, Arity} = Tuple, Args, E) ->
check_deprecated(Meta, Receiver, Name, Arity, E),
Required = (Receiver == ?key(E, module)) orelse required(Meta) orelse is_element(Receiver, ?key(E, requires)),
case is_element(Tuple, get_macros(Receiver, Required)) of
true when Required ->
check_deprecated(Meta, macro, Receiver, Name, Arity, E),
elixir_env:trace({remote_macro, Meta, Receiver, Name, Arity}, E),
{ok, Receiver, expand_macro_named(Meta, Receiver, Name, Arity, Args, E)};
true ->
Info = {unrequired_module, {Receiver, Name, length(Args)}},
elixir_errors:form_error(Meta, E, ?MODULE, Info);
false ->
check_deprecated(Meta, function, Receiver, Name, Arity, E),
error
end.
@@ -361,12 +362,13 @@ elixir_imported_macros() ->
error:undef -> []
end.
check_deprecated(_, erlang, _, _, _) ->
ok;
check_deprecated(_, ?kernel, _, _, _) ->
ok;
check_deprecated(Meta, Receiver, Name, Arity, E) ->
case (?key(E, function) == nil) andalso get_deprecations(Receiver) of
check_deprecated(_, _, erlang, _, _, _) -> ok;
check_deprecated(_, _, elixir_def, _, _, _) -> ok;
check_deprecated(_, _, elixir_module, _, _, _) -> ok;
check_deprecated(_, _, ?kernel, _, _, _) -> ok;
check_deprecated(Meta, Kind, Receiver, Name, Arity, E) ->
%% Any compile time behaviour cannot be verified by the runtime group pass.
case ((?key(E, function) == nil) or (Kind == macro)) andalso get_deprecations(Receiver) of
[_ | _] = Deprecations ->
case lists:keyfind({Name, Arity}, 1, Deprecations) of
{_, Message} ->
+2 -2
View File
@@ -223,9 +223,9 @@ translate({{'.', _, [Left, Right]}, Meta, []}, S) when is_tuple(Left), is_atom(R
Ann = ?ann(Meta),
TRight = {atom, Ann, Right},
{Var, SV} = elixir_erl_var:build('_', SL),
TVar = {var, Ann, Var},
Generated = erl_anno:set_generated(true, Ann),
{Var, SV} = elixir_erl_var:build('_', SL),
TVar = {var, Generated, Var},
TError = {tuple, Ann, [{atom, Ann, badkey}, TRight, TVar]},
{{'case', Generated, TLeft, [
+6 -10
View File
@@ -1161,23 +1161,19 @@ format_error({invalid_alias, Expr}) ->
"it at runtime. If instead you wanted to invoke a function or access a field, "
"wrap the function or field name in double quotes",
io_lib:format(Message, ['Elixir.Macro':to_string(Expr)]);
format_error({op_ambiguity, Name, {Op, _, [Arg]}}) ->
format_error({op_ambiguity, Name, Arg}) ->
NameString = atom_to_binary(Name, utf8),
OpString = atom_to_binary(Op, utf8),
ArgString = 'Elixir.Macro':to_string(Arg),
Message =
"\"~ts ~ts~ts\" looks like a function call but there is a variable named \"~ts\".\n"
"\"~ts ~ts\" looks like a function call but there is a variable named \"~ts\". "
"If you want to perform a function call, use parentheses:\n"
"\n"
" ~ts(~ts~ts)\n"
" ~ts(~ts)\n"
"\n"
"If you want to perform an operation on the variable ~ts, use even spaces instead:\n"
"\n"
" ~ts ~ts ~ts",
io_lib:format(Message, [NameString, OpString, ArgString, NameString,
NameString, OpString, ArgString, NameString,
NameString, OpString, ArgString]);
"If you want to perform an operation on the variable ~ts, use spaces "
"around the unary operator",
io_lib:format(Message, [NameString, ArgString, NameString, NameString, ArgString, NameString]);
format_error({invalid_clauses, Name}) ->
Message =
"the function \"~ts\" cannot handle clauses with the -> operator because it is not a macro. "
@@ -141,4 +141,71 @@ defmodule Date.RangeTest do
Date.range(~D[2000-01-01], ~D[2000-01-31], step)
end
end
describe "old date ranges" do
test "inspect" do
asc = %{
__struct__: Date.Range,
first: ~D[2021-07-14],
first_in_iso_days: 738_350,
last: ~D[2021-07-17],
last_in_iso_days: 738_353
}
desc = %{
__struct__: Date.Range,
first: ~D[2021-07-17],
first_in_iso_days: 738_353,
last: ~D[2021-07-14],
last_in_iso_days: 738_350
}
assert inspect(asc) == "#DateRange<~D[2021-07-14], ~D[2021-07-17]>"
assert inspect(desc) == "#DateRange<~D[2021-07-17], ~D[2021-07-14], -1>"
end
test "enumerable" do
asc = %{
__struct__: Date.Range,
first: ~D[2021-07-14],
first_in_iso_days: 738_350,
last: ~D[2021-07-17],
last_in_iso_days: 738_353
}
desc = %{
__struct__: Date.Range,
first: ~D[2021-07-17],
first_in_iso_days: 738_353,
last: ~D[2021-07-14],
last_in_iso_days: 738_350
}
# member? implementations tests also empty?
assert Enumerable.member?(asc, ~D[2021-07-15])
assert {:ok, 4, _} = Enumerable.slice(asc)
assert Enum.reduce(asc, [], fn x, acc -> [x | acc] end) == [
~D[2021-07-17],
~D[2021-07-16],
~D[2021-07-15],
~D[2021-07-14]
]
assert Enum.count(asc) == 4
# member? implementations tests also empty?
assert Enumerable.member?(desc, ~D[2021-07-15])
assert {:ok, 4, _} = Enumerable.slice(desc)
assert Enum.reduce(desc, [], fn x, acc -> [x | acc] end) == [
~D[2021-07-14],
~D[2021-07-15],
~D[2021-07-16],
~D[2021-07-17]
]
assert Enum.count(desc) == 4
end
end
end
@@ -548,6 +548,21 @@ defmodule Code.Formatter.IntegrationTest do
"""
end
test "multiline expression inside interpolation" do
bad = ~S"""
Logger.info("Example: #{
inspect(%{
a: 1,
b: 2
})
}")
"""
assert_format bad, ~S"""
Logger.info("Example: #{inspect(%{a: 1, b: 2})}")
"""
end
test "comment inside operator with when" do
bad = """
raise function(x) ::
+4
View File
@@ -167,6 +167,10 @@ defmodule CodeTest do
assert Code.eval_string("two = 1 + 1") == {2, [two: 2]}
end
test "keeps bindings on optimized evals" do
assert Code.eval_string("import Enum", x: 1) == {Enum, [x: 1]}
end
test "supports a %Macro.Env{} struct as the third argument" do
assert {3, _} = Code.eval_string("a + b", [a: 1, b: 2], __ENV__)
end
+3 -1
View File
@@ -396,6 +396,8 @@ defmodule EnumTest do
test "into/2" do
assert Enum.into([a: 1, b: 2], %{}) == %{a: 1, b: 2}
assert Enum.into([a: 1, b: 2], %{c: 3}) == %{a: 1, b: 2, c: 3}
assert Enum.into(MapSet.new(a: 1, b: 2), %{}) == %{a: 1, b: 2}
assert Enum.into(MapSet.new(a: 1, b: 2), %{c: 3}) == %{a: 1, b: 2, c: 3}
assert Enum.into(%{a: 1, b: 2}, []) == [a: 1, b: 2]
assert Enum.into(1..3, []) == [1, 2, 3]
assert Enum.into(["H", "i"], "") == "Hi"
@@ -405,7 +407,7 @@ defmodule EnumTest do
assert Enum.into([1, 2, 3], [], fn x -> x * 2 end) == [2, 4, 6]
assert Enum.into([1, 2, 3], "numbers: ", &to_string/1) == "numbers: 123"
assert_raise FunctionClauseError, fn ->
assert_raise MatchError, fn ->
Enum.into([2, 3], %{a: 1}, & &1)
end
end
+5 -4
View File
@@ -470,11 +470,12 @@ defmodule ExceptionTest do
* min/1
"""
assert blame_message(:erlang, & &1.gt_cookie()) == """
function :erlang.gt_cookie/0 is undefined or private. Did you mean one of:
assert blame_message(:erlang, & &1.hal()) == """
function :erlang.hal/0 is undefined or private. Did you mean one of:
* get_cookie/0
* set_cookie/2
* halt/0
* halt/1
* halt/2
"""
end
@@ -2538,10 +2538,10 @@ defmodule Kernel.ExpansionTest do
describe "op ambiguity" do
test "raises when a call is ambiguous" do
# We use string_to_quoted! here to avoid the formatter adding parentheses
message = ~r["a -1" looks like a function call but there is a variable named "a"]
assert_raise CompileError, message, fn ->
# We use string_to_quoted! here to avoid the formatter adding parentheses to "a -1".
code =
Code.string_to_quoted!("""
a = 1
@@ -2550,6 +2550,18 @@ defmodule Kernel.ExpansionTest do
expand(code)
end
message = ~r["a -1\.\.a \+ 1" looks like a function call but there is a variable named "a"]
assert_raise CompileError, message, fn ->
code =
Code.string_to_quoted!("""
a = 1
a -1 .. a + 1
""")
expand(code)
end
end
end
@@ -202,5 +202,20 @@ defmodule Kernel.LexicalTrackerTest do
assert URI in exports
refute URI in runtime
end
test "compile_env! does not add a compile dependency" do
{{compile, exports, runtime, _}, _binding} =
Code.eval_string("""
defmodule Kernel.LexicalTrackerTest.CompileEnvStruct do
Application.compile_env(:elixir, URI)
Application.compile_env(:elixir, [:foo, URI, :bar])
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
end |> elem(3)
""")
refute URI in compile
refute URI in exports
assert URI in runtime
end
end
end
@@ -43,7 +43,9 @@ defmodule Kernel.ParallelCompilerTest do
assert HelloWorld in modules
end)
assert profile =~ ~r"\[profile\] .*tmp/profile_time/bar.ex compiled in \d+ms"
assert profile =~
~r"\[profile\] [\s\d]{6}ms compiling \+ 0ms waiting for .*tmp/profile_time/bar.ex"
assert profile =~ ~r"\[profile\] Finished compilation cycle of 1 modules in \d+ms"
assert profile =~ ~r"\[profile\] Finished group pass check of 1 modules in \d+ms"
after
+2 -1
View File
@@ -1036,7 +1036,8 @@ defmodule MacroTest do
assert Macro.camelize("foo__bar") == "FooBar"
assert Macro.camelize("foo/bar") == "Foo.Bar"
assert Macro.camelize("Foo.Bar") == "Foo.Bar"
assert Macro.camelize("foo1_0") == "Foo1_0"
assert Macro.camelize("foo1_0") == "Foo10"
assert Macro.camelize("foo_123_4_567") == "Foo1234567"
assert Macro.camelize("FOO_BAR") == "FOO_BAR"
assert Macro.camelize("FOO.BAR") == "FOO.BAR"
assert Macro.camelize("") == ""
@@ -663,6 +663,31 @@ defmodule Module.Types.IntegrationTest do
assert_warnings(files, warning)
end
test "reports macro" do
files = %{
"a.ex" => """
defmodule A do
@deprecated "oops"
defmacro a, do: :ok
end
""",
"b.ex" => """
defmodule B do
require A
def b, do: A.a()
end
"""
}
warning = """
warning: A.a/0 is deprecated. oops
b.ex:3: B.b/0
"""
assert_warnings(files, warning)
end
end
defp assert_warnings(files, expected) when is_binary(expected) do
+45
View File
@@ -94,4 +94,49 @@ defmodule RangeTest do
assert Range.disjoint?(1..1, 1..1) == false
end
end
describe "old ranges" do
test "inspect" do
asc = %{__struct__: Range, first: 1, last: 3}
desc = %{__struct__: Range, first: 3, last: 1}
assert inspect(asc) == "1..3"
assert inspect(desc) == "3..1//-1"
end
test "enum" do
asc = %{__struct__: Range, first: 1, last: 3}
desc = %{__struct__: Range, first: 3, last: 1}
assert Enum.to_list(asc) == [1, 2, 3]
assert Enum.member?(asc, 2)
assert Enum.count(asc) == 3
assert Enum.drop(asc, 1) == [2, 3]
assert Enum.slice([1, 2, 3, 4, 5, 6], asc) == [2, 3, 4]
# testing private Enum.aggregate
assert Enum.max(asc) == 3
assert Enum.sum(asc) == 6
assert Enum.min_max(asc) == {1, 3}
assert Enum.reduce(asc, 0, fn a, b -> a + b end) == 6
assert Enum.to_list(desc) == [3, 2, 1]
assert Enum.member?(desc, 2)
assert Enum.count(desc) == 3
assert Enum.drop(desc, 1) == [2, 1]
assert Enum.slice([1, 2, 3, 4, 5, 6], desc) == []
# testing private Enum.aggregate
assert Enum.max(desc) == 3
assert Enum.sum(desc) == 6
assert Enum.min_max(desc) == {1, 3}
assert Enum.reduce(desc, 0, fn a, b -> a + b end) == 6
end
test "string" do
asc = %{__struct__: Range, first: 1, last: 3}
desc = %{__struct__: Range, first: 3, last: 1}
assert String.slice("elixir", asc) == "lix"
assert String.slice("elixir", desc) == ""
end
end
end
+2 -2
View File
@@ -138,7 +138,7 @@ defmodule String.Casing do
codes =
special_path
|> File.read!()
|> String.split("\n", trim: true)
|> String.split(["\r\n", "\n"], trim: true)
|> Enum.reduce(codes, fn
"", acc ->
acc
@@ -325,7 +325,7 @@ defmodule String.Break do
whitespace =
prop_path
|> File.read!()
|> String.split("\n")
|> String.split(["\r\n", "\n"])
|> Enum.reduce([], fn line, acc ->
case :binary.split(line, ";") do
[<<first::4-bytes, "..", last::4-bytes, _::binary>>, <<" White_Space", _::binary>>] ->
+1 -1
View File
@@ -47,7 +47,7 @@ defmodule String.Tokenizer do
{start, continue, patterns} =
prop_path
|> File.read!()
|> String.split("\n", trim: true)
|> String.split(["\r\n", "\n"], trim: true)
|> Enum.reduce({start, continue, []}, fn line, acc ->
[codepoints | category] = :binary.split(line, ";")
+4
View File
@@ -163,6 +163,10 @@ defmodule IEx.Autocomplete do
{:ok, List.to_atom(var)}
end
defp expand_dot_path({:module_attribute, _}, _shell) do
:error
end
defp expand_dot_path({:dot, parent, call}, shell) do
case expand_dot_path(parent, shell) do
{:ok, %{} = map} -> Map.fetch(map, List.to_atom(call))
+4 -3
View File
@@ -161,6 +161,7 @@ defmodule IEx.AutocompleteTest do
assert expand('Xyz') == {:no, '', []}
assert expand('x.Foo') == {:no, '', []}
assert expand('x.Foo.get_by') == {:no, '', []}
assert expand('@foo.bar') == {:no, '', []}
end
test "Elixir root submodule completion" do
@@ -221,21 +222,21 @@ defmodule IEx.AutocompleteTest do
assert expand('map.f') == {:no, '', []}
end
test "autocompletion off a bound variable only works for modules and maps" do
test "bound variables for modules and maps" do
eval("num = 5; map = %{nested: %{num: 23}}")
assert expand('num.print') == {:no, '', []}
assert expand('map.nested.num.f') == {:no, '', []}
assert expand('map.nested.num.key.f') == {:no, '', []}
end
test "autocompletion using access syntax does is not supported" do
test "access syntax is not supported" do
eval("map = %{nested: %{deeply: %{num: 23}}}")
assert expand('map[:nested][:deeply].n') == {:no, '', []}
assert expand('map[:nested].deeply.n') == {:no, '', []}
assert expand('map.nested.[:deeply].n') == {:no, '', []}
end
test "autocompletion off of unbound variables is not supported" do
test "unbound variables is not supported" do
eval("num = 5")
assert expand('other_var.f') == {:no, '', []}
assert expand('a.b.c.d') == {:no, '', []}
+30 -10
View File
@@ -433,11 +433,11 @@ defmodule Mix do
end
@doc """
Raises a Mix error that is nicely formatted, defaulting to exit code `1`.
Raises a Mix error that is nicely formatted, defaulting to exit status `1`.
"""
@spec raise(binary) :: no_return
def raise(message) do
__MODULE__.raise(message, exit_code: 1)
__MODULE__.raise(message, exit_status: 1)
end
@doc """
@@ -445,13 +445,14 @@ defmodule Mix do
## Options
* `:exit_code` - defines exit code value, defaults to `1`
* `:exit_status` - defines exit status, defaults to `1`
"""
@doc since: "1.12.0"
@spec raise(binary, exit_code: non_neg_integer()) :: no_return
@doc since: "1.12.3"
@spec raise(binary, exit_status: non_neg_integer()) :: no_return
def raise(message, opts) when is_binary(message) and is_list(opts) do
Kernel.raise(Mix.Error, mix: Keyword.get(opts, :exit_code, 1), message: message)
status = opts[:exit_status] || opts[:exit_code] || 1
Kernel.raise(Mix.Error, mix: status, message: message)
end
@doc """
@@ -518,8 +519,17 @@ defmodule Mix do
deps =
Enum.map(deps, fn
dep when is_atom(dep) -> {dep, ">= 0.0.0"}
dep -> dep
dep when is_atom(dep) ->
{dep, ">= 0.0.0"}
{app, opts} when is_atom(app) and is_list(opts) ->
{app, maybe_expand_path_dep(opts)}
{app, requirement, opts} when is_atom(app) and is_binary(requirement) and is_list(opts) ->
{app, requirement, maybe_expand_path_dep(opts)}
other ->
other
end)
force? = !!opts[:force]
@@ -567,9 +577,10 @@ defmodule Mix do
:ok = Mix.Local.append_archives()
:ok = Mix.ProjectStack.push(__MODULE__.InstallProject, config, "nofile")
build_dir = Path.join(dir, "_build")
try do
run_deps? = not File.dir?(Path.join(dir, "_build"))
run_deps? = not File.dir?(build_dir)
File.mkdir_p!(dir)
File.cd!(dir, fn ->
@@ -577,7 +588,8 @@ defmodule Mix do
Mix.Task.rerun("deps.get")
end
Mix.Task.run("compile")
Mix.Task.rerun("deps.loadpaths")
Mix.Task.rerun("compile")
end)
for app <- Mix.Project.deps_apps() do
@@ -590,4 +602,12 @@ defmodule Mix do
Mix.ProjectStack.pop()
end
end
defp maybe_expand_path_dep(opts) do
if Keyword.has_key?(opts, :path) do
Keyword.update!(opts, :path, &Path.expand/1)
else
opts
end
end
end
+9
View File
@@ -226,6 +226,15 @@ defmodule Mix.Compilers.Elixir do
dest
)
# Now sort the files so the ones changed more recently come first.
# We do an optimized version of sort_by since we don't care about
# stable sorting.
changed =
changed
|> Enum.map(&{-elem(Map.fetch!(sources_stats, &1), 0), &1})
|> Enum.sort()
|> Enum.map(&elem(&1, 1))
{modules, exports, changed, sources_stats}
end
+1 -1
View File
@@ -123,7 +123,7 @@ defmodule Mix.Rebar do
# Translate a Rebar dependency declaration to a Mix declaration
# From http://www.rebar3.org/docs/dependencies#section-declaring-dependencies
defp parse_dep(app) when is_atom(app) do
{app, nil, override: true}
{app, override: true}
end
defp parse_dep({app, req}) when is_list(req) do
+32 -4
View File
@@ -418,13 +418,41 @@ defmodule Mix.Release do
:ok
{:error, reason} ->
{:error,
"Could not read configuration file. It likely has invalid configuration terms " <>
"such as functions, references, and pids. Please make sure your configuration " <>
"is made of numbers, atoms, strings, maps, tuples and lists. Reason: #{inspect(reason)}"}
invalid =
for {app, kv} <- sys_config,
{key, value} <- kv,
not valid_config?(value),
do: """
Application: #{inspect(app)}
Key: #{inspect(key)}
Value: #{inspect(value)}
"""
message =
case invalid do
[] ->
"Could not read configuration file. Reason: #{inspect(reason)}"
_ ->
"Could not read configuration file. It has invalid configuration terms " <>
"such as functions, references, and pids. Please make sure your configuration " <>
"is made of numbers, atoms, strings, maps, tuples and lists. The following entries " <>
"are wrong:\n#{Enum.join(invalid)}"
end
{:error, message}
end
end
defp valid_config?(n) when is_number(n), do: true
defp valid_config?(a) when is_atom(a), do: true
defp valid_config?(b) when is_binary(b), do: true
defp valid_config?(l) when is_list(l), do: Enum.all?(l, &valid_config?/1)
defp valid_config?(m) when is_map(m), do: Enum.all?(m, &valid_config?/1)
defp valid_config?(t) when is_tuple(t), do: Enum.all?(Tuple.to_list(t), &valid_config?/1)
defp valid_config?(_), do: false
defp merge_provider_config(%{config_providers: []}, sys_config, _), do: {sys_config, false}
defp merge_provider_config(release, sys_config, config_path) do
+65 -32
View File
@@ -14,6 +14,7 @@ defmodule Mix.Tasks.Compile.All do
Mix.Project.get!()
config = Mix.Project.config()
validate_compile_env? = "--no-validate-compile-env" not in args
lib_path = Path.join(Mix.Project.build_path(config), "lib")
# Make sure Mix.Dep is cached to avoid loading dependencies
# during compilation. It is likely this will be invoked anyway,
@@ -21,7 +22,7 @@ defmodule Mix.Tasks.Compile.All do
Mix.Dep.cached()
unless "--no-app-loading" in args do
load_apps(config, validate_compile_env?)
load_apps(config, lib_path, validate_compile_env?)
end
result =
@@ -39,8 +40,9 @@ defmodule Mix.Tasks.Compile.All do
end)
end
app = config[:app]
_ = Code.prepend_path(Mix.Project.compile_path())
load_app(config[:app], validate_compile_env?)
load_app(app, lib_path, validate_compile_env?)
result
end
@@ -93,44 +95,63 @@ defmodule Mix.Tasks.Compile.All do
## App loading helpers
defp load_apps(config, validate_compile_env?) do
defp load_apps(config, lib_path, validate_compile_env?) do
{runtime, optional} = Mix.Tasks.Compile.App.project_apps(config)
parent = self()
opts = [ordered: false, timeout: :infinity]
deps = for dep <- Mix.Dep.cached(), into: %{}, do: {dep.app, lib_path}
%{}
|> load_apps(runtime, validate_compile_env?)
|> load_apps(optional, validate_compile_env?)
stream_apps(runtime ++ optional, deps)
|> Task.async_stream(&load_stream_app(&1, parent, validate_compile_env?), opts)
|> Stream.run()
end
defp load_stream_app({app, lib_path}, parent, validate_compile_env?) do
children =
case load_app(app, lib_path, validate_compile_env?) do
:ok ->
Application.spec(app, :applications) ++ Application.spec(app, :included_applications)
:error ->
[]
end
send(parent, {:done, app, children})
:ok
end
defp load_apps(seen, apps, validate_compile_env?) do
Enum.reduce(apps, seen, fn app, seen ->
if Map.has_key?(seen, app) do
seen
else
seen = Map.put(seen, app, true)
case load_app(app, validate_compile_env?) do
:ok ->
seen
|> load_apps(Application.spec(app, :applications), validate_compile_env?)
|> load_apps(Application.spec(app, :included_applications), validate_compile_env?)
:error ->
seen
end
end
end)
defp stream_apps(initial, deps) do
Stream.unfold({initial, %{}, %{}, deps}, &stream_app/1)
end
defp load_app(app, validate_compile_env?) do
# We already processed this app, skip it.
defp stream_app({[app | apps], seen, done, deps}) when is_map_key(seen, app) do
stream_app({apps, seen, done, deps})
end
# We haven't processed this app, emit it.
defp stream_app({[app | apps], seen, done, deps}) do
{{app, deps[app]}, {apps, Map.put(seen, app, true), done, deps}}
end
# We have processed all apps and all seen have been done.
defp stream_app({[], seen, done, _deps}) when map_size(seen) == map_size(done) do
nil
end
# We have processed all apps but there is work being done.
defp stream_app({[], seen, done, deps}) do
receive do
{:done, app, children} -> stream_app({children, seen, Map.put(done, app, true), deps})
end
end
defp load_app(app, lib_path, validate_compile_env?) do
if Application.spec(app, :vsn) do
:ok
else
name = Atom.to_charlist(app) ++ '.app'
with [_ | _] = path <- :code.where_is_file(name),
{:ok, {:application, _, properties} = application_data} <- consult_app_file(path),
with {:ok, bin} <- read_app(app, lib_path),
{:ok, {:application, _, properties} = application_data} <- consult_app_file(bin),
:ok <- :application.load(application_data) do
if compile_env = validate_compile_env? && properties[:compile_env] do
Config.Provider.validate_compile_env(compile_env, false)
@@ -143,10 +164,22 @@ defmodule Mix.Tasks.Compile.All do
end
end
defp consult_app_file(path) do
# The app didn't come from a dep, go through the slow path (code/erl_prim_loader)
defp read_app(app, nil) do
name = Atom.to_charlist(app) ++ '.app'
with [_ | _] = path <- :code.where_is_file(name),
{:ok, bin, _full_name} <- :erl_prim_loader.get_file(path),
do: {:ok, bin}
end
defp read_app(app, lib_path) do
File.read("#{lib_path}/#{app}/ebin/#{app}.app")
end
defp consult_app_file(bin) do
# The path could be located in an .ez archive, so we use the prim loader.
with {:ok, bin, _full_name} <- :erl_prim_loader.get_file(path),
{:ok, tokens, _} <- :erl_scan.string(String.to_charlist(bin)) do
with {:ok, tokens, _} <- :erl_scan.string(String.to_charlist(bin)) do
:erl_parse.parse_term(tokens)
end
end
+1 -1
View File
@@ -52,7 +52,7 @@ defmodule Mix.Tasks.Compile.Elixir do
* `--debug-info` (`--no-debug-info`) - attaches (or not) debug info to compiled modules
* `--ignore-module-conflict` - does not emit warnings if a module was previously defined
* `--warnings-as-errors` - treats warnings in the current project as errors and
return a non-zero exit code
return a non-zero exit status
* `--long-compilation-threshold N` - sets the "long compilation" threshold
(in seconds) to `N` (see the docs for `Kernel.ParallelCompiler.compile/2`)
* `--profile` - if set to `time`, outputs timing information of compilation steps
-14
View File
@@ -206,20 +206,6 @@ defmodule Mix.Tasks.Deps.Compile do
File.mkdir_p!(dep_path)
File.write!(config_path, rebar_config(dep))
# We need to copy/symlink include/priv in case it is
# needed for compiling the dependency itself. Later on
# we will build the whole structure and also symlink/copy
# ebin.
#
# Note both configurations are necessary when using the
# bare compiler with compiler output dir, otherwise includes
# and priv files can't be found. For example, without those
# options, we were unable to compile certifi, which uses
# both parse transforms and reads from priv at compilation.
Mix.Utils.symlink_or_copy(Path.join(opts[:dest], "include"), Path.join(dep_path, "include"))
Mix.Utils.symlink_or_copy(Path.join(opts[:dest], "priv"), Path.join(dep_path, "priv"))
do_command(dep, config, cmd, false, env)
end
+3 -10
View File
@@ -116,8 +116,7 @@ defmodule Mix.Tasks.Test do
* `--export-coverage` - the name of the file to export coverage results to.
Only has an effect when used with `--cover`
* `--failed` - runs only tests that failed the last time they ran.
If there are no pending --failed tests, `mix test` will run all available tests
* `--failed` - runs only tests that failed the last time they ran
* `--force` - forces compilation regardless of modification times
@@ -175,7 +174,7 @@ defmodule Mix.Tasks.Test do
Note that in trace mode test timeouts will be ignored as timeout is set to `:infinity`
* `--warnings-as-errors` - (since v1.12.0) treats warnings as errors and returns a non-zero
exit code. This option only applies to test files. To treat warnings as errors during
exit status. This option only applies to test files. To treat warnings as errors during
compilation and during tests, run:
MIX_ENV=test mix do compile --warnings-as-errors, test --warnings-as-errors
@@ -702,13 +701,7 @@ defmodule Mix.Tasks.Test do
end
{allowed_files, failed_ids} = ExUnit.Filters.failure_info(manifest_file)
if MapSet.size(failed_ids) == 0 do
Mix.shell().info("No pending --failed tests, re-running all available tests...")
{opts, nil}
else
{Keyword.put(opts, :only_test_ids, failed_ids), allowed_files}
end
{Keyword.put(opts, :only_test_ids, failed_ids), allowed_files}
else
{opts, nil}
end
+19 -9
View File
@@ -1,6 +1,9 @@
defmodule Mix.Utils do
@moduledoc false
# For bootstrapping purposes
@compile {:no_warn_undefined, Logger}
@doc """
Gets the Mix home.
@@ -638,17 +641,24 @@ defmodule Mix.Utils do
# If a proxy environment variable was supplied add a proxy to httpc.
http_options = [relaxed: true] ++ proxy_config(path)
case httpc_request(request, http_options) do
{:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}
when inet in [:inet, :inet6] and reason in [:ehostunreach, :enetunreach] ->
:httpc.set_options([ipfamily: fallback(inet)], :mix)
request |> httpc_request(http_options) |> httpc_response()
# Silence the warning from OTP as we verify the contents
level = Logger.level()
Logger.configure(level: :error)
response ->
httpc_response(response)
try do
case httpc_request(request, http_options) do
{:error, {:failed_connect, [{:to_address, _}, {inet, _, reason}]}}
when inet in [:inet, :inet6] and reason in [:ehostunreach, :enetunreach] ->
:httpc.set_options([ipfamily: fallback(inet)], :mix)
request |> httpc_request(http_options) |> httpc_response()
response ->
httpc_response(response)
end
after
Logger.configure(level: level)
:inets.stop(:httpc, :mix)
end
after
:inets.stop(:httpc, :mix)
end
defp fallback(:inet), do: :inet6
+2 -2
View File
@@ -20,7 +20,7 @@ defmodule Mix.CLITest do
end
@tag :unix
test "Mix.raise/2 can set exit code", %{tmp_dir: tmp_dir} do
test "Mix.raise/2 can set exit status", %{tmp_dir: tmp_dir} do
File.cd!(tmp_dir, fn ->
File.mkdir_p!("lib")
@@ -34,7 +34,7 @@ defmodule Mix.CLITest do
defp aliases do
[
custom: &error(&1, exit_code: 99),
custom: &error(&1, exit_status: 99),
]
end
+2
View File
@@ -112,6 +112,8 @@ defmodule Mix.RebarTest do
assert parse_dep({:git_rebar, '', {:git, @git_rebar_charlist, {:ref, '64691eb'}}}) ==
{:git_rebar, ~r"", override: true, git: @git_rebar_string, ref: "64691eb"}
assert parse_dep(:git_rebar) == {:git_rebar, override: true}
end
end
+1 -1
View File
@@ -584,7 +584,7 @@ defmodule Mix.ReleaseTest do
test "errors on bad config" do
assert {:error, "Could not read configuration file." <> _} =
make_sys_config(release([]), [foo: self()], "unused/runtime/path")
make_sys_config(release([]), [foo: [bar: self()]], "unused/runtime/path")
end
end
+2 -7
View File
@@ -214,13 +214,8 @@ defmodule Mix.Tasks.TestTest do
System.put_env("PASS_FAILING_TESTS", "true")
assert mix(["test", "--failed"]) =~ "2 tests, 0 failures"
# All tests should be run if we try it again with no failing tests.
# This prevents `mix test --failed` from passing in cases where
# `mix test` had a compilation error before having failing tests.
# It also provides a better workflow as you can always run with --failed.
output = mix(["test", "--failed"])
assert output =~ "No pending --failed tests, re-running all available tests..."
assert output =~ "4 tests, 0 failures"
# Nothing should get run if we try it again since everything is passing.
assert mix(["test", "--failed"]) =~ "There are no tests to run"
# `--failed` and `--stale` cannot be combined
output = mix(["test", "--failed", "--stale"])
+14
View File
@@ -85,6 +85,20 @@ defmodule MixTest do
end
end
test "install after errors", %{tmp_dir: tmp_dir} do
assert_raise Mix.Error, "Can't continue due to errors on dependencies", fn ->
Mix.install([
{:bad, path: Path.join(tmp_dir, "bad")}
])
end
Mix.install([
{:install_test, path: Path.join(tmp_dir, "install_test")}
])
assert apply(InstallTest, :hello, []) == :world
end
test "consolidate_protocols: false", %{tmp_dir: tmp_dir} do
Mix.install(
[