Compare commits

...
66 Commits
Author SHA1 Message Date
Wojtek Mach 7df646d960 Backport building docs_config.js (#12620)
Cherry-picked from https://github.com/elixir-lang/elixir/commit/6f2c669a98c33d3c98a50c023b0434bb79041c89
2023-05-30 10:21:42 +02:00
José ValimandEric Meadows-Jönsson 74fe558c17 Fix generated docs_config.js on CI (#12618) (#12619)
Co-authored-by: Eric Meadows-Jönsson <eric.meadows.jonsson@gmail.com>
2023-05-30 09:47:54 +02:00
José Valim 7918d64700 Add branch docs publishing 2023-05-30 08:20:24 +02:00
José Valim 81d6007410 Release v1.14.5 2023-05-22 16:39:51 +02:00
José Valim be9fd499e6 Update Ubuntu instance 2023-05-22 16:39:25 +02:00
José Valim a5958e31a1 Release v1.14.5 2023-05-22 16:23:17 +02:00
Thanabodee Charoenpiriyakij 29067a0a7d Bump ubuntu to 20.04 (#12062)
The ubuntu-18.04 nows deprecated today [1]. This changes start moving it
to version 20.04 instead.

[1] https://github.com/actions/virtual-environments/issues/6002
2023-05-22 16:22:39 +02:00
José Valim ce8f0ac7d0 Release v1.14.5 2023-05-22 15:04:16 +02:00
José Valim 2d71e7144b Ensure conversion of maps to erlang ast is deterministic 2023-05-19 23:32:31 +02:00
José Valim 0c1395c215 Do not set encoding on Erlang/OTP 26 2023-05-18 11:55:24 +02:00
José Valim 1b3f9f8d5c Use httpc:ssl_verify_host_options/1 2023-04-07 11:28:02 +02:00
markovicmarko dbfdedf9b3 Update CHANGELOG.md (#12509)
Fix typo
2023-04-03 10:59:05 +02:00
José Valim 6f96693b35 Release v1.14.4 2023-04-03 09:33:52 +02:00
José Valim 4e5a8d48ba Update CHANGELOG 2023-04-03 09:31:59 +02:00
sabiwara acb5832c69 Fix dialyzer tests in OTP26 (#12486) 2023-04-03 09:28:11 +02:00
Zach Allaun f026cb3756 Fix column count when tokenizing escaped interpolations (#12504) 2023-03-30 23:04:21 +02:00
joepstender 73d58ec01b Fix typo in documentation (#12496) 2023-03-25 14:18:54 +01:00
José Valim e9a6c9e3b3 No need to revert trap exits 2023-03-24 23:17:23 +01:00
Kevin 237f3bc566 Backport test fixes (#12493) 2023-03-24 10:51:12 +01:00
Kevin 1a7545655c Update tests to new OTP start behaviour (#12490) 2023-03-23 15:23:52 +01:00
sabiwara 22fa810200 Fix logger formatting in OTP26 (#12434) 2023-03-23 14:12:40 +01:00
José Valim c3e0908c52 Clarify error message on missing certificates 2023-03-23 11:50:53 +01:00
Erick Dennis 3d0273afb3 Don't call formatter on directories (#12488) 2023-03-23 11:50:53 +01:00
Kevin 2b9d8b720e Add explicit :verify_none for httpc (#12485) 2023-03-23 11:50:22 +01:00
Andrea Leopardi 4687556f8f Add missing entry to CHANGELOG for v1.14.3 (#12468) 2023-03-15 09:20:07 +01:00
Alexandre a8824bab09 Fix Stream.zip/1 hanging on empty list (#12461) 2023-03-10 21:49:30 +01:00
José Valim 476b4fdf6e Bump timeout to infinity 2023-03-02 19:19:16 +01:00
Kevin 2229ed7924 Sort metadata before printing (#12440) 2023-03-02 11:34:46 +01:00
sabiwara f9c053d60c Make mix test reports deterministic in OTP26 (#12438)
* Make mix test reports deterministic in OTP26

The reports for the following were both relying on map keys ordering:
- mix test
- mix test.cover

* Fix non-deterministic test
2023-03-02 07:59:53 +01:00
sabiwara 1108a6c3d2 Fix more map inspection tests (#12433) 2023-02-28 12:56:27 +01:00
Shilov Konstantin 32c929b339 fix(supervisor): update documentation by call instead of cast (#12426) 2023-02-26 20:11:21 +01:00
José Valim 00e7245715 Update CHANGELOG 2023-02-23 15:09:03 +01:00
José Valim f596cf1629 Use get_until in IEx.Server
This change is necessary for forward compatibility
with Erlang/OTP 26+.
2023-02-23 15:08:03 +01:00
José Valim 12927b8840 Do not use private APIs in StringIO 2023-02-23 15:08:03 +01:00
José Valim 130ed60f4d Use infinity timeouts in parallel checker, closes #12421 2023-02-21 13:47:40 +01:00
José Valim 3eb94a482e Make sure protocols are loaded before dispatching
Closes https://github.com/hexpm/hex/issues/983.
2023-02-20 11:19:04 +01:00
sabiwara 6eca976752 Deterministic struct expansion (#12418) 2023-02-19 11:36:34 +01:00
sabiwara e977f023a0 Remove badfunc test on OTP26 (#12415) 2023-02-18 09:34:26 +01:00
sabiwara dc471cd876 Add :sort_maps to Inspect.Opts.custom_options (#12404) 2023-02-16 08:47:21 +01:00
sabiwara 6c31a6c8bb Fix Enum tests for maps in OTP26 (#12405) 2023-02-16 08:37:26 +01:00
José Valim 7535c0c403 Store complete path on quote keep, closes #12393 2023-02-13 12:56:31 +01:00
Zach Allaun 9fd9bceeaa Code.Normalizer: Use correct meta fields in normalize_call/2 (#12389) 2023-02-10 16:14:07 +01:00
José Valim 2ca0f1a99c Remove misplaced temporary files 2023-02-06 20:16:18 +01:00
Bram Verburg 6a1c5a449f Fix elixir_erl:debug_info/4 returning core_v1 (#12376) 2023-02-02 22:01:11 +01:00
José Valim c31d1b3eb0 Optimize application tracer 2023-01-28 11:31:07 +01:00
José Valim 6730d669fb Release v1.14.3 2023-01-14 16:22:53 +01:00
José Valim fb4ccfa656 Properly handle Macro inputs and output types in expansion
Closes #12299
Closes #12324
2023-01-14 16:00:45 +01:00
sabiwara ff2ea69710 Fix Macro.to_string/1 for large negative integers (#12326) 2023-01-10 10:50:51 +01:00
José Valim 2146bb449f Remove unecessary :beam_lib.info/1 call, closes #12317 2023-01-06 12:12:36 +01:00
José Valim 5a583c753b Consistently merge precisions in Calendar subsecond operations, closes #12303 2022-12-18 12:18:23 +01:00
José Valim ec31cc2331 Escape maps when expanding __ENV__ 2022-12-12 19:31:13 +01:00
Alex NaserandJosé Valim 6549d209d9 Precompile runtime module paths (#12294)
Co-authored-by: José Valim <jose.valim@dashbit.co>
2022-12-08 19:37:01 +01:00
José Valim 71d27919ad Revert "Ensure all runtime_modules loaded before mapping their paths (#12287)"
We don't want to load all modules because some may be
specified as `@compile {:autoload, false}`.

This reverts commit a8aea1c763.
2022-12-08 06:38:31 +01:00
Alex NaserandAlex Naser a8aea1c763 Ensure all runtime_modules loaded before mapping their paths (#12287)
Co-authored-by: Alex Naser <alex.naser@remote.com>
2022-12-05 20:46:27 +01:00
José Valim a72f18b38b Fix Range.disjoint?/2 2022-12-05 16:20:44 +01:00
José Valim a1a37c67b6 Optimize range membership implementation, closes #12282 2022-12-04 13:02:56 +01:00
José Valim ff73830103 Return values from running doctests and order tests 2022-12-01 13:22:03 +01:00
Thanabodee Charoenpiriyakij 950bcc0704 Fix Calendar.strftime/2 format negative year incorrectly (#12268)
Closes #12266
2022-11-24 09:37:01 +01:00
José Valim 80564fc954 Do not quote doctest excerpt 2022-11-23 18:01:31 +01:00
José Valim 94e0e386ce Do not crash for missing line info on type warnings
Closes #12263.
2022-11-23 05:35:09 +01:00
José Valim bfa799b31f Do not warn on captured underscored vars from defmodule
Closes #12262
2022-11-23 05:19:58 +01:00
Marcus Kruse abe2af477b Fix wildcard edge case (#12255) 2022-11-20 11:06:15 +01:00
sabiwara 2a82c35e39 Fix unused for optimization (#12252) 2022-11-19 10:27:40 +01:00
José Valim 1f14211a5e Handle false positive on List.to_charlist/1 formatting, closes #12248 2022-11-17 20:12:59 +01:00
José Valim 1ae36fa079 Keep expression when normalizing is_struct in blame
Closes #12243.
2022-11-15 23:03:53 +01:00
José Valim e3fc947b92 Pass the relevant module when generating unique vars for guards 2022-11-11 12:59:36 +01:00
79 changed files with 964 additions and 395 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: 25
otp_version: '25.3'
build_docs: build_docs
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 50
- name: Get tags
run: git fetch --tags origin
- uses: ./.github/workflows/release_pre_built
with:
otp_version: ${{ matrix.otp_version }}
otp: ${{ matrix.otp }}
build_docs: ${{ matrix.build_docs }}
- name: Utils.sh
run: |
cat << 'EOF' > utils.sh
function purge_key() {
curl \
-X POST \
-H "Fastly-Key: ${FASTLY_KEY}" \
-H "Accept: application/json" \
-H "Content-Length: 0" \
"https://api.fastly.com/service/$1/purge/$2"
}
function purge() {
purge_key ${FASTLY_REPO_SERVICE_ID} $1
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
sleep 2
purge_key ${FASTLY_REPO_SERVICE_ID} $1
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
sleep 2
purge_key ${FASTLY_REPO_SERVICE_ID} $1
purge_key ${FASTLY_BUILDS_SERVICE_ID} $1
}
EOF
chmod +x utils.sh
- name: Upload Docs to S3
if: ${{ matrix.build_docs }}
env:
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
run: |
source utils.sh
version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
for f in doc/*; do
if [ -d "$f" ]; then
app=`echo $f | sed s/"doc\/"//`
tarball="${app}-${version}.tar.gz"
surrogate_key="docs/${app}-${version}"
tar -czf "${tarball}" -C "doc/${app}" .
aws s3 cp "${tarball}" "s3://${{ env.AWS_S3_BUCKET }}/docs/${tarball}" \
--cache-control "public,max-age=3600" \
--metadata "{\"surrogate-key\":\"${surrogate_key}\",\"surrogate-control\":\"public,max-age=604800\"}"
purge "${surrogate_key}"
fi
done
+4 -4
View File
@@ -13,7 +13,7 @@ permissions:
jobs:
test_linux:
name: Linux, ${{ matrix.otp_release }}, Ubuntu 18.04
name: Linux, ${{ matrix.otp_release }}, Ubuntu 20.04
strategy:
fail-fast: false
matrix:
@@ -28,7 +28,7 @@ jobs:
development: true
- otp_release: maint
development: true
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
@@ -36,7 +36,7 @@ jobs:
- name: Install Erlang/OTP
run: |
cd $RUNNER_TEMP
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-18.04/${{ matrix.otp_release }}.tar.gz
wget -O otp.tar.gz https://repo.hex.pm/builds/otp/ubuntu-20.04/${{ matrix.otp_release }}.tar.gz
mkdir -p otp
tar zxf otp.tar.gz -C otp --strip-components=1
otp/Install -minimal $(pwd)/otp
@@ -113,7 +113,7 @@ jobs:
check_posix_compliant:
name: Check POSIX-compliant
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
+3 -3
View File
@@ -10,7 +10,7 @@ permissions:
jobs:
notify:
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
name: Notify
steps:
- uses: actions/checkout@v3
@@ -18,8 +18,8 @@ jobs:
fetch-depth: 50
- uses: erlef/setup-beam@v1
with:
otp-version: 24.3
elixir-version: 1.13.4
otp-version: '25.0'
elixir-version: '1.14.0'
- name: Run Elixir script
env:
ELIXIR_FORUM_TOKEN: ${{ secrets.ELIXIR_FORUM_TOKEN }}
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
create_draft_release:
permissions:
contents: write
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
@@ -39,7 +39,7 @@ jobs:
- otp: 25
otp_version: 25.0
build_docs: build_docs
runs-on: ubuntu-18.04
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
with:
@@ -0,0 +1,51 @@
name: "Release pre built"
description: "Builds elixir release, ExDoc and generates docs"
inputs:
otp:
description: "The major OTP version"
otp_version:
description: "The exact OTP version (major.minor[.patch])"
build_docs:
description: "If docs have to be built or not"
runs:
using: "composite"
steps:
- uses: erlef/setup-beam@v1
with:
otp-version: ${{ inputs.otp_version }}
version-type: strict
- name: Build Elixir Release
shell: bash
run: |
make Precompiled.zip
mv Precompiled.zip elixir-otp-${{ inputs.otp }}.zip
shasum -a 1 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha1sum
shasum -a 256 elixir-otp-${{ inputs.otp }}.zip > elixir-otp-${{ inputs.otp }}.zip.sha256sum
echo "$PWD/bin" >> $GITHUB_PATH
- name: Get latest stable ExDoc version
if: ${{ inputs.build_docs }}
shell: bash
run: |
EX_DOC_LATEST_STABLE_VERSION=$(curl -s https://hex.pm/api/packages/ex_doc | jq --raw-output '.latest_stable_version')
echo "EX_DOC_LATEST_STABLE_VERSION=${EX_DOC_LATEST_STABLE_VERSION}" >> $GITHUB_ENV
- uses: actions/checkout@v3
if: ${{ inputs.build_docs }}
with:
repository: elixir-lang/ex_doc
ref: v${{ env.EX_DOC_LATEST_STABLE_VERSION }}
path: ex_doc
- name: Build ex_doc
if: ${{ inputs.build_docs }}
shell: bash
run: |
mv ex_doc ../ex_doc
cd ../ex_doc
../elixir/bin/mix do local.rebar --force + local.hex --force + deps.get + compile
cd ../elixir
- name: Build Docs
if: ${{ inputs.build_docs }}
shell: bash
run: |
make Docs.zip
shasum -a 1 Docs.zip > Docs.zip.sha1sum
shasum -a 256 Docs.zip > Docs.zip.sha256sum
+88 -1
View File
@@ -241,6 +241,93 @@ protocol, giving developers more control over the struct representation.
See the updated documentation for `Inspect` for a general rundown on
the approaches and options available.
## v1.14.5 (2023-05-22)
This release contains fixes for Erlang/OTP 26.
### Bug fixes
#### Elixir
* [CLI] Fix a bug where stdout would block when there was no attached terminal on Windows when running on Erlang/OTP 26
#### Mix
* [Mix] Properly set SSL configuration for Mix downloads when running on Erlang/OTP 26
## v1.14.4 (2023-04-03)
This release adds basic support for Erlang/OTP 26. When migrating
to Erlang/OTP 26, keep it mind it changes how maps are stored
internally and they will be printed and traversed in a different
order (note maps never provided a guarantee of their order).
To aid migration, this release adds `:sort_maps` to `inspect`
custom options, in case you want to sort them before inspection:
inspect(map, custom_options: [sort_maps: true])
### Enhancements
#### Elixir
* [Inspect] Add `:sort_maps` to `Inspect.Opts.custom_options`
#### IEx
* [IEx] Support shell history in Erlang/OTP 26+
#### Mix
* [mix compile.elixir] Optimize application tracer
### Bug fixes
#### Elixir
* [Code] Properly handle blocks with comments in all cases in `Code.quoted_to_string_with_comments/2`
* [Kernel] Fix `debug_info/4` when returning core_v1
* [Kernel] Store complete path on `quote keep: true` to avoid invalid stacktraces
* [Kernel] Fix column count when tokenizing escaped interpolations
* [Stream] Fix `Stream.zip/1` hanging on empty list
#### Mix
* [mix format] Don't call formatter on directories
## v1.14.3 (2023-01-14)
### 1. Enhancements
#### Elixir
* [Kernel] Speed up loading of runtime modules in the parallel compiler
* [Range] Optimize range membership implementation
#### ExUnit
* [ExUnit] Return values from running doctests and make their order consistent
### 2. Bug fixes
#### Elixir
* [Calendar] Fix handling of negative years in `Calendar.strftime/2`
* [DateTime] Consistently merge precision of subsecond operations (the bug was that subsecond precision was lost in functions like `DateTime.add/3`)
* [Exception] Improve blaming of FunctionClauseError with `is_struct/2` guards
* [Kernel] Fix invalid variable scoping in `defguard` expansion
* [Kernel] Do not warn on captured underscored vars from `defmodule`
* [Kernel] Do not crash for missing line info on type warnings
* [NaiveDateTime] Consistently merge precision of subsecond operations (the bug was that subsecond precision was lost in functions like `NaiveDateTime.add/3`)
* [Macro] Fix `Macro.to_string/1` for large negative integers
* [Macro] Properly type and escape expansion of `__ENV__` in macros
* [Path] Make sure `Path.wildcard/2` expands `..` symlinks accordingly
* [Range] Address corner cases in `Range.disjoint?/2` implementation
* [Time] Consistently merge precision of subsecond operations (the bug was that subsecond precision was lost in functions like `Time.add/3`)
#### ExUnit
* [ExUnit.DocTest] Remove unnecessary literal quotes from error message on reports
## v1.14.2 (2022-11-11)
### 1. Enhancements
@@ -435,7 +522,7 @@ the approaches and options available.
* [Module] Fix loop while unifying type variables
* [Protocol] Warn if a protocol has no definitions
* [Regex] Show list options when inspecting a Regex manually defined with `Regex.compile/2`
* [String] Allow slices to overflow on both starting and ending positions
* [String] Allow slices to overflow on both starting and ending positions
* [System] Raise non-generic exception on missing env in `System.fetch_env!/1` to mirror map operations
#### ExUnit
+15 -8
View File
@@ -181,39 +181,46 @@ clean_residual_files:
LOGO_PATH = $(shell test -f ../docs/logo.png && echo "--logo ../docs/logo.png")
SOURCE_REF = $(shell tag="$(call GIT_TAG)" revision="$(call GIT_REVISION)"; echo "$${tag:-$$revision}")
COMPILE_DOCS = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
DOCS_COMPILE = CANONICAL=$(CANONICAL) bin/elixir ../ex_doc/bin/ex_doc "$(1)" "$(VERSION)" "lib/$(2)/ebin" --main "$(3)" --source-url "https://github.com/elixir-lang/elixir" --source-ref "$(call SOURCE_REF)" $(call LOGO_PATH) --output doc/$(2) --canonical "https://hexdocs.pm/$(2)/$(CANONICAL)" --homepage-url "https://elixir-lang.org/docs.html" --formatter "$(DOCS_FORMAT)" $(4)
DOCS_CONFIG = bin/elixir lib/elixir/scripts/docs_config.exs "$(1)"
docs: compile ../ex_doc/bin/ex_doc docs_elixir docs_eex docs_mix docs_iex docs_ex_unit docs_logger
docs_elixir: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (elixir)"
$(Q) rm -rf doc/elixir
$(call COMPILE_DOCS,Elixir,elixir,Kernel,--config "lib/elixir/docs.exs")
$(call DOCS_COMPILE,Elixir,elixir,Kernel,--config "lib/elixir/scripts/elixir_docs.exs")
$(call DOCS_CONFIG,elixir)
docs_eex: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (eex)"
$(Q) rm -rf doc/eex
$(call COMPILE_DOCS,EEx,eex,EEx,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,EEx,eex,EEx,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,eex)
docs_mix: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (mix)"
$(Q) rm -rf doc/mix
$(call COMPILE_DOCS,Mix,mix,Mix,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,Mix,mix,Mix,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,mix)
docs_iex: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (iex)"
$(Q) rm -rf doc/iex
$(call COMPILE_DOCS,IEx,iex,IEx,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,IEx,iex,IEx,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,iex)
docs_ex_unit: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (ex_unit)"
$(Q) rm -rf doc/ex_unit
$(call COMPILE_DOCS,ExUnit,ex_unit,ExUnit,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,ExUnit,ex_unit,ExUnit,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,ex_unit)
docs_logger: compile ../ex_doc/bin/ex_doc
@ echo "==> ex_doc (logger)"
$(Q) rm -rf doc/logger
$(call COMPILE_DOCS,Logger,logger,Logger,--config "lib/mix/docs.exs")
$(call DOCS_COMPILE,Logger,logger,Logger,--config "lib/elixir/scripts/mix_docs.exs")
$(call DOCS_CONFIG,logger)
../ex_doc/bin/ex_doc:
@ echo "ex_doc is not found in ../ex_doc as expected. See README for more information."
@@ -228,7 +235,7 @@ Docs.zip: docs
Precompiled.zip: build_man compile
rm -f Precompiled.zip
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE man NOTICE README.md VERSION
zip -9 -r Precompiled.zip bin CHANGELOG.md lib/*/ebin lib/*/lib LICENSE Makefile man NOTICE README.md VERSION
@ echo "Precompiled file created $(CURDIR)/Precompiled.zip"
#==> Test tasks
+1 -1
View File
@@ -1 +1 @@
1.14.2
1.14.5
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.14.2
ELIXIR_VERSION=1.14.5
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
+1 -1
View File
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set ELIXIR_VERSION=1.14.2
set ELIXIR_VERSION=1.14.5
setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation
+8 -1
View File
@@ -791,7 +791,14 @@ defmodule Calendar do
# Year
defp format_modifiers("Y" <> rest, width, pad, datetime, format_options, acc) do
result = datetime.year |> Integer.to_string() |> pad_leading(width, pad)
{sign, year} =
if datetime.year < 0 do
{?-, -datetime.year}
else
{[], datetime.year}
end
result = [sign | year |> Integer.to_string() |> pad_leading(width, pad)]
parse(rest, datetime, format_options, [result | acc])
end
+8
View File
@@ -1493,6 +1493,13 @@ defmodule DateTime do
iex> dt |> DateTime.add(1, :day, FakeTimeZoneDatabase)
#DateTime<2019-04-01 02:00:00+02:00 CEST Europe/Copenhagen>
This operation merges the precision of the naive date time with the given unit:
iex> result = DateTime.add(~U[2014-10-02 00:29:10Z], 21, :millisecond)
~U[2014-10-02 00:29:10.021Z]
iex> result.microsecond
{21000, 3}
"""
@doc since: "1.8.0"
@spec add(
@@ -1537,6 +1544,7 @@ defmodule DateTime do
ppd = System.convert_time_unit(86400, :second, unit)
total_offset = System.convert_time_unit(utc_offset + std_offset, :second, unit)
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
result =
datetime
+11
View File
@@ -247,6 +247,17 @@ defmodule Calendar.ISO do
defguardp is_utc_offset(offset) when is_integer(offset)
defguardp is_std_offset(offset) when is_integer(offset)
@doc """
Converts a `t:System.time_unit/0` to precision.
Integer-based time units always get maximum precision.
"""
def time_unit_to_precision(:nanosecond), do: 6
def time_unit_to_precision(:microsecond), do: 6
def time_unit_to_precision(:millisecond), do: 3
def time_unit_to_precision(:second), do: 0
def time_unit_to_precision(_), do: 6
@doc """
Parses a time `string` in the `:extended` format.
+8 -12
View File
@@ -361,8 +361,7 @@ defmodule NaiveDateTime do
`t:System.time_unit/0`. It defaults to `:second`. Negative values
will move backwards in time.
This function always consider the unit to be computed according
to the `Calendar.ISO`.
This function always considers the unit to be computed according to the `Calendar.ISO`.
## Examples
@@ -379,7 +378,7 @@ defmodule NaiveDateTime do
It can also work with subsecond precisions:
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10], 2_000, :millisecond)
~N[2014-10-02 00:29:12]
~N[2014-10-02 00:29:12.000]
As well as days/hours/minutes:
@@ -390,16 +389,12 @@ defmodule NaiveDateTime do
iex> NaiveDateTime.add(~N[2015-02-28 00:29:10], 60, :minute)
~N[2015-02-28 01:29:10]
This operation keeps the precision of the naive date time:
This operation merges the precision of the naive date time with the given unit:
iex> NaiveDateTime.add(~N[2014-10-02 00:29:10.021], 21, :second)
~N[2014-10-02 00:29:31.021]
And ignores any changes below the precision:
iex> hidden = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
iex> hidden.microsecond # ~N[2014-10-02 00:29:10]
{21000, 0}
iex> result = NaiveDateTime.add(~N[2014-10-02 00:29:10], 21, :millisecond)
~N[2014-10-02 00:29:10.021]
iex> result.microsecond
{21000, 3}
Operations on top of gregorian seconds or the Unix epoch are optimized:
@@ -440,6 +435,7 @@ defmodule NaiveDateTime do
)
when is_integer(amount_to_add) do
ppd = System.convert_time_unit(86400, :second, unit)
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
naive_datetime
|> to_iso_days()
+10 -2
View File
@@ -493,6 +493,13 @@ defmodule Time do
iex> Time.add(~T[17:10:05], 30, :minute)
~T[17:40:05]
This operation merges the precision of the time with the given unit:
iex> result = Time.add(~T[00:29:10], 21, :millisecond)
~T[00:29:10.021]
iex> result.microsecond
{21000, 3}
"""
@doc since: "1.6.0"
@spec add(Calendar.time(), integer, :hour | :minute | System.time_unit()) :: t
@@ -511,6 +518,7 @@ defmodule Time do
amount_to_add = System.convert_time_unit(amount_to_add, unit, :microsecond)
total = time_to_microseconds(time) + amount_to_add
parts = Integer.mod(total, @parts_per_day)
precision = max(Calendar.ISO.time_unit_to_precision(unit), precision)
{hour, minute, second, {microsecond, _}} =
calendar.time_from_day_fraction({parts, @parts_per_day})
@@ -736,14 +744,14 @@ defmodule Time do
hour: hour1,
minute: minute1,
second: second1,
microsecond: {microsecond1, @parts_per_day}
microsecond: {microsecond1, _}
},
%{
calendar: Calendar.ISO,
hour: hour2,
minute: minute2,
second: second2,
microsecond: {microsecond2, @parts_per_day}
microsecond: {microsecond2, _}
},
unit
) do
+4
View File
@@ -1583,6 +1583,10 @@ defmodule Code.Formatter do
insert_underscores(int_part) <> "." <> decimal_part
end
defp insert_underscores("-" <> digits) do
"-" <> insert_underscores(digits)
end
defp insert_underscores(digits) do
cond do
digits =~ "_" ->
+32 -25
View File
@@ -70,15 +70,15 @@ defmodule Code.Normalizer do
# Atoms with interpolations
defp do_normalize(
{{:., dot_meta, [:erlang, :binary_to_atom]}, call_meta,
[{:<<>>, _, args} = string, :utf8]},
[{:<<>>, _, parts} = string, :utf8]},
state
)
when is_list(args) do
when is_list(parts) do
dot_meta = patch_meta_line(dot_meta, state.parent_meta)
call_meta = patch_meta_line(call_meta, dot_meta)
utf8 =
if args == [] or interpolated?(string) do
if parts == [] or binary_interpolated?(parts) do
# a non-normalized :utf8 atom signals an atom interpolation
:utf8
else
@@ -96,23 +96,27 @@ defmodule Code.Normalizer do
end
# Charlists with interpolations
defp do_normalize({{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}, state) do
parts =
Enum.map(parts, fn
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args} ->
args = normalize_args(args, state)
defp do_normalize({{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]} = quoted, state) do
if list_interpolated?(parts) do
parts =
Enum.map(parts, fn
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args} ->
args = normalize_args(args, state)
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args}
{{:., part_dot_meta, [Kernel, :to_string]}, part_call_meta, args}
part ->
if state.escape do
maybe_escape_literal(part, state)
else
part
end
end)
part when is_binary(part) ->
if state.escape do
maybe_escape_literal(part, state)
else
part
end
end)
{{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}
{{:., dot_meta, [List, :to_charlist]}, call_meta, [parts]}
else
normalize_call(quoted, state)
end
end
# Don't normalize the `Access` atom in access syntax
@@ -358,7 +362,7 @@ defmodule Code.Normalizer do
normalize_kw_blocks(form, meta, args, state)
allow_keyword?(form, arity) ->
args = normalize_args(args, %{state | parent_meta: state.parent_meta})
args = normalize_args(args, %{state | parent_meta: meta})
{last_arg, leading_args} = List.pop_at(args, -1, [])
last_args =
@@ -380,7 +384,7 @@ defmodule Code.Normalizer do
{form, meta, leading_args ++ last_args}
true ->
args = normalize_args(args, %{state | parent_meta: state.parent_meta})
args = normalize_args(args, %{state | parent_meta: meta})
{form, meta, args}
end
end
@@ -389,11 +393,11 @@ defmodule Code.Normalizer do
defp allow_keyword?(:{}, _), do: false
defp allow_keyword?(op, arity), do: not is_atom(op) or not Macro.operator?(op, arity)
defp normalize_bitstring({:<<>>, meta, parts} = quoted, state, escape_interpolation \\ false) do
defp normalize_bitstring({:<<>>, meta, parts}, state, escape_interpolation \\ false) do
meta = patch_meta_line(meta, state.parent_meta)
parts =
if interpolated?(quoted) do
if binary_interpolated?(parts) do
normalize_interpolation_parts(parts, %{state | parent_meta: meta}, escape_interpolation)
else
state = %{state | parent_meta: meta}
@@ -543,8 +547,7 @@ defmodule Code.Normalizer do
term
end
# Check if we have an interpolated string.
defp interpolated?({:<<>>, _, [_ | _] = parts}) do
defp binary_interpolated?(parts) do
Enum.all?(parts, fn
{:"::", _, [{{:., _, [Kernel, :to_string]}, _, [_]}, {:binary, _, _}]} -> true
binary when is_binary(binary) -> true
@@ -552,8 +555,12 @@ defmodule Code.Normalizer do
end)
end
defp interpolated?(_) do
false
defp list_interpolated?(parts) do
Enum.all?(parts, fn
{{:., _, [Kernel, :to_string]}, _, [_]} -> true
binary when is_binary(binary) -> true
_ -> false
end)
end
defp patch_meta_line(meta, parent_meta) do
+3 -3
View File
@@ -2276,13 +2276,13 @@ defmodule Enum do
iex> Enum.split_with([5, 4, 3, 2, 1, 0], fn x -> rem(x, 2) == 0 end)
{[4, 2, 0], [5, 3, 1]}
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v < 0 end)
iex> Enum.split_with([a: 1, b: -2, c: 1, d: -3], fn {_k, v} -> v < 0 end)
{[b: -2, d: -3], [a: 1, c: 1]}
iex> Enum.split_with(%{a: 1, b: -2, c: 1, d: -3}, fn {_k, v} -> v > 50 end)
iex> Enum.split_with([a: 1, b: -2, c: 1, d: -3], fn {_k, v} -> v > 50 end)
{[], [a: 1, b: -2, c: 1, d: -3]}
iex> Enum.split_with(%{}, fn {_k, v} -> v > 50 end)
iex> Enum.split_with([], fn {_k, v} -> v > 50 end)
{[], []}
"""
+3 -5
View File
@@ -294,13 +294,11 @@ defmodule Exception do
end
end
defp undo_is_struct_guard(
{:and, meta, [_, %{node: {_, _, [{_, _, [_, {struct, _, _}]} | optional]}}]}
) do
defp undo_is_struct_guard({:and, meta, [_, %{node: {_, _, [{_, _, [_, arg]} | optional]}}]}) do
args =
case optional do
[] -> [{struct, meta, nil}]
[module] -> [{struct, meta, nil}, module]
[] -> [arg]
[module] -> [arg, module]
end
%{match?: meta[:value], node: {:is_struct, meta, args}}
+6 -1
View File
@@ -302,7 +302,12 @@ end
defimpl Inspect, for: Map do
def inspect(map, opts) do
list = Map.to_list(map)
list =
if Keyword.get(opts.custom_options, :sort_maps) do
map |> Map.to_list() |> :lists.sort()
else
Map.to_list(map)
end
fun =
if Inspect.List.keyword?(list) do
+5
View File
@@ -31,6 +31,11 @@ defmodule Inspect.Opts do
options. Useful when implementing the `Inspect` protocol for nested structs
to pass the custom options through.
It supports some pre-defined keys:
- `:sort_maps` (since v1.15.0) - if set to `true`, sorts key-value pairs in maps.
This can be helpful to make map inspection deterministic for testing,
especially since key order is random since OTP 26.
* `:inspect_fun` (since v1.9.0) - a function to build algebra documents.
Defaults to `Inspect.Opts.default_inspect_fun/0`.
+3 -1
View File
@@ -78,7 +78,9 @@ defmodule IO.ANSI.Docs do
@metadata_filter [:deprecated, :guard, :since]
defp print_each_metadata(metadata, options) do
Enum.reduce(metadata, false, fn
metadata
|> Enum.sort()
|> Enum.reduce(false, fn
{key, value}, _printed when is_binary(value) and key in @metadata_filter ->
label = metadata_label(key, options)
indent = String.duplicate(" ", length_without_escape(label, 0) + 1)
+1 -1
View File
@@ -4806,7 +4806,7 @@ defmodule Kernel do
var_meta =
case prune do
true -> [generated: true, keep_unused: true]
false -> [generated: false]
false -> [generated: true]
end
module_vars = :lists.map(&module_var(&1, var_meta), :maps.keys(versioned_vars))
@@ -271,12 +271,6 @@ defmodule Kernel.ParallelCompiler do
for {{:module, module}, _} <- result,
do: module
runtime_modules =
for module <- runtime_modules,
path = :code.which(module),
is_list(path) and path != [],
do: {module, path}
profile_checker(profile, compiled_modules, runtime_modules, fn ->
Module.ParallelChecker.verify(checker, runtime_modules)
end)
+3 -3
View File
@@ -331,7 +331,7 @@ defmodule Kernel.Utils do
quote do
case Macro.Env.in_guard?(__CALLER__) do
true -> unquote(literal_quote(unquote_every_ref(expr, vars)))
false -> unquote(literal_quote(unquote_refs_once(expr, vars)))
false -> unquote(literal_quote(unquote_refs_once(expr, vars, env.module)))
end
end
end
@@ -361,7 +361,7 @@ defmodule Kernel.Utils do
end
# Prefaces `guard` with unquoted versions of `refs`.
defp unquote_refs_once(guard, refs) do
defp unquote_refs_once(guard, refs, module) do
{guard, used_refs} =
Macro.postwalk(guard, %{}, fn
{ref, meta, context} = var, acc when is_atom(ref) and is_atom(context) ->
@@ -375,7 +375,7 @@ defmodule Kernel.Utils do
%{} ->
generated = String.to_atom("arg" <> Integer.to_string(map_size(acc) + 1))
new_var = Macro.unique_var(generated, Elixir)
new_var = Macro.unique_var(generated, module)
{new_var, Map.put(acc, pair, {new_var, var})}
end
+11 -6
View File
@@ -1763,7 +1763,7 @@ defmodule Macro do
end
"""
@spec expand_once(t(), Macro.Env.t()) :: t()
@spec expand_once(input(), Macro.Env.t()) :: output()
def expand_once(ast, env) do
elem(do_expand_once(ast, env), 0)
end
@@ -1795,13 +1795,15 @@ defmodule Macro do
defp do_expand_once({:__DIR__, _, atom}, env) when is_atom(atom),
do: {:filename.dirname(env.file), true}
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom),
do: {{:%{}, [], Map.to_list(env)}, true}
defp do_expand_once({:__ENV__, _, atom}, env) when is_atom(atom) do
env = update_in(env.versioned_vars, &maybe_escape_map/1)
{maybe_escape_map(env), true}
end
defp do_expand_once({{:., _, [{:__ENV__, _, atom}, field]}, _, []} = original, env)
when is_atom(atom) and is_atom(field) do
if Map.has_key?(env, field) do
{Map.get(env, field), true}
{maybe_escape_map(Map.get(env, field)), true}
else
{original, false}
end
@@ -1884,6 +1886,9 @@ defmodule Macro do
# Anything else is just returned
defp do_expand_once(other, _env), do: {other, false}
defp maybe_escape_map(map) when is_map(map), do: {:%{}, [], Map.to_list(map)}
defp maybe_escape_map(other), do: other
@doc """
Returns `true` if the given name and arity is a special form.
"""
@@ -1995,7 +2000,7 @@ defmodule Macro do
when they change.
"""
@doc since: "1.14.1"
@spec expand_literals(t(), Macro.Env.t()) :: t()
@spec expand_literals(input(), Macro.Env.t()) :: output()
def expand_literals(ast, env) do
{ast, :ok} = expand_literals(ast, :ok, fn node, :ok -> {expand(node, env), :ok} end)
ast
@@ -2075,7 +2080,7 @@ defmodule Macro do
This function uses `expand_once/2` under the hood. Check
it out for more information and examples.
"""
@spec expand(t(), Macro.Env.t()) :: t()
@spec expand(input(), Macro.Env.t()) :: output()
def expand(ast, env) do
expand_until({ast, true}, env)
end
+8 -6
View File
@@ -62,7 +62,10 @@ defmodule Module.ParallelChecker do
if is_map(info) do
info
else
info |> File.read!() |> maybe_module_map(module)
case File.read(info) do
{:ok, binary} -> maybe_module_map(binary, module)
{:error, _} -> nil
end
end
module_map && cache_from_module_map(ets, module_map)
@@ -136,13 +139,13 @@ defmodule Module.ParallelChecker do
the modules and adds the ExCk chunk to the binaries. Returns the updated
list of warnings from the verification.
"""
@spec verify(pid(), [{module(), binary()}]) :: [warning()]
@spec verify(pid(), [{module(), Path.t()}]) :: [warning()]
def verify(checker, runtime_files) do
for {module, file} <- runtime_files do
spawn({self(), checker}, module, file)
end
modules = :gen_server.call(checker, :start)
modules = :gen_server.call(checker, :start, :infinity)
collect_results(modules, [])
end
@@ -168,7 +171,7 @@ defmodule Module.ParallelChecker do
"""
def test_cache do
{:ok, checker} = start_link()
{checker, :gen_server.call(checker, :ets)}
{checker, :gen_server.call(checker, :ets, :infinity)}
end
@doc """
@@ -324,8 +327,7 @@ defmodule Module.ParallelChecker do
defp cache_from_chunk(ets, module) do
with {^module, binary, _filename} <- :code.get_object_code(module),
{:ok, ^module} <- binary |> :beam_lib.info() |> Keyword.fetch(:module),
{:ok, {_, [{'ExCk', chunk}]}} <- :beam_lib.chunks(binary, ['ExCk']),
{:ok, {^module, [{~c"ExCk", chunk}]}} <- :beam_lib.chunks(binary, [~c"ExCk"]),
{:elixir_checker_v1, contents} <- :erlang.binary_to_term(chunk) do
cache_chunk(ets, module, contents.exports)
true
+5 -5
View File
@@ -135,8 +135,7 @@ defmodule Module.Types do
# Collect relevant information from context and traces to report error
def error_to_warning(:unable_apply, {mfa, args, expected, signature, stack}, context) do
{fun, arity} = context.function
line = get_meta(stack.last_expr)[:line]
location = {context.file, line, {context.module, fun, arity}}
location = {context.file, get_line(stack), {context.module, fun, arity}}
traces = type_traces(stack, context)
{[signature | args], traces} = lift_all_types([signature | args], traces, context)
@@ -146,8 +145,7 @@ defmodule Module.Types do
def error_to_warning(:unable_unify, {left, right, stack}, context) do
{fun, arity} = context.function
line = get_meta(stack.last_expr)[:line]
location = {context.file, line, {context.module, fun, arity}}
location = {context.file, get_line(stack), {context.module, fun, arity}}
traces = type_traces(stack, context)
{[left, right], traces} = lift_all_types([left, right], traces, context)
@@ -155,6 +153,8 @@ defmodule Module.Types do
{Module.Types, error, location}
end
defp get_line(stack), do: stack.last_expr |> get_meta() |> Keyword.get(:line, 0)
# Collect relevant traces from context.traces using stack.unify_stack
defp type_traces(stack, context) do
# TODO: Do we need the unify_stack or is enough to only get the last variable
@@ -252,7 +252,7 @@ defmodule Module.Types do
"undefined field \"#{atom}\" ",
format_expr(expr, location),
"expected one of the following fields: ",
Enum.map_join(Enum.sort(known_atoms), ", ", & &1),
Enum.join(Enum.sort(known_atoms), ", "),
"\n\n",
traces,
format_message_hints(hints),
+4
View File
@@ -603,6 +603,10 @@ defmodule Path do
:file.read_link_info(file)
end
def read_file_info(file) do
:file.read_file_info(file)
end
def list_dir(dir) do
case :file.list_dir(dir) do
{:ok, files} -> {:ok, for(file <- files, hd(file) != ?., do: file)}
+31 -27
View File
@@ -294,22 +294,24 @@ defmodule Range do
iex> Range.disjoint?(1..10//2, 2..10//2)
true
# First element in common in all below is 29
iex> Range.disjoint?(2..100//3, 9..100//5)
false
iex> Range.disjoint?(101..2//-3, 99..9//-5)
false
# First element in common is 29
iex> Range.disjoint?(1..100//14, 8..100//21)
false
iex> Range.disjoint?(57..-1//-14, 8..100//21)
false
iex> Range.disjoint?(1..100//14, 51..8//-21)
iex> Range.disjoint?(1..100//14, 50..8//-21)
false
# If 29 is out of range
iex> Range.disjoint?(1..28//14, 8..28//21)
true
# First element in common is 14
iex> Range.disjoint?(2..28//3, 9..28//5)
false
iex> Range.disjoint?(26..2//-3, 29..9//-5)
false
# Starting from the back without alignment
iex> Range.disjoint?(27..11//-3, 30..0//-7)
true
"""
@@ -334,21 +336,28 @@ defmodule Range do
# progressions and see if they belong within the ranges
# https://math.stackexchange.com/questions/1656120/formula-to-find-the-first-intersection-of-two-arithmetic-progressions
{gcd, u, v} = Integer.extended_gcd(-step1, step2)
c = first1 - first2 + step2 - step1
t1 = -c / step1 * u
t2 = -c / step2 * v
t = max(floor(t1) + 1, floor(t2) + 1)
x = div(c * u + t * step2, gcd) - 1
y = div(c * v + t * step1, gcd) - 1
x < 0 or first1 + x * step1 > last1 or
y < 0 or first2 + y * step2 > last2
if rem(first2 - first1, gcd) == 0 do
c = first1 - first2 + step2 - step1
t1 = -c / step2 * u
t2 = -c / step1 * v
t = max(floor(t1) + 1, floor(t2) + 1)
x = div(c * u + t * step2, gcd) - 1
y = div(c * v + t * step1, gcd) - 1
x < 0 or first1 + x * step1 > last1 or
y < 0 or first2 + y * step2 > last2
else
true
end
end
end
end
@compile inline: [normalize: 3]
defp normalize(first, last, step) when first > last, do: {last, first, -step}
defp normalize(first, last, step) when first > last,
do: {first - abs(div(first - last, step) * step), first, -step}
defp normalize(first, last, step), do: {first, last, step}
@doc false
@@ -387,16 +396,11 @@ defimpl Enumerable, for: Range do
{:done, acc}
end
def member?(first..last//step = range, value) when is_integer(value) do
cond do
Range.size(range) == 0 ->
{:ok, false}
first <= last ->
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
true ->
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
def member?(first..last//step, value) when is_integer(value) do
if step > 0 do
{:ok, first <= value and value <= last and rem(value - first, step) == 0}
else
{:ok, last <= value and value <= first and rem(value - first, step) == 0}
end
end
+2
View File
@@ -1315,6 +1315,8 @@ defmodule Stream do
{:suspended, acc, &zip_list(enumerables, &1, fun, zip_fun)}
end
defp zip_list([], {:cont, acc}, _fun, _zip_fun), do: {:done, acc}
defp zip_list(enumerables, {:cont, acc}, fun, zip_fun) do
case zip_list_heads_tails(enumerables, [], []) do
{heads, tails} -> zip_list(tails, fun.(zip_fun.(heads), acc), fun, zip_fun)
+2 -1
View File
@@ -366,6 +366,7 @@ defmodule StringIO do
defp get_until(encoding, prompt, mod, fun, args, %{input: input} = state) do
case get_until(input, encoding, mod, fun, args, [], 0) do
{result, input, count} ->
# Convert :eof to "" as they are both treated the same
input =
case input do
:eof -> ""
@@ -392,7 +393,7 @@ defmodule StringIO do
defp get_until(chars, encoding, mod, fun, args, continuation, count) do
case bytes_until_eol(chars, encoding, 0) do
{kind, size} when kind in [:split, :replace_split] ->
{line, rest} = :erlang.split_binary(chars, size)
<<line::binary-size(size), rest::binary>> = chars
case apply(mod, fun, [continuation, binary_to_list(line, encoding) | args]) do
{:done, result, :eof} ->
+1 -1
View File
@@ -82,7 +82,7 @@ defmodule Supervisor do
GenServer.call(Counter, :get)
#=> 0
GenServer.cast(Counter, {:bump, 3})
GenServer.call(Counter, {:bump, 3})
#=> 0
GenServer.call(Counter, :get)
@@ -42,7 +42,7 @@ Erlang/OTP versioning is independent from the versioning of Elixir. Erlang relea
Elixir version | Supported Erlang/OTP versions
:------------- | :-------------------------------
1.14 | 23 - 25
1.14 | 23 - 25 (and Erlang/OTP 26 from v1.14.5)
1.13 | 22 - 24 (and Erlang/OTP 25 from v1.13.4)
1.12 | 22 - 24
1.11 | 21 - 23 (and Erlang/OTP 24 from v1.11.4)
+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 = Version.to_string(version)
~s[{"version":"v#{version_string}", "url":"https://hexdocs.pm/#{app}/#{version_string}"}]
end)
File.mkdir_p!("doc/#{app}")
File.write!("doc/#{app}/docs_config.js", ["var versionNodes = [", list_contents, "];\n"])
+6 -3
View File
@@ -25,9 +25,9 @@
-export([start/2, stop/1, config_change/3]).
start(_Type, _Args) ->
_ = parse_otp_release(),
OTP = parse_otp_release(),
preload_common_modules(),
set_stdio_and_stderr_to_binary_and_maybe_utf8(),
set_stdio_and_stderr_to_binary_and_maybe_utf8(OTP),
check_file_encoding(file:native_name_encoding()),
case application:get_env(elixir, check_endianness, true) of
@@ -87,7 +87,10 @@ stop(Tab) ->
config_change(_Changed, _New, _Remove) ->
ok.
set_stdio_and_stderr_to_binary_and_maybe_utf8() ->
set_stdio_and_stderr_to_binary_and_maybe_utf8(OTP) when OTP >= 26 ->
ok = io:setopts(standard_io, [binary]),
ok;
set_stdio_and_stderr_to_binary_and_maybe_utf8(_OTP) ->
%% In case there is a shell, we can't really change its
%% encoding, so we just set binary to true. Otherwise
%% we must set the encoding as the user with no shell
+3 -2
View File
@@ -24,7 +24,7 @@ debug_info(core_v1, _Module, {elixir_v1, Map, Specs}, Opts) ->
%% warnings nor the other functionality provided there.
case elixir_erl_compiler:erl_to_core(Prefix ++ Specs ++ Forms, AllOpts) of
{ok, CoreForms, _} ->
try compile:noenv_forms(CoreForms, [no_spawn_compiler_process, from_core, core, return | AllOpts]) of
try compile:noenv_forms(CoreForms, [no_spawn_compiler_process, from_core, to_core, return | AllOpts]) of
{ok, _, Core, _} -> {ok, Core};
_What -> {error, failed_conversion}
catch
@@ -65,7 +65,8 @@ elixir_to_erl([], Ann) ->
elixir_to_erl(<<>>, Ann) ->
{bin, Ann, []};
elixir_to_erl(#{} = Map, Ann) ->
Assocs = [{map_field_assoc, Ann, elixir_to_erl(K, Ann), elixir_to_erl(V, Ann)} || {K, V} <- maps:to_list(Map)],
Assocs = [{map_field_assoc, Ann, elixir_to_erl(K, Ann), elixir_to_erl(V, Ann)}
|| {K, V} <- lists:sort(maps:to_list(Map))],
{map, Ann, Assocs};
elixir_to_erl(Tree, Ann) when is_list(Tree) ->
elixir_to_erl_cons(Tree, Ann);
+1 -1
View File
@@ -29,7 +29,7 @@ translate_into(Meta, Cases, Expr, Opts, S) ->
{TInto, SI} =
case lists:keyfind(into, 1, Opts) of
{into, Into} -> elixir_erl_pass:translate(Into, Ann, S);
false -> {{nil, Ann}, S}
false -> {false, S}
end,
TUniq = lists:keyfind(uniq, 1, Opts) == {uniq, true},
+1 -1
View File
@@ -173,7 +173,7 @@ expand({quote, Meta, [Opts, Do]}, S, E) when is_list(Do) ->
{File, Line} = case lists:keyfind(location, 1, EOpts) of
{location, keep} ->
{elixir_utils:relative_to_cwd(?key(E, file)), false};
{?key(E, file), false};
false ->
{proplists:get_value(file, EOpts, nil), proplists:get_value(line, EOpts, false)}
end,
+1 -1
View File
@@ -39,7 +39,7 @@ extract([$\\, Last, Last, Last | Rest], Buffer, Output, Line, Column, Scope, Int
extract(Rest, [Last, Last, Last | Buffer], Output, Line, Column+4, Scope, Interpol, All);
extract([$\\, $#, ${ | Rest], Buffer, Output, Line, Column, Scope, true, Last) ->
extract(Rest, [${, $#, $\\ | Buffer], Output, Line, Column+1, Scope, true, Last);
extract(Rest, [${, $#, $\\ | Buffer], Output, Line, Column+3, Scope, true, Last);
extract([$#, ${ | Rest], Buffer, Output, Line, Column, Scope, true, Last) ->
Output1 = build_string(Buffer, Output),
+1 -1
View File
@@ -25,7 +25,7 @@ expand_struct(Meta, Left, {'%{}', MapMeta, MapArgs}, S, #{context := Context} =
AssocKeys = [K || {K, _} <- Assocs],
Struct = load_struct(Meta, ELeft, [Assocs], AssocKeys, EE),
Keys = ['__struct__'] ++ AssocKeys,
WithoutKeys = maps:to_list(maps:without(Keys, Struct)),
WithoutKeys = lists:sort(maps:to_list(maps:without(Keys, Struct))),
StructAssocs = elixir_quote:escape(WithoutKeys, none, false),
{{'%', Meta, [ELeft, {'%{}', MapMeta, StructAssocs ++ Assocs}]}, SE, EE};
+7
View File
@@ -364,5 +364,12 @@ defmodule CalendarTest do
Calendar.strftime(~D[2019-08-15], "%D", unknown: "option")
end
end
test "zero padding for negative year" do
assert Calendar.strftime(Date.new!(-1, 1, 1), "%Y") == "-0001"
assert Calendar.strftime(Date.new!(-11, 1, 1), "%Y") == "-0011"
assert Calendar.strftime(Date.new!(-111, 1, 1), "%Y") == "-0111"
assert Calendar.strftime(Date.new!(-1111, 1, 1), "%Y") == "-1111"
end
end
end
@@ -507,6 +507,28 @@ defmodule Code.Normalizer.FormatterASTTest do
# after
""",
literal_encoder: fn literal, _ -> {:ok, literal} end
assert_same """
block do
# before 1
1 + 1
# before 2
2 + 2
end
""",
literal_encoder: fn literal, _ -> {:ok, literal} end
assert_same """
block do
# before 1
Mix.install([1 + 1])
# before 2
Mix.install([2 + 2])
end
""",
literal_encoder: fn literal, _ -> {:ok, literal} end
end
test "before and after expressions with newlines" do
@@ -515,6 +515,17 @@ defmodule Code.Normalizer.QuotedASTTest do
test "charlist" do
assert quoted_to_string(quote(do: [])) == "[]"
assert quoted_to_string(quote(do: 'abc')) == "'abc'"
# False positive
assert quoted_to_string(
quote do
:"Elixir.List".to_charlist([
case var do
var -> var
end
])
end
) =~ "List.to_charlist([\n case var do\n var -> var\n end\n])"
end
test "string" do
+61 -64
View File
@@ -735,11 +735,11 @@ defmodule EnumTest do
assert Enum.split_with(%{}, fn x -> rem(x, 2) == 0 end) == {[], []}
assert Enum.split_with(%{a: 1, b: 2, c: 3}, fn {_k, v} -> rem(v, 2) == 0 end) ==
{[b: 2], [a: 1, c: 3]}
assert Enum.split_with(%{a: 1, b: 2}, fn {_k, v} -> rem(v, 2) == 0 end) ==
{[b: 2], [a: 1]}
assert Enum.split_with(%{b: 2, d: 4, f: 6}, fn {_k, v} -> rem(v, 2) == 0 end) ==
{[b: 2, d: 4, f: 6], []}
{Map.to_list(%{b: 2, d: 4, f: 6}), []}
end
test "random/1" do
@@ -1503,14 +1503,11 @@ defmodule EnumTest do
assert result == [2, 4, 6, 8, 10]
# Maps
result = Enum.zip_with(%{a: 7, c: 9}, 3..4, fn {key, value}, b -> {key, value + b} end)
assert result == [a: 10, c: 13]
result = Enum.zip_with(%{a: 7}, 3..4, fn {key, value}, b -> {key, value + b} end)
assert result == [a: 10]
colour_1 = %{r: 176, g: 175, b: 255}
colour_2 = %{r: 12, g: 176, b: 176}
result = Enum.zip_with(colour_1, colour_2, fn {k, left}, {k, right} -> {k, left + right} end)
assert result == [b: 431, g: 351, r: 188]
result = Enum.zip_with(3..4, %{a: 7}, fn a, {key, value} -> {key, value + a} end)
assert result == [a: 10]
end
test "zip_with/2" do
@@ -1518,8 +1515,10 @@ defmodule EnumTest do
result = Enum.zip_with([[:a, :b], [1, 2], ["foo", "bar"]], zip_fun)
assert result == [{:a, 1, "foo"}, {:b, 2, "bar"}]
lots = Enum.zip_with([[:a, :b], [1, 2], ["foo", "bar"], %{a: :b, c: :d}], zip_fun)
assert lots == [{:a, 1, "foo", {:a, :b}}, {:b, 2, "bar", {:c, :d}}]
map = %{a: :b, c: :d}
[x1, x2] = Map.to_list(map)
lots = Enum.zip_with([[:a, :b], [1, 2], ["foo", "bar"], map], zip_fun)
assert lots == [{:a, 1, "foo", x1}, {:b, 2, "bar", x2}]
assert Enum.zip_with([[:a, :b], [1, 2, 3, 4], ["foo", "bar", "baz", "qux"]], zip_fun) ==
[{:a, 1, "foo"}, {:b, 2, "bar"}]
@@ -1556,21 +1555,11 @@ defmodule EnumTest do
assert result == [2, 4, 6, 8, 10]
# Maps
result = Enum.zip_with([%{a: 7, c: 9}, 3..4], fn [{key, value}, b] -> {key, value + b} end)
assert result == [a: 10, c: 13]
result = Enum.zip_with([%{a: 7}, 3..4], fn [{key, value}, b] -> {key, value + b} end)
assert result == [a: 10]
colour_1 = %{r: 176, g: 175, b: 255}
colour_2 = %{r: 12, g: 176, b: 176}
result =
Enum.zip_with([colour_1, colour_2], fn [{k, left}, {k, right}] -> {k, left + right} end)
assert result == [b: 431, g: 351, r: 188]
assert Enum.zip_with([%{a: :b, c: :d}, %{e: :f, g: :h}], & &1) == [
[a: :b, e: :f],
[c: :d, g: :h]
]
result = Enum.zip_with([%{a: 7}, 3..4], fn [{key, value}, b] -> {key, value + b} end)
assert result == [a: 10]
end
end
@@ -2396,16 +2385,17 @@ defmodule EnumTest.Map do
test "random/1" do
map = %{a: 1, b: 2, c: 3}
[x1, x2, x3] = Map.to_list(map)
seed1 = {1406, 407_414, 139_258}
seed2 = {1406, 421_106, 567_597}
:rand.seed(:exsss, seed1)
assert Enum.random(map) == {:c, 3}
assert Enum.random(map) == {:a, 1}
assert Enum.random(map) == {:b, 2}
assert Enum.random(map) == x3
assert Enum.random(map) == x1
assert Enum.random(map) == x2
:rand.seed(:exsss, seed2)
assert Enum.random(map) == {:c, 3}
assert Enum.random(map) == {:b, 2}
assert Enum.random(map) == x3
assert Enum.random(map) == x2
end
test "take_random/2" do
@@ -2418,35 +2408,37 @@ defmodule EnumTest.Map do
# set a fixed seed so the test can be deterministic
# please note the order of following assertions is important
map = %{a: 1, b: 2, c: 3}
[x1, x2, x3] = Map.to_list(map)
seed1 = {1406, 407_414, 139_258}
seed2 = {1406, 421_106, 567_597}
:rand.seed(:exsss, seed1)
assert Enum.take_random(map, 1) == [c: 3]
assert Enum.take_random(map, 1) == [x3]
:rand.seed(:exsss, seed1)
assert Enum.take_random(map, 2) == [c: 3, a: 1]
assert Enum.take_random(map, 2) == [x3, x1]
:rand.seed(:exsss, seed1)
assert Enum.take_random(map, 3) == [c: 3, a: 1, b: 2]
assert Enum.take_random(map, 3) == [x3, x1, x2]
:rand.seed(:exsss, seed1)
assert Enum.take_random(map, 4) == [c: 3, a: 1, b: 2]
assert Enum.take_random(map, 4) == [x3, x1, x2]
:rand.seed(:exsss, seed2)
assert Enum.take_random(map, 1) == [a: 1]
assert Enum.take_random(map, 1) == [x1]
:rand.seed(:exsss, seed2)
assert Enum.take_random(map, 2) == [a: 1, c: 3]
assert Enum.take_random(map, 2) == [x1, x3]
:rand.seed(:exsss, seed2)
assert Enum.take_random(map, 3) == [a: 1, c: 3, b: 2]
assert Enum.take_random(map, 3) == [x1, x3, x2]
:rand.seed(:exsss, seed2)
assert Enum.take_random(map, 4) == [a: 1, c: 3, b: 2]
assert Enum.take_random(map, 4) == [x1, x3, x2]
end
test "reverse/1" do
assert Enum.reverse(%{}) == []
assert Enum.reverse(MapSet.new()) == []
assert Enum.reverse(%{a: 1, b: 2, c: 3}) == [c: 3, b: 2, a: 1]
map = %{a: 1, b: 2, c: 3}
assert Enum.reverse(map) == Map.to_list(map) |> Enum.reverse()
end
test "reverse/2" do
assert Enum.reverse([a: 1, b: 2, c: 3, a: 1], %{x: 1, y: 2, z: 3}) ==
[a: 1, c: 3, b: 2, a: 1, x: 1, y: 2, z: 3]
assert Enum.reverse([a: 1, b: 2, c: 3, a: 1], %{x: 1}) == [a: 1, c: 3, b: 2, a: 1, x: 1]
assert Enum.reverse([], %{a: 1}) == [a: 1]
assert Enum.reverse([], %{}) == []
@@ -2456,44 +2448,49 @@ defmodule EnumTest.Map do
test "fetch/2" do
map = %{a: 1, b: 2, c: 3, d: 4, e: 5}
assert Enum.fetch(map, 0) == {:ok, {:a, 1}}
assert Enum.fetch(map, -2) == {:ok, {:d, 4}}
[x1, _x2, _x3, x4, x5] = Map.to_list(map)
assert Enum.fetch(map, 0) == {:ok, x1}
assert Enum.fetch(map, -2) == {:ok, x4}
assert Enum.fetch(map, -6) == :error
assert Enum.fetch(map, 5) == :error
assert Enum.fetch(%{}, 0) == :error
assert Stream.take(map, 3) |> Enum.fetch(3) == :error
assert Stream.take(map, 5) |> Enum.fetch(4) == {:ok, {:e, 5}}
assert Stream.take(map, 5) |> Enum.fetch(4) == {:ok, x5}
end
test "map_intersperse/3" do
assert Enum.map_intersperse(%{}, :a, & &1) == []
assert Enum.map_intersperse(%{foo: :bar}, :a, & &1) == [{:foo, :bar}]
assert Enum.map_intersperse(%{foo: :bar, baz: :bat}, :a, & &1) ==
[{:baz, :bat}, :a, {:foo, :bar}]
map = %{foo: :bar, baz: :bat}
[x1, x2] = Map.to_list(map)
assert Enum.map_intersperse(map, :a, & &1) == [x1, :a, x2]
end
test "slice/2" do
map = %{a: 1, b: 2, c: 3, d: 4, e: 5}
assert Enum.slice(map, 0..0) == [a: 1]
assert Enum.slice(map, 0..1) == [a: 1, b: 2]
assert Enum.slice(map, 0..2) == [a: 1, b: 2, c: 3]
[x1, x2, x3 | _] = Map.to_list(map)
assert Enum.slice(map, 0..0) == [x1]
assert Enum.slice(map, 0..1) == [x1, x2]
assert Enum.slice(map, 0..2) == [x1, x2, x3]
end
test "slice/3" do
map = %{a: 1, b: 2, c: 3, d: 4, e: 5}
assert Enum.slice(map, 1, 2) == [b: 2, c: 3]
[x1, x2, x3, x4, x5] = Map.to_list(map)
assert Enum.slice(map, 1, 2) == [x2, x3]
assert Enum.slice(map, 1, 0) == []
assert Enum.slice(map, 2, 5) == [c: 3, d: 4, e: 5]
assert Enum.slice(map, 2, 6) == [c: 3, d: 4, e: 5]
assert Enum.slice(map, 2, 5) == [x3, x4, x5]
assert Enum.slice(map, 2, 6) == [x3, x4, x5]
assert Enum.slice(map, 5, 5) == []
assert Enum.slice(map, 6, 5) == []
assert Enum.slice(map, 6, 0) == []
assert Enum.slice(map, -6, 0) == []
assert Enum.slice(map, -6, 5) == [a: 1, b: 2, c: 3, d: 4, e: 5]
assert Enum.slice(map, -2, 5) == [d: 4, e: 5]
assert Enum.slice(map, -3, 1) == [c: 3]
assert Enum.slice(map, -6, 5) == [x1, x2, x3, x4, x5]
assert Enum.slice(map, -2, 5) == [x4, x5]
assert Enum.slice(map, -3, 1) == [x3]
assert_raise FunctionClauseError, fn ->
Enum.slice(map, 0, -1)
@@ -2508,19 +2505,19 @@ defmodule EnumTest.Map do
end
assert Enum.slice(map, 0, 0) == []
assert Enum.slice(map, 0, 1) == [a: 1]
assert Enum.slice(map, 0, 2) == [a: 1, b: 2]
assert Enum.slice(map, 1, 2) == [b: 2, c: 3]
assert Enum.slice(map, 0, 1) == [x1]
assert Enum.slice(map, 0, 2) == [x1, x2]
assert Enum.slice(map, 1, 2) == [x2, x3]
assert Enum.slice(map, 1, 0) == []
assert Enum.slice(map, 2, 5) == [c: 3, d: 4, e: 5]
assert Enum.slice(map, 2, 6) == [c: 3, d: 4, e: 5]
assert Enum.slice(map, 2, 5) == [x3, x4, x5]
assert Enum.slice(map, 2, 6) == [x3, x4, x5]
assert Enum.slice(map, 5, 5) == []
assert Enum.slice(map, 6, 5) == []
assert Enum.slice(map, 6, 0) == []
assert Enum.slice(map, -6, 0) == []
assert Enum.slice(map, -6, 5) == [a: 1, b: 2, c: 3, d: 4, e: 5]
assert Enum.slice(map, -2, 5) == [d: 4, e: 5]
assert Enum.slice(map, -3, 1) == [c: 3]
assert Enum.slice(map, -6, 5) == [x1, x2, x3, x4, x5]
assert Enum.slice(map, -2, 5) == [x4, x5]
assert Enum.slice(map, -3, 1) == [x3]
assert_raise FunctionClauseError, fn ->
Enum.slice(map, 0, -1)
+28 -6
View File
@@ -18,13 +18,20 @@ defmodule ExceptionTest do
end
end
assert Exception.message(%{__struct__: BadException, __exception__: true, raise: true}) =~
"got RuntimeError with message \"oops\" while retrieving Exception.message/1 " <>
"for %{__exception__: true, __struct__: ExceptionTest.BadException, raise: true}"
assert "got RuntimeError with message \"oops\" while retrieving Exception.message/1 for %{" <>
inspected =
Exception.message(%{__struct__: BadException, __exception__: true, raise: true})
assert Exception.message(%{__struct__: BadException, __exception__: true, raise: false}) =~
"got nil while retrieving Exception.message/1 " <>
"for %{__exception__: true, __struct__: ExceptionTest.BadException, raise: false}"
assert inspected =~ "raise: true"
assert inspected =~ "__exception__: true"
assert inspected =~ "__struct__: ExceptionTest.BadException"
assert "got nil while retrieving Exception.message/1 for %{" <> inspected =
Exception.message(%{__struct__: BadException, __exception__: true, raise: false})
assert inspected =~ "raise: false"
assert inspected =~ "__exception__: true"
assert inspected =~ "__struct__: ExceptionTest.BadException"
end
test "normalize/2" do
@@ -426,6 +433,8 @@ defmodule ExceptionTest do
when is_binary(url) or (is_struct(url) and is_struct(url, url_module) and false) do
url
end
def sub_get!(url) when is_struct(url.sub, URI), do: url.sub
end
)
@@ -475,6 +484,19 @@ defmodule ExceptionTest do
def get!(url) when -is_binary(url)- or is_struct(url) and is_struct(url, URI) and -false-
"""
assert blame_message(Req, & &1.sub_get!(%{})) =~ """
no function clause matching in ExceptionTest.Req.sub_get!/1
The following arguments were given to ExceptionTest.Req.sub_get!/1:
# 1
%{}
Attempted function clauses (showing 1 out of 1):
def sub_get!(url) when -is_struct(url.sub, URI)-
"""
end
test "annotates badarg on apply" do
+22 -11
View File
@@ -417,14 +417,20 @@ defmodule Inspect.MapTest do
test "basic" do
assert inspect(%{1 => "b"}) == "%{1 => \"b\"}"
assert inspect(%{1 => "b", 2 => "c"}, pretty: true, width: 1) ==
assert inspect(%{1 => "b", 2 => "c"},
pretty: true,
width: 1,
custom_options: [sort_maps: true]
) ==
"%{\n 1 => \"b\",\n 2 => \"c\"\n}"
end
test "keyword" do
assert inspect(%{a: 1}) == "%{a: 1}"
assert inspect(%{a: 1, b: 2}) == "%{a: 1, b: 2}"
assert inspect(%{a: 1, b: 2, c: 3}) == "%{a: 1, b: 2, c: 3}"
assert inspect(%{a: 1, b: 2}, custom_options: [sort_maps: true]) == "%{a: 1, b: 2}"
assert inspect(%{a: 1, b: 2, c: 3}, custom_options: [sort_maps: true]) ==
"%{a: 1, b: 2, c: 3}"
end
test "with limit" do
@@ -446,12 +452,12 @@ defmodule Inspect.MapTest do
test "public modified struct" do
public = %Public{key: 1}
assert inspect(Map.put(public, :foo, :bar)) ==
assert inspect(Map.put(public, :foo, :bar), custom_options: [sort_maps: true]) ==
"%{__struct__: Inspect.MapTest.Public, foo: :bar, key: 1}"
end
test "private struct" do
assert inspect(%{__struct__: Private, key: 1}) ==
assert inspect(%{__struct__: Private, key: 1}, custom_options: [sort_maps: true]) ==
"%{__struct__: Inspect.MapTest.Private, key: 1}"
end
@@ -486,7 +492,7 @@ defmodule Inspect.MapTest do
%{__struct__: Inspect.MapTest.Failing, name: "Foo"}
'''
assert inspect(%Failing{name: "Foo"}) =~ message
assert inspect(%Failing{name: "Foo"}, custom_options: [sort_maps: true]) =~ message
end
test "safely inspect bad implementation disables colors" do
@@ -505,7 +511,10 @@ defmodule Inspect.MapTest do
%{__struct__: Inspect.MapTest.Failing, name: "Foo"}
'''
assert inspect(%Failing{name: "Foo"}, syntax_colors: [atom: [:green]]) =~ message
assert inspect(%Failing{name: "Foo"},
syntax_colors: [atom: [:green]],
custom_options: [sort_maps: true]
) =~ message
end
test "unsafely inspect bad implementation" do
@@ -524,7 +533,7 @@ defmodule Inspect.MapTest do
'''
try do
inspect(%Failing{name: "Foo"}, safe: false)
inspect(%Failing{name: "Foo"}, safe: false, custom_options: [sort_maps: true])
rescue
exception in Inspect.Error ->
assert Exception.message(exception) =~ exception_message
@@ -552,14 +561,16 @@ defmodule Inspect.MapTest do
while inspecting:
%{__struct__: Inspect.MapTest.Failing, name: "Foo"}
'''
try do
Enum.to_list(%Failing{name: "Foo"})
rescue
exception in Protocol.UndefinedError ->
assert Exception.message(exception) =~ exception_message
message = Exception.message(exception)
assert message =~ exception_message
assert message =~ "__struct__: Inspect.MapTest.Failing"
assert message =~ "name: \"Foo\""
assert [
{Enumerable, :impl_for!, 1, _} | _
@@ -617,7 +628,7 @@ defmodule Inspect.MapTest do
)
)
assert inspect(%Failing{name: "Foo"}) =~ message
assert inspect(%Failing{name: "Foo"}, custom_options: [sort_maps: true]) =~ message
assert inspected =~ message
end
@@ -22,7 +22,26 @@ defmodule Kernel.DialyzerTest do
end
# Add a few key Elixir modules for types and macro functions
mods = [:elixir, :elixir_env, Atom, Enum, Exception, Kernel, Macro, Macro.Env, String]
mods = [
:elixir,
:elixir_env,
ArgumentError,
Atom,
Enum,
Exception,
ExUnit.AssertionError,
ExUnit.Assertions,
IO,
Kernel,
Kernel.Utils,
List,
Macro,
Macro.Env,
Module,
String,
String.Chars
]
files = Enum.map(mods, &:code.which/1)
dialyzer_run(analysis_type: :plt_build, output_plt: plt, apps: [:erts], files: files)
@@ -102,6 +121,7 @@ defmodule Kernel.DialyzerTest do
copy_beam!(context, ProtocolOpaque)
copy_beam!(context, ProtocolOpaque.Entity)
copy_beam!(context, ProtocolOpaque.Entity.Any)
copy_beam!(context, ProtocolOpaque.Duck)
assert_dialyze_no_warnings!(context)
+1 -1
View File
@@ -21,7 +21,7 @@ defmodule Kernel.QuoteTest do
line = __ENV__.line + 2
assert quote(location: :keep, do: bar(1, 2, 3)) ==
{:bar, [keep: {Path.relative_to_cwd(__ENV__.file), line}], [1, 2, 3]}
{:bar, [keep: {__ENV__.file, line}], [1, 2, 3]}
end
test "fixed line" do
@@ -411,6 +411,7 @@ defmodule Kernel.RaiseTest do
fun = BadFunction.Missing.fun()
:code.delete(BadFunction.Missing)
:code.purge(BadFunction.Missing)
defmodule BadFunction.Missing do
def fun, do: fn -> :another end
+20 -2
View File
@@ -172,10 +172,18 @@ defmodule MacroTest do
end
test "env" do
env = %{__ENV__ | line: 0}
assert Macro.expand_once(quote(do: __ENV__), env) == {:%{}, [], Map.to_list(env)}
env = %{__ENV__ | line: 0, lexical_tracker: self()}
expanded = Macro.expand_once(quote(do: __ENV__), env)
assert Macro.validate(expanded) == :ok
assert Code.eval_quoted(expanded) == {env, []}
assert Macro.expand_once(quote(do: __ENV__.file), env) == env.file
assert Macro.expand_once(quote(do: __ENV__.unknown), env) == quote(do: __ENV__.unknown)
expanded = Macro.expand_once(quote(do: __ENV__.versioned_vars), env)
assert Macro.validate(expanded) == :ok
assert Code.eval_quoted(expanded) == {env.versioned_vars, []}
end
defmacro local_macro(), do: raise("ignored")
@@ -421,6 +429,16 @@ defmodule MacroTest do
test "converts quoted to string" do
assert Macro.to_string(quote do: hello(world)) == "hello(world)"
end
test "large number literals" do
# with quote
assert Macro.to_string(quote do: 576_460_752_303_423_455) == "576_460_752_303_423_455"
assert Macro.to_string(quote do: -576_460_752_303_423_455) == "-576_460_752_303_423_455"
# without quote
assert Macro.to_string(576_460_752_303_423_455) == "576_460_752_303_423_455"
assert Macro.to_string(-576_460_752_303_423_455) == "-576_460_752_303_423_455"
end
end
describe "to_string/2" do
+1 -1
View File
@@ -206,7 +206,7 @@ defmodule MapTest do
assert Map.replace!(map, :b, 10) == %{c: 3, b: 10, a: 1}
assert Map.replace!(map, :a, 1) == map
assert_raise KeyError, "key :x not found in: %{a: 1, b: 2, c: 3}", fn ->
assert_raise KeyError, ~r/key :x not found in: %{.*a: 1.*}/, fn ->
Map.replace!(map, :x, 10)
end
@@ -562,6 +562,22 @@ defmodule Module.Types.IntegrationTest do
end
end
describe "regressions" do
test "handle missing location info from quoted" do
assert capture_io(:stderr, fn ->
quote do
defmodule X do
def f() do
x = %{}
%{x | key: :value}
end
end
end
|> Code.compile_quoted()
end) =~ "warning:"
end
end
describe "after_verify" do
test "reports functions" do
files = %{
@@ -140,25 +140,25 @@ defmodule Module.Types.PatternTest do
end
test "struct" do
assert quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{}) ==
{:ok,
{:map,
[
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
{:required, {:atom, :bar}, :dynamic},
{:required, {:atom, :baz}, :dynamic},
{:required, {:atom, :foo}, :dynamic}
]}}
assert {:ok, {:map, fields}} = quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{})
assert quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{foo: 123, bar: :atom}) ==
{:ok,
{:map,
[
{:required, {:atom, :foo}, :integer},
{:required, {:atom, :bar}, {:atom, :atom}},
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
{:required, {:atom, :baz}, :dynamic}
]}}
assert Enum.sort(fields) == [
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
{:required, {:atom, :bar}, :dynamic},
{:required, {:atom, :baz}, :dynamic},
{:required, {:atom, :foo}, :dynamic}
]
assert {:ok, {:map, fields}} =
quoted_pattern(%:"Elixir.Module.Types.PatternTest.Struct"{foo: 123, bar: :atom})
assert Enum.sort(fields) ==
[
{:required, {:atom, :__struct__}, {:atom, Module.Types.PatternTest.Struct}},
{:required, {:atom, :bar}, {:atom, :atom}},
{:required, {:atom, :baz}, :dynamic},
{:required, {:atom, :foo}, :integer}
]
end
test "struct var" do
+7
View File
@@ -332,6 +332,13 @@ defmodule ModuleTest do
assert Elixir.ModuleTest.NonAtomAlias.hello() == :world
end
test "does not warn on captured underscored vars" do
_unused = 123
defmodule __MODULE__.NoVarWarning do
end
end
@compile {:no_warn_undefined, ModuleCreateSample}
test "create" do
+15
View File
@@ -38,6 +38,21 @@ defmodule PathTest do
File.rm_rf(Path.join(config.tmp_dir, "wildcard"))
end
@tag :tmp_dir
test "wildcard/2 follows ..", config do
hello = Path.join(config.tmp_dir, "wildcard/hello")
world = Path.join(config.tmp_dir, "wildcard/world")
File.mkdir_p(hello)
File.touch(world)
assert Path.wildcard(Path.join(config.tmp_dir, "wildcard/w*/../h*")) == []
assert Path.wildcard(Path.join(config.tmp_dir, "wildcard/h*/../w*")) ==
[Path.join(config.tmp_dir, "wildcard/hello/../world")]
after
File.rm_rf(Path.join(config.tmp_dir, "wildcard"))
end
test "wildcard/2 raises on null byte" do
assert_raise ArgumentError, ~r/null byte/, fn -> Path.wildcard("foo\0bar") end
end
+2
View File
@@ -1340,6 +1340,8 @@ defmodule StreamTest do
assert Stream.chunk_every([0, 1, 2, 3], 2) |> Stream.zip() |> Enum.to_list() ==
[{0, 2}, {1, 3}]
assert Stream.zip([]) |> Enum.to_list() == []
stream = %HaltAcc{acc: 1..3}
assert Stream.zip([1..3, stream]) |> Enum.to_list() == [{1, 1}, {2, 2}, {3, 3}]
+1 -1
View File
@@ -22,7 +22,7 @@ extract_interpolations_without_interpolation_test() ->
extract_interpolations_with_escaped_interpolation_test() ->
["f\\#{o}o"] = extract_interpolations("f\\#{o}o"),
{1, 8, ["f\\#{o}o"], [], _} =
{1, 10, ["f\\#{o}o"], [], _} =
elixir_interpolation:extract(1, 2, #elixir_tokenizer{}, true, "f\\#{o}o\"", $").
extract_interpolations_with_interpolation_test() ->
@@ -199,6 +199,11 @@ interpolation_test() ->
{concat_op, {1, 10, nil}, '<>'},
{bin_string, {1, 13, nil}, [<<>>]}] = tokenize("\"f#{oo}\" <> \"\"").
escaped_interpolation_test() ->
[{bin_string, {1, 1, nil}, [<<"f#{oo}">>]},
{concat_op, {1, 11, nil}, '<>'},
{bin_string, {1, 14, nil}, [<<>>]}] = tokenize("\"f\\#{oo}\" <> \"\"").
capture_test() ->
% Parens precedence
[{capture_op, {1, 1, nil}, '&'},
+3 -1
View File
@@ -337,7 +337,9 @@ defmodule ExUnit.CLIFormatter do
end
defp format_test_type_counts(%{test_counter: test_counter} = _config) do
Enum.map(test_counter, fn {test_type, count} ->
test_counter
|> Enum.sort()
|> Enum.map(fn {test_type, count} ->
type_pluralized = pluralize(count, test_type, ExUnit.plural_rule(test_type |> to_string()))
"#{count} #{type_pluralized}, "
end)
+27 -33
View File
@@ -238,9 +238,9 @@ defmodule ExUnit.DocTest do
extract(module)
|> filter_by_opts(module, opts)
|> Stream.with_index(1)
|> Enum.map(fn {test, acc} ->
compile_test(test, module, import, acc, file, tags)
|> Enum.sort_by(& &1.line)
|> Enum.with_index(fn test, index ->
compile_test(test, module, import, index + 1, file, tags)
end)
end
@@ -313,8 +313,8 @@ defmodule ExUnit.DocTest do
end
tests =
Enum.map(exprs, fn {expr, expected, formatted} ->
test_case_content(expr, expected, location, stack, formatted)
Enum.map(exprs, fn {expr, expected, doctest} ->
test_case_content(expr, expected, location, stack, doctest)
end)
{:__block__, [], test_import(module, do_import) ++ tests}
@@ -327,12 +327,11 @@ defmodule ExUnit.DocTest do
end) > 1
end
defp test_case_content(expr, :test, location, stack, formatted) do
string_to_quoted(location, stack, expr, "\n" <> formatted) |> insert_assertions()
defp test_case_content(expr, :test, location, stack, doctest) do
string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
end
defp test_case_content(expr, {:test, expected}, location, stack, formatted) do
doctest = "\n" <> formatted <> "\n" <> expected
defp test_case_content(expr, {:test, expected}, location, stack, doctest) do
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
expected_ast = string_to_quoted(location, stack, expected, doctest)
last_expr = Macro.to_string(last_expr(expr_ast))
@@ -340,56 +339,53 @@ defmodule ExUnit.DocTest do
quote do
value = unquote(expr_ast)
expected = unquote(expected_ast)
formatted = unquote(formatted)
doctest = unquote(doctest)
last_expr = unquote(last_expr)
expected_expr = unquote(expected)
stack = unquote(stack)
ExUnit.DocTest.__test__(value, expected, formatted, last_expr, expected_expr, stack)
ExUnit.DocTest.__test__(value, expected, doctest, last_expr, expected_expr, stack)
end
end
defp test_case_content(expr, {:inspect, expected}, location, stack, formatted) do
doctest = "\n" <> formatted <> "\n" <> expected
defp test_case_content(expr, {:inspect, expected}, location, stack, doctest) do
expr_ast = string_to_quoted(location, stack, expr, doctest) |> insert_assertions()
expected_ast = string_to_quoted(location, stack, expected, doctest)
last_expr = Macro.to_string(last_expr(expr_ast))
quote do
value = unquote(expr_ast)
expected = unquote(expected_ast)
formatted = unquote(formatted)
expected = unquote(expected)
doctest = unquote(doctest)
last_expr = unquote(last_expr)
expected_expr = unquote(expected)
expected_expr = unquote(inspect(expected))
stack = unquote(stack)
ExUnit.DocTest.__inspect__(value, expected, formatted, last_expr, expected_expr, stack)
ExUnit.DocTest.__inspect__(value, expected, doctest, last_expr, expected_expr, stack)
end
end
defp test_case_content(expr, {:error, exception, message}, location, stack, formatted) do
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
defp test_case_content(expr, {:error, exception, message}, location, stack, doctest) do
expr_ast = string_to_quoted(location, stack, expr, doctest)
quote do
stack = unquote(stack)
message = unquote(message)
formatted = unquote(formatted)
doctest = unquote(doctest)
exception = unquote(exception)
ExUnit.DocTest.__error__(fn -> unquote(expr_ast) end, message, exception, formatted, stack)
ExUnit.DocTest.__error__(fn -> unquote(expr_ast) end, message, exception, doctest, stack)
end
end
@doc false
def __test__(value, expected, formatted, last_expr, expected_expr, stack) do
def __test__(value, expected, doctest, last_expr, expected_expr, stack) do
case value do
^expected ->
:ok
{:ok, value}
_ ->
error = [
message: "Doctest failed",
doctest: "\n" <> formatted <> "\n" <> expected_expr,
doctest: doctest,
expr: "#{last_expr} === #{String.trim(expected_expr)}",
left: value,
right: expected
@@ -400,7 +396,7 @@ defmodule ExUnit.DocTest do
end
@doc false
def __inspect__(value, expected, formatted, last_expr, expected_expr, parent_stack) do
def __inspect__(value, expected, doctest, last_expr, expected_expr, parent_stack) do
result =
try do
inspect(value, safe: false)
@@ -415,10 +411,9 @@ defmodule ExUnit.DocTest do
case result do
:ok ->
:ok
{:ok, value}
{extra, stack} ->
doctest = "\n" <> formatted <> "\n" <> expected_expr
expr = "inspect(#{last_expr}) === #{String.trim(expected_expr)}"
error = [doctest: doctest, expr: expr] ++ extra
reraise ExUnit.AssertionError, error, stack ++ parent_stack
@@ -426,7 +421,7 @@ defmodule ExUnit.DocTest do
end
@doc false
def __error__(fun, message, exception, formatted, stack) do
def __error__(fun, message, exception, doctest, stack) do
try do
fun.()
rescue
@@ -451,12 +446,10 @@ defmodule ExUnit.DocTest do
end
if failed do
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
reraise ExUnit.AssertionError, [message: failed, doctest: doctest], stack
end
else
_ ->
doctest = "\n" <> formatted <> "\n** (#{inspect(exception)}) #{inspect(message)}"
failed = "Doctest failed: expected exception #{inspect(exception)} but nothing was raised"
error = [message: failed, doctest: doctest]
reraise ExUnit.AssertionError, error, stack
@@ -862,7 +855,8 @@ defmodule ExUnit.DocTest do
end
defp add_expr(%{exprs: exprs} = test, expr, expected, formatted) do
%{test | exprs: [{expr, tag_expected(expected), formatted} | exprs]}
doctest = "\n" <> formatted <> "\n" <> expected
%{test | exprs: [{expr, tag_expected(expected), doctest} | exprs]}
end
defp tag_expected(string) do
@@ -876,7 +870,7 @@ defmodule ExUnit.DocTest do
_ ->
if inspectable?(string) do
{:inspect, inspect(string)}
{:inspect, string}
else
{:test, string}
end
+46 -53
View File
@@ -577,7 +577,7 @@ defmodule ExUnit.DocTestTest do
Doctest failed
doctest:
iex> :oops
"#Inspect<[]>"
#Inspect<[]>
code: inspect(:oops) === "#Inspect<[]>"
left: ":oops"
right: "#Inspect<[]>"
@@ -600,7 +600,7 @@ defmodule ExUnit.DocTestTest do
Doctest failed: expected exception WhatIsThis but got RuntimeError with message "oops"
doctest:
iex> raise "oops"
** (WhatIsThis) "oops"
** (WhatIsThis) oops
stacktrace:
test/ex_unit/doc_test_test.exs:#{starting_line + 18}: ExUnit.DocTestTest.Invalid (module)
"""
@@ -615,7 +615,7 @@ defmodule ExUnit.DocTestTest do
"oops"
doctest:
iex> raise "oops"
** (RuntimeError) "hello"
** (RuntimeError) hello
stacktrace:
test/ex_unit/doc_test_test.exs:#{starting_line + 21}: ExUnit.DocTestTest.Invalid (module)
"""
@@ -635,18 +635,17 @@ defmodule ExUnit.DocTestTest do
"""
assert output =~ """
9) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (9) (ExUnit.DocTestTest.ActuallyCompiled)
9) doctest ExUnit.DocTestTest.Invalid.b/0 (9) (ExUnit.DocTestTest.ActuallyCompiled)
test/ex_unit/doc_test_test.exs:#{doctest_line}
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 57}:5: unexpected token: "`" (column 5, code point U+0060)
#{line_placeholder(starting_line + 57)} |
#{starting_line + 57} | ```
#{line_placeholder(starting_line + 57)} | ^
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 33}:6: syntax error before: '*'
#{line_placeholder(starting_line + 33)} |
#{starting_line + 33} | 1 + * 1
#{line_placeholder(starting_line + 33)} | ^
doctest:
iex> 1 + 2
3
```
iex> 1 + * 1
1
stacktrace:
test/ex_unit/doc_test_test.exs:#{starting_line + 56}: ExUnit.DocTestTest.Invalid (module)
test/ex_unit/doc_test_test.exs:#{starting_line + 33}: ExUnit.DocTestTest.Invalid (module)
"""
assert output =~ """
@@ -680,7 +679,22 @@ defmodule ExUnit.DocTestTest do
"""
assert output =~ """
12) doctest ExUnit.DocTestTest.Invalid.invalid_utf8/0 (12) (ExUnit.DocTestTest.ActuallyCompiled)
12) doctest ExUnit.DocTestTest.Invalid.dedented_past_fence/0 (12) (ExUnit.DocTestTest.ActuallyCompiled)
test/ex_unit/doc_test_test.exs:#{doctest_line}
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 57}:5: unexpected token: "`" (column 5, code point U+0060)
#{line_placeholder(starting_line + 57)} |
#{starting_line + 57} | ```
#{line_placeholder(starting_line + 57)} | ^
doctest:
iex> 1 + 2
3
```
stacktrace:
test/ex_unit/doc_test_test.exs:#{starting_line + 56}: ExUnit.DocTestTest.Invalid (module)
"""
assert output =~ """
13) doctest ExUnit.DocTestTest.Invalid.invalid_utf8/0 (13) (ExUnit.DocTestTest.ActuallyCompiled)
test/ex_unit/doc_test_test.exs:#{doctest_line}
Doctest did not compile, got: (UnicodeConversionError) invalid encoding starting at <<255, 34, 41>>
stacktrace:
@@ -688,7 +702,7 @@ defmodule ExUnit.DocTestTest do
"""
assert output =~ """
13) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (13) (ExUnit.DocTestTest.ActuallyCompiled)
14) doctest ExUnit.DocTestTest.Invalid.misplaced_opaque_type/0 (14) (ExUnit.DocTestTest.ActuallyCompiled)
test/ex_unit/doc_test_test.exs:#{doctest_line}
Doctest did not compile, got: (TokenMissingError) test/ex_unit/doc_test_test.exs:#{starting_line + 69}:7: missing terminator: } (for "{" starting at line #{starting_line + 69})
#{line_placeholder(starting_line + 69)} |
@@ -702,28 +716,8 @@ defmodule ExUnit.DocTestTest do
test/ex_unit/doc_test_test.exs:#{starting_line + 69}: ExUnit.DocTestTest.Invalid (module)
"""
assert output =~ "14) doctest ExUnit.DocTestTest.Invalid.raising_inspect/0"
assert output =~ "iex> ExUnit.DocTestTest.Haiku.new(:this, :is, {:not, :a, :haiku})"
assert output =~
"test/ex_unit/doc_test_test.exs:#{starting_line + 82}: ExUnit.DocTestTest.Invalid (module)"
assert output =~ """
15) doctest ExUnit.DocTestTest.Invalid.b/0 (15) (ExUnit.DocTestTest.ActuallyCompiled)
test/ex_unit/doc_test_test.exs:#{doctest_line}
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 33}:6: syntax error before: '*'
#{line_placeholder(starting_line + 33)} |
#{starting_line + 33} | 1 + * 1
#{line_placeholder(starting_line + 33)} | ^
doctest:
iex> 1 + * 1
1
stacktrace:
test/ex_unit/doc_test_test.exs:#{starting_line + 33}: ExUnit.DocTestTest.Invalid (module)
"""
assert output =~ """
16) doctest ExUnit.DocTestTest.Invalid.t/0 (16) (ExUnit.DocTestTest.ActuallyCompiled)
15) doctest ExUnit.DocTestTest.Invalid.t/0 (15) (ExUnit.DocTestTest.ActuallyCompiled)
test/ex_unit/doc_test_test.exs:#{doctest_line}
Doctest did not compile, got: (SyntaxError) test/ex_unit/doc_test_test.exs:#{starting_line + 75}:6: syntax error before: '*'
#{line_placeholder(starting_line + 75)} |
@@ -735,6 +729,12 @@ defmodule ExUnit.DocTestTest do
stacktrace:
test/ex_unit/doc_test_test.exs:#{starting_line + 75}: ExUnit.DocTestTest.Invalid (module)
"""
assert output =~ "16) doctest ExUnit.DocTestTest.Invalid.raising_inspect/0"
assert output =~ "iex> ExUnit.DocTestTest.Haiku.new(:this, :is, {:not, :a, :haiku})"
assert output =~
"test/ex_unit/doc_test_test.exs:#{starting_line + 82}: ExUnit.DocTestTest.Invalid (module)"
end
test "pattern matching assertions in doctests" do
@@ -900,24 +900,7 @@ defmodule ExUnit.DocTestTest do
end
end
test "fails when testing single function not found" do
message = """
test/ex_unit/doc_test_test\.exs: undefined or private function given to doctest:
ExUnit.DocTestTest.SomewhatGoodModuleWithOnly.three/0
"""
assert_raise ExUnit.DocTest.Error, message, fn ->
defmodule NeverCompiled do
import ExUnit.DocTest
doctest ExUnit.DocTestTest.SomewhatGoodModuleWithOnly, only: [three: 0], import: true
end
end
end
test "fails when testing multiple functions not found" do
test "fails when testing functions are not found" do
message = """
test/ex_unit/doc_test_test\.exs: undefined or private functions given to doctest:
@@ -989,6 +972,16 @@ defmodule ExUnit.DocTestTest do
end
end
test "doctest direct invocation" do
defmodule Direct do
use ExUnit.Case, register: false
doctest ExUnit.DocTestTest.GoodModule, import: true
end
[head | _] = Direct.__ex_unit__().tests
assert apply(Direct, head.name, [%{}]) == {:ok, 2}
end
defp line_placeholder(line_number) do
digits =
line_number
+4 -1
View File
@@ -523,7 +523,10 @@ defmodule IEx.Helpers do
:code.get_path()
|> Protocol.extract_protocols()
|> Enum.uniq()
|> Enum.reject(fn protocol -> is_nil(protocol.impl_for(term)) end)
|> Enum.filter(fn protocol ->
Code.ensure_loaded?(protocol) and function_exported?(protocol, :impl_for, 1) and
protocol.impl_for(term) != nil
end)
|> Enum.sort()
|> Enum.map_join(", ", &inspect/1)
end
+21 -1
View File
@@ -374,9 +374,29 @@ defmodule IEx.Server do
defp io_get(pid, prompt_type, prefix, counter) do
prompt = prompt(prompt_type, prefix, counter)
send(pid, {:input, self(), IO.gets(:stdio, prompt)})
send(pid, {:input, self(), io_get(prompt)})
end
defp io_get(prompt) do
pid = Process.group_leader()
ref = Process.monitor(pid)
command = {:get_until, :unicode, prompt, __MODULE__, :__io_get__, []}
send(pid, {:io_request, self(), ref, command})
receive do
{:io_reply, ^ref, reply} ->
Process.demonitor(ref, [:flush])
reply
{:DOWN, ^ref, _, _, _} ->
{:error, :terminated}
end
end
@doc false
def __io_get__([], :eof), do: {:done, :eof, []}
def __io_get__([], chars), do: {:done, List.to_string(chars), []}
defp prompt(prompt_type, prefix, counter) do
{mode, prefix} =
if Node.alive?() do
+3 -3
View File
@@ -31,9 +31,9 @@ defmodule IEx.AutocompleteTest do
end
test "Erlang module multiple values completion" do
{:yes, '', list} = expand(':user')
assert 'user' in list
assert 'user_drv' in list
{:yes, '', list} = expand(':logger')
assert 'logger' in list
assert 'logger_proxy' in list
end
test "Erlang root completion" do
+7 -2
View File
@@ -126,8 +126,13 @@ defmodule IEx.InteractionTest do
test "inspect opts" do
opts = [inspect: [binaries: :as_binaries, charlists: :as_lists, structs: false, limit: 4]]
assert capture_iex("<<45, 46, 47>>\n[45, 46, 47]\n%IO.Stream{}", opts) ==
"<<45, 46, 47>>\n[45, 46, 47]\n%{__struct__: IO.Stream, device: nil, line_or_bytes: :line, raw: true}"
assert "<<45, 46, 47>>\n[45, 46, 47]\n%{" <> map =
capture_iex("<<45, 46, 47>>\n[45, 46, 47]\n%IO.Stream{}", opts)
assert map =~ "__struct__: IO.Stream"
assert map =~ "device: nil"
assert map =~ "line_or_bytes: :line"
assert map =~ "raw: true"
end
test "exception" do
+2 -1
View File
@@ -134,7 +134,8 @@ defmodule Logger.Utils do
pad_char: ?\s,
precision: :none,
strings: true,
width: :none
width: :none,
maps_order: :undefined
}
defp handle_format_spec(%{control_char: char} = spec, opts) when char in 'wWpP' do
+24 -23
View File
@@ -772,12 +772,13 @@ defmodule Logger.TranslatorTest do
end
test "translates Supervisor reports start error" do
Process.flag(:trap_exit, true)
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
children = [worker(__MODULE__, [], function: :error)]
Supervisor.start_link(children, strategy: :one_for_one)
receive do: ({:EXIT, _, {:shutdown, {:failed_to_start_child, _, _}}} -> :ok)
Process.flag(:trap_exit, trap)
assert {:error, {:shutdown, {:failed_to_start_child, __MODULE__, :stop}}} =
Supervisor.start_link(children, strategy: :one_for_one)
end) =~ ~r"""
\[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) failed to start
\*\* \(exit\) :stop
@@ -786,12 +787,13 @@ defmodule Logger.TranslatorTest do
end
test "translates Supervisor reports start error with raise" do
Process.flag(:trap_exit, true)
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
children = [worker(__MODULE__, [], function: :undef)]
Supervisor.start_link(children, strategy: :one_for_one)
receive do: ({:EXIT, _, {:shutdown, {:failed_to_start_child, _, _}}} -> :ok)
Process.flag(:trap_exit, trap)
assert {:error, {:shutdown, {:failed_to_start_child, __MODULE__, {:EXIT, _}}}} =
Supervisor.start_link(children, strategy: :one_for_one)
end) =~ ~r"""
\[error\] Child Logger.TranslatorTest of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) failed to start
\*\* \(exit\) an exception was raised:
@@ -804,12 +806,12 @@ defmodule Logger.TranslatorTest do
end
test "translates Supervisor reports terminated" do
Process.flag(:trap_exit, true)
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
children = [worker(Task, [Kernel, :exit, [:stop]])]
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one, max_restarts: 0)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[error\] Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) terminated
\*\* \(exit\) :stop
@@ -827,12 +829,12 @@ defmodule Logger.TranslatorTest do
end
test "translates Supervisor reports max restarts shutdown" do
Process.flag(:trap_exit, true)
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
children = [worker(Task, [Kernel, :exit, [:stop]])]
{:ok, pid} = Supervisor.start_link(children, strategy: :one_for_one, max_restarts: 0)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[error\] Child Task of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) caused shutdown
\*\* \(exit\) :reached_max_restart_intensity
@@ -888,14 +890,14 @@ defmodule Logger.TranslatorTest do
end
test "translates DynamicSupervisor reports abnormal shutdown" do
Process.flag(:trap_exit, true)
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
child = %{id: __MODULE__, start: {__MODULE__, :abnormal, []}}
{:ok, pid} = DynamicSupervisor.start_link(strategy: :one_for_one)
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
Process.exit(pid, :normal)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[error\] Child :undefined of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shut down abnormally
\*\* \(exit\) :stop
@@ -909,9 +911,9 @@ defmodule Logger.TranslatorTest do
end
test "translates DynamicSupervisor reports abnormal shutdown including extra_arguments" do
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
Process.flag(:trap_exit, true)
assert capture_log(:info, fn ->
{:ok, pid} =
DynamicSupervisor.start_link(strategy: :one_for_one, extra_arguments: [:extra])
@@ -919,7 +921,6 @@ defmodule Logger.TranslatorTest do
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
Process.exit(pid, :normal)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[error\] Child :undefined of Supervisor #PID<\d+\.\d+\.\d+> \(Supervisor\.Default\) shut down abnormally
\*\* \(exit\) :stop
@@ -933,14 +934,14 @@ defmodule Logger.TranslatorTest do
end
test "translates named DynamicSupervisor reports abnormal shutdown" do
Process.flag(:trap_exit, true)
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
child = %{id: __MODULE__, start: {__MODULE__, :abnormal, []}}
{:ok, pid} = DynamicSupervisor.start_link(strategy: :one_for_one, name: __MODULE__)
{:ok, _pid2} = DynamicSupervisor.start_child(pid, child)
Process.exit(pid, :normal)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[error\] Child :undefined of Supervisor Logger.TranslatorTest shut down abnormally
\*\* \(exit\) :stop
@@ -954,11 +955,11 @@ defmodule Logger.TranslatorTest do
end
test "translates :supervisor_bridge progress" do
Process.flag(:trap_exit, true)
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
{:ok, pid} = :supervisor_bridge.start_link(MyBridge, :normal)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[info\] Child of Supervisor #PID<\d+\.\d+\.\d+> \(Logger\.TranslatorTest\.MyBridge\) started
Pid: #PID<\d+\.\d+\.\d+>
@@ -967,11 +968,11 @@ defmodule Logger.TranslatorTest do
end
test "translates :supervisor_bridge reports" do
Process.flag(:trap_exit, true)
assert capture_log(:info, fn ->
trap = Process.flag(:trap_exit, true)
{:ok, pid} = :supervisor_bridge.start_link(MyBridge, :stop)
receive do: ({:EXIT, ^pid, _} -> :ok)
Process.flag(:trap_exit, trap)
end) =~ ~r"""
\[error\] Child of Supervisor #PID<\d+\.\d+\.\d+> \(Logger\.TranslatorTest\.MyBridge\) terminated
\*\* \(exit\) :stop
+45 -15
View File
@@ -107,17 +107,18 @@ defmodule Mix.Compilers.ApplicationTracer do
:ets.delete(table, module)
end
warnings =
{warnings, _} =
:ets.foldl(
fn {module, function, arity, env_module, env_function, env_file, env_line}, acc ->
fn {module, function, arity, env_module, env_function, env_file, env_line},
{acc, cache} ->
# If the module is preloaded, it is always available, so we skip it.
# If the module is non existing, the compiler will warn, so we skip it.
# If the module belongs to this application (from another compiler), we skip it.
# If the module is excluded, we skip it.
with path when is_list(path) <- :code.which(module),
{:ok, module_app} <- app(path),
true <- module_app != app,
false <- module in excludes or {module, function, arity} in excludes do
{module_app, cache} = app_for_module(module, cache)
if module_app != nil and module_app != app and module not in excludes and
{module, function, arity} not in excludes do
env_mfa =
if env_function do
{env_module, elem(env_function, 0), elem(env_function, 1)}
@@ -126,12 +127,12 @@ defmodule Mix.Compilers.ApplicationTracer do
end
warning = {:undefined_app, module_app, module, function, arity}
[{__MODULE__, warning, {env_file, env_line, env_mfa}} | acc]
{[{__MODULE__, warning, {env_file, env_line, env_mfa}} | acc], cache}
else
_ -> acc
{acc, cache}
end
end,
[],
{[], %{}},
table
)
@@ -140,15 +141,44 @@ defmodule Mix.Compilers.ApplicationTracer do
|> Module.ParallelChecker.emit_warnings()
end
# ../elixir/ebin/elixir.beam -> elixir
# ../ssl-9.6/ebin/ssl.beam -> ssl
defp app(path) do
case path |> Path.split() |> Enum.take(-3) do
[dir, "ebin", _beam] -> {:ok, dir |> String.split("-") |> hd()}
_ -> :error
defp app_for_module(module, cache) do
case cache do
%{^module => maybe_app} ->
{maybe_app, cache}
%{} ->
maybe_app = app_for_module(module)
{maybe_app, Map.put(cache, module, maybe_app)}
end
end
# ../elixir/ebin/elixir.beam -> elixir
# ../ssl-9.6/ebin/ssl.beam -> ssl
defp app_for_module(module) do
with [_ | _] = path when is_list(path) <- :code.which(module),
[_ | _] = app <-
path |> Enum.reverse() |> discard_dir() |> discard_ebin() |> collect_dir([]) do
List.to_string(app)
else
_ -> nil
end
end
defp discard_dir([?\\ | path]), do: path
defp discard_dir([?/ | path]), do: path
defp discard_dir([_ | path]), do: discard_dir(path)
defp discard_dir([]), do: []
defp discard_ebin(~c"nibe/" ++ path), do: path
defp discard_ebin(~c"nibe\\" ++ path), do: path
defp discard_ebin(_), do: []
defp collect_dir([?\\ | _], acc), do: acc
defp collect_dir([?/ | _], acc), do: acc
defp collect_dir([?- | path], _acc), do: collect_dir(path, [])
defp collect_dir([head | path], acc), do: collect_dir(path, [head | acc])
defp collect_dir([], acc), do: acc
def stop(pending_save_manifest \\ nil) do
try do
:ets.delete(warnings_table())
+5 -1
View File
@@ -465,7 +465,11 @@ defmodule Mix.Compilers.Elixir do
modules_set = Map.from_keys(modules, true)
{_, runtime_modules} = fixpoint_runtime_modules(sources, modules_set)
{:runtime, runtime_modules, warnings}
runtime_paths =
Enum.map(runtime_modules, &{&1, Path.join(compile_path, Atom.to_string(&1) <> ".beam")})
{:runtime, runtime_paths, warnings}
else
Mix.Utils.compiling_n(length(changed), :ex)
+4 -2
View File
@@ -202,7 +202,7 @@ defmodule Mix.Tasks.Format do
args
|> expand_args(dot_formatter, formatter_opts_and_subs, opts)
|> Task.async_stream(&format_file(&1, opts), ordered: false, timeout: 30000)
|> Task.async_stream(&format_file(&1, opts), ordered: false, timeout: :infinity)
|> Enum.reduce({[], []}, &collect_status/2)
|> check!()
end
@@ -551,7 +551,9 @@ defmodule Mix.Tasks.Format do
end
defp stdin_or_wildcard("-"), do: [:stdin]
defp stdin_or_wildcard(path), do: path |> Path.expand() |> Path.wildcard(match_dot: true)
defp stdin_or_wildcard(path),
do: path |> Path.expand() |> Path.wildcard(match_dot: true) |> Enum.filter(&File.regular?/1)
defp elixir_format(content, formatter_opts) do
case Code.format_string!(content, formatter_opts) do
+1
View File
@@ -173,6 +173,7 @@ defmodule Mix.Tasks.Test.Coverage do
if apps_paths = Mix.Project.apps_paths(config) do
build_path = Mix.Project.build_path(config)
apps_paths = Enum.sort(apps_paths)
compile_paths =
Enum.map(apps_paths, fn {app, _} ->
+19 -1
View File
@@ -654,13 +654,31 @@ defmodule Mix.Utils do
headers = [{'user-agent', 'Mix/#{System.version()}'}]
request = {:binary.bin_to_list(path), headers}
# Use the system certificates if available, otherwise skip peer verification
# TODO: Always use system certificates when OTP >= 25.1 is required
ssl_options =
if Code.ensure_loaded?(:httpc) and function_exported?(:httpc, :ssl_verify_host_options, 1) do
try do
apply(:httpc, :ssl_verify_host_options, [true])
rescue
_ ->
Mix.shell().error(
"warning: failed to load system certificates. SSL peer verification will be skipped but downloads are still verified with a checksum"
)
[verify: :verify_none]
end
else
[verify: :verify_none]
end
# We are using relaxed: true because some servers is returning a Location
# header with relative paths, which does not follow the spec. This would
# cause the request to fail with {:error, :no_scheme} unless :relaxed
# is given.
#
# If a proxy environment variable was supplied add a proxy to httpc.
http_options = [relaxed: true] ++ proxy_config(path)
http_options = [relaxed: true, ssl: ssl_options] ++ proxy_config(path)
# Silence the warning from OTP as we verify the contents
level = Logger.level()
+29 -16
View File
@@ -488,6 +488,10 @@ defmodule Mix.DepTest do
Mix.RemoteConverger.register(nil)
end
defp sorted_keys(map) do
map |> Map.keys() |> Enum.sort()
end
test "deps_paths" do
deps = [
{:abc_repo, "0.1.0", path: "custom/abc_repo"},
@@ -502,33 +506,42 @@ defmodule Mix.DepTest do
[:abc_repo, :git_repo, :deps_repo]
]
assert Map.keys(Mix.Project.deps_paths()) == [:abc_repo, :deps_repo, :git_repo]
assert sorted_keys(Mix.Project.deps_paths()) == [:abc_repo, :deps_repo, :git_repo]
assert Map.keys(Mix.Project.deps_paths(depth: 1)) == [:abc_repo, :deps_repo]
assert Map.keys(Mix.Project.deps_paths(depth: 2)) == [:abc_repo, :deps_repo, :git_repo]
assert Map.keys(Mix.Project.deps_paths(depth: 3)) == [:abc_repo, :deps_repo, :git_repo]
assert sorted_keys(Mix.Project.deps_paths(depth: 1)) == [:abc_repo, :deps_repo]
assert sorted_keys(Mix.Project.deps_paths(depth: 2)) == [:abc_repo, :deps_repo, :git_repo]
assert sorted_keys(Mix.Project.deps_paths(depth: 3)) == [:abc_repo, :deps_repo, :git_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo])) == [:abc_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:deps_repo])) == [:deps_repo, :git_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:git_repo])) == [:git_repo]
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo])) == [:abc_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 1)) == [:abc_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 1)) == [:deps_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 1)) == [:git_repo]
assert sorted_keys(Mix.Project.deps_paths(parents: [:deps_repo])) == [
:deps_repo,
:git_repo
]
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 2)) == [:abc_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 2)) == [:git_repo]
assert sorted_keys(Mix.Project.deps_paths(parents: [:git_repo])) == [:git_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 2)) ==
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 1)) == [:abc_repo]
assert sorted_keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 1)) == [
:deps_repo
]
assert sorted_keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 1)) == [:git_repo]
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo], depth: 2)) == [:abc_repo]
assert sorted_keys(Mix.Project.deps_paths(parents: [:git_repo], depth: 2)) == [:git_repo]
assert sorted_keys(Mix.Project.deps_paths(parents: [:deps_repo], depth: 2)) ==
[:deps_repo, :git_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo])) ==
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo])) ==
[:abc_repo, :deps_repo, :git_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 1)) ==
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 1)) ==
[:abc_repo, :deps_repo]
assert Map.keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 2)) ==
assert sorted_keys(Mix.Project.deps_paths(parents: [:abc_repo, :deps_repo], depth: 2)) ==
[:abc_repo, :deps_repo, :git_repo]
end)
end)
+10
View File
@@ -81,6 +81,16 @@ defmodule Mix.Tasks.FormatTest do
end)
end
test "does not try to format a directory that matches a given pattern", context do
in_tmp(context.test, fn ->
File.mkdir_p!("a.ex")
assert_raise Mix.Error, ~r"Could not find a file to format", fn ->
Mix.Tasks.Format.run(["*.ex"])
end
end)
end
test "reads file from stdin and prints to stdout", context do
in_tmp(context.test, fn ->
File.write!("a.ex", """