Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
175c8243b2 | ||
|
|
2c1a836db3 | ||
|
|
f29e18bca1 | ||
|
|
329442c481 | ||
|
|
01a88e7137 | ||
|
|
4def31f8ab | ||
|
|
88c75bcbf5 | ||
|
|
786f3ce797 | ||
|
|
98aeee34b6 | ||
|
|
6f7eaf1122 | ||
|
|
9593cef2f5 | ||
|
|
c78f426bf5 | ||
|
|
ff29628602 | ||
|
|
f5e42ba11a | ||
|
|
b0dae83353 | ||
|
|
40da7e8857 | ||
|
|
ac910c1cc2 | ||
|
|
5c340e488e | ||
|
|
defef37856 | ||
|
|
432d6fa1c2 | ||
|
|
24714c68a7 | ||
|
|
f088fc9cdb | ||
|
|
dddb8f7b19 | ||
|
|
2da3300bfc | ||
|
|
c7a841baae | ||
|
|
f3dfb72c69 | ||
|
|
7e149619f5 | ||
|
|
6dbb932bf3 | ||
|
|
0cb79b0ba1 | ||
|
|
265b9aaeb3 | ||
|
|
b9bd0e3dd9 | ||
|
|
6a3301f237 | ||
|
|
4911916f62 | ||
|
|
546a0db392 | ||
|
|
463f1aa593 | ||
|
|
a3a632efd1 | ||
|
|
b7a5fd7b56 | ||
|
|
04378bd9a6 | ||
|
|
fae36c5e49 | ||
|
|
712f24af0a | ||
|
|
8c9f303e37 | ||
|
|
1a2be16109 | ||
|
|
4adaac702e | ||
|
|
a1be4fbc86 | ||
|
|
d4e6a558cb | ||
|
|
4e3203f19a | ||
|
|
4a5fb1eb66 | ||
|
|
0fe6f68c34 | ||
|
|
5aa049ccb3 | ||
|
|
e4c86d4b5f | ||
|
|
e5033c94ce | ||
|
|
5d65d60721 |
@@ -55,7 +55,6 @@ jobs:
|
||||
git push origin $ref_name --force
|
||||
|
||||
build:
|
||||
needs: create_draft_release
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
@@ -80,26 +79,6 @@ jobs:
|
||||
otp: ${{ matrix.otp }}
|
||||
build_docs: ${{ matrix.build_docs }}
|
||||
|
||||
- name: "Attest release .exe provenance"
|
||||
uses: actions/attest-build-provenance@v2
|
||||
id: attest-exe-provenance
|
||||
with:
|
||||
subject-path: "elixir-otp-${{ matrix.otp }}.exe"
|
||||
- name: "Copy release .exe provenance"
|
||||
run: cp "$ATTESTATION" elixir-otp-${{ matrix.otp }}.exe.sigstore
|
||||
env:
|
||||
ATTESTATION: "${{ steps.attest-exe-provenance.outputs.bundle-path }}"
|
||||
|
||||
- name: "Attest release .zip provenance"
|
||||
uses: actions/attest-build-provenance@v2
|
||||
id: attest-zip-provenance
|
||||
with:
|
||||
subject-path: "elixir-otp-${{ matrix.otp }}.zip"
|
||||
- name: "Copy release .zip provenance"
|
||||
run: cp "$ATTESTATION" elixir-otp-${{ matrix.otp }}.zip.sigstore
|
||||
env:
|
||||
ATTESTATION: "${{ steps.attest-zip-provenance.outputs.bundle-path }}"
|
||||
|
||||
- name: "Attest docs provenance"
|
||||
uses: actions/attest-build-provenance@v2
|
||||
id: attest-docs-provenance
|
||||
@@ -112,11 +91,23 @@ jobs:
|
||||
env:
|
||||
ATTESTATION: "${{ steps.attest-docs-provenance.outputs.bundle-path }}"
|
||||
|
||||
- name: "Upload release artifacts"
|
||||
- name: Create Docs Hashes
|
||||
if: ${{ matrix.build_docs }}
|
||||
run: |
|
||||
shasum -a 1 Docs.zip > Docs.zip.sha1sum
|
||||
shasum -a 256 Docs.zip > Docs.zip.sha256sum
|
||||
|
||||
- name: "Upload linux release artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: elixir-otp-${{ matrix.otp }}
|
||||
path: elixir-otp-${{ matrix.otp }}*
|
||||
name: build-linux-elixir-otp-${{ matrix.otp }}
|
||||
path: elixir-otp-${{ matrix.otp }}.zip
|
||||
|
||||
- name: "Upload windows release artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-windows-elixir-otp-${{ matrix.otp }}
|
||||
path: elixir-otp-${{ matrix.otp }}.exe
|
||||
|
||||
- name: "Upload doc artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -125,20 +116,26 @@ jobs:
|
||||
name: Docs
|
||||
path: Docs.zip*
|
||||
|
||||
upload-release:
|
||||
needs: build
|
||||
runs-on: windows-2022
|
||||
sign:
|
||||
needs: [build]
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
otp: [25, 26, 27]
|
||||
flavor: [windows, linux]
|
||||
|
||||
env:
|
||||
RELEASE_FILE: elixir-otp-${{ matrix.otp }}.${{ matrix.flavor == 'linux' && 'zip' || 'exe' }}
|
||||
|
||||
runs-on: ${{ matrix.flavor == 'linux' && 'ubuntu-22.04' || 'windows-2022' }}
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
|
||||
- run: |
|
||||
mv elixir-otp-*/* .
|
||||
mv Docs/* .
|
||||
shell: bash
|
||||
with:
|
||||
name: build-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
|
||||
|
||||
- name: "Sign files with Trusted Signing"
|
||||
if: github.repository == 'elixir-lang/elixir'
|
||||
if: github.repository == 'elixir-lang/elixir' && matrix.flavor == 'windows'
|
||||
uses: azure/trusted-signing-action@v0.5.0
|
||||
with:
|
||||
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
@@ -153,6 +150,50 @@ jobs:
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
|
||||
- name: "Attest release provenance"
|
||||
uses: actions/attest-build-provenance@v2
|
||||
id: attest-provenance
|
||||
with:
|
||||
subject-path: ${{ env.RELEASE_FILE }}
|
||||
- name: "Copy release .zip provenance"
|
||||
shell: bash
|
||||
run: cp "$ATTESTATION" "${RELEASE_FILE}.sigstore"
|
||||
env:
|
||||
ATTESTATION: "${{ steps.attest-provenance.outputs.bundle-path }}"
|
||||
|
||||
- name: Create Release Hashes
|
||||
if: matrix.flavor == 'windows'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$sha1 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA1
|
||||
$sha1.Hash.ToLower() + " " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha1sum"
|
||||
|
||||
$sha256 = Get-FileHash "$env:RELEASE_FILE" -Algorithm SHA256
|
||||
$sha256.Hash.ToLower() + " " + $env:RELEASE_FILE | Out-File "$env:RELEASE_FILE.sha256sum"
|
||||
|
||||
- name: Create Release Hashes
|
||||
if: matrix.flavor == 'linux'
|
||||
shell: bash
|
||||
run: |
|
||||
shasum -a 1 "$RELEASE_FILE" > "${RELEASE_FILE}.sha1sum"
|
||||
shasum -a 256 "$RELEASE_FILE" > "${RELEASE_FILE}.sha256sum"
|
||||
|
||||
- name: "Upload linux release artifacts"
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sign-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
|
||||
path: ${{ env.RELEASE_FILE }}*
|
||||
|
||||
upload-release:
|
||||
needs: [create_draft_release, build, sign]
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: "{sign-*-elixir-otp-*,Docs}"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Upload Pre-built
|
||||
shell: bash
|
||||
env:
|
||||
@@ -179,7 +220,7 @@ jobs:
|
||||
Docs.zip.sigstore
|
||||
|
||||
upload-builds-hex-pm:
|
||||
needs: build
|
||||
needs: [build, sign]
|
||||
runs-on: ubuntu-22.04
|
||||
concurrency: builds-hex-pm
|
||||
env:
|
||||
@@ -193,6 +234,9 @@ jobs:
|
||||
OTP_GENERIC_VERSION: "25"
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: "{sign-*-elixir-otp-*,Docs}"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Init purge keys file
|
||||
run: |
|
||||
@@ -202,7 +246,6 @@ jobs:
|
||||
run: |
|
||||
ref_name=${{ github.ref_name }}
|
||||
|
||||
mv elixir-otp-*/* .
|
||||
for zip in $(find . -type f -name 'elixir-otp-*.zip' | sed 's/^\.\///'); do
|
||||
dest=${zip/elixir/${ref_name}}
|
||||
surrogate_key=${dest/.zip$/}
|
||||
@@ -221,7 +264,6 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Upload Docs to S3
|
||||
working-directory: Docs
|
||||
run: |
|
||||
version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ runs:
|
||||
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: Install NSIS
|
||||
shell: bash
|
||||
@@ -34,8 +32,6 @@ runs:
|
||||
export ELIXIR_ZIP=$PWD/elixir-otp-${{ inputs.otp }}.zip
|
||||
(cd lib/elixir/scripts/windows_installer && ./build.sh)
|
||||
mv lib/elixir/scripts/windows_installer/tmp/elixir-otp-${{ inputs.otp }}.exe .
|
||||
shasum -a 1 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha1sum
|
||||
shasum -a 256 elixir-otp-${{ inputs.otp }}.exe > elixir-otp-${{ inputs.otp }}.exe.sha256sum
|
||||
- name: Get ExDoc ref
|
||||
if: ${{ inputs.build_docs }}
|
||||
shell: bash
|
||||
@@ -66,5 +62,3 @@ runs:
|
||||
run: |
|
||||
git fetch --tags
|
||||
make Docs.zip
|
||||
shasum -a 1 Docs.zip > Docs.zip.sha1sum
|
||||
shasum -a 256 Docs.zip > Docs.zip.sha256sum
|
||||
|
||||
@@ -225,6 +225,70 @@ You may also prefer to write using guards:
|
||||
|
||||
def foo(x, y, z) when x == y and y == z
|
||||
|
||||
## v1.18.2 (2024-01-22)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Add `--color`/`--no-color` for enabling and disabling of ANSI colors
|
||||
* [Code.Fragment] Provide more AST context when invoking `container_cursor_to_quoted` with trailing fragments
|
||||
* [Regex] Ensure compatibility with Erlang/OTP 28+ new Regex engine
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix] Print compilation lock waiting message to stderr
|
||||
* [mix] Add an environment variable to optionally disable compilation locking
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Temporarily remove PowerShell scripts for `elixir`, `elixirc`, and `mix` on Windows, as they leave the shell broken after quitting Erlang
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit] Fix crash when diffing bitstring specifiers
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Autocomplete] Fix crashing whhen autocompleting structs with runtime values
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix] Track compilation locks per user to avoid permission errors
|
||||
* [mix deps.update] Ensure Git dependencies can be upgraded by doing so against the origin
|
||||
|
||||
## v1.18.1 (2024-12-24)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
* [Kernel] Do not emit type violation warnings when comparing or matching against literals
|
||||
* [Kernel] Do not validate clauses of private overridable functions
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Code.Fragment] Ensure `Code.Fragment.container_cursor_to_quoted/2` with `:trailing_fragment` parses expressions that were supported in previous versions
|
||||
* [Kernel] Do not crash when typing violation is detected on dynamic dispatch
|
||||
* [Kernel] Properly annotate the source for warnings emitted by the compiler with the `@file` annotation
|
||||
* [Kernel] Properly annotate the source for warnings emitted by the type system with the `@file` annotation
|
||||
* [Kernel] Remove `:no_parens` metadata when using capture with arity on all cases
|
||||
* [Kernel] Ensure diagnostic traces are kept backwards compatible
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Case] Ensure async groups do not run concurrenly while the test suite is still loading
|
||||
* [ExUnit.Case] Ensure `--repeat-until-failure` can be combined with groups
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile.elixir] Store compilation results if compilation fails due to `--warnings-as-errors`
|
||||
* [mix deps.loadpaths] Add build lock
|
||||
* [mix escript.build] Ensure build succeeds when protocol consolidation is disabled
|
||||
* [Mix.Shell] Ensure encoding is properly respected on Windows and Unix systems
|
||||
|
||||
## v1.18.0 (2024-12-19)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
@@ -2,7 +2,7 @@ PREFIX ?= /usr/local
|
||||
TEST_FILES ?= "*_test.exs"
|
||||
SHARE_PREFIX ?= $(PREFIX)/share
|
||||
MAN_PREFIX ?= $(SHARE_PREFIX)/man
|
||||
CANONICAL := v1.18/
|
||||
# CANONICAL := main/
|
||||
ELIXIRC := bin/elixirc --ignore-module-conflict $(ELIXIRC_OPTS)
|
||||
ERLC := erlc -I lib/elixir/include
|
||||
ERL_MAKE := erl -make
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
|
||||
### Back in main
|
||||
|
||||
1. Bump /VERSION file, bin/elixir and bin/elixir.bat
|
||||
1. Bump /VERSION file, bin/elixir, bin/elixir.bat, and bin/elixir.ps1
|
||||
|
||||
2. Start new /CHANGELOG.md
|
||||
|
||||
|
||||
+3
-2
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
ELIXIR_VERSION=1.18.0
|
||||
ELIXIR_VERSION=1.18.2
|
||||
|
||||
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
|
||||
cat <<USAGE >&2
|
||||
@@ -18,6 +18,7 @@ Usage: $(basename "$0") [options] [.exs file] [data]
|
||||
-pz "PATH" Appends the given path to Erlang code path (*)
|
||||
-v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
|
||||
--color, --no-color Enables or disables ANSI coloring
|
||||
--erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
--eval "COMMAND" Evaluates the given command, same as -e (*)
|
||||
--logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
@@ -111,7 +112,7 @@ while [ $I -le $LENGTH ]; do
|
||||
C=1
|
||||
MODE="iex"
|
||||
;;
|
||||
-v|--no-halt)
|
||||
-v|--no-halt|--color|--no-color)
|
||||
C=1
|
||||
;;
|
||||
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
|
||||
|
||||
+6
-3
@@ -1,6 +1,6 @@
|
||||
@echo off
|
||||
|
||||
set ELIXIR_VERSION=1.18.0
|
||||
set ELIXIR_VERSION=1.18.2
|
||||
|
||||
if ""%1""=="""" if ""%2""=="""" goto documentation
|
||||
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
|
||||
@@ -24,6 +24,7 @@ echo -pa "PATH" Prepends the given path to Erlang code path
|
||||
echo -pz "PATH" Appends the given path to Erlang code path (*)
|
||||
echo -v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
echo.
|
||||
echo --color, --no-color Enables or disables ANSI coloring
|
||||
echo --erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
echo --eval "COMMAND" Evaluates the given command, same as -e (*)
|
||||
echo --logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
@@ -107,6 +108,8 @@ if ""==!par:-pz=! (shift && goto startloop)
|
||||
if ""==!par:-v=! (goto startloop)
|
||||
if ""==!par:--version=! (goto startloop)
|
||||
if ""==!par:--no-halt=! (goto startloop)
|
||||
if ""==!par:--color=! (goto startloop)
|
||||
if ""==!par:--no-color=! (goto startloop)
|
||||
if ""==!par:--remsh=! (shift && goto startloop)
|
||||
if ""==!par:--dot-iex=! (shift && goto startloop)
|
||||
if ""==!par:--dbg=! (shift && goto startloop)
|
||||
@@ -133,9 +136,9 @@ if not defined useIEx (
|
||||
set beforeExtra=-noshell -elixir_root "%SCRIPT_PATH%..\lib" -pa "%SCRIPT_PATH%..\lib\elixir\ebin" %beforeExtra%
|
||||
|
||||
if defined ELIXIR_CLI_DRY_RUN (
|
||||
echo "%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
echo "%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
) else (
|
||||
"%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
"%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
|
||||
)
|
||||
exit /B %ERRORLEVEL%
|
||||
:end
|
||||
|
||||
-304
@@ -1,304 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
$ELIXIR_VERSION = "1.18.0"
|
||||
|
||||
$scriptPath = Split-Path -Parent $PSCommandPath
|
||||
$erlExec = "erl"
|
||||
|
||||
# The iex.ps1, elixirc.ps1 and mix.ps1 scripts may populate this var.
|
||||
if ($null -eq $allArgs) {
|
||||
$allArgs = $args
|
||||
}
|
||||
|
||||
function PrintElixirHelp {
|
||||
$scriptName = Split-Path -Leaf $PSCommandPath
|
||||
$help = @"
|
||||
Usage: $scriptName [options] [.exs file] [data]
|
||||
|
||||
## General options
|
||||
|
||||
-e "COMMAND" Evaluates the given command (*)
|
||||
-h, --help Prints this message (standalone)
|
||||
-r "FILE" Requires the given files/patterns (*)
|
||||
-S SCRIPT Finds and executes the given script in `$PATH
|
||||
-pr "FILE" Requires the given files/patterns in parallel (*)
|
||||
-pa "PATH" Prepends the given path to Erlang code path (*)
|
||||
-pz "PATH" Appends the given path to Erlang code path (*)
|
||||
-v, --version Prints Erlang/OTP and Elixir versions (standalone)
|
||||
|
||||
--erl "SWITCHES" Switches to be passed down to Erlang (*)
|
||||
--eval "COMMAND" Evaluates the given command, same as -e (*)
|
||||
--logger-otp-reports BOOL Enables or disables OTP reporting
|
||||
--logger-sasl-reports BOOL Enables or disables SASL reporting
|
||||
--no-halt Does not halt the Erlang VM after execution
|
||||
--short-version Prints Elixir version (standalone)
|
||||
|
||||
Options given after the .exs file or -- are passed down to the executed code.
|
||||
Options can be passed to the Erlang runtime using `$ELIXIR_ERL_OPTIONS or --erl.
|
||||
|
||||
## Distribution options
|
||||
|
||||
The following options are related to node distribution.
|
||||
|
||||
--cookie COOKIE Sets a cookie for this distributed node
|
||||
--hidden Makes a hidden node
|
||||
--name NAME Makes and assigns a name to the distributed node
|
||||
--rpc-eval NODE "COMMAND" Evaluates the given command on the given remote node (*)
|
||||
--sname NAME Makes and assigns a short name to the distributed node
|
||||
|
||||
--name and --sname may be set to undefined so one is automatically generated.
|
||||
|
||||
## Release options
|
||||
|
||||
The following options are generally used under releases.
|
||||
|
||||
--boot "FILE" Uses the given FILE.boot to start the system
|
||||
--boot-var VAR "VALUE" Makes `$VAR available as VALUE to FILE.boot (*)
|
||||
--erl-config "FILE" Loads configuration in FILE.config written in Erlang (*)
|
||||
--vm-args "FILE" Passes the contents in file as arguments to the VM
|
||||
|
||||
--pipe-to is not supported via PowerShell.
|
||||
|
||||
** Options marked with (*) can be given more than once.
|
||||
** Standalone options can't be combined with other options.
|
||||
"@
|
||||
|
||||
Write-Host $help
|
||||
}
|
||||
|
||||
if (($allArgs.Count -eq 1) -and ($allArgs[0] -eq "--short-version")) {
|
||||
Write-Host "$ELIXIR_VERSION"
|
||||
exit
|
||||
}
|
||||
|
||||
if (($allArgs.Count -eq 0) -or (($allArgs.Count -eq 1) -and ($allArgs[0] -in @("-h", "--help")))) {
|
||||
PrintElixirHelp
|
||||
exit 1
|
||||
}
|
||||
|
||||
function NormalizeArg {
|
||||
param(
|
||||
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
|
||||
[string[]] $Items
|
||||
)
|
||||
$Items -join ","
|
||||
}
|
||||
|
||||
function QuoteString {
|
||||
param(
|
||||
[Parameter(ValueFromPipeline = $true)]
|
||||
[string] $Item
|
||||
)
|
||||
|
||||
# We surround the string with double quotes, in order to preserve its contents as
|
||||
# only one command arg.
|
||||
# This is needed because PowerShell consider spaces as separator of arguments.
|
||||
# The double quotes around will be removed when PowerShell process the argument.
|
||||
# See: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_quoting_rules?view=powershell-7.4#passing-quoted-strings-to-external-commands
|
||||
if ($Item.Contains(" ")) {
|
||||
'"' + $Item + '"'
|
||||
}
|
||||
else {
|
||||
$Item
|
||||
}
|
||||
}
|
||||
|
||||
$elixirParams = @()
|
||||
$erlangParams = @()
|
||||
$beforeExtras = @()
|
||||
$allOtherParams = @()
|
||||
|
||||
$runErlPipe = $null
|
||||
$runErlLog = $null
|
||||
|
||||
for ($i = 0; $i -lt $allArgs.Count; $i++) {
|
||||
$private:arg = $allArgs[$i]
|
||||
|
||||
switch -exact ($arg) {
|
||||
{ $_ -in @("-e", "-r", "-pr", "-pa", "-pz", "--eval", "--remsh", "--dot-iex", "--dbg") } {
|
||||
$private:nextArg = NormalizeArg($allArgs[++$i])
|
||||
|
||||
$elixirParams += $arg
|
||||
$elixirParams += $nextArg
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
{ $_ -in @("-v", "--version") } {
|
||||
# Standalone options goes only once in the Elixir params, when they are empty.
|
||||
if (($elixirParams.Count -eq 0) -and ($allOtherParams.Count -eq 0)) {
|
||||
$elixirParams += $arg
|
||||
}
|
||||
else {
|
||||
$allOtherParams += $arg
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
"--no-halt" {
|
||||
$elixirParams += $arg
|
||||
break
|
||||
}
|
||||
|
||||
"--cookie" {
|
||||
$erlangParams += "-setcookie"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--hidden" {
|
||||
$erlangParams += "-hidden"
|
||||
break
|
||||
}
|
||||
|
||||
"--name" {
|
||||
$erlangParams += "-name"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--sname" {
|
||||
$erlangParams += "-sname"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--boot" {
|
||||
$erlangParams += "-boot"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--erl-config" {
|
||||
$erlangParams += "-config"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--vm-args" {
|
||||
$erlangParams += "-args_file"
|
||||
$erlangParams += $allArgs[++$i]
|
||||
break
|
||||
}
|
||||
|
||||
"--logger-otp-reports" {
|
||||
$private:tempVal = $allArgs[$i + 1]
|
||||
if ($tempVal -in @("true", "false")) {
|
||||
$erlangParams += @("-logger", "handle_otp_reports", $allArgs[++$i])
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
"--logger-sasl-reports" {
|
||||
$private:tempVal = $allArgs[$i + 1]
|
||||
if ($tempVal -in @("true", "false")) {
|
||||
$erlangParams += @("-logger", "handle_sasl_reports", $allArgs[++$i])
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
"--erl" {
|
||||
$private:erlFlags = $allArgs[++$i] -split " "
|
||||
$beforeExtras += $erlFlags
|
||||
break
|
||||
}
|
||||
|
||||
"+iex" {
|
||||
$elixirParams += "+iex"
|
||||
$useIex = $true
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
"+elixirc" {
|
||||
$elixirParams += "+elixirc"
|
||||
break
|
||||
}
|
||||
|
||||
"--rpc-eval" {
|
||||
$private:key = $allArgs[++$i]
|
||||
$private:value = $allArgs[++$i]
|
||||
|
||||
if ($null -eq $key) {
|
||||
Write-Error "--rpc-eval: NODE must be present"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($null -eq $value) {
|
||||
Write-Error "--rpc-eval: COMMAND for the '$key' node must be present"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$elixirParams += "--rpc-eval"
|
||||
$elixirParams += $key
|
||||
$elixirParams += $value
|
||||
break
|
||||
}
|
||||
|
||||
"--boot-var" {
|
||||
$private:key = $allArgs[++$i]
|
||||
$private:value = $allArgs[++$i]
|
||||
|
||||
if ($null -eq $key) {
|
||||
Write-Error "--boot-var: VAR must be present"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($null -eq $value) {
|
||||
Write-Error "--boot-var: Value for the '$key' var must be present"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$elixirParams += "-boot_var"
|
||||
$elixirParams += $key
|
||||
$elixirParams += $value
|
||||
break
|
||||
}
|
||||
|
||||
Default {
|
||||
$private:normalized = NormalizeArg $arg
|
||||
$allOtherParams += $normalized
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($null -eq $useIEx) {
|
||||
$beforeExtras = @("-s", "elixir", "start_cli") + $beforeExtras
|
||||
}
|
||||
|
||||
$beforeExtras = @("-pa", "$(Join-Path $scriptPath -ChildPath "../lib/elixir/ebin")") + $beforeExtras
|
||||
$beforeExtras = @("-noshell", "-elixir_root", "$(Join-Path $scriptPath -ChildPath "../lib")") + $beforeExtras
|
||||
|
||||
$allParams = @()
|
||||
|
||||
if ($null -ne $env:ELIXIR_ERL_OPTIONS) {
|
||||
$private:erlFlags = $env:ELIXIR_ERL_OPTIONS -split " "
|
||||
$allParams += $erlFlags
|
||||
}
|
||||
|
||||
$allParams += $erlangParams
|
||||
$allParams += $beforeExtras
|
||||
$allParams += "-extra"
|
||||
$allParams += $elixirParams
|
||||
$allParams += $allOtherParams
|
||||
|
||||
$binSuffix = ""
|
||||
|
||||
# The variable is available after PowerShell 7.2. Previous to that, PS only worked on Windows.
|
||||
if ($isWindows -or ($null -eq $isWindows)) {
|
||||
$binSuffix = ".exe"
|
||||
}
|
||||
|
||||
$binPath = "$erlExec$binSuffix"
|
||||
|
||||
# We double the double-quotes because they are going to be escaped by arguments parsing.
|
||||
$paramsPart = $allParams | ForEach-Object -Process { QuoteString($_ -replace "`"", "`"`"") }
|
||||
|
||||
if ($env:ELIXIR_CLI_DRY_RUN) {
|
||||
Write-Host "$binPath $paramsPart"
|
||||
}
|
||||
else {
|
||||
$output = Start-Process -FilePath $binPath -ArgumentList $paramsPart -NoNewWindow -Wait -PassThru
|
||||
exit $output.ExitCode
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
$scriptName = Split-Path -Leaf $PSCommandPath
|
||||
|
||||
if (($args.Count -eq 0) -or ($args[0] -in @("-h", "--help"))) {
|
||||
Write-Host @"
|
||||
Usage: $scriptName [elixir switches] [compiler switches] [.ex files]
|
||||
|
||||
-h, --help Prints this message and exits
|
||||
-o The directory to output compiled files
|
||||
-v, --version Prints Elixir version and exits (standalone)
|
||||
|
||||
--ignore-module-conflict Does not emit warnings if a module was previously defined
|
||||
--no-debug-info Does not attach debug info to compiled modules
|
||||
--no-docs Does not attach documentation to compiled modules
|
||||
--profile time Profile the time to compile modules
|
||||
--verbose Prints compilation status
|
||||
--warnings-as-errors Treats warnings as errors and returns non-zero exit status
|
||||
|
||||
** Options given after -- are passed down to the executed code
|
||||
** Options can be passed to the Erlang runtime using ELIXIR_ERL_OPTIONS
|
||||
** Options can be passed to the Erlang compiler using ERL_COMPILER_OPTIONS
|
||||
"@
|
||||
exit
|
||||
}
|
||||
|
||||
$scriptPath = Split-Path -Parent $PSCommandPath
|
||||
$elixirMainScript = Join-Path -Path $scriptPath -ChildPath "elixir.ps1"
|
||||
|
||||
$prependedArgs = @("+elixirc")
|
||||
|
||||
$allArgs = $prependedArgs + $args
|
||||
|
||||
# The dot is going to evaluate the script with the vars defined here.
|
||||
. $elixirMainScript
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
$scriptName = Split-Path -Leaf $PSCommandPath
|
||||
|
||||
if ($args[0] -in @("-h", "--help")) {
|
||||
Write-Host @"
|
||||
Usage: $scriptName [options] [.exs file] [data]
|
||||
|
||||
The following options are exclusive to IEx:
|
||||
|
||||
--dbg pry Sets the backend for Kernel.dbg/2 to IEx.pry/0
|
||||
--dot-iex "FILE" Evaluates FILE, line by line, to set up IEx' environment.
|
||||
Defaults to evaluating .iex.exs or ~/.iex.exs, if any exists.
|
||||
If FILE is empty, then no file will be loaded.
|
||||
--remsh NAME Connects to a node using a remote shell.
|
||||
|
||||
It accepts all other options listed by "elixir --help".
|
||||
"@
|
||||
exit
|
||||
}
|
||||
|
||||
$scriptPath = Split-Path -Parent $PSCommandPath
|
||||
$elixirMainScript = Join-Path -Path $scriptPath -ChildPath "elixir.ps1"
|
||||
|
||||
$prependedArgs = @("--no-halt", "--erl", "-user elixir", "+iex")
|
||||
|
||||
$allArgs = $prependedArgs + $args
|
||||
|
||||
# The dot is going to evaluate the script with the vars defined here.
|
||||
. $elixirMainScript
|
||||
+21
-11
@@ -1,13 +1,23 @@
|
||||
#!/usr/bin/env pwsh
|
||||
# Store path to mix.bat as a FileInfo object
|
||||
$mixBatPath = (Get-ChildItem (((Get-ChildItem $MyInvocation.MyCommand.Path).Directory.FullName) + '\mix.bat'))
|
||||
$newArgs = @()
|
||||
|
||||
$scriptPath = Split-Path -Parent $PSCommandPath
|
||||
$elixirMainScript = Join-Path -Path $scriptPath -ChildPath "elixir.ps1"
|
||||
for ($i = 0; $i -lt $args.length; $i++)
|
||||
{
|
||||
if ($args[$i] -is [array])
|
||||
{
|
||||
# Commas created the array so we need to reintroduce those commas
|
||||
for ($j = 0; $j -lt $args[$i].length - 1; $j++)
|
||||
{
|
||||
$newArgs += ($args[$i][$j] + ',')
|
||||
}
|
||||
$newArgs += $args[$i][-1]
|
||||
}
|
||||
else
|
||||
{
|
||||
$newArgs += $args[$i]
|
||||
}
|
||||
}
|
||||
|
||||
$mixFile = Join-Path -Path $scriptPath -ChildPath "mix"
|
||||
|
||||
$prependedArgs = @($mixFile)
|
||||
|
||||
$allArgs = $prependedArgs + $args
|
||||
|
||||
# The dot is going to evaluate the script with the vars defined here.
|
||||
. $elixirMainScript
|
||||
# Corrected arguments are ready to pass to batch file
|
||||
& $mixBatPath $newArgs
|
||||
@@ -1709,7 +1709,9 @@ defmodule Code do
|
||||
def put_compiler_option(:warnings_as_errors, _value) do
|
||||
IO.warn(
|
||||
":warnings_as_errors is deprecated as part of Code.put_compiler_option/2, " <>
|
||||
"pass it as option to Kernel.ParallelCompiler instead"
|
||||
"instead you must pass it as a --warnings-as-errors flag. " <>
|
||||
"If you need to set it as a default in a mix task, you can also set it under aliases: " <>
|
||||
"[compile: \"compile --warnings-as-errors\"]"
|
||||
)
|
||||
|
||||
:ok
|
||||
|
||||
@@ -1151,7 +1151,7 @@ defmodule Code.Fragment do
|
||||
{rev_tokens, rev_terminators} =
|
||||
with [close, open, {_, _, :__cursor__} = cursor | rev_tokens] <- rev_tokens,
|
||||
{_, [_ | after_fn]} <- Enum.split_while(rev_terminators, &(elem(&1, 0) != :fn)),
|
||||
true <- maybe_missing_stab?(rev_tokens),
|
||||
true <- maybe_missing_stab?(rev_tokens, false),
|
||||
[_ | rev_tokens] <- Enum.drop_while(rev_tokens, &(elem(&1, 0) != :fn)) do
|
||||
{[close, open, cursor | rev_tokens], after_fn}
|
||||
else
|
||||
@@ -1165,7 +1165,7 @@ defmodule Code.Fragment do
|
||||
tokens =
|
||||
with {before_start, [_ | _] = after_start} <-
|
||||
Enum.split_while(rev_terminators, &(elem(&1, 0) not in [:do, :fn])),
|
||||
true <- maybe_missing_stab?(rev_tokens),
|
||||
true <- maybe_missing_stab?(rev_tokens, true),
|
||||
opts =
|
||||
Keyword.put(opts, :check_terminators, {:cursor, before_start}),
|
||||
{:error, {meta, _, ~c"end"}, _rest, _warnings, trailing_rev_tokens} <-
|
||||
@@ -1173,6 +1173,14 @@ defmodule Code.Fragment do
|
||||
trailing_tokens =
|
||||
reverse_tokens(meta[:line], meta[:column], trailing_rev_tokens, after_start)
|
||||
|
||||
# If the cursor has its own line, then we do not trim new lines trailing tokens.
|
||||
# Otherwise we want to drop any newline so we drop the next tokens after eol.
|
||||
trailing_tokens =
|
||||
case rev_tokens do
|
||||
[_close, _open, {_, _, :__cursor__}, {:eol, _} | _] -> trailing_tokens
|
||||
_ -> Enum.drop_while(trailing_tokens, &match?({:eol, _}, &1))
|
||||
end
|
||||
|
||||
Enum.reverse(rev_tokens, drop_tokens(trailing_tokens, 0))
|
||||
else
|
||||
_ -> reverse_tokens(line, column, rev_tokens, rev_terminators)
|
||||
@@ -1196,12 +1204,16 @@ defmodule Code.Fragment do
|
||||
Enum.reverse(tokens, terminators)
|
||||
end
|
||||
|
||||
# Otherwise we drop all tokens, trying to build a minimal AST
|
||||
# for cursor completion.
|
||||
defp drop_tokens([{:"}", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:"]", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:")", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:">>", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:end, _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:",", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:";", _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:eol, _} | _] = tokens, 0), do: tokens
|
||||
defp drop_tokens([{:stab_op, _, :->} | _] = tokens, 0), do: tokens
|
||||
|
||||
defp drop_tokens([{:"}", _} | tokens], counter), do: drop_tokens(tokens, counter - 1)
|
||||
@@ -1220,16 +1232,13 @@ defmodule Code.Fragment do
|
||||
defp drop_tokens([_ | tokens], counter), do: drop_tokens(tokens, counter)
|
||||
defp drop_tokens([], 0), do: []
|
||||
|
||||
defp maybe_missing_stab?([{:after, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:do, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:fn, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:else, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:catch, _} | _]), do: true
|
||||
defp maybe_missing_stab?([{:rescue, _} | _]), do: true
|
||||
|
||||
defp maybe_missing_stab?([{:stab_op, _, :->} | _]), do: false
|
||||
defp maybe_missing_stab?([{:eol, _}, next | _]) when elem(next, 0) != :",", do: false
|
||||
|
||||
defp maybe_missing_stab?([_ | tail]), do: maybe_missing_stab?(tail)
|
||||
defp maybe_missing_stab?([]), do: false
|
||||
defp maybe_missing_stab?([{:after, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:do, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:fn, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:else, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:catch, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:rescue, _} | _], _stab_choice?), do: true
|
||||
defp maybe_missing_stab?([{:stab_op, _, :->} | _], stab_choice?), do: stab_choice?
|
||||
defp maybe_missing_stab?([_ | tail], stab_choice?), do: maybe_missing_stab?(tail, stab_choice?)
|
||||
defp maybe_missing_stab?([], _stab_choice?), do: false
|
||||
end
|
||||
|
||||
+31
-32
@@ -4584,6 +4584,9 @@ defmodule Kernel do
|
||||
defp in_var(true, {atom, _, context} = var, fun) when is_atom(atom) and is_atom(context),
|
||||
do: fun.(var)
|
||||
|
||||
defp in_var(true, var, fun) when is_atom(var) or is_binary(var) or is_number(var),
|
||||
do: fun.(var)
|
||||
|
||||
defp in_var(true, ast, fun) do
|
||||
quote do
|
||||
var = unquote(ast)
|
||||
@@ -5460,33 +5463,21 @@ defmodule Kernel do
|
||||
use `@type`.
|
||||
"""
|
||||
defmacro defstruct(fields) do
|
||||
header =
|
||||
quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
|
||||
{struct, derive, escaped_struct, kv, body} =
|
||||
Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
|
||||
quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
|
||||
{struct, derive, escaped_struct, kv, body} =
|
||||
Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
|
||||
|
||||
case derive do
|
||||
[] -> :ok
|
||||
_ -> Protocol.__derive__(derive, __MODULE__, __ENV__)
|
||||
end
|
||||
case derive do
|
||||
[] -> :ok
|
||||
_ -> Protocol.__derive__(derive, __MODULE__, __ENV__)
|
||||
end
|
||||
|
||||
# We attach the line: 0 to struct functions because we don't want
|
||||
# the generated callbacks to count towards code coverage and metrics,
|
||||
# especially since they are often expanded at compile-time.
|
||||
functions =
|
||||
quote line: 0, unquote: false do
|
||||
def __struct__(), do: unquote(escaped_struct)
|
||||
def __struct__(unquote(kv)), do: unquote(body)
|
||||
end
|
||||
def __struct__(), do: unquote(escaped_struct)
|
||||
def __struct__(unquote(kv)), do: unquote(body)
|
||||
|
||||
footer =
|
||||
quote do
|
||||
Kernel.Utils.announce_struct(__MODULE__)
|
||||
struct
|
||||
end
|
||||
|
||||
{:__block__, [], [header, functions, footer]}
|
||||
Kernel.Utils.announce_struct(__MODULE__)
|
||||
struct
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
@@ -6446,29 +6437,37 @@ defmodule Kernel do
|
||||
"""
|
||||
defmacro sigil_r(term, modifiers)
|
||||
|
||||
defmacro sigil_r({:<<>>, _meta, [string]}, options) when is_binary(string) do
|
||||
binary = :elixir_interpolation.unescape_string(string, ®ex_unescape_map/1)
|
||||
regex = Regex.compile!(binary, :binary.list_to_bin(options))
|
||||
Macro.escape(regex)
|
||||
defmacro sigil_r({:<<>>, _meta, [binary]}, options) when is_binary(binary) do
|
||||
binary = :elixir_interpolation.unescape_string(binary, ®ex_unescape_map/1)
|
||||
compile_regex(binary, options)
|
||||
end
|
||||
|
||||
defmacro sigil_r({:<<>>, meta, pieces}, options) do
|
||||
binary = {:<<>>, meta, unescape_tokens(pieces, ®ex_unescape_map/1)}
|
||||
quote(do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options))))
|
||||
tuple = {:<<>>, meta, unescape_tokens(pieces, ®ex_unescape_map/1)}
|
||||
compile_regex(tuple, options)
|
||||
end
|
||||
|
||||
defp regex_unescape_map(:newline), do: true
|
||||
defp regex_unescape_map(_), do: false
|
||||
|
||||
@doc false
|
||||
defmacro sigil_R({:<<>>, _meta, [string]}, options) when is_binary(string) do
|
||||
defmacro sigil_R({:<<>>, _meta, [binary]}, options) when is_binary(binary) do
|
||||
IO.warn(
|
||||
"~R/.../ is deprecated, use ~r/.../ instead",
|
||||
Macro.Env.stacktrace(__CALLER__)
|
||||
)
|
||||
|
||||
regex = Regex.compile!(string, :binary.list_to_bin(options))
|
||||
Macro.escape(regex)
|
||||
compile_regex(binary, options)
|
||||
end
|
||||
|
||||
defp compile_regex(binary_or_tuple, options) do
|
||||
case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
|
||||
true ->
|
||||
Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options)))
|
||||
|
||||
false ->
|
||||
quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(:binary.list_to_bin(options))))
|
||||
end
|
||||
end
|
||||
|
||||
@doc ~S"""
|
||||
|
||||
@@ -295,6 +295,16 @@ defmodule Kernel.CLI do
|
||||
parse_argv(t, %{config | commands: [{:parallel_require, h} | config.commands]})
|
||||
end
|
||||
|
||||
defp parse_argv([~c"--color" | t], config) do
|
||||
Application.put_env(:elixir, :ansi_enabled, true)
|
||||
parse_argv(t, config)
|
||||
end
|
||||
|
||||
defp parse_argv([~c"--no-color" | t], config) do
|
||||
Application.put_env(:elixir, :ansi_enabled, true)
|
||||
parse_argv(t, config)
|
||||
end
|
||||
|
||||
## Compiler
|
||||
|
||||
defp parse_argv([~c"-o", h | t], %{mode: :elixirc} = config) do
|
||||
|
||||
@@ -257,28 +257,13 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
{status, modules_or_errors, info} =
|
||||
try do
|
||||
outcome = spawn_workers(schedulers, cache, files, output, options)
|
||||
{outcome, Keyword.get(options, :warnings_as_errors, false)}
|
||||
spawn_workers(schedulers, cache, files, output, options)
|
||||
else
|
||||
{{:ok, _, %{runtime_warnings: r_warnings, compile_warnings: c_warnings} = info}, true}
|
||||
when r_warnings != [] or c_warnings != [] ->
|
||||
message =
|
||||
"Compilation failed due to warnings while using the --warnings-as-errors option"
|
||||
|
||||
IO.puts(:stderr, message)
|
||||
errors = Enum.map(r_warnings ++ c_warnings, &Map.replace!(&1, :severity, :error))
|
||||
{:error, errors, %{info | runtime_warnings: [], compile_warnings: []}}
|
||||
|
||||
{{:ok, outcome, info}, _} ->
|
||||
{:ok, outcome, info} ->
|
||||
beam_timestamp = Keyword.get(options, :beam_timestamp)
|
||||
{:ok, write_module_binaries(outcome, output, beam_timestamp), info}
|
||||
|
||||
{{:error, errors, info}, true} ->
|
||||
%{runtime_warnings: r_warnings, compile_warnings: c_warnings} = info
|
||||
info = %{info | runtime_warnings: [], compile_warnings: []}
|
||||
{:error, c_warnings ++ r_warnings ++ errors, info}
|
||||
|
||||
{{:error, errors, info}, _} ->
|
||||
{:error, errors, info} ->
|
||||
{:error, errors, info}
|
||||
after
|
||||
Module.ParallelChecker.stop(cache)
|
||||
|
||||
@@ -938,7 +938,7 @@ defmodule Kernel.Typespec do
|
||||
|
||||
## Helpers
|
||||
|
||||
# This is a backport of Macro.expand/2 because we want to expand
|
||||
# This is a modified backport of Macro.expand/2 because we want to expand
|
||||
# aliases but we don't them to become compile-time references.
|
||||
defp expand_remote({:__aliases__, meta, list} = alias, env) do
|
||||
case :elixir_aliases.expand_or_concat(meta, list, env, true) do
|
||||
@@ -946,9 +946,12 @@ defmodule Kernel.Typespec do
|
||||
receiver
|
||||
|
||||
[head | tail] ->
|
||||
case Macro.expand_once(head, env) do
|
||||
head when is_atom(head) -> :elixir_aliases.concat([head | tail])
|
||||
_ -> alias
|
||||
case Macro.expand(head, env) do
|
||||
head when is_atom(head) ->
|
||||
:elixir_aliases.concat([head | tail])
|
||||
|
||||
_ ->
|
||||
compile_error(env, "unexpected expression in typespec: #{Macro.to_string(alias)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -174,14 +174,14 @@ defmodule Kernel.Utils do
|
||||
true ->
|
||||
case enforce_keys do
|
||||
[] ->
|
||||
quote do
|
||||
quote line: 0, generated: true do
|
||||
Enum.reduce(kv, unquote(escaped_struct), fn {key, val}, map ->
|
||||
%{map | key => val}
|
||||
end)
|
||||
end
|
||||
|
||||
_ ->
|
||||
quote do
|
||||
quote line: 0, generated: true do
|
||||
{map, keys} =
|
||||
Enum.reduce(kv, {unquote(escaped_struct), unquote(enforce_keys)}, fn
|
||||
{key, val}, {map, keys} ->
|
||||
@@ -201,7 +201,7 @@ defmodule Kernel.Utils do
|
||||
end
|
||||
|
||||
false ->
|
||||
quote do
|
||||
quote line: 0, generated: true do
|
||||
:lists.foldl(
|
||||
fn {key, val}, acc -> %{acc | key => val} end,
|
||||
unquote(escaped_struct),
|
||||
|
||||
@@ -146,8 +146,6 @@ defmodule Macro do
|
||||
* `:delimiter` - contains the opening delimiter for sigils, strings,
|
||||
and charlists as a string (such as `"{"`, `"/"`, `"'"`, and the like)
|
||||
|
||||
* `:format` - set to `:keyword` when an atom is defined as a keyword
|
||||
|
||||
* `:do` - contains metadata about the `do` location in a function call with
|
||||
`do`-`end` blocks (when `:token_metadata` is true)
|
||||
|
||||
@@ -159,10 +157,18 @@ defmodule Macro do
|
||||
expressions inside "blocks of code", which are either direct children
|
||||
of a `__block__` or the right side of `->`. The last expression of the
|
||||
block does not have metadata if it is not followed by an end of line
|
||||
character (either a newline or `;`)
|
||||
character (either a newline or `;`). This entry may appear multiple times
|
||||
in the same metadata if the expression is surround by parens
|
||||
|
||||
* `:format` - set to `:keyword` when an atom is defined as a keyword.
|
||||
It may also be set to `:atom` to distinguish `nil`, `false`, and `true`
|
||||
|
||||
* `:indentation` - indentation of a sigil heredoc
|
||||
|
||||
* `:parens` - denotes a node was surrounded by parens for grouping.
|
||||
This entry may appear multiple times in the same metadata if
|
||||
multiple pairs are used for grouping
|
||||
|
||||
The following metadata keys are private:
|
||||
|
||||
* `:alias` - Used for alias hygiene.
|
||||
|
||||
@@ -239,7 +239,7 @@ defmodule Module.ParallelChecker do
|
||||
|> Module.Types.warnings(file, definitions, no_warn_undefined, cache)
|
||||
|> Kernel.++(behaviour_warnings)
|
||||
|> group_warnings()
|
||||
|> emit_warnings(log?)
|
||||
|> emit_warnings(file, log?)
|
||||
|
||||
Enum.each(after_verify, fn {verify_mod, verify_fun} ->
|
||||
apply(verify_mod, verify_fun, [module])
|
||||
@@ -320,9 +320,9 @@ defmodule Module.ParallelChecker do
|
||||
Enum.sort(ungrouped ++ grouped)
|
||||
end
|
||||
|
||||
defp emit_warnings(warnings, log?) do
|
||||
defp emit_warnings(warnings, file, log?) do
|
||||
Enum.flat_map(warnings, fn {locations, diagnostic} ->
|
||||
diagnostics = Enum.map(locations, &to_diagnostic(diagnostic, &1))
|
||||
diagnostics = Enum.map(locations, &to_diagnostic(diagnostic, file, &1))
|
||||
log? and print_diagnostics(diagnostics)
|
||||
diagnostics
|
||||
end)
|
||||
@@ -336,10 +336,12 @@ defmodule Module.ParallelChecker do
|
||||
:elixir_errors.print_diagnostics(diagnostics)
|
||||
end
|
||||
|
||||
defp to_diagnostic(diagnostic, {file, position, mfa}) when is_list(position) do
|
||||
defp to_diagnostic(diagnostic, source, {file, position, mfa}) when is_list(position) do
|
||||
file = Path.absname(file)
|
||||
|
||||
%{
|
||||
severity: :warning,
|
||||
source: file,
|
||||
source: source,
|
||||
file: file,
|
||||
position: position_to_tuple(position),
|
||||
stacktrace: [to_stacktrace(file, position, mfa)],
|
||||
|
||||
@@ -182,9 +182,12 @@ defmodule Module.Types do
|
||||
end
|
||||
|
||||
defp warn_unused_clauses(defs, stack, context) do
|
||||
for {fun_arity, pending} <- context.local_used, pending != [], reduce: context do
|
||||
for {fun_arity, pending} <- context.local_used,
|
||||
pending != [],
|
||||
{_fun_arity, kind, meta, clauses} = List.keyfind(defs, fun_arity, 0),
|
||||
not Keyword.get(meta, :from_super, false),
|
||||
reduce: context do
|
||||
context ->
|
||||
{_fun_arity, kind, _meta, clauses} = List.keyfind(defs, fun_arity, 0)
|
||||
{_kind, _inferred, mapping} = Map.fetch!(context.local_sigs, fun_arity)
|
||||
|
||||
clauses_indexes =
|
||||
|
||||
@@ -269,7 +269,7 @@ defmodule Module.Types.Apply do
|
||||
|
||||
case signature(name, arity) do
|
||||
:none -> {dynamic(), context}
|
||||
info -> apply_remote(info, args_types, expr, stack, context)
|
||||
info -> apply_remote(nil, name, info, args_types, expr, stack, context)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -287,10 +287,14 @@ defmodule Module.Types.Apply do
|
||||
{value_type, context}
|
||||
|
||||
:badtuple ->
|
||||
{error_type(), badremote_error(expr, [integer(), tuple], stack, context)}
|
||||
{error_type(),
|
||||
badremote_error(:erlang, :element, expr, [integer(), tuple], stack, context)}
|
||||
|
||||
reason ->
|
||||
{error_type(), error({reason, expr, tuple, index - 1, context}, meta, stack, context)}
|
||||
:badindex ->
|
||||
mfac = mfac(expr, :erlang, :element, 2)
|
||||
|
||||
{error_type(),
|
||||
error({:badindex, mfac, expr, tuple, index - 1, context}, meta, stack, context)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -308,10 +312,16 @@ defmodule Module.Types.Apply do
|
||||
{value_type, context}
|
||||
|
||||
:badtuple ->
|
||||
{error_type(), badremote_error(expr, [integer(), tuple, value], stack, context)}
|
||||
args_types = [integer(), tuple, value]
|
||||
|
||||
reason ->
|
||||
{error_type(), error({reason, expr, tuple, index - 2, context}, meta, stack, context)}
|
||||
{error_type(),
|
||||
badremote_error(:erlang, :insert_element, expr, args_types, stack, context)}
|
||||
|
||||
:badindex ->
|
||||
mfac = mfac(expr, :erlang, :insert_element, 3)
|
||||
|
||||
{error_type(),
|
||||
error({:badindex, mfac, expr, tuple, index - 2, context}, meta, stack, context)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -322,10 +332,16 @@ defmodule Module.Types.Apply do
|
||||
{value_type, context}
|
||||
|
||||
:badtuple ->
|
||||
{error_type(), badremote_error(expr, [integer(), tuple], stack, context)}
|
||||
args_types = [integer(), tuple]
|
||||
|
||||
reason ->
|
||||
{error_type(), error({reason, expr, tuple, index - 1, context}, meta, stack, context)}
|
||||
{error_type(),
|
||||
badremote_error(:erlang, :delete_element, expr, args_types, stack, context)}
|
||||
|
||||
:badindex ->
|
||||
mfac = mfac(expr, :erlang, :delete_element, 2)
|
||||
|
||||
{error_type(),
|
||||
error({:badindex, mfac, expr, tuple, index - 1, context}, meta, stack, context)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -340,7 +356,7 @@ defmodule Module.Types.Apply do
|
||||
{value_type, context}
|
||||
|
||||
:badnonemptylist ->
|
||||
{error_type(), badremote_error(expr, [list], stack, context)}
|
||||
{error_type(), badremote_error(:erlang, :hd, expr, [list], stack, context)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -350,7 +366,7 @@ defmodule Module.Types.Apply do
|
||||
{value_type, context}
|
||||
|
||||
:badnonemptylist ->
|
||||
{error_type(), badremote_error(expr, [list], stack, context)}
|
||||
{error_type(), badremote_error(:erlang, :tl, expr, [list], stack, context)}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -384,11 +400,19 @@ defmodule Module.Types.Apply do
|
||||
end
|
||||
end
|
||||
|
||||
def remote(:erlang, name, [left, right] = args_types, expr, stack, context)
|
||||
def remote(
|
||||
:erlang,
|
||||
name,
|
||||
[left, right] = args_types,
|
||||
{_, _, args} = expr,
|
||||
stack,
|
||||
context
|
||||
)
|
||||
when name in [:==, :"/=", :"=:=", :"=/="] do
|
||||
context =
|
||||
cond do
|
||||
stack.mode == :infer ->
|
||||
# We ignore quoted literals as they most likely come from generated code.
|
||||
stack.mode == :infer or Macro.quoted_literal?(args) ->
|
||||
context
|
||||
|
||||
name in [:==, :"/="] and number_type?(left) and number_type?(right) ->
|
||||
@@ -405,30 +429,36 @@ defmodule Module.Types.Apply do
|
||||
{return(boolean(), args_types, stack), context}
|
||||
end
|
||||
|
||||
def remote(mod, name, args_types, expr, stack, context) do
|
||||
def remote(mod, fun, args_types, expr, stack, context) do
|
||||
arity = length(args_types)
|
||||
|
||||
case :elixir_rewrite.inline(mod, name, arity) do
|
||||
{mod, name} ->
|
||||
remote(mod, name, args_types, expr, stack, context)
|
||||
case :elixir_rewrite.inline(mod, fun, arity) do
|
||||
{new_mod, new_fun} ->
|
||||
expr = inline_meta(expr, mod, fun)
|
||||
remote(new_mod, new_fun, args_types, expr, stack, context)
|
||||
|
||||
false ->
|
||||
{info, context} = signature(mod, name, arity, elem(expr, 1), stack, context)
|
||||
apply_remote(info, args_types, expr, stack, context)
|
||||
{info, context} = signature(mod, fun, arity, elem(expr, 1), stack, context)
|
||||
apply_remote(mod, fun, info, args_types, expr, stack, context)
|
||||
end
|
||||
end
|
||||
|
||||
defp apply_remote(info, args_types, expr, stack, context) do
|
||||
defp apply_remote(mod, fun, info, args_types, expr, stack, context) do
|
||||
case apply_signature(info, args_types, stack) do
|
||||
{:ok, _indexes, type} ->
|
||||
{type, context}
|
||||
|
||||
{:error, domain, clauses} ->
|
||||
error = {:badremote, expr, args_types, domain, clauses, context}
|
||||
mfac = mfac(expr, mod, fun, length(args_types))
|
||||
error = {:badremote, mfac, expr, args_types, domain, clauses, context}
|
||||
{error_type(), error(error, elem(expr, 1), stack, context)}
|
||||
end
|
||||
end
|
||||
|
||||
defp inline_meta({node, meta, args}, mod, fun) do
|
||||
{node, [inline: {mod, fun}] ++ meta, args}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the type of a remote capture.
|
||||
"""
|
||||
@@ -718,22 +748,28 @@ defmodule Module.Types.Apply do
|
||||
error(__MODULE__, warning, meta, stack, context)
|
||||
end
|
||||
|
||||
defp badremote_error({{:., _, [mod, fun]}, meta, _} = expr, args_types, stack, context) do
|
||||
{_type, domain, [{args, _} | _] = clauses} = signature(mod, fun, length(args_types))
|
||||
error({:badremote, expr, args_types, domain || args, clauses, context}, meta, stack, context)
|
||||
defp badremote_error(mod, fun, {_, meta, _} = expr, args_types, stack, context) do
|
||||
arity = length(args_types)
|
||||
mfac = mfac(expr, mod, fun, arity)
|
||||
{_type, domain, [{args, _} | _] = clauses} = signature(mod, fun, arity)
|
||||
domain = domain || args
|
||||
tuple = {:badremote, mfac, expr, args_types, domain, clauses, context}
|
||||
error(tuple, meta, stack, context)
|
||||
end
|
||||
|
||||
## Diagnosstics
|
||||
## Diagnostics
|
||||
|
||||
def format_diagnostic({:badindex, expr, type, index, context}) do
|
||||
def format_diagnostic({:badindex, mfac, expr, type, index, context}) do
|
||||
traces = collect_traces(expr, context)
|
||||
{mod, fun, arity, _converter} = mfac
|
||||
mfa = Exception.format_mfa(mod, fun, arity)
|
||||
|
||||
%{
|
||||
details: %{typing_traces: traces},
|
||||
message:
|
||||
IO.iodata_to_binary([
|
||||
"""
|
||||
expected a tuple with at least #{pluralize(index + 1, "element", "elements")} in #{format_mfa(expr)}:
|
||||
expected a tuple with at least #{pluralize(index + 1, "element", "elements")} in #{mfa}:
|
||||
|
||||
#{expr_to_string(expr) |> indent(4)}
|
||||
|
||||
@@ -749,7 +785,7 @@ defmodule Module.Types.Apply do
|
||||
def format_diagnostic({:badlocal, expr, args_types, domain, clauses, context}) do
|
||||
traces = collect_traces(expr, context)
|
||||
converter = &Function.identity/1
|
||||
{fun, _, _} = expr
|
||||
{fun, meta, _} = expr
|
||||
|
||||
explanation =
|
||||
empty_arg_reason(args_types) ||
|
||||
@@ -758,16 +794,29 @@ defmodule Module.Types.Apply do
|
||||
#{clauses_args_to_quoted_string(clauses, converter)}
|
||||
"""
|
||||
|
||||
%{
|
||||
details: %{typing_traces: traces},
|
||||
message:
|
||||
IO.iodata_to_binary([
|
||||
banner =
|
||||
case fun == :super && meta[:default] && meta[:super] do
|
||||
{_kind, fun} ->
|
||||
"""
|
||||
incompatible types given as default arguments to #{fun}/#{length(args_types)}:
|
||||
"""
|
||||
|
||||
_ ->
|
||||
"""
|
||||
incompatible types given to #{fun}/#{length(args_types)}:
|
||||
|
||||
#{expr_to_string(expr) |> indent(4)}
|
||||
|
||||
given types:
|
||||
"""
|
||||
end
|
||||
|
||||
%{
|
||||
details: %{typing_traces: traces},
|
||||
message:
|
||||
IO.iodata_to_binary([
|
||||
banner,
|
||||
"""
|
||||
|
||||
#{args_to_quoted_string(args_types, domain, converter) |> indent(4)}
|
||||
|
||||
@@ -778,10 +827,9 @@ defmodule Module.Types.Apply do
|
||||
}
|
||||
end
|
||||
|
||||
def format_diagnostic({:badremote, expr, args_types, domain, clauses, context}) do
|
||||
def format_diagnostic({:badremote, mfac, expr, args_types, domain, clauses, context}) do
|
||||
traces = collect_traces(expr, context)
|
||||
{{:., _, [mod, fun]}, _, args} = expr
|
||||
{mod, fun, args, converter} = :elixir_rewrite.erl_to_ex(mod, fun, args)
|
||||
{mod, fun, arity, converter} = mfac
|
||||
|
||||
explanation =
|
||||
empty_arg_reason(converter.(args_types)) ||
|
||||
@@ -790,12 +838,15 @@ defmodule Module.Types.Apply do
|
||||
#{clauses_args_to_quoted_string(clauses, converter)}
|
||||
"""
|
||||
|
||||
mfa_or_fa =
|
||||
if mod, do: Exception.format_mfa(mod, fun, arity), else: "#{fun}/#{arity}"
|
||||
|
||||
%{
|
||||
details: %{typing_traces: traces},
|
||||
message:
|
||||
IO.iodata_to_binary([
|
||||
"""
|
||||
incompatible types given to #{Exception.format_mfa(mod, fun, length(args))}:
|
||||
incompatible types given to #{mfa_or_fa}:
|
||||
|
||||
#{expr_to_string(expr) |> indent(4)}
|
||||
|
||||
@@ -932,9 +983,19 @@ defmodule Module.Types.Apply do
|
||||
defp pluralize(1, singular, _), do: "1 #{singular}"
|
||||
defp pluralize(i, _, plural), do: "#{i} #{plural}"
|
||||
|
||||
defp format_mfa({{:., _, [mod, fun]}, _, args}) do
|
||||
{mod, fun, args, _} = :elixir_rewrite.erl_to_ex(mod, fun, args)
|
||||
Exception.format_mfa(mod, fun, length(args))
|
||||
defp mfac({_, [inline: {mod, fun}] ++ _, _}, _mod, _fun, arity) do
|
||||
{mod, fun, arity, & &1}
|
||||
end
|
||||
|
||||
defp mfac({{:., _, [mod, fun]}, _, args}, _mod, _fun, _arity)
|
||||
when is_atom(mod) and is_atom(fun) do
|
||||
{mod, fun, args, converter} = :elixir_rewrite.erl_to_ex(mod, fun, args)
|
||||
{mod, fun, length(args), converter}
|
||||
end
|
||||
|
||||
defp mfac(_, mod, fun, arity)
|
||||
when is_atom(mod) and is_atom(fun) and is_integer(arity) do
|
||||
{mod, fun, arity, & &1}
|
||||
end
|
||||
|
||||
## Algebra helpers
|
||||
|
||||
@@ -1329,11 +1329,10 @@ defmodule Module.Types.Descr do
|
||||
:maps.next(iterator) |> map_literal_intersection_loop(acc)
|
||||
|
||||
_ ->
|
||||
# If the key is marked as not_set in the open map, we can ignore it.
|
||||
if type1 == @not_set do
|
||||
:maps.next(iterator) |> map_literal_intersection_loop(acc)
|
||||
else
|
||||
throw(:empty)
|
||||
# If the key is optional in the open map, we can ignore it
|
||||
case type1 do
|
||||
%{optional: 1} -> :maps.next(iterator) |> map_literal_intersection_loop(acc)
|
||||
_ -> throw(:empty)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -263,8 +263,9 @@ defmodule Module.Types.Expr do
|
||||
{case_type, context} = of_expr(case_expr, stack, context)
|
||||
|
||||
# If we are only type checking the expression and the expression is a literal,
|
||||
# let's mark it as generated, as it is most likely a macro code.
|
||||
if is_atom(case_expr) and {:type_check, :expr} in meta do
|
||||
# let's mark it as generated, as it is most likely a macro code. However, if
|
||||
# no clause is matched, we should still check for that.
|
||||
if Macro.quoted_literal?(case_expr) do
|
||||
for {:->, meta, args} <- clauses, do: {:->, [generated: true] ++ meta, args}
|
||||
else
|
||||
clauses
|
||||
@@ -517,7 +518,7 @@ defmodule Module.Types.Expr do
|
||||
|
||||
defp with_clause({:<-, _meta, [left, right]} = expr, stack, context) do
|
||||
{pattern, guards} = extract_head([left])
|
||||
{_type, context} = Pattern.of_match(pattern, guards, dynamic(), :with, expr, stack, context)
|
||||
{_type, context} = Pattern.of_match(pattern, guards, dynamic(), expr, :with, stack, context)
|
||||
{_, context} = of_expr(right, stack, context)
|
||||
context
|
||||
end
|
||||
|
||||
@@ -85,6 +85,9 @@ defmodule Module.Types.Helpers do
|
||||
|
||||
@doc """
|
||||
Collect traces from variables in expression.
|
||||
|
||||
This information is exposed to language servers and
|
||||
therefore must remain backwards compatible.
|
||||
"""
|
||||
def collect_traces(expr, %{vars: vars}) do
|
||||
{_, versions} =
|
||||
@@ -135,16 +138,17 @@ defmodule Module.Types.Helpers do
|
||||
formatter -> formatter.(expr)
|
||||
end
|
||||
|
||||
# This information is exposed to language servers and
|
||||
# therefore must remain backwards compatible.
|
||||
%{
|
||||
file: file,
|
||||
line: meta[:line],
|
||||
column: meta[:column],
|
||||
hints: formatter_hints ++ expr_hints(expr),
|
||||
meta: meta,
|
||||
formatted_expr: formatted_expr,
|
||||
formatted_hints: format_hints(formatter_hints ++ expr_hints(expr)),
|
||||
formatted_type: Module.Types.Descr.to_quoted_string(type, collapse_structs: true)
|
||||
}
|
||||
end)
|
||||
|> Enum.sort_by(&{&1.line, &1.column})
|
||||
|> Enum.sort_by(&{&1.meta[:line], &1.meta[:column]})
|
||||
|> Enum.dedup()
|
||||
end
|
||||
|
||||
@@ -161,7 +165,7 @@ defmodule Module.Types.Helpers do
|
||||
location =
|
||||
trace.file
|
||||
|> Path.relative_to_cwd()
|
||||
|> Exception.format_file_line(trace.line, trace.column)
|
||||
|> Exception.format_file_line(trace.meta[:line], trace.meta[:column])
|
||||
|> String.replace_suffix(":", "")
|
||||
|
||||
[
|
||||
@@ -173,7 +177,7 @@ defmodule Module.Types.Helpers do
|
||||
""",
|
||||
indent(trace.formatted_expr, 4),
|
||||
?\n,
|
||||
format_hints(trace.hints)
|
||||
trace.formatted_hints
|
||||
]
|
||||
end
|
||||
|
||||
|
||||
@@ -884,7 +884,7 @@ defmodule Path do
|
||||
* A `..` component would make it so that the path would traverse up above
|
||||
the root of `relative_to`.
|
||||
|
||||
* A symbolic link in the path points to something above the root of `cwd`.
|
||||
* A symbolic link in the path points to something above the root of `relative_to`.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -906,10 +906,10 @@ defmodule Path do
|
||||
"""
|
||||
@doc since: "1.14.0"
|
||||
@spec safe_relative(t, t) :: {:ok, binary} | :error
|
||||
def safe_relative(path, cwd \\ File.cwd!()) do
|
||||
def safe_relative(path, relative_to \\ File.cwd!()) do
|
||||
path = IO.chardata_to_string(path)
|
||||
|
||||
case :filelib.safe_relative_path(path, cwd) do
|
||||
case :filelib.safe_relative_path(path, relative_to) do
|
||||
:unsafe -> :error
|
||||
relative_path -> {:ok, IO.chardata_to_string(relative_path)}
|
||||
end
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
@@ -157,7 +157,7 @@ say me
|
||||
say me
|
||||
```
|
||||
|
||||
Yes, it works! However, does it *scale*?
|
||||
Yes, it works! However, can it handle more than one client?
|
||||
|
||||
Try to connect two telnet clients at the same time. When you do so, you will notice that the second client doesn't echo:
|
||||
|
||||
@@ -303,4 +303,8 @@ Luckily, this can be done by using `Supervisor.child_spec/2`, which allows us to
|
||||
|
||||
Now we have an always running acceptor that starts temporary task processes under an always running task supervisor.
|
||||
|
||||
## Wrapping up
|
||||
|
||||
In this chapter, we implemented a basic TCP acceptor while exploring concurrency and fault-tolerance. Our acceptor can manage concurrent connections, but it is still not ready for production. Production-ready TCP servers run a pool of acceptors, each with their own supervisor. Elixir's `PartitionSupervisor` might be used to partition and scale the acceptor, but it is out of scope for this guide. In practice, you will use existing packages tailored for this use-case, such as [Ranch](https://github.com/ninenines/ranch) (in Erlang) or [Thousand Island](https://github.com/mtrudel/thousand_island) (in Elixir).
|
||||
|
||||
In the next chapter, we will start parsing the client requests and sending responses, finishing our server.
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# Typespecs reference
|
||||
|
||||
Elixir comes with a notation for declaring types and specifications. This document is a
|
||||
reference into their uses and syntax.
|
||||
> #### Typespecs are not set-theoretic types {: .warning}
|
||||
>
|
||||
> Elixir is in the process of implementing its
|
||||
> [own type system](./gradual-set-theoretic-types.md) based on set-theoretic types.
|
||||
> Typespecs, which are described in the following document, are a distinct notation
|
||||
> for declaring types and specifications based on Erlang.
|
||||
> Typespecs may be phased out as the set-theoretic type effort moves forward.
|
||||
|
||||
Elixir is a dynamically typed language, and as such, type specifications are never used by the compiler to optimize or modify code. Still, using type specifications is useful because:
|
||||
|
||||
@@ -48,6 +53,13 @@ The syntax Elixir provides for type specifications is similar to [the one in Erl
|
||||
|
||||
The notation to represent the union of types is the pipe `|`. For example, the typespec `type :: atom() | pid() | tuple()` creates a type `type` that can be either an `atom`, a `pid`, or a `tuple`. This is usually called a [sum type](https://en.wikipedia.org/wiki/Tagged_union) in other languages
|
||||
|
||||
> #### Differences with set-theoretic types {: .warning}
|
||||
>
|
||||
> While they do share some similarities, the types below do not map one-to-one
|
||||
> to the new types from the set theoretic type system.
|
||||
> For example, there is no plan to support subsets of the `integer()` type such
|
||||
> as positive, ranges or literals.
|
||||
|
||||
### Basic types
|
||||
|
||||
type ::
|
||||
|
||||
@@ -314,7 +314,7 @@ unpack_defaults(Kind, Meta, Name, Args, S, E) ->
|
||||
unpack_expanded(Kind, Meta, Name, [{'\\\\', DefaultMeta, [Expr, _]} | T] = List, VersionOffset, Acc, Clauses) ->
|
||||
Base = match_defaults(Acc, length(Acc) + VersionOffset, []),
|
||||
{Args, Invoke} = extract_defaults(List, length(Base) + VersionOffset, [], []),
|
||||
Clause = {Meta, Base ++ Args, [], {super, [{super, {Kind, Name}} | DefaultMeta], Base ++ Invoke}},
|
||||
Clause = {Meta, Base ++ Args, [], {super, [{super, {Kind, Name}}, {default, true} | DefaultMeta], Base ++ Invoke}},
|
||||
unpack_expanded(Kind, Meta, Name, T, VersionOffset, [Expr | Acc], [Clause | Clauses]);
|
||||
unpack_expanded(Kind, Meta, Name, [H | T], VersionOffset, Acc, Clauses) ->
|
||||
unpack_expanded(Kind, Meta, Name, T, VersionOffset, [H | Acc], Clauses);
|
||||
|
||||
@@ -92,7 +92,10 @@ emit_diagnostic(Severity, Position, File, Message, Stacktrace, Options) ->
|
||||
|
||||
Diagnostic = #{
|
||||
severity => Severity,
|
||||
source => File,
|
||||
source => case get(elixir_compiler_file) of
|
||||
undefined -> File;
|
||||
CompilerFile -> CompilerFile
|
||||
end,
|
||||
file => File,
|
||||
position => Position,
|
||||
message => unicode:characters_to_binary(Message),
|
||||
|
||||
@@ -112,6 +112,10 @@ capture_expr(Meta, Expr, S, E, Escaped, Sequential) ->
|
||||
case escape(Expr, E, Escaped) of
|
||||
{_, []} when not Sequential ->
|
||||
invalid_capture(Meta, Expr, E);
|
||||
{{{'.', _, [_, _]} = Dot, _, Args}, []} ->
|
||||
Meta2 = lists:keydelete(no_parens, 1, Meta),
|
||||
Fn = {fn, Meta2, [{'->', Meta2, [[], {Dot, Meta2, Args}]}]},
|
||||
{expand, Fn, S, E};
|
||||
{EExpr, EDict} ->
|
||||
EVars = validate(Meta, EDict, 1, E),
|
||||
Fn = {fn, Meta, [{'->', Meta, [EVars, EExpr]}]},
|
||||
|
||||
@@ -75,8 +75,9 @@ store_not_overridden(Module) ->
|
||||
%% Private
|
||||
|
||||
store(Set, Module, Tuple, {_, Count, Def, Overridden}, Hidden) ->
|
||||
{{{def, {Name, Arity}}, Kind, Meta, File, _Check,
|
||||
{{{def, {Name, Arity}}, Kind, BaseMeta, File, _Check,
|
||||
{Defaults, _HasBody, _LastDefaults}}, Clauses} = Def,
|
||||
Meta = [{from_super, Hidden} | BaseMeta],
|
||||
|
||||
{FinalKind, FinalName, FinalArity, FinalClauses} =
|
||||
case Hidden of
|
||||
|
||||
@@ -818,9 +818,10 @@ handle_strings(T, Line, Column, H, Scope, Tokens) ->
|
||||
NewScope =
|
||||
case H of
|
||||
$' ->
|
||||
Message = "single-quoted strings represent charlists. "
|
||||
Message = "using single-quoted strings to represent charlists is deprecated.\n"
|
||||
"Use ~c\"\" if you indeed want a charlist or use \"\" instead.\n"
|
||||
"You may run \"mix format --migrate\" to fix this warning automatically.",
|
||||
"You may run \"mix format --migrate\" to change all single-quoted\n"
|
||||
"strings to use the ~c sigil and fix this warning.",
|
||||
prepend_warning(Line, Column-1, Message, InterScope);
|
||||
|
||||
_ ->
|
||||
@@ -1762,84 +1763,81 @@ add_cursor(_Line, Column, noprune, Terminators, Tokens) ->
|
||||
{Column, Terminators, Tokens};
|
||||
add_cursor(Line, Column, prune_and_cursor, Terminators, Tokens) ->
|
||||
PrePrunedTokens = prune_identifier(Tokens),
|
||||
{PrunedTokens, PrunedTerminators} = prune_tokens(PrePrunedTokens, [], Terminators),
|
||||
PrunedTokens = prune_tokens(PrePrunedTokens, []),
|
||||
CursorTokens = [
|
||||
{')', {Line, Column + 11, nil}},
|
||||
{'(', {Line, Column + 10, nil}},
|
||||
{paren_identifier, {Line, Column, nil}, '__cursor__'}
|
||||
| PrunedTokens
|
||||
],
|
||||
{Column + 12, PrunedTerminators, CursorTokens}.
|
||||
{Column + 12, Terminators, CursorTokens}.
|
||||
|
||||
prune_identifier([{identifier, _, _} | Tokens]) -> Tokens;
|
||||
prune_identifier(Tokens) -> Tokens.
|
||||
|
||||
%%% Any terminator needs to be closed
|
||||
prune_tokens([{'end', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, ['end' | Opener], Terminators);
|
||||
prune_tokens([{')', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, [')' | Opener], Terminators);
|
||||
prune_tokens([{']', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, [']' | Opener], Terminators);
|
||||
prune_tokens([{'}', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, ['}' | Opener], Terminators);
|
||||
prune_tokens([{'>>', _} | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, ['>>' | Opener], Terminators);
|
||||
prune_tokens([{'end', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, ['end' | Opener]);
|
||||
prune_tokens([{')', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, [')' | Opener]);
|
||||
prune_tokens([{']', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, [']' | Opener]);
|
||||
prune_tokens([{'}', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, ['}' | Opener]);
|
||||
prune_tokens([{'>>', _} | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, ['>>' | Opener]);
|
||||
%%% Close opened terminators
|
||||
prune_tokens([{'fn', _} | Tokens], ['end' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'do', _} | Tokens], ['end' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'(', _} | Tokens], [')' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'[', _} | Tokens], [']' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'{', _} | Tokens], ['}' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([{'<<', _} | Tokens], ['>>' | Opener], Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
%%% Handle anonymous functions
|
||||
prune_tokens([{'(', _}, {capture_op, _, _} | Tokens], [], [{'(', _, _} | Terminators]) ->
|
||||
prune_tokens(Tokens, [], Terminators);
|
||||
prune_tokens([{'fn', _} | Tokens], ['end' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'do', _} | Tokens], ['end' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'(', _} | Tokens], [')' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'[', _} | Tokens], [']' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'{', _} | Tokens], ['}' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([{'<<', _} | Tokens], ['>>' | Opener]) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
%%% or it is time to stop...
|
||||
prune_tokens([{';', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'eol', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{',', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'fn', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'do', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'(', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'[', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'{', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{'<<', _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{identifier, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{block_identifier, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{kw_identifier, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{kw_identifier_safe, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{kw_identifier_unsafe, _, _} | _] = Tokens, [], Terminators) ->
|
||||
{Tokens, Terminators};
|
||||
prune_tokens([{OpType, _, _} | _] = Tokens, [], Terminators)
|
||||
prune_tokens([{';', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'eol', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{',', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'fn', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'do', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'(', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'[', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'{', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{'<<', _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{identifier, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{block_identifier, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{kw_identifier, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{kw_identifier_safe, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{kw_identifier_unsafe, _, _} | _] = Tokens, []) ->
|
||||
Tokens;
|
||||
prune_tokens([{OpType, _, _} | _] = Tokens, [])
|
||||
when OpType =:= comp_op; OpType =:= at_op; OpType =:= unary_op; OpType =:= and_op;
|
||||
OpType =:= or_op; OpType =:= arrow_op; OpType =:= match_op; OpType =:= in_op;
|
||||
OpType =:= in_match_op; OpType =:= type_op; OpType =:= dual_op; OpType =:= mult_op;
|
||||
OpType =:= power_op; OpType =:= concat_op; OpType =:= range_op; OpType =:= xor_op;
|
||||
OpType =:= pipe_op; OpType =:= stab_op; OpType =:= when_op; OpType =:= assoc_op;
|
||||
OpType =:= rel_op; OpType =:= ternary_op; OpType =:= capture_op; OpType =:= ellipsis_op ->
|
||||
{Tokens, Terminators};
|
||||
Tokens;
|
||||
%%% or we traverse until the end.
|
||||
prune_tokens([_ | Tokens], Opener, Terminators) ->
|
||||
prune_tokens(Tokens, Opener, Terminators);
|
||||
prune_tokens([], [], Terminators) ->
|
||||
{[], Terminators}.
|
||||
prune_tokens([_ | Tokens], Opener) ->
|
||||
prune_tokens(Tokens, Opener);
|
||||
prune_tokens([], _Opener) ->
|
||||
[].
|
||||
|
||||
@@ -1315,9 +1315,15 @@ defmodule CodeFragmentTest do
|
||||
assert cc2q!("(fn x ->", trailing_fragment: ":ok end)") ==
|
||||
s2q!("(fn x -> __cursor__() end)")
|
||||
|
||||
assert cc2q!("(fn x ->", trailing_fragment: ":ok end)") ==
|
||||
assert cc2q!("(fn x ->", trailing_fragment: "\n:ok end)") ==
|
||||
s2q!("(fn x -> __cursor__() end)")
|
||||
|
||||
assert cc2q!("(fn x when ", trailing_fragment: "-> :ok end)") ==
|
||||
s2q!("(fn x when __cursor__() -> :ok end)")
|
||||
|
||||
assert cc2q!("(fn x when ", trailing_fragment: "->\n:ok end)") ==
|
||||
s2q!("(fn x when __cursor__() -> :ok end)")
|
||||
|
||||
assert cc2q!("(fn") == s2q!("(__cursor__())")
|
||||
assert cc2q!("(fn x") == s2q!("(__cursor__())")
|
||||
assert cc2q!("(fn x,") == s2q!("(__cursor__())")
|
||||
@@ -1327,6 +1333,26 @@ defmodule CodeFragmentTest do
|
||||
assert cc2q!("(fn x, y -> x + y end") == s2q!("(__cursor__())")
|
||||
end
|
||||
|
||||
test "do -> end" do
|
||||
assert cc2q!("if do\nx ->\n", trailing_fragment: "y\nz ->\nw\nend") ==
|
||||
s2q!("if do\nx ->\n__cursor__()\nz -> \nw\nend")
|
||||
|
||||
assert cc2q!("if do\nx ->\ny", trailing_fragment: "\nz ->\nw\nend") ==
|
||||
s2q!("if do\nx ->\n__cursor__()\nz -> \nw\nend")
|
||||
|
||||
assert cc2q!("if do\nx ->\ny\n", trailing_fragment: "\nz ->\nw\nend") ==
|
||||
s2q!("if do\nx ->\ny\n__cursor__()\nz -> \nw\nend")
|
||||
|
||||
assert cc2q!("for x <- [], reduce: %{} do\ny, ", trailing_fragment: "-> :ok\nend") ==
|
||||
s2q!("for x <- [], reduce: %{} do\ny, __cursor__() -> :ok\nend")
|
||||
|
||||
assert cc2q!("for x <- [], reduce: %{} do\ny, z when ", trailing_fragment: "-> :ok\nend") ==
|
||||
s2q!("for x <- [], reduce: %{} do\ny, z when __cursor__() -> :ok\nend")
|
||||
|
||||
assert cc2q!("case do\na -> a\nb = ", trailing_fragment: "c -> c\nend") ==
|
||||
s2q!("case do\na -> a\nb = __cursor__() -> c\nend")
|
||||
end
|
||||
|
||||
test "removes tokens until opening" do
|
||||
assert cc2q!("(123") == s2q!("(__cursor__())")
|
||||
assert cc2q!("[foo") == s2q!("[__cursor__()]")
|
||||
|
||||
@@ -64,9 +64,7 @@ end
|
||||
test_parameters =
|
||||
if(PathHelpers.windows?(),
|
||||
do: [%{cli_extension: ".bat"}],
|
||||
else:
|
||||
[%{cli_extension: ""}] ++
|
||||
if(System.find_executable("pwsh"), do: [%{cli_extension: ".ps1"}], else: [])
|
||||
else: [%{cli_extension: ""}]
|
||||
)
|
||||
|
||||
defmodule Kernel.CLI.ExecutableTest do
|
||||
|
||||
@@ -1203,6 +1203,11 @@ defmodule Kernel.ExpansionTest do
|
||||
[{:->, [{:line, 1}], [[{:capture, [line: 1], nil}], {:capture, [line: 1], nil}]}]}
|
||||
end
|
||||
|
||||
test "removes no_parens when expanding 0-arity capture to fn" do
|
||||
assert expand(quote(do: &foo().bar/0)) ==
|
||||
quote(do: fn -> foo().bar() end)
|
||||
end
|
||||
|
||||
test "expands remotes" do
|
||||
assert expand(quote(do: &List.flatten/2)) ==
|
||||
quote(do: &:"Elixir.List".flatten/2)
|
||||
|
||||
@@ -95,6 +95,12 @@ defmodule Kernel.FnTest do
|
||||
assert (&mod.flatten/1) == (&List.flatten/1)
|
||||
end
|
||||
|
||||
test "capture with module from local call" do
|
||||
assert (&math_mod().pi/0).() == :math.pi()
|
||||
end
|
||||
|
||||
defp math_mod, do: :math
|
||||
|
||||
test "local partial application" do
|
||||
assert (&atb(&1, :utf8)).(:a) == "a"
|
||||
assert (&atb(List.to_atom(&1), :utf8)).(~c"a") == "a"
|
||||
|
||||
@@ -421,40 +421,6 @@ defmodule Kernel.ParallelCompilerTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "supports warnings as errors" do
|
||||
[fixture] =
|
||||
write_tmp(
|
||||
"warnings_as_errors",
|
||||
warnings_as_errors: """
|
||||
defmodule WarningsSample do
|
||||
def hello(a), do: a
|
||||
def hello(b), do: b
|
||||
end
|
||||
"""
|
||||
)
|
||||
|
||||
output = tmp_path("not_to_be_used")
|
||||
|
||||
try do
|
||||
msg =
|
||||
capture_io(:stderr, fn ->
|
||||
assert {:error, [error], []} =
|
||||
Kernel.ParallelCompiler.compile_to_path([fixture], output,
|
||||
warnings_as_errors: true
|
||||
)
|
||||
|
||||
assert {^fixture, {3, 7}, "this clause " <> _} = error
|
||||
end)
|
||||
|
||||
assert msg =~
|
||||
"Compilation failed due to warnings while using the --warnings-as-errors option\n"
|
||||
after
|
||||
purge([WarningsSample])
|
||||
end
|
||||
|
||||
refute File.exists?(output)
|
||||
end
|
||||
|
||||
test "does not use incorrect line number when error originates in another file" do
|
||||
File.mkdir_p!(tmp_path())
|
||||
|
||||
@@ -587,33 +553,5 @@ defmodule Kernel.ParallelCompilerTest do
|
||||
"cannot compile module WithBehaviourAndStruct (errors have been logged)"
|
||||
end) =~ expected_msg
|
||||
end
|
||||
|
||||
test "supports warnings as errors" do
|
||||
[fixture] =
|
||||
write_tmp(
|
||||
"warnings_as_errors",
|
||||
warnings_as_errors: """
|
||||
defmodule WarningsSample do
|
||||
def hello(a), do: a
|
||||
def hello(b), do: b
|
||||
end
|
||||
"""
|
||||
)
|
||||
|
||||
try do
|
||||
msg =
|
||||
capture_io(:stderr, fn ->
|
||||
assert {:error, [error], []} =
|
||||
Kernel.ParallelCompiler.require([fixture], warnings_as_errors: true)
|
||||
|
||||
assert {^fixture, {3, 7}, "this clause " <> _} = error
|
||||
end)
|
||||
|
||||
assert msg =~
|
||||
"Compilation failed due to warnings while using the --warnings-as-errors option\n"
|
||||
after
|
||||
purge([WarningsSample])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -205,6 +205,10 @@ defmodule KernelTest do
|
||||
|
||||
defmodule User do
|
||||
assert is_map(defstruct name: "john")
|
||||
# Ensure we keep the line information around.
|
||||
# It is important for debugging tools, ExDoc, etc.
|
||||
{:v1, :def, anno, _clauses} = Module.get_definition(__MODULE__, {:__struct__, 1})
|
||||
anno[:line] == __ENV__.line - 4
|
||||
end
|
||||
|
||||
test "struct/1 and struct/2" do
|
||||
|
||||
@@ -163,6 +163,9 @@ defmodule Module.Types.DescrTest do
|
||||
assert intersection(closed_map(a: integer()), open_map(b: not_set())) ==
|
||||
closed_map(a: integer())
|
||||
|
||||
assert intersection(closed_map(a: integer()), open_map(b: if_set(integer()))) ==
|
||||
closed_map(a: integer())
|
||||
|
||||
assert equal?(
|
||||
intersection(closed_map(a: integer()), closed_map(a: if_set(integer()))),
|
||||
closed_map(a: integer())
|
||||
|
||||
@@ -243,6 +243,35 @@ defmodule Module.Types.ExprTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "calling a function with invalid arguments on variables" do
|
||||
assert typeerror!(
|
||||
(
|
||||
x = List
|
||||
x.to_tuple(123)
|
||||
)
|
||||
)
|
||||
|> strip_ansi() ==
|
||||
~l"""
|
||||
incompatible types given to List.to_tuple/1:
|
||||
|
||||
x.to_tuple(123)
|
||||
|
||||
given types:
|
||||
|
||||
integer()
|
||||
|
||||
but expected one of:
|
||||
|
||||
list(term())
|
||||
|
||||
where "x" was given the type:
|
||||
|
||||
# type: List
|
||||
# from: types_test.ex:LINE-5
|
||||
x = List
|
||||
"""
|
||||
end
|
||||
|
||||
test "capture a function with non atoms" do
|
||||
assert typeerror!([<<x::integer>>], &x.foo_bar/2) ==
|
||||
~l"""
|
||||
@@ -831,7 +860,11 @@ defmodule Module.Types.ExprTest do
|
||||
test "in dynamic mode" do
|
||||
assert typedyn!([x = 123, y = 456.0], x < y) == dynamic(boolean())
|
||||
assert typedyn!([x = 123, y = 456.0], x == y) == dynamic(boolean())
|
||||
assert typedyn!(123 == 456) == boolean()
|
||||
assert typedyn!([x = 123, y = 456], x == y) == dynamic(boolean())
|
||||
end
|
||||
|
||||
test "using literals" do
|
||||
assert typecheck!(:foo == :bar) == boolean()
|
||||
end
|
||||
|
||||
test "min/max" do
|
||||
@@ -1031,6 +1064,15 @@ defmodule Module.Types.ExprTest do
|
||||
end
|
||||
|
||||
describe "case" do
|
||||
test "does not type check literals" do
|
||||
assert typecheck!(
|
||||
case :dev do
|
||||
:dev -> :ok
|
||||
:prod -> :error
|
||||
end
|
||||
) == atom([:ok, :error])
|
||||
end
|
||||
|
||||
test "returns unions of all clauses" do
|
||||
assert typecheck!(
|
||||
[x],
|
||||
@@ -1086,7 +1128,7 @@ defmodule Module.Types.ExprTest do
|
||||
end
|
||||
|
||||
describe "conditionals" do
|
||||
test "if does not report on literal atoms" do
|
||||
test "if does not report on literals" do
|
||||
assert typecheck!(
|
||||
if true do
|
||||
:ok
|
||||
@@ -1094,15 +1136,15 @@ defmodule Module.Types.ExprTest do
|
||||
) == atom([:ok, nil])
|
||||
end
|
||||
|
||||
test "and does not report on literal atoms" do
|
||||
test "and does not report on literals" do
|
||||
assert typecheck!(false and true) == boolean()
|
||||
end
|
||||
|
||||
test "and reports on non-atom literals" do
|
||||
assert typeerror!(1 and true) == ~l"""
|
||||
test "and reports violations" do
|
||||
assert typeerror!([x = 123], x and true) =~ """
|
||||
the following conditional expression will always evaluate to integer():
|
||||
|
||||
1
|
||||
x
|
||||
"""
|
||||
end
|
||||
end
|
||||
@@ -1400,6 +1442,16 @@ defmodule Module.Types.ExprTest do
|
||||
|
||||
assert typewarn!(:string.__info__(:functions)) ==
|
||||
{dynamic(), ":string.__info__/1 is undefined or private"}
|
||||
|
||||
assert typeerror!([x], x.__info__(:whatever)) |> strip_ansi() =~ """
|
||||
incompatible types given to __info__/1:
|
||||
|
||||
x.__info__(:whatever)
|
||||
|
||||
given types:
|
||||
|
||||
:whatever
|
||||
"""
|
||||
end
|
||||
|
||||
test "behaviour_info/1" do
|
||||
|
||||
@@ -36,7 +36,7 @@ defmodule Module.Types.IntegrationTest do
|
||||
"""
|
||||
}
|
||||
|
||||
modules = compile(files)
|
||||
modules = compile_modules(files)
|
||||
|
||||
assert [
|
||||
{{:c, 0}, %{}},
|
||||
@@ -195,6 +195,118 @@ defmodule Module.Types.IntegrationTest do
|
||||
|
||||
assert_warnings(files, warnings)
|
||||
end
|
||||
|
||||
test "unused generated private clauses" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
defmodule A do
|
||||
use B
|
||||
def public(x), do: private(List.to_tuple(x))
|
||||
end
|
||||
""",
|
||||
"b.ex" => """
|
||||
defmodule B do
|
||||
defmacro __using__(_) do
|
||||
quote generated: true do
|
||||
defp private({:ok, ok}), do: ok
|
||||
defp private(:error), do: :error
|
||||
end
|
||||
end
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
assert_no_warnings(files)
|
||||
end
|
||||
|
||||
test "unused overridable private clauses" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
defmodule A do
|
||||
use B
|
||||
def public(x), do: private(x)
|
||||
defp private(x), do: super(List.to_tuple(x))
|
||||
end
|
||||
""",
|
||||
"b.ex" => """
|
||||
defmodule B do
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
defp private({:ok, ok}), do: ok
|
||||
defp private(:error), do: :error
|
||||
defoverridable private: 1
|
||||
end
|
||||
end
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
assert_no_warnings(files)
|
||||
end
|
||||
|
||||
test "incompatible default argument" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
defmodule A do
|
||||
def ok(x = :ok \\\\ nil) do
|
||||
x
|
||||
end
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
warnings = [
|
||||
~S"""
|
||||
warning: incompatible types given as default arguments to ok/1:
|
||||
|
||||
-nil-
|
||||
|
||||
but expected one of:
|
||||
|
||||
dynamic(:ok)
|
||||
|
||||
typing violation found at:
|
||||
│
|
||||
2 │ def ok(x = :ok \\ nil) do
|
||||
│ ~
|
||||
│
|
||||
└─ a.ex:2:18: A.ok/0
|
||||
"""
|
||||
]
|
||||
|
||||
assert_warnings(files, warnings)
|
||||
end
|
||||
|
||||
test "returns diagnostics with source and file" do
|
||||
files = %{
|
||||
"a.ex" => """
|
||||
defmodule A do
|
||||
@file "generated.ex"
|
||||
def fun(arg) do
|
||||
:ok = List.to_tuple(arg)
|
||||
end
|
||||
end
|
||||
"""
|
||||
}
|
||||
|
||||
{_modules, warnings} = with_compile_warnings(files)
|
||||
|
||||
assert [
|
||||
%{
|
||||
message: "the following pattern will never match" <> _,
|
||||
file: file,
|
||||
source: source
|
||||
}
|
||||
] = warnings.runtime_warnings
|
||||
|
||||
assert String.ends_with?(source, "a.ex")
|
||||
assert Path.type(source) == :absolute
|
||||
assert String.ends_with?(file, "generated.ex")
|
||||
assert Path.type(file) == :absolute
|
||||
after
|
||||
:code.delete(A)
|
||||
:code.purge(A)
|
||||
end
|
||||
end
|
||||
|
||||
describe "undefined warnings" do
|
||||
@@ -932,26 +1044,39 @@ defmodule Module.Types.IntegrationTest do
|
||||
defp capture_compile_warnings(files) do
|
||||
in_tmp(fn ->
|
||||
paths = generate_files(files)
|
||||
capture_io(:stderr, fn -> compile_files(paths) end)
|
||||
capture_io(:stderr, fn -> compile_to_path(paths) end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp compile(files) do
|
||||
defp with_compile_warnings(files) do
|
||||
in_tmp(fn ->
|
||||
paths = generate_files(files)
|
||||
compile_files(paths)
|
||||
with_io(:stderr, fn -> compile_to_path(paths) end) |> elem(0)
|
||||
end)
|
||||
end
|
||||
|
||||
defp compile_files(paths) do
|
||||
{:ok, modules, _warnings} = Kernel.ParallelCompiler.compile_to_path(paths, ".")
|
||||
defp compile_modules(files) do
|
||||
in_tmp(fn ->
|
||||
paths = generate_files(files)
|
||||
{modules, _warnings} = compile_to_path(paths)
|
||||
|
||||
Map.new(modules, fn module ->
|
||||
{^module, binary, _filename} = :code.get_object_code(module)
|
||||
Map.new(modules, fn module ->
|
||||
{^module, binary, _filename} = :code.get_object_code(module)
|
||||
{module, binary}
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp compile_to_path(paths) do
|
||||
{:ok, modules, warnings} =
|
||||
Kernel.ParallelCompiler.compile_to_path(paths, ".", return_diagnostics: true)
|
||||
|
||||
for module <- modules do
|
||||
:code.delete(module)
|
||||
:code.purge(module)
|
||||
{module, binary}
|
||||
end)
|
||||
end
|
||||
|
||||
{modules, warnings}
|
||||
end
|
||||
|
||||
defp generate_files(files) do
|
||||
|
||||
@@ -75,6 +75,14 @@ defmodule TypespecTest do
|
||||
@type my_type :: %URI.t(){}
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"unexpected expression in typespec: t\.Foo",
|
||||
fn ->
|
||||
test_module do
|
||||
@type my_type :: t.Foo
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
test "invalid function specification" do
|
||||
@@ -120,7 +128,7 @@ defmodule TypespecTest do
|
||||
|
||||
test "redefined type" do
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:126",
|
||||
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:134",
|
||||
fn ->
|
||||
test_module do
|
||||
@type foo :: atom
|
||||
@@ -129,7 +137,7 @@ defmodule TypespecTest do
|
||||
end
|
||||
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"type foo/2 is already defined in .*test/elixir/typespec_test.exs:136",
|
||||
~r"type foo/2 is already defined in .*test/elixir/typespec_test.exs:144",
|
||||
fn ->
|
||||
test_module do
|
||||
@type foo :: atom
|
||||
@@ -139,7 +147,7 @@ defmodule TypespecTest do
|
||||
end
|
||||
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:145",
|
||||
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:153",
|
||||
fn ->
|
||||
test_module do
|
||||
@type foo :: atom
|
||||
@@ -841,6 +849,19 @@ defmodule TypespecTest do
|
||||
assert [{:atom, _, Keyword}, {:atom, _, :t}, [{:var, _, :value}]] = kw_with_value_args
|
||||
end
|
||||
|
||||
test "@type with macro in alias" do
|
||||
bytecode =
|
||||
test_module do
|
||||
defmacro module() do
|
||||
quote do: __MODULE__
|
||||
end
|
||||
|
||||
@type my_type :: module().Foo
|
||||
end
|
||||
|
||||
assert [type: {:my_type, {:atom, _, TypespecTest.TypespecSample.Foo}, []}] = types(bytecode)
|
||||
end
|
||||
|
||||
test "@type with a reserved signature" do
|
||||
assert_raise Kernel.TypespecError,
|
||||
~r"type required\/1 is a reserved type and it cannot be defined",
|
||||
|
||||
@@ -786,7 +786,7 @@ defmodule ExUnit.Assertions do
|
||||
## Examples
|
||||
|
||||
assert_raise ArithmeticError, "bad argument in arithmetic expression", fn ->
|
||||
1 + "test"
|
||||
1 / 0
|
||||
end
|
||||
|
||||
assert_raise RuntimeError, ~r/^today's lucky number is 0\.\d+!$/, fn ->
|
||||
|
||||
@@ -566,6 +566,10 @@ defmodule ExUnit.Case do
|
||||
group = Keyword.get(opts, :group, nil)
|
||||
parameterize = Keyword.get(opts, :parameterize, nil)
|
||||
|
||||
if not is_boolean(async?) do
|
||||
raise ArgumentError, ":async must be a boolean, got: #{inspect(async?)}"
|
||||
end
|
||||
|
||||
if not (parameterize == nil or (is_list(parameterize) and Enum.all?(parameterize, &is_map/1))) do
|
||||
raise ArgumentError, ":parameterize must be a list of maps, got: #{inspect(parameterize)}"
|
||||
end
|
||||
|
||||
@@ -878,7 +878,8 @@ defmodule ExUnit.Diff do
|
||||
end
|
||||
|
||||
defp rebuild_split_strings(%{contents: contents, delimiter: delimiter}, right) do
|
||||
%{contents: contents ++ [{false, right}], delimiter: delimiter}
|
||||
{new_right, diff} = extract_diff_meta(right)
|
||||
%{contents: contents ++ [{diff, new_right}], delimiter: delimiter}
|
||||
end
|
||||
|
||||
defp rebuild_concat_string(literal, nil, []) do
|
||||
|
||||
@@ -149,8 +149,6 @@ defmodule ExUnit.DocTest do
|
||||
suite run.
|
||||
"""
|
||||
|
||||
@opaque_type_regex ~r/#[\w\.]+</
|
||||
|
||||
defmodule Error do
|
||||
@moduledoc """
|
||||
Exception raised when there's an error with the syntax or semantics of a doctest.
|
||||
@@ -554,7 +552,7 @@ defmodule ExUnit.DocTest do
|
||||
message = "Doctest did not compile, got: #{ex_message}"
|
||||
|
||||
message =
|
||||
if e.__struct__ == TokenMissingError and expr =~ @opaque_type_regex do
|
||||
if e.__struct__ == TokenMissingError and expr =~ ~r/#[\w\.]+</ do
|
||||
message <>
|
||||
"""
|
||||
\nIf you are planning to assert on the result of an iex> expression \
|
||||
|
||||
@@ -125,7 +125,7 @@ defmodule ExUnit.Runner do
|
||||
|
||||
# Run all sync modules directly
|
||||
for pair <- sync_modules do
|
||||
running = spawn_modules(config, [[pair]], false, %{})
|
||||
running = spawn_modules(config, [{nil, [pair]}], false, %{})
|
||||
running != %{} and wait_until_available(config, running)
|
||||
end
|
||||
|
||||
@@ -161,7 +161,7 @@ defmodule ExUnit.Runner do
|
||||
running
|
||||
end
|
||||
|
||||
defp spawn_modules(config, [[_ | _] = modules | groups], async?, running) do
|
||||
defp spawn_modules(config, [{_group, [_ | _] = modules} | groups], async?, running) do
|
||||
if max_failures_reached?(config) do
|
||||
running
|
||||
else
|
||||
|
||||
@@ -57,9 +57,10 @@ defmodule ExUnit.Server do
|
||||
state = %{
|
||||
loaded: System.monotonic_time(),
|
||||
waiting: nil,
|
||||
async_groups: %{},
|
||||
groups: %{},
|
||||
async_groups: [],
|
||||
async_modules: :queue.new(),
|
||||
sync_modules: :queue.new()
|
||||
sync_modules: []
|
||||
}
|
||||
|
||||
{:ok, state}
|
||||
@@ -72,31 +73,31 @@ defmodule ExUnit.Server do
|
||||
|
||||
# Called once after all async modules have been sent and reverts the state.
|
||||
def handle_call(:take_sync_modules, _from, state) do
|
||||
%{waiting: nil, loaded: :done, async_modules: async_modules} = state
|
||||
0 = :queue.len(async_modules)
|
||||
%{waiting: nil, loaded: :done, async_groups: []} = state
|
||||
true = :queue.is_empty(state.async_modules)
|
||||
|
||||
{:reply, :queue.to_list(state.sync_modules),
|
||||
%{state | sync_modules: :queue.new(), loaded: System.monotonic_time()}}
|
||||
{:reply, state.sync_modules, %{state | sync_modules: [], loaded: System.monotonic_time()}}
|
||||
end
|
||||
|
||||
# Called by the runner when --repeat-until-failure is used.
|
||||
def handle_call({:restore_modules, async_modules, sync_modules}, _from, state) do
|
||||
{async_modules, async_groups} =
|
||||
Enum.map_reduce(async_modules, %{}, fn
|
||||
{nil, [module]}, {modules, groups} ->
|
||||
{[{:module, module} | modules], groups}
|
||||
{async_modules, async_groups, groups} =
|
||||
Enum.reduce(async_modules, {[], [], []}, fn
|
||||
{nil, [module]}, {async_modules, async_groups, groups} ->
|
||||
{[module | async_modules], async_groups, groups}
|
||||
|
||||
{group, group_modules}, {modules, groups} ->
|
||||
{[{:group, group} | modules], Map.put(groups, group, group_modules)}
|
||||
{group, group_modules}, {async_modules, async_groups, groups} ->
|
||||
{async_modules, [group | async_groups], [{group, group_modules} | groups]}
|
||||
end)
|
||||
|
||||
{:reply, :ok,
|
||||
%{
|
||||
state
|
||||
| loaded: :done,
|
||||
groups: Map.new(groups),
|
||||
async_groups: async_groups,
|
||||
async_modules: :queue.from_list(async_modules),
|
||||
sync_modules: :queue.from_list(sync_modules)
|
||||
sync_modules: sync_modules
|
||||
}}
|
||||
end
|
||||
|
||||
@@ -108,22 +109,24 @@ defmodule ExUnit.Server do
|
||||
when is_integer(loaded) do
|
||||
state =
|
||||
if uniq? do
|
||||
async_groups =
|
||||
Map.new(state.async_groups, fn {group, modules} ->
|
||||
{group, Enum.uniq(modules)}
|
||||
end)
|
||||
|
||||
groups = Map.new(state.groups, fn {group, modules} -> {group, Enum.uniq(modules)} end)
|
||||
async_groups = state.async_groups |> Enum.uniq() |> Enum.reverse()
|
||||
async_modules = :queue.to_list(state.async_modules) |> Enum.uniq() |> :queue.from_list()
|
||||
sync_modules = :queue.to_list(state.sync_modules) |> Enum.uniq() |> :queue.from_list()
|
||||
sync_modules = state.sync_modules |> Enum.uniq() |> Enum.reverse()
|
||||
|
||||
%{
|
||||
state
|
||||
| async_groups: async_groups,
|
||||
| groups: groups,
|
||||
async_groups: async_groups,
|
||||
async_modules: async_modules,
|
||||
sync_modules: sync_modules
|
||||
}
|
||||
else
|
||||
state
|
||||
%{
|
||||
state
|
||||
| async_groups: Enum.reverse(state.async_groups),
|
||||
sync_modules: Enum.reverse(state.sync_modules)
|
||||
}
|
||||
end
|
||||
|
||||
diff = System.convert_time_unit(System.monotonic_time() - loaded, :native, :microsecond)
|
||||
@@ -132,9 +135,7 @@ defmodule ExUnit.Server do
|
||||
|
||||
def handle_call({:add, false = _async, _group, names}, _from, %{loaded: loaded} = state)
|
||||
when is_integer(loaded) do
|
||||
state =
|
||||
update_in(state.sync_modules, &Enum.reduce(names, &1, fn name, q -> :queue.in(name, q) end))
|
||||
|
||||
state = update_in(state.sync_modules, &Enum.reverse(names, &1))
|
||||
{:reply, :ok, state}
|
||||
end
|
||||
|
||||
@@ -143,7 +144,7 @@ defmodule ExUnit.Server do
|
||||
state =
|
||||
update_in(
|
||||
state.async_modules,
|
||||
&Enum.reduce(names, &1, fn name, q -> :queue.in({:module, name}, q) end)
|
||||
&Enum.reduce(names, &1, fn name, q -> :queue.in(name, q) end)
|
||||
)
|
||||
|
||||
{:reply, :ok, take_modules(state)}
|
||||
@@ -151,17 +152,16 @@ defmodule ExUnit.Server do
|
||||
|
||||
def handle_call({:add, true = _async, group, names}, _from, %{loaded: loaded} = state)
|
||||
when is_integer(loaded) do
|
||||
{async_groups, async_modules} =
|
||||
case state.async_groups do
|
||||
%{^group => entries} = async_groups ->
|
||||
{%{async_groups | group => names ++ entries}, state.async_modules}
|
||||
{groups, async_groups} =
|
||||
case state.groups do
|
||||
%{^group => entries} = groups ->
|
||||
{%{groups | group => Enum.reverse(names, entries)}, state.async_groups}
|
||||
|
||||
%{} = async_groups ->
|
||||
{Map.put(async_groups, group, names), :queue.in({:group, group}, state.async_modules)}
|
||||
%{} = groups ->
|
||||
{Map.put(groups, group, names), [group | state.async_groups]}
|
||||
end
|
||||
|
||||
{:reply, :ok,
|
||||
take_modules(%{state | async_groups: async_groups, async_modules: async_modules})}
|
||||
{:reply, :ok, take_modules(%{state | groups: groups, async_groups: async_groups})}
|
||||
end
|
||||
|
||||
def handle_call({:add, _async?, _group, _names}, _from, state) do
|
||||
@@ -173,50 +173,42 @@ defmodule ExUnit.Server do
|
||||
end
|
||||
|
||||
defp take_modules(%{waiting: {from, count}} = state) do
|
||||
has_async_modules? = not :queue.is_empty(state.async_modules)
|
||||
|
||||
cond do
|
||||
not has_async_modules? and state.loaded == :done ->
|
||||
not :queue.is_empty(state.async_modules) ->
|
||||
{reply, remaining_modules} = take_until(count, state.async_modules)
|
||||
GenServer.reply(from, reply)
|
||||
%{state | async_modules: remaining_modules, waiting: nil}
|
||||
|
||||
state.async_groups != [] and state.loaded == :done ->
|
||||
{groups, remaining_groups} = Enum.split(state.async_groups, count)
|
||||
|
||||
{reply, groups} =
|
||||
Enum.map_reduce(groups, state.groups, fn group, acc ->
|
||||
{entries, acc} = Map.pop!(acc, group)
|
||||
{{group, Enum.reverse(entries)}, acc}
|
||||
end)
|
||||
|
||||
GenServer.reply(from, reply)
|
||||
%{state | groups: groups, async_groups: remaining_groups, waiting: nil}
|
||||
|
||||
state.loaded == :done ->
|
||||
GenServer.reply(from, nil)
|
||||
%{state | waiting: nil}
|
||||
|
||||
not has_async_modules? ->
|
||||
state
|
||||
|
||||
true ->
|
||||
{async_modules, remaining_modules} = take_until(count, state.async_modules)
|
||||
|
||||
{async_modules, remaining_groups} =
|
||||
Enum.map_reduce(async_modules, state.async_groups, fn
|
||||
{:module, module}, async_groups ->
|
||||
{[module], async_groups}
|
||||
|
||||
{:group, group}, async_groups ->
|
||||
{group_modules, async_groups} = Map.pop!(async_groups, group)
|
||||
{Enum.reverse(group_modules), async_groups}
|
||||
end)
|
||||
|
||||
GenServer.reply(from, async_modules)
|
||||
|
||||
%{
|
||||
state
|
||||
| async_groups: remaining_groups,
|
||||
async_modules: remaining_modules,
|
||||
waiting: nil
|
||||
}
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
# :queue.split fails if the provided count is larger than the queue size;
|
||||
# as we also want to return the values as a list later, we directly
|
||||
# return {list, queue} instead of {queue, queue}
|
||||
# :queue.split fails if the provided count is larger than the queue size.
|
||||
# We also want to return the values as tuples of shape {group, [modules]}.
|
||||
defp take_until(n, queue), do: take_until(n, queue, [])
|
||||
|
||||
defp take_until(0, queue, acc), do: {Enum.reverse(acc), queue}
|
||||
|
||||
defp take_until(n, queue, acc) do
|
||||
case :queue.out(queue) do
|
||||
{{:value, item}, queue} -> take_until(n - 1, queue, [item | acc])
|
||||
{{:value, item}, queue} -> take_until(n - 1, queue, [{nil, [item]} | acc])
|
||||
{:empty, queue} -> {Enum.reverse(acc), queue}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1133,6 +1133,12 @@ defmodule ExUnit.DiffTest do
|
||||
"-<<trap::binary-size(3)>> <> \"baz\"-",
|
||||
"+\"foobar\"+"
|
||||
)
|
||||
|
||||
refute_diff(
|
||||
"hello " <> <<_::binary-size(6)>> = "hello world",
|
||||
"\"hello \" <> -<<_::binary-size(6)>>-",
|
||||
"\"hello +world+\""
|
||||
)
|
||||
end
|
||||
|
||||
test "underscore" do
|
||||
|
||||
@@ -997,6 +997,24 @@ defmodule ExUnitTest do
|
||||
assert length(runs) == 6
|
||||
end
|
||||
|
||||
test "repeats tests up to the configured number of times with groups" do
|
||||
defmodule TestGroupedRepeatUntilFailureReached do
|
||||
use ExUnit.Case, async: true, group: :example
|
||||
test __ENV__.line, do: assert(true)
|
||||
end
|
||||
|
||||
configure_and_reload_on_exit(repeat_until_failure: 5)
|
||||
|
||||
output =
|
||||
capture_io(fn ->
|
||||
assert ExUnit.run() == %{total: 1, failures: 0, skipped: 0, excluded: 0}
|
||||
end)
|
||||
|
||||
runs = String.split(output, "Running ExUnit", trim: true)
|
||||
# 6 runs in total, 5 repeats
|
||||
assert length(runs) == 6
|
||||
end
|
||||
|
||||
test "stops on failure" do
|
||||
{:ok, pid} = Agent.start_link(fn -> 0 end)
|
||||
Process.register(pid, :ex_unit_repeat_until_failure_count)
|
||||
|
||||
@@ -363,7 +363,7 @@ defmodule IEx.Autocomplete do
|
||||
end)
|
||||
|
||||
entries =
|
||||
for {key, _value} <- pairs,
|
||||
for key when key != :__struct__ <- Map.keys(pairs),
|
||||
name = Atom.to_string(key),
|
||||
if(hint == "",
|
||||
do: not String.starts_with?(name, "_"),
|
||||
|
||||
@@ -430,6 +430,19 @@ defmodule IEx.AutocompleteTest do
|
||||
assert {:yes, ~c"ry: ", []} = expand(~c"%URI{var | path: \"foo\", que")
|
||||
assert {:no, [], []} = expand(~c"%URI{var | path: \"foo\", unkno")
|
||||
assert {:no, [], []} = expand(~c"%Unknown{var | path: \"foo\", unkno")
|
||||
|
||||
eval("var = %URI{}")
|
||||
|
||||
assert {:yes, ~c"", entries} = expand(~c"%{var | ")
|
||||
assert ~c"path:" in entries
|
||||
assert ~c"query:" in entries
|
||||
|
||||
assert {:yes, ~c"", entries} = expand(~c"%{var | path: \"foo\",")
|
||||
assert ~c"path:" not in entries
|
||||
assert ~c"query:" in entries
|
||||
|
||||
assert {:yes, ~c"ry: ", []} = expand(~c"%{var | path: \"foo\", que")
|
||||
assert {:no, [], []} = expand(~c"%URI{var | path: \"foo\", unkno")
|
||||
end
|
||||
|
||||
test "completion for map keys in update syntax" do
|
||||
|
||||
@@ -352,6 +352,11 @@ defmodule Mix do
|
||||
* `MIX_INSTALL_DIR` *(since v1.12.0)* - specifies directory where `Mix.install/2` keeps
|
||||
install cache
|
||||
|
||||
* `MIX_OS_CONCURRENCY_LOCK` - when set to `0` or `false`, disables mix compilation locking.
|
||||
While not recommended, this may be necessary in cases where hard links or TCP sockets are
|
||||
not available. When opting for this behaviour, make sure to not start concurrent compilations
|
||||
of the same project.
|
||||
|
||||
* `MIX_PATH` - appends extra code paths
|
||||
|
||||
* `MIX_PROFILE` - a list of comma-separated Mix tasks to profile the time spent on
|
||||
|
||||
@@ -208,14 +208,18 @@ defmodule Mix.Compilers.Elixir do
|
||||
end
|
||||
|
||||
Mix.Task.Compiler.notify_modules_compiled(lazy_modules_diff)
|
||||
|
||||
unless_previous_warnings_as_errors(previous_warnings, opts, {:ok, all_warnings})
|
||||
unless_warnings_as_errors(opts, {:ok, all_warnings})
|
||||
|
||||
{:error, errors, %{runtime_warnings: r_warnings, compile_warnings: c_warnings}, state} ->
|
||||
{errors, warnings} =
|
||||
if opts[:warnings_as_errors],
|
||||
do: {errors ++ r_warnings ++ c_warnings, []},
|
||||
else: {errors, r_warnings ++ c_warnings}
|
||||
|
||||
# In case of errors, we show all previous warnings and all new ones.
|
||||
{_, _, sources, _, _, _} = state
|
||||
errors = Enum.map(errors, &diagnostic/1)
|
||||
warnings = Enum.map(r_warnings ++ c_warnings, &diagnostic/1)
|
||||
warnings = Enum.map(warnings, &diagnostic/1)
|
||||
all_warnings = Keyword.get(opts, :all_warnings, errors == [])
|
||||
{:error, previous_warnings(sources, all_warnings) ++ warnings ++ errors}
|
||||
after
|
||||
@@ -254,8 +258,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
|
||||
all_warnings = Keyword.get(opts, :all_warnings, true)
|
||||
previous_warnings = previous_warnings(sources, all_warnings)
|
||||
|
||||
unless_previous_warnings_as_errors(previous_warnings, opts, {status, previous_warnings})
|
||||
unless_warnings_as_errors(opts, {status, previous_warnings})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1012,8 +1015,8 @@ defmodule Mix.Compilers.Elixir do
|
||||
File.rm(manifest <> ".checkpoint")
|
||||
end
|
||||
|
||||
defp unless_previous_warnings_as_errors(previous_warnings, opts, {status, all_warnings}) do
|
||||
if previous_warnings != [] and opts[:warnings_as_errors] do
|
||||
defp unless_warnings_as_errors(opts, {status, all_warnings}) do
|
||||
if all_warnings != [] and opts[:warnings_as_errors] do
|
||||
message = "Compilation failed due to warnings while using the --warnings-as-errors option"
|
||||
IO.puts(:stderr, message)
|
||||
{:error, all_warnings}
|
||||
@@ -1049,7 +1052,6 @@ defmodule Mix.Compilers.Elixir do
|
||||
threshold = opts[:long_compilation_threshold] || 10
|
||||
profile = opts[:profile]
|
||||
verbose = opts[:verbose] || false
|
||||
warnings_as_errors = opts[:warnings_as_errors] || false
|
||||
|
||||
pid =
|
||||
spawn_link(fn ->
|
||||
@@ -1071,8 +1073,7 @@ defmodule Mix.Compilers.Elixir do
|
||||
long_compilation_threshold: threshold,
|
||||
profile: profile,
|
||||
beam_timestamp: timestamp,
|
||||
return_diagnostics: true,
|
||||
warnings_as_errors: warnings_as_errors
|
||||
return_diagnostics: true
|
||||
]
|
||||
|
||||
response = Kernel.ParallelCompiler.compile_to_path(stale, dest, compile_opts)
|
||||
|
||||
@@ -917,7 +917,10 @@ defmodule Mix.Project do
|
||||
build_path = build_path(config)
|
||||
|
||||
on_taken = fn os_pid ->
|
||||
Mix.shell().info("Waiting for lock on the build directory (held by process #{os_pid})")
|
||||
Mix.shell().error([
|
||||
IO.ANSI.reset(),
|
||||
"Waiting for lock on the build directory (held by process #{os_pid})"
|
||||
])
|
||||
end
|
||||
|
||||
Mix.Sync.Lock.with_lock(build_path, fun, on_taken: on_taken)
|
||||
@@ -931,7 +934,10 @@ defmodule Mix.Project do
|
||||
deps_path = deps_path(config)
|
||||
|
||||
on_taken = fn os_pid ->
|
||||
Mix.shell().info("Waiting for lock on the deps directory (held by process #{os_pid})")
|
||||
Mix.shell().error([
|
||||
IO.ANSI.reset(),
|
||||
"Waiting for lock on the deps directory (held by process #{os_pid})"
|
||||
])
|
||||
end
|
||||
|
||||
Mix.Sync.Lock.with_lock(deps_path, fun, on_taken: on_taken)
|
||||
|
||||
@@ -126,17 +126,17 @@ defmodule Mix.SCM.Git do
|
||||
update_origin(opts[:git])
|
||||
|
||||
# Fetch external data
|
||||
rev = get_lock_rev(opts[:lock], opts) || get_opts_rev(opts)
|
||||
lock_rev = get_lock_rev(opts[:lock], opts)
|
||||
|
||||
["--git-dir=.git", "fetch", "--force", "--quiet"]
|
||||
|> Kernel.++(progress_switch(git_version()))
|
||||
|> Kernel.++(tags_switch(opts[:tag]))
|
||||
|> Kernel.++(depth_switch(opts[:depth]))
|
||||
|> Kernel.++(refspec_switch(opts, rev))
|
||||
|> Kernel.++(refspec_switch(opts, lock_rev || get_opts_rev(opts)))
|
||||
|> git!()
|
||||
|
||||
# Migrate the Git repo
|
||||
rev = rev || default_branch()
|
||||
rev = lock_rev || get_origin_opts_rev(opts) || default_branch()
|
||||
git!(["--git-dir=.git", "checkout", "--quiet", rev])
|
||||
|
||||
if opts[:submodules] do
|
||||
@@ -314,6 +314,14 @@ defmodule Mix.SCM.Git do
|
||||
opts[:branch] || opts[:ref] || opts[:tag]
|
||||
end
|
||||
|
||||
defp get_origin_opts_rev(opts) do
|
||||
if branch = opts[:branch] do
|
||||
"origin/#{branch}"
|
||||
else
|
||||
opts[:ref] || opts[:tag]
|
||||
end
|
||||
end
|
||||
|
||||
defp redact_uri(git) do
|
||||
case URI.parse(git) do
|
||||
%{userinfo: nil} -> git
|
||||
|
||||
@@ -100,12 +100,12 @@ defmodule Mix.Shell.IO do
|
||||
"""
|
||||
def cmd(command, opts \\ []) do
|
||||
print_app? = Keyword.get(opts, :print_app, true)
|
||||
windows? = match?({:win32, _}, :os.type())
|
||||
|
||||
Mix.Shell.cmd(command, opts, fn data ->
|
||||
if print_app?, do: print_app()
|
||||
# Due to encoding of shell command on Windows,
|
||||
# let's write the data as is
|
||||
IO.binwrite(data)
|
||||
# Due to encoding of shell command on Windows, write the data as is.
|
||||
if windows?, do: IO.binwrite(data), else: IO.write(data)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,6 +79,10 @@ defmodule Mix.Sync.Lock do
|
||||
This function can also be called if this process already has the
|
||||
lock. In such case the function is executed immediately.
|
||||
|
||||
When the `MIX_OS_CONCURRENCY_LOCK` environment variable is set to
|
||||
a falsy value, the lock is ignored and the function is executed
|
||||
immediately.
|
||||
|
||||
## Options
|
||||
|
||||
* `:on_taken` - a one-arity function called if the lock is held
|
||||
@@ -93,12 +97,12 @@ defmodule Mix.Sync.Lock do
|
||||
opts = Keyword.validate!(opts, [:on_taken])
|
||||
|
||||
hash = key |> :erlang.md5() |> Base.url_encode64(padding: false)
|
||||
path = Path.join([System.tmp_dir!(), "mix_lock", hash])
|
||||
path = Path.join(base_path(), hash)
|
||||
|
||||
pdict_key = {__MODULE__, path}
|
||||
has_lock? = Process.get(pdict_key)
|
||||
has_lock? = Process.get(pdict_key, false)
|
||||
|
||||
if has_lock? do
|
||||
if has_lock? or lock_disabled?() do
|
||||
fun.()
|
||||
else
|
||||
lock = lock(path, opts[:on_taken])
|
||||
@@ -115,6 +119,14 @@ defmodule Mix.Sync.Lock do
|
||||
end
|
||||
end
|
||||
|
||||
defp base_path do
|
||||
# We include user in the dir to avoid permission conflicts across users
|
||||
user = System.get_env("USER", "default")
|
||||
Path.join(System.tmp_dir!(), "mix_lock_#{Base.url_encode64(user, padding: false)}")
|
||||
end
|
||||
|
||||
defp lock_disabled?(), do: System.get_env("MIX_OS_CONCURRENCY_LOCK") in ~w(0 false)
|
||||
|
||||
defp lock(path, on_taken) do
|
||||
File.mkdir_p!(path)
|
||||
|
||||
@@ -198,11 +210,12 @@ defmodule Mix.Sync.Lock do
|
||||
:invalidated
|
||||
|
||||
{:error, reason} ->
|
||||
raise File.LinkError,
|
||||
reason: reason,
|
||||
action: "create hard link",
|
||||
existing: port_path,
|
||||
new: lock_path
|
||||
Mix.raise("""
|
||||
could not create hard link from #{port_path} to "#{lock_path}: #{:file.format_error(reason)}.
|
||||
|
||||
Hard link support is required for Mix compilation locking. If your system \
|
||||
does not support hard links, set MIX_OS_CONCURRENCY_LOCK=0\
|
||||
""")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -274,7 +274,13 @@ defmodule Mix.Sync.PubSub do
|
||||
|
||||
defp path(hash) do
|
||||
hash = Base.url_encode64(hash, padding: false)
|
||||
Path.join([System.tmp_dir!(), "mix_pubsub", hash])
|
||||
Path.join(base_path(), hash)
|
||||
end
|
||||
|
||||
defp base_path do
|
||||
# We include user in the dir to avoid permission conflicts across users
|
||||
user = System.get_env("USER", "default")
|
||||
Path.join(System.tmp_dir!(), "mix_pubsub_#{Base.url_encode64(user, padding: false)}")
|
||||
end
|
||||
|
||||
defp recv(socket, size, timeout \\ :infinity) do
|
||||
|
||||
@@ -48,6 +48,12 @@ defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
Mix.Task.run("archive.check", args)
|
||||
end
|
||||
|
||||
config = Mix.Project.config()
|
||||
|
||||
if "--no-elixir-version-check" not in args do
|
||||
check_elixir_version(config)
|
||||
end
|
||||
|
||||
all = Mix.Dep.load_and_cache()
|
||||
|
||||
all =
|
||||
@@ -57,14 +63,8 @@ defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
all
|
||||
end
|
||||
|
||||
config = Mix.Project.config()
|
||||
|
||||
if "--no-elixir-version-check" not in args do
|
||||
check_elixir_version(config)
|
||||
end
|
||||
|
||||
if "--no-deps-check" not in args do
|
||||
deps_check(all, "--no-compile" in args)
|
||||
deps_check(config, all, "--no-compile" in args)
|
||||
end
|
||||
|
||||
Code.prepend_paths(Enum.flat_map(all, &Mix.Dep.load_paths/1), cache: true)
|
||||
@@ -93,25 +93,38 @@ defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
end
|
||||
end
|
||||
|
||||
defp deps_check(config, all, no_compile?) do
|
||||
with {:compile, _to_compile} <- deps_check(all, no_compile?) do
|
||||
# We need to compile, we first grab the lock, then, we check
|
||||
# again and compile if still applicable
|
||||
Mix.Project.with_build_lock(config, fn ->
|
||||
all = reload_deps(all)
|
||||
|
||||
with {:compile, to_compile} <- deps_check(all, no_compile?) do
|
||||
Mix.Tasks.Deps.Compile.compile(to_compile)
|
||||
|
||||
to_compile
|
||||
|> reload_deps()
|
||||
|> Enum.filter(&(not Mix.Dep.ok?(&1)))
|
||||
|> show_not_ok!()
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp deps_check(all, no_compile?) do
|
||||
all = Enum.map(all, &check_lock/1)
|
||||
{not_ok, compile} = partition(all, [], [])
|
||||
{not_ok, to_compile} = partition(all, [], [])
|
||||
|
||||
cond do
|
||||
not_ok != [] ->
|
||||
show_not_ok!(not_ok)
|
||||
|
||||
compile == [] or no_compile? ->
|
||||
to_compile == [] or no_compile? ->
|
||||
:ok
|
||||
|
||||
true ->
|
||||
Mix.Tasks.Deps.Compile.compile(compile)
|
||||
|
||||
compile
|
||||
|> Enum.map(& &1.app)
|
||||
|> Mix.Dep.filter_by_name(Mix.Dep.load_and_cache())
|
||||
|> Enum.filter(&(not Mix.Dep.ok?(&1)))
|
||||
|> show_not_ok!()
|
||||
{:compile, to_compile}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -136,6 +149,12 @@ defmodule Mix.Tasks.Deps.Loadpaths do
|
||||
{Enum.reverse(not_ok), Enum.reverse(compile)}
|
||||
end
|
||||
|
||||
defp reload_deps(deps) do
|
||||
deps
|
||||
|> Enum.map(& &1.app)
|
||||
|> Mix.Dep.filter_by_name(Mix.Dep.load_and_cache())
|
||||
end
|
||||
|
||||
# Those are compiled by umbrella.
|
||||
defp from_umbrella?(dep) do
|
||||
dep.opts[:from_umbrella]
|
||||
|
||||
@@ -342,7 +342,7 @@ defmodule Mix.Tasks.Escript.Build do
|
||||
{zip_path, consolidated[Path.basename(path)] || path}
|
||||
end
|
||||
else
|
||||
[]
|
||||
files
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ defmodule Mix.Tasks.Test do
|
||||
and imports (but not local functions). You can press `n` for the next line
|
||||
and `c` for the next test. This automatically sets `--trace`
|
||||
|
||||
* `--color` - enables color in the output
|
||||
* `--color` - enables color in ExUnit formatting results
|
||||
|
||||
* `--cover` - runs coverage tool. See "Coverage" section below
|
||||
|
||||
|
||||
@@ -1045,12 +1045,15 @@ defmodule Mix.Tasks.Compile.ElixirTest do
|
||||
|
||||
# Compiles with missing external resources
|
||||
file = Path.absname("lib/a.eex")
|
||||
source = Path.absname("lib/a.ex")
|
||||
|
||||
assert capture_io(:stderr, fn ->
|
||||
assert {:ok, [%Mix.Task.Compiler.Diagnostic{file: ^file, position: 13}]} =
|
||||
assert {:ok,
|
||||
[%Mix.Task.Compiler.Diagnostic{source: ^source, file: ^file, position: 13}]} =
|
||||
Mix.Tasks.Compile.Elixir.run([])
|
||||
|
||||
assert {:noop, [%Mix.Task.Compiler.Diagnostic{file: ^file, position: 13}]} =
|
||||
assert {:noop,
|
||||
[%Mix.Task.Compiler.Diagnostic{source: ^source, file: ^file, position: 13}]} =
|
||||
Mix.Tasks.Compile.Elixir.run(["--all-warnings"])
|
||||
end) =~ "oops"
|
||||
|
||||
|
||||
@@ -50,6 +50,20 @@ defmodule Mix.Tasks.EscriptTest do
|
||||
end)
|
||||
end
|
||||
|
||||
test "generate escript without protocol consolidation" do
|
||||
in_fixture("escript_test", fn ->
|
||||
push_project_with_config(Escript, consolidate_protocols: false)
|
||||
|
||||
Mix.Tasks.Escript.Build.run([])
|
||||
assert_received {:mix_shell, :info, ["Generated escript escript_test with MIX_ENV=dev"]}
|
||||
assert System.cmd("escript", ["escript_test"]) == {"TEST\n", 0}
|
||||
assert count_abstract_code("escript_test") == 0
|
||||
|
||||
# Does not consolidate protocols
|
||||
assert System.cmd("escript", ["escript_test", "--protocol", "Enumerable"]) == {"false\n", 0}
|
||||
end)
|
||||
end
|
||||
|
||||
test "generate escript with --no-compile option" do
|
||||
in_fixture("escript_test", fn ->
|
||||
push_project_with_config(Escript)
|
||||
|
||||
Reference in New Issue
Block a user