Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dfe6612e54 | ||
|
|
5230d73968 | ||
|
|
ef36b163b3 | ||
|
|
a622a8ef92 | ||
|
|
66bb6da847 | ||
|
|
eee55adcd0 | ||
|
|
cbd5ffce3e | ||
|
|
a3dda22035 | ||
|
|
7184679788 | ||
|
|
aeb2ea1dda | ||
|
|
7b20c281d5 | ||
|
|
c69f4407fd | ||
|
|
88f65ae1a6 | ||
|
|
14d406fcd9 | ||
|
|
08a9d4b9fc | ||
|
|
b3ab3696ec | ||
|
|
afe4704666 | ||
|
|
c51acfcbc5 | ||
|
|
332ff949a0 | ||
|
|
ab17a3c050 | ||
|
|
2522bba1bf | ||
|
|
d00f172c59 | ||
|
|
0eb5289cd0 | ||
|
|
7e01628b5d | ||
|
|
6432e8655c | ||
|
|
e34495e304 | ||
|
|
64c4ecf9c7 | ||
|
|
ef002e2b15 | ||
|
|
f89c5076f9 | ||
|
|
18d367c01b | ||
|
|
65baed8681 | ||
|
|
76c64a0f32 | ||
|
|
6ac1d10f77 | ||
|
|
ca57cfe6f3 | ||
|
|
c32f72581a | ||
|
|
8115bd38a1 | ||
|
|
e35ffc5a90 | ||
|
|
178643f9eb | ||
|
|
75677b9dab | ||
|
|
25ab648502 | ||
|
|
8493f1934a | ||
|
|
c63aeb9f77 | ||
|
|
d89fabe5e4 | ||
|
|
d51153b56e | ||
|
|
69990a5d1d | ||
|
|
930ec69739 | ||
|
|
d7478095e0 | ||
|
|
b823f9efdf | ||
|
|
6ecb430614 | ||
|
|
b615c8435a | ||
|
|
120467f83c | ||
|
|
4b09a836e0 | ||
|
|
55c05d943e | ||
|
|
8e61baacab | ||
|
|
7426acb1b9 | ||
|
|
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 |
@@ -3,12 +3,12 @@ name: CI for Markdown content
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- "main"
|
||||
paths:
|
||||
- 'lib/**/*.md'
|
||||
- "lib/**/*.md"
|
||||
pull_request:
|
||||
paths:
|
||||
- 'lib/**/*.md'
|
||||
- "lib/**/*.md"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out the repository
|
||||
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
name: Windows Server 2019, Erlang/OTP ${{ matrix.otp_version }}
|
||||
strategy:
|
||||
matrix:
|
||||
otp_version: ["25.3", "26.2", "27.1"]
|
||||
otp_version: ["25.3", "26.2", "27.3"]
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: Configure Git
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
notify:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-latest
|
||||
name: Notify
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -18,8 +18,8 @@ jobs:
|
||||
fetch-depth: 50
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: '25.0'
|
||||
elixir-version: '1.14.0'
|
||||
otp-version: "27.3"
|
||||
elixir-version: "1.18.3"
|
||||
- name: Run Elixir script
|
||||
env:
|
||||
ELIXIR_FORUM_TOKEN: ${{ secrets.ELIXIR_FORUM_TOKEN }}
|
||||
|
||||
+109
-49
@@ -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,34 +116,94 @@ jobs:
|
||||
name: Docs
|
||||
path: Docs.zip*
|
||||
|
||||
upload-release:
|
||||
needs: build
|
||||
runs-on: windows-2022
|
||||
sign:
|
||||
needs: [build]
|
||||
environment: release
|
||||
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' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
|
||||
- run: |
|
||||
mv elixir-otp-*/* .
|
||||
mv Docs/* .
|
||||
shell: bash
|
||||
|
||||
- name: "Sign files with Trusted Signing"
|
||||
if: github.repository == 'elixir-lang/elixir'
|
||||
uses: azure/trusted-signing-action@v0.5.0
|
||||
with:
|
||||
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
name: build-${{ matrix.flavor }}-elixir-otp-${{ matrix.otp }}
|
||||
|
||||
- name: Log in to Azure
|
||||
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
||||
with:
|
||||
client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
|
||||
- name: "Sign files with Trusted Signing"
|
||||
uses: azure/trusted-signing-action@0d74250c661747df006298d0fb49944c10f16e03 # v0.5.1
|
||||
if: ${{ matrix.flavor == 'windows' && vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
with:
|
||||
endpoint: https://eus.codesigning.azure.net/
|
||||
trusted-signing-account-name: trusted-signing-elixir
|
||||
certificate-profile-name: Elixir
|
||||
trusted-signing-account-name: ${{ vars.AZURE_TRUSTED_SIGNING_ACCOUNT_NAME }}
|
||||
certificate-profile-name: ${{ vars.AZURE_CERTIFICATE_PROFILE_NAME }}
|
||||
files-folder: ${{ github.workspace }}
|
||||
files-folder-filter: exe
|
||||
file-digest: SHA256
|
||||
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,20 +230,27 @@ jobs:
|
||||
Docs.zip.sigstore
|
||||
|
||||
upload-builds-hex-pm:
|
||||
needs: build
|
||||
needs: [build, sign]
|
||||
runs-on: ubuntu-22.04
|
||||
concurrency: builds-hex-pm
|
||||
environment: release
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.HEX_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.HEX_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ secrets.HEX_AWS_S3_BUCKET }}
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
AWS_REGION: ${{ vars.HEX_AWS_REGION }}
|
||||
AWS_S3_BUCKET: ${{ vars.HEX_AWS_S3_BUCKET }}
|
||||
OTP_GENERIC_VERSION: "25"
|
||||
steps:
|
||||
- name: "Check if variables are set up"
|
||||
if: "${{ ! vars.HEX_AWS_REGION }}"
|
||||
run: |
|
||||
echo "Required variables for uploading to hex.pm are not set up, skipping..."
|
||||
exit 1
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: "{sign-*-elixir-otp-*,Docs}"
|
||||
merge-multiple: true
|
||||
|
||||
- name: Init purge keys file
|
||||
run: |
|
||||
@@ -202,7 +260,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 +278,6 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Upload Docs to S3
|
||||
working-directory: Docs
|
||||
run: |
|
||||
version=$(echo ${{ github.ref_name }} | sed -e 's/^v//g')
|
||||
|
||||
@@ -295,3 +351,7 @@ jobs:
|
||||
for key in $(cat purge_keys.txt); do
|
||||
purge "${key}"
|
||||
done
|
||||
env:
|
||||
FASTLY_REPO_SERVICE_ID: ${{ secrets.HEX_FASTLY_REPO_SERVICE_ID }}
|
||||
FASTLY_BUILDS_SERVICE_ID: ${{ secrets.HEX_FASTLY_BUILDS_SERVICE_ID }}
|
||||
FASTLY_KEY: ${{ secrets.HEX_FASTLY_KEY }}
|
||||
|
||||
@@ -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
|
||||
|
||||
+163
-11
@@ -162,17 +162,15 @@ Both encoder and decoder fully conform to [RFC 8259](https://tools.ietf.org/html
|
||||
|
||||
Encoding can be done via `JSON.encode!/1` and `JSON.encode_to_iodata!/1` functions. The default encoding rules are applied as follows:
|
||||
|
||||
| **Elixir** | **JSON** |
|
||||
|------------------------|----------|
|
||||
| `integer() \| float()` | Number |
|
||||
| `true \| false ` | Boolean |
|
||||
| `nil` | Null |
|
||||
| `binary()` | String |
|
||||
| `atom()` | String |
|
||||
| `list()` | Array |
|
||||
| `%{binary() => _}` | Object |
|
||||
| `%{atom() => _}` | Object |
|
||||
| `%{integer() => _}` | Object |
|
||||
| **Elixir** | **JSON** |
|
||||
|-----------------------------|----------|
|
||||
| `integer() \| float()` | Number |
|
||||
| `true \| false ` | Boolean |
|
||||
| `nil` | Null |
|
||||
| `binary()` | String |
|
||||
| `atom()` | String |
|
||||
| `list()` | Array |
|
||||
| `%{String.Chars.t() => _}` | Object |
|
||||
|
||||
You may also implement the `JSON.Encoder` protocol for custom data structures. Elixir already implements the protocol for all Calendar types.
|
||||
|
||||
@@ -225,6 +223,154 @@ You may also prefer to write using guards:
|
||||
|
||||
def foo(x, y, z) when x == y and y == z
|
||||
|
||||
## v1.18.5 (2026-08-28)
|
||||
|
||||
### 1. Security
|
||||
|
||||
* [List] Avoid recursion when invalid charlists are given to `List.to_string/1` or `List.to_charlist/1` (CVE-2026-75758, GHSA-jf5q-v438-665c)
|
||||
|
||||
## v1.18.4 (2025-05-21)
|
||||
|
||||
This release includes initial support for Erlang/OTP 28, for those who want to try it out. In such cases, you may use Elixir v1.18.4 precompiled for Erlang/OTP 27, as it is binary compatible with Erlang/OTP 28. Note, however, that Erlang/OTP 28 no longer allows regexes to be defined in the module body and interpolated into an attribute. If you do this:
|
||||
|
||||
```elixir
|
||||
@some_attribute ~r/foo/
|
||||
def some_fun, do: @some_attribute
|
||||
```
|
||||
|
||||
You must rewrite it to:
|
||||
|
||||
```elixir
|
||||
def some_fun, do: ~r/foo/
|
||||
```
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Helpers] Add `IEx.Helpers.process_info/1` which prints process information
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix compile] Support the `--no-listeners` option
|
||||
* [mix local] Retry HTTP requests with disabled middlebox comp mode depending on the failure reason
|
||||
* [mix local.hex] Install Hex per OTP release
|
||||
* [mix local.rebar] Install Hex per OTP release
|
||||
* [mix run] Support the `--no-listeners` option
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Emit trace events for `@on_definition` callbacks
|
||||
* [Kernel] Emit trace events for `@on_load` callbacks
|
||||
* [Kernel] Emit trace events for `super` calls
|
||||
* [Kernel] Emit trace events for imported function calls
|
||||
* [Kernel] Optimize map unions to avoid building long lists
|
||||
* [Kernel] Do not crash when type checking nested bitstrings in patterns
|
||||
* [Kernel] Do not crash when non-binary bitstring is given as struct default value
|
||||
* [Kernel] Recompile regexes when escaped from module attributes for Erlang/OTP 28 compatibility
|
||||
* [Kernel] Preserve backwards compatibility in `elixir_erl`
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix deps.get] Ensure git checkout works when there are untracked files in the dependency
|
||||
* [mix loadpaths] Do not run listeners when not checking the deps
|
||||
|
||||
## v1.18.3 (2025-03-06)
|
||||
|
||||
### 1. Enhancements
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [JSON] Encode any JSON key to string
|
||||
* [Kernel] Allow `<<_::3*8>>` in typespecs
|
||||
|
||||
#### Mix
|
||||
|
||||
* [mix loadpaths] Support `--no-listeners` option
|
||||
|
||||
### 2. Bug fixes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [CLI] Fix `--no-color` not setting `:ansi_enabled` to false
|
||||
* [Protocol] Return correct implementation for an invalid struct pointing to `nil`
|
||||
* [Stream] Do not raise when `Stream.cycle/1` is explicitly halted
|
||||
|
||||
#### ExUnit
|
||||
|
||||
* [ExUnit.Diff] Fix regression when diffing nested improper lists
|
||||
|
||||
#### IEx
|
||||
|
||||
* [IEx.Autocomplete] Fix autocomplete crash when expanding struct with `__MODULE__`
|
||||
* [IEx.Helpers] Do not purge on `recompile` if IEx is not running
|
||||
|
||||
## v1.18.2 (2025-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 when 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
|
||||
@@ -348,6 +494,12 @@ You may also prefer to write using guards:
|
||||
* [mix test] `:warnings_as_errors` configuration in `:test_elixirc_options` is deprecated. Instead pass the `--warnings-as-errors` flag to `mix test`. Alternatively, you might alias the task: `aliases: [test: "test --warnings-as-errors"]`
|
||||
* [Mix.Tasks.Compile] Deprecate `compilers/0` in favor of `Mix.Task.Compiler.compilers/0`
|
||||
|
||||
### 5. Potential breaking changes
|
||||
|
||||
#### Elixir
|
||||
|
||||
* [Kernel] Using unquote is deprecated when quote is used inside a pattern or guard
|
||||
|
||||
## v1.17
|
||||
|
||||
The CHANGELOG for v1.17 releases can be found [in the v1.17 branch](https://github.com/elixir-lang/elixir/blob/v1.17/CHANGELOG.md).
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
## Shipping a new version
|
||||
|
||||
1. Update version in /VERSION, bin/elixir, bin/elixir.bat, and bin/elixir.ps1
|
||||
1. Update version in /VERSION, bin/elixir, and bin/elixir.bat
|
||||
|
||||
2. Ensure /CHANGELOG.md is updated, versioned and add the current date
|
||||
|
||||
@@ -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.5
|
||||
|
||||
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.5
|
||||
|
||||
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
|
||||
|
||||
@@ -283,7 +283,6 @@ defmodule Code.Typespec do
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:type, anno, :binary, [arg1, arg2]}) do
|
||||
[arg1, arg2] = for arg <- [arg1, arg2], do: typespec_to_quoted(arg)
|
||||
line = meta(anno)[:line]
|
||||
|
||||
case {typespec_to_quoted(arg1), typespec_to_quoted(arg2)} do
|
||||
@@ -329,10 +328,14 @@ defmodule Code.Typespec do
|
||||
{erl_to_ex_var(var), meta(anno), nil}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:op, anno, op, arg}) do
|
||||
defp typespec_to_quoted({:op, anno, op, arg}) when op in [:+, :-] do
|
||||
{op, meta(anno), [typespec_to_quoted(arg)]}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:op, anno, :*, arg1, arg2}) do
|
||||
{:*, meta(anno), [typespec_to_quoted(arg1), typespec_to_quoted(arg2)]}
|
||||
end
|
||||
|
||||
defp typespec_to_quoted({:remote_type, anno, [mod, name, args]}) do
|
||||
remote_type(anno, mod, name, args)
|
||||
end
|
||||
|
||||
@@ -3837,6 +3837,9 @@ defmodule Enum do
|
||||
@doc """
|
||||
Enumerates the `enumerable`, removing all duplicate elements.
|
||||
|
||||
The first occurrence of each element is kept and all following
|
||||
duplicates are removed. The overall order is preserved.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Enum.uniq([1, 2, 3, 3, 2, 1])
|
||||
@@ -3862,7 +3865,8 @@ defmodule Enum do
|
||||
considered duplicates if the return value of `fun` is equal for
|
||||
both of them.
|
||||
|
||||
The first occurrence of each element is kept.
|
||||
The first occurrence of each element is kept and all following
|
||||
duplicates are removed. The overall order is preserved.
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -2530,7 +2530,7 @@ defmodule ErlangError do
|
||||
{:ok, reason, IO.iodata_to_binary([":\n\n" | Enum.map(args_errors, &arg_error/1)])}
|
||||
|
||||
general = extra[:general] ->
|
||||
{:ok, reason, ": " <> general}
|
||||
{:ok, reason, ": " <> IO.chardata_to_string(general)}
|
||||
|
||||
true ->
|
||||
:error
|
||||
|
||||
@@ -343,13 +343,20 @@ defimpl Inspect, for: List do
|
||||
close = color_doc("]", :list, opts)
|
||||
|
||||
cond do
|
||||
lists == :as_charlists or (lists == :infer and List.ascii_printable?(term, printable_limit)) ->
|
||||
inspected =
|
||||
case Identifier.escape(IO.chardata_to_string(term), ?", printable_limit) do
|
||||
{escaped, ""} -> [?~, ?c, ?", escaped, ?"]
|
||||
{escaped, _} -> [?~, ?c, ?", escaped, ?", " ++ ..."]
|
||||
(lists == :as_charlists and unicode_list?(term, printable_limit)) or
|
||||
(lists == :infer and List.ascii_printable?(term, printable_limit)) ->
|
||||
{split, tail} =
|
||||
if is_integer(printable_limit) do
|
||||
case Enum.split(term, printable_limit) do
|
||||
{split, []} -> {split, []}
|
||||
{split, _} -> {split, " ++ ..."}
|
||||
end
|
||||
else
|
||||
{term, []}
|
||||
end
|
||||
|
||||
{escaped, _} = Identifier.escape(IO.chardata_to_string(split), ?")
|
||||
inspected = [?~, ?c, ?", escaped, ?" | tail]
|
||||
color_doc(IO.iodata_to_binary(inspected), :charlist, opts)
|
||||
|
||||
keyword?(term) ->
|
||||
@@ -360,6 +367,19 @@ defimpl Inspect, for: List do
|
||||
end
|
||||
end
|
||||
|
||||
defp unicode_list?(_, 0), do: true
|
||||
|
||||
defp unicode_list?([char | rest], counter)
|
||||
when char in 0..0xD7FF or char in 0xE000..0x10FFFF,
|
||||
do: unicode_list?(rest, decrement(counter))
|
||||
|
||||
defp unicode_list?([], _counter), do: true
|
||||
defp unicode_list?(_, _counter), do: false
|
||||
|
||||
@compile {:inline, decrement: 1}
|
||||
defp decrement(:infinity), do: :infinity
|
||||
defp decrement(counter), do: counter - 1
|
||||
|
||||
@doc false
|
||||
def keyword({key, value}, opts) do
|
||||
key = color_doc(Macro.inspect_atom(:key, key), :atom, opts)
|
||||
|
||||
@@ -17,8 +17,8 @@ defmodule Inspect.Opts do
|
||||
is `:decimal` and if it is printable, otherwise in bit syntax. See
|
||||
`String.printable?/1` to learn when a string is printable.
|
||||
|
||||
* `:charlists` - when `:as_charlists` all lists will be printed as charlists,
|
||||
non-printable elements will be escaped.
|
||||
* `:charlists` - when `:as_charlists` all charlists will be printed as charlists,
|
||||
non-printable code points will be escaped. Other lists will be printed as lists.
|
||||
|
||||
When `:as_lists` all lists will be printed as lists.
|
||||
|
||||
|
||||
+33
-13
@@ -146,8 +146,30 @@ end
|
||||
|
||||
defimpl JSON.Encoder, for: Map do
|
||||
def encode(value, encoder) do
|
||||
:elixir_json.encode_map(value, encoder)
|
||||
case :maps.next(:maps.iterator(value)) do
|
||||
:none ->
|
||||
"{}"
|
||||
|
||||
{key, value, iterator} ->
|
||||
[?{, key(key, encoder), ?:, encoder.(value, encoder) | next(iterator, encoder)]
|
||||
end
|
||||
end
|
||||
|
||||
defp next(iterator, encoder) do
|
||||
case :maps.next(iterator) do
|
||||
:none ->
|
||||
"}"
|
||||
|
||||
{key, value, iterator} ->
|
||||
[?,, key(key, encoder), ?:, encoder.(value, encoder) | next(iterator, encoder)]
|
||||
end
|
||||
end
|
||||
|
||||
# Erlang supports only numbers, binaries, and atoms as keys,
|
||||
# we support anything that implements the String.Chars protocol.
|
||||
defp key(key, encoder) when is_atom(key), do: encoder.(Atom.to_string(key), encoder)
|
||||
defp key(key, encoder) when is_binary(key), do: encoder.(key, encoder)
|
||||
defp key(key, encoder), do: encoder.(String.Chars.to_string(key), encoder)
|
||||
end
|
||||
|
||||
defimpl JSON.Encoder, for: [Date, Time, NaiveDateTime, DateTime, Duration] do
|
||||
@@ -175,17 +197,15 @@ defmodule JSON do
|
||||
|
||||
Elixir built-in data structures are encoded to JSON as follows:
|
||||
|
||||
| **Elixir** | **JSON** |
|
||||
|------------------------|----------|
|
||||
| `integer() \| float()` | Number |
|
||||
| `true \| false ` | Boolean |
|
||||
| `nil` | Null |
|
||||
| `binary()` | String |
|
||||
| `atom()` | String |
|
||||
| `list()` | Array |
|
||||
| `%{binary() => _}` | Object |
|
||||
| `%{atom() => _}` | Object |
|
||||
| `%{integer() => _}` | Object |
|
||||
| **Elixir** | **JSON** |
|
||||
|----------------------------|----------|
|
||||
| `integer() \| float()` | Number |
|
||||
| `true \| false ` | Boolean |
|
||||
| `nil` | Null |
|
||||
| `binary()` | String |
|
||||
| `atom()` | String |
|
||||
| `list()` | Array |
|
||||
| `%{String.Chars.t() => _}` | Object |
|
||||
|
||||
You may also implement the `JSON.Encoder` protocol for custom data structures.
|
||||
|
||||
@@ -403,7 +423,7 @@ defmodule JSON do
|
||||
do: :elixir_json.encode_list(value, encoder)
|
||||
|
||||
def protocol_encode(%{} = value, encoder) when not is_map_key(value, :__struct__),
|
||||
do: :elixir_json.encode_map(value, encoder)
|
||||
do: JSON.Encoder.Map.encode(value, encoder)
|
||||
|
||||
def protocol_encode(value, encoder),
|
||||
do: JSON.Encoder.encode(value, encoder)
|
||||
|
||||
+55
-45
@@ -3716,26 +3716,25 @@ defmodule Kernel do
|
||||
|
||||
case function? do
|
||||
true ->
|
||||
value =
|
||||
case Module.__get_attribute__(env.module, name, line, false) do
|
||||
{_, doc} when doc_attr? -> doc
|
||||
other -> other
|
||||
end
|
||||
case Module.__get_attribute__(env.module, name, line, false) do
|
||||
{_, doc} when doc_attr? ->
|
||||
do_at_escape(name, doc)
|
||||
|
||||
try do
|
||||
:elixir_quote.escape(value, :none, false)
|
||||
rescue
|
||||
ex in [ArgumentError] ->
|
||||
raise ArgumentError,
|
||||
"cannot inject attribute @#{name} into function/macro because " <>
|
||||
Exception.message(ex)
|
||||
%{__struct__: Regex, source: source, opts: opts} = regex ->
|
||||
case :erlang.system_info(:otp_release) < [?2, ?8] do
|
||||
true -> do_at_escape(name, regex)
|
||||
false -> quote(do: Regex.compile!(unquote(source), unquote(opts)))
|
||||
end
|
||||
|
||||
value ->
|
||||
do_at_escape(name, value)
|
||||
end
|
||||
|
||||
false when doc_attr? ->
|
||||
quote do
|
||||
case Module.__get_attribute__(__MODULE__, unquote(name), unquote(line), false) do
|
||||
{_, doc} -> doc
|
||||
other -> other
|
||||
value -> value
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3770,6 +3769,17 @@ defmodule Kernel do
|
||||
raise ArgumentError, "expected 0 or 1 argument for @#{name}, got: #{length(args)}"
|
||||
end
|
||||
|
||||
defp do_at_escape(name, value) do
|
||||
try do
|
||||
:elixir_quote.escape(value, :none, false)
|
||||
rescue
|
||||
ex in [ArgumentError] ->
|
||||
raise ArgumentError,
|
||||
"cannot inject attribute @#{name} into function/macro because " <>
|
||||
Exception.message(ex)
|
||||
end
|
||||
end
|
||||
|
||||
# Those are always compile-time dependencies, so we can skip the trace.
|
||||
defp collect_traces(:before_compile, arg, _env), do: {arg, []}
|
||||
defp collect_traces(:after_compile, arg, _env), do: {arg, []}
|
||||
@@ -4584,6 +4594,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 +5473,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 +6447,38 @@ 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
|
||||
# TODO: Remove this when we require Erlang/OTP 28+
|
||||
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, false)
|
||||
parse_argv(t, config)
|
||||
end
|
||||
|
||||
## Compiler
|
||||
|
||||
defp parse_argv([~c"-o", h | t], %{mode: :elixirc} = config) do
|
||||
|
||||
@@ -257,29 +257,7 @@ 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)}
|
||||
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}, _} ->
|
||||
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}
|
||||
spawn_workers(schedulers, cache, files, output, options)
|
||||
after
|
||||
Module.ParallelChecker.stop(cache)
|
||||
end
|
||||
@@ -303,6 +281,7 @@ defmodule Kernel.ParallelCompiler do
|
||||
|
||||
{outcome, state} =
|
||||
spawn_workers(files, %{}, %{}, [], %{}, [], [], %{
|
||||
beam_timestamp: Keyword.get(options, :beam_timestamp),
|
||||
dest: Keyword.get(options, :dest),
|
||||
each_cycle: Keyword.get(options, :each_cycle, fn -> {:runtime, [], []} end),
|
||||
each_file: Keyword.get(options, :each_file, fn _, _ -> :ok end) |> each_file(),
|
||||
@@ -360,9 +339,10 @@ defmodule Kernel.ParallelCompiler do
|
||||
## Verification
|
||||
|
||||
defp verify_modules(result, compile_warnings, dependent_modules, state) do
|
||||
modules = write_module_binaries(result, state.output, state.beam_timestamp)
|
||||
runtime_warnings = maybe_check_modules(result, dependent_modules, state)
|
||||
info = %{compile_warnings: Enum.reverse(compile_warnings), runtime_warnings: runtime_warnings}
|
||||
{{:ok, result, info}, state}
|
||||
{{:ok, modules, info}, state}
|
||||
end
|
||||
|
||||
defp maybe_check_modules(result, runtime_modules, state) do
|
||||
|
||||
@@ -493,6 +493,24 @@ defmodule Kernel.Typespec do
|
||||
state}
|
||||
end
|
||||
|
||||
defp typespec(
|
||||
{:<<>>, meta, [{:"::", _, [{:_, _, ctx1}, {:*, prod_meta, [size, unit]}]}]},
|
||||
_,
|
||||
_,
|
||||
state
|
||||
)
|
||||
when is_atom(ctx1) and is_integer(size) and size >= 0 and unit in 1..256 do
|
||||
location = location(meta)
|
||||
prod_location = location(prod_meta)
|
||||
|
||||
{{:type, location, :binary,
|
||||
[
|
||||
{:op, prod_location, :*, {:integer, prod_location, size},
|
||||
{:integer, prod_location, unit}},
|
||||
{:integer, location, 0}
|
||||
]}, state}
|
||||
end
|
||||
|
||||
defp typespec({:<<>>, meta, [{:"::", size_meta, [{:_, _, ctx}, size]}]}, _, _, state)
|
||||
when is_atom(ctx) and is_integer(size) and size >= 0 do
|
||||
location = location(meta)
|
||||
@@ -938,7 +956,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 +964,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),
|
||||
|
||||
@@ -1133,7 +1133,7 @@ defmodule List do
|
||||
|
||||
Please check the given list or call inspect/1 to get the list representation, got:
|
||||
|
||||
#{inspect(list)}
|
||||
#{inspect(list, charlists: :as_lists)}
|
||||
"""
|
||||
else
|
||||
result when is_binary(result) ->
|
||||
@@ -1185,7 +1185,7 @@ defmodule List do
|
||||
|
||||
Please check the given list or call inspect/1 to get the list representation, got:
|
||||
|
||||
#{inspect(list)}
|
||||
#{inspect(list, charlists: :as_lists)}
|
||||
"""
|
||||
else
|
||||
result when is_list(result) ->
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -946,7 +946,8 @@ defmodule Module do
|
||||
@doc """
|
||||
Concatenates two aliases and returns a new alias.
|
||||
|
||||
It handles binaries and atoms.
|
||||
It handles binaries and atoms. If one of the aliases
|
||||
is nil, it is discarded.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -956,6 +957,9 @@ defmodule Module do
|
||||
iex> Module.concat(Foo, "Bar")
|
||||
Foo.Bar
|
||||
|
||||
iex> Module.concat(Foo, nil)
|
||||
Foo
|
||||
|
||||
"""
|
||||
@spec concat(binary | atom, binary | atom) :: atom
|
||||
def concat(left, right)
|
||||
|
||||
@@ -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)],
|
||||
@@ -437,10 +439,16 @@ defmodule Module.ParallelChecker do
|
||||
|
||||
defp cache_chunk(table, module, exports) do
|
||||
Enum.each(exports, fn {{fun, arity}, info} ->
|
||||
# TODO: Match on signature directly in Elixir v1.22+
|
||||
sig =
|
||||
case info do
|
||||
%{sig: {:strong, _, _} = sig} -> sig
|
||||
%{sig: {:infer, _} = sig} -> sig
|
||||
_ -> :none
|
||||
end
|
||||
|
||||
:ets.insert(
|
||||
table,
|
||||
{{module, {fun, arity}}, Map.get(info, :deprecated), Map.get(info, :sig, :none)}
|
||||
{{module, {fun, arity}}, Map.get(info, :deprecated), sig}
|
||||
)
|
||||
end)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1264,8 +1264,115 @@ defmodule Module.Types.Descr do
|
||||
|
||||
defp map_only?(descr), do: empty?(Map.delete(descr, :map))
|
||||
|
||||
# Union is list concatenation
|
||||
defp map_union(dnf1, dnf2), do: dnf1 ++ (dnf2 -- dnf1)
|
||||
defp map_union(dnf1, dnf2) do
|
||||
# Union is just concatenation, but we rely on some optimization strategies to
|
||||
# avoid the list to grow when possible
|
||||
|
||||
# first pass trying to identify patterns where two maps can be fused as one
|
||||
with [map1] <- dnf1,
|
||||
[map2] <- dnf2,
|
||||
optimized when optimized != nil <- maybe_optimize_map_union(map1, map2) do
|
||||
[optimized]
|
||||
else
|
||||
# otherwise we just concatenate and remove structural duplicates
|
||||
_ -> dnf1 ++ (dnf2 -- dnf1)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_optimize_map_union({tag1, pos1, []} = map1, {tag2, pos2, []} = map2) do
|
||||
case map_union_optimization_strategy(tag1, pos1, tag2, pos2) do
|
||||
:all_equal ->
|
||||
map1
|
||||
|
||||
:any_map ->
|
||||
{:open, %{}, []}
|
||||
|
||||
{:one_key_difference, key, v1, v2} ->
|
||||
new_pos = Map.put(pos1, key, union(v1, v2))
|
||||
{tag1, new_pos, []}
|
||||
|
||||
:left_subtype_of_right ->
|
||||
map2
|
||||
|
||||
:right_subtype_of_left ->
|
||||
map1
|
||||
|
||||
nil ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_optimize_map_union(_, _), do: nil
|
||||
|
||||
defp map_union_optimization_strategy(tag1, pos1, tag2, pos2)
|
||||
defp map_union_optimization_strategy(tag, pos, tag, pos), do: :all_equal
|
||||
defp map_union_optimization_strategy(:open, empty, _, _) when empty == %{}, do: :any_map
|
||||
defp map_union_optimization_strategy(_, _, :open, empty) when empty == %{}, do: :any_map
|
||||
|
||||
defp map_union_optimization_strategy(tag, pos1, tag, pos2)
|
||||
when map_size(pos1) == map_size(pos2) do
|
||||
:maps.iterator(pos1)
|
||||
|> :maps.next()
|
||||
|> do_map_union_optimization_strategy(pos2, :all_equal)
|
||||
end
|
||||
|
||||
defp map_union_optimization_strategy(:open, pos1, _, pos2)
|
||||
when map_size(pos1) <= map_size(pos2) do
|
||||
:maps.iterator(pos1)
|
||||
|> :maps.next()
|
||||
|> do_map_union_optimization_strategy(pos2, :right_subtype_of_left)
|
||||
end
|
||||
|
||||
defp map_union_optimization_strategy(_, pos1, :open, pos2)
|
||||
when map_size(pos1) >= map_size(pos2) do
|
||||
:maps.iterator(pos2)
|
||||
|> :maps.next()
|
||||
|> do_map_union_optimization_strategy(pos1, :right_subtype_of_left)
|
||||
|> case do
|
||||
:right_subtype_of_left -> :left_subtype_of_right
|
||||
nil -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp map_union_optimization_strategy(_, _, _, _), do: nil
|
||||
|
||||
defp do_map_union_optimization_strategy(:none, _, status), do: status
|
||||
|
||||
defp do_map_union_optimization_strategy({key, v1, iterator}, pos2, status) do
|
||||
with %{^key => v2} <- pos2,
|
||||
next_status when next_status != nil <- map_union_next_strategy(key, v1, v2, status) do
|
||||
do_map_union_optimization_strategy(:maps.next(iterator), pos2, next_status)
|
||||
else
|
||||
_ -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp map_union_next_strategy(key, v1, v2, status)
|
||||
|
||||
# structurally equal values do not impact the ongoing strategy
|
||||
defp map_union_next_strategy(_key, same, same, status), do: status
|
||||
|
||||
defp map_union_next_strategy(key, v1, v2, :all_equal) do
|
||||
if key != :__struct__, do: {:one_key_difference, key, v1, v2}
|
||||
end
|
||||
|
||||
defp map_union_next_strategy(_key, v1, v2, {:one_key_difference, _, d1, d2}) do
|
||||
# we have at least two key differences now, we switch strategy
|
||||
# if both are subtypes in one direction, keep checking
|
||||
cond do
|
||||
subtype?(d1, d2) and subtype?(v1, v2) -> :left_subtype_of_right
|
||||
subtype?(d2, d1) and subtype?(v2, v1) -> :right_subtype_of_left
|
||||
true -> nil
|
||||
end
|
||||
end
|
||||
|
||||
defp map_union_next_strategy(_key, v1, v2, :left_subtype_of_right) do
|
||||
if subtype?(v1, v2), do: :left_subtype_of_right
|
||||
end
|
||||
|
||||
defp map_union_next_strategy(_key, v1, v2, :right_subtype_of_left) do
|
||||
if subtype?(v2, v1), do: :right_subtype_of_left
|
||||
end
|
||||
|
||||
# Given two unions of maps, intersects each pair of maps.
|
||||
defp map_intersection(dnf1, dnf2) do
|
||||
@@ -1275,7 +1382,16 @@ defmodule Module.Types.Descr do
|
||||
acc ->
|
||||
try do
|
||||
{tag, fields} = map_literal_intersection(tag1, pos1, tag2, pos2)
|
||||
[{tag, fields, negs1 ++ negs2} | acc]
|
||||
entry = {tag, fields, negs1 ++ negs2}
|
||||
|
||||
# Imagine a, b, c, where a is closed and b and c are open with
|
||||
# no keys in common. The result in both cases will be a and we
|
||||
# want to avoid adding duplicates, especially as intersection
|
||||
# is a cartesian product.
|
||||
case :lists.member(entry, acc) do
|
||||
true -> acc
|
||||
false -> [entry | acc]
|
||||
end
|
||||
catch
|
||||
:empty -> acc
|
||||
end
|
||||
@@ -1329,11 +1445,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
|
||||
@@ -1739,29 +1854,19 @@ defmodule Module.Types.Descr do
|
||||
|
||||
defp map_non_negated_fuse(maps) do
|
||||
Enum.reduce(maps, [], fn map, acc ->
|
||||
case Enum.split_while(acc, &non_fusible_maps?(map, &1)) do
|
||||
{_, []} ->
|
||||
[map | acc]
|
||||
|
||||
{others, [match | rest]} ->
|
||||
fused = map_non_negated_fuse_pair(map, match)
|
||||
others ++ [fused | rest]
|
||||
end
|
||||
fuse_with_first_fusible(map, acc)
|
||||
end)
|
||||
end
|
||||
|
||||
# Two maps are fusible if they differ in at most one element.
|
||||
defp non_fusible_maps?({_, fields1, []}, {_, fields2, []}) do
|
||||
Enum.count_until(fields1, fn {key, value} -> Map.fetch!(fields2, key) != value end, 2) > 1
|
||||
end
|
||||
defp fuse_with_first_fusible(map, []), do: [map]
|
||||
|
||||
defp map_non_negated_fuse_pair({tag, fields1, []}, {_, fields2, []}) do
|
||||
fields =
|
||||
symmetrical_merge(fields1, fields2, fn _k, v1, v2 ->
|
||||
if v1 == v2, do: v1, else: union(v1, v2)
|
||||
end)
|
||||
|
||||
{tag, fields, []}
|
||||
defp fuse_with_first_fusible(map, [candidate | rest]) do
|
||||
if fused = maybe_optimize_map_union(map, candidate) do
|
||||
# we found a fusible candidate, we're done
|
||||
[fused | rest]
|
||||
else
|
||||
[candidate | fuse_with_first_fusible(map, rest)]
|
||||
end
|
||||
end
|
||||
|
||||
# If all fields are the same except one, we can optimize map difference.
|
||||
@@ -1932,8 +2037,16 @@ defmodule Module.Types.Descr do
|
||||
reduce: [] do
|
||||
acc ->
|
||||
case tuple_literal_intersection(tag1, elements1, tag2, elements2) do
|
||||
{tag, fields} -> [{tag, fields, negs1 ++ negs2} | acc]
|
||||
:empty -> acc
|
||||
{tag, elements} ->
|
||||
entry = {tag, elements, negs1 ++ negs2}
|
||||
|
||||
case :lists.member(entry, acc) do
|
||||
true -> acc
|
||||
false -> [entry | acc]
|
||||
end
|
||||
|
||||
:empty ->
|
||||
acc
|
||||
end
|
||||
end
|
||||
|> case do
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -335,6 +335,10 @@ defmodule Module.Types.Pattern do
|
||||
{type, context}
|
||||
end
|
||||
|
||||
def of_match_var({:<<>>, _meta, args}, _expected, _expr, stack, context) do
|
||||
{binary(), Of.binary(args, :match, stack, context)}
|
||||
end
|
||||
|
||||
def of_match_var(ast, expected, expr, stack, context) do
|
||||
of_match(ast, expected, expr, :default, stack, context)
|
||||
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
|
||||
|
||||
@@ -219,6 +219,16 @@ defmodule Process do
|
||||
|
||||
Inlined by the compiler.
|
||||
|
||||
> #### Differences to `Kernel.exit/1` {: .info }
|
||||
>
|
||||
> The functions `Kernel.exit/1` and `Process.exit/2` are
|
||||
> named similarly but provide very different functionalities. The
|
||||
> `Kernel:exit/1` function should be used when the intent is to stop the current
|
||||
> process while `Process.exit/2` should be used when the intent is to send an
|
||||
> exit signal to another process. Note also that `Kernel.exit/1` can be caught
|
||||
> with `try/1` while `Process.exit/2` can only be handled by trapping exits and
|
||||
> when the signal is different than `:kill`.
|
||||
|
||||
## Examples
|
||||
|
||||
Process.exit(pid, :kill)
|
||||
|
||||
@@ -347,7 +347,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp assert_impl!(protocol, base, extra) do
|
||||
impl = Module.concat(protocol, base)
|
||||
impl = Protocol.__concat__(protocol, base)
|
||||
|
||||
try do
|
||||
Code.ensure_compiled!(impl)
|
||||
@@ -678,7 +678,7 @@ defmodule Protocol do
|
||||
end
|
||||
|
||||
defp load_impl(protocol, for) do
|
||||
Module.concat(protocol, for)
|
||||
Protocol.__concat__(protocol, for)
|
||||
end
|
||||
|
||||
# Finally compile the module and emit its bytecode.
|
||||
@@ -831,7 +831,7 @@ defmodule Protocol do
|
||||
# Define the implementation for built-ins
|
||||
:lists.foreach(
|
||||
fn {guard, mod} ->
|
||||
target = Module.concat(__MODULE__, mod)
|
||||
target = Protocol.__concat__(__MODULE__, mod)
|
||||
|
||||
Kernel.def impl_for(data) when :erlang.unquote(guard)(data) do
|
||||
case Code.ensure_compiled(unquote(target)) do
|
||||
@@ -875,7 +875,7 @@ defmodule Protocol do
|
||||
|
||||
# Internal handler for Structs
|
||||
Kernel.defp struct_impl_for(struct) do
|
||||
case Code.ensure_compiled(Module.concat(__MODULE__, struct)) do
|
||||
case Code.ensure_compiled(Protocol.__concat__(__MODULE__, struct)) do
|
||||
{:module, module} -> module
|
||||
{:error, _} -> unquote(any_impl_for)
|
||||
end
|
||||
@@ -948,7 +948,7 @@ defmodule Protocol do
|
||||
quote do
|
||||
protocol = unquote(protocol)
|
||||
for = unquote(for)
|
||||
name = Module.concat(protocol, for)
|
||||
name = Protocol.__concat__(protocol, for)
|
||||
|
||||
Protocol.assert_protocol!(protocol)
|
||||
Protocol.__ensure_defimpl__(protocol, for, __ENV__)
|
||||
@@ -994,7 +994,7 @@ defmodule Protocol do
|
||||
else
|
||||
# TODO: Deprecate this on Elixir v1.22+
|
||||
assert_impl!(protocol, Any, extra)
|
||||
{Module.concat(protocol, Any), [for, Macro.struct!(for, env), opts]}
|
||||
{Protocol.__concat__(protocol, Any), [for, Macro.struct!(for, env), opts]}
|
||||
end
|
||||
|
||||
# Clean up variables from eval context
|
||||
@@ -1006,7 +1006,7 @@ defmodule Protocol do
|
||||
else
|
||||
__ensure_defimpl__(protocol, for, env)
|
||||
assert_impl!(protocol, Any, extra)
|
||||
impl = Module.concat(protocol, Any)
|
||||
impl = Protocol.__concat__(protocol, Any)
|
||||
|
||||
funs =
|
||||
for {fun, arity} <- protocol.__protocol__(:functions) do
|
||||
@@ -1031,7 +1031,11 @@ defmodule Protocol do
|
||||
def __impl__(:for), do: unquote(for)
|
||||
end
|
||||
|
||||
Module.create(Module.concat(protocol, for), [quoted | funs], Macro.Env.location(env))
|
||||
Module.create(
|
||||
Protocol.__concat__(protocol, for),
|
||||
[quoted | funs],
|
||||
Macro.Env.location(env)
|
||||
)
|
||||
end
|
||||
end)
|
||||
end
|
||||
@@ -1070,4 +1074,17 @@ defmodule Protocol do
|
||||
is_reference: Reference
|
||||
]
|
||||
end
|
||||
|
||||
@doc false
|
||||
def __concat__(left, right) do
|
||||
String.to_atom(
|
||||
ensure_prefix(Atom.to_string(left)) <> "." <> remove_prefix(Atom.to_string(right))
|
||||
)
|
||||
end
|
||||
|
||||
defp ensure_prefix("Elixir." <> _ = left), do: left
|
||||
defp ensure_prefix(left), do: "Elixir." <> left
|
||||
|
||||
defp remove_prefix("Elixir." <> right), do: right
|
||||
defp remove_prefix(right), do: right
|
||||
end
|
||||
|
||||
@@ -209,17 +209,14 @@ defmodule Regex do
|
||||
|
||||
## Examples
|
||||
|
||||
iex> Regex.compile("foo")
|
||||
{:ok, ~r/foo/}
|
||||
Regex.compile("foo")
|
||||
#=> {:ok, ~r/foo/}
|
||||
|
||||
iex> Regex.compile("*foo")
|
||||
{:error, {~c"nothing to repeat", 0}}
|
||||
Regex.compile("foo", "i")
|
||||
#=>{:ok, ~r/foo/i}
|
||||
|
||||
iex> Regex.compile("foo", "i")
|
||||
{:ok, ~r/foo/i}
|
||||
|
||||
iex> Regex.compile("foo", [:caseless])
|
||||
{:ok, Regex.compile!("foo", [:caseless])}
|
||||
Regex.compile("*foo")
|
||||
#=> {:error, {~c"nothing to repeat", 0}}
|
||||
|
||||
"""
|
||||
@spec compile(binary, binary | [term]) :: {:ok, t} | {:error, term}
|
||||
|
||||
@@ -1444,7 +1444,7 @@ defmodule Stream do
|
||||
defp check_cycle_first_element(reduce) do
|
||||
fn acc ->
|
||||
case reduce.(acc) do
|
||||
{state, []} when state in [:done, :halted] ->
|
||||
{state, []} when state in [:done, :halted] and elem(acc, 0) != :halt ->
|
||||
raise ArgumentError, "cannot cycle over an empty enumerable"
|
||||
|
||||
other ->
|
||||
|
||||
@@ -488,6 +488,15 @@ defmodule String do
|
||||
iex> String.split(String.normalize("é", :nfc), "e")
|
||||
["é"]
|
||||
|
||||
When using both the `:trim` and the `:parts` option, the empty values
|
||||
are removed as the parts are computed (if any). No trimming happens
|
||||
after all parts are computed:
|
||||
|
||||
iex> String.split(" a b c ", " ", trim: true, parts: 2)
|
||||
["a", " b c "]
|
||||
iex> String.split(" a b c ", " ", trim: true, parts: 3)
|
||||
["a", "b", " c "]
|
||||
|
||||
"""
|
||||
@spec split(t, pattern | Regex.t(), keyword) :: [t]
|
||||
def split(string, pattern, options \\ [])
|
||||
|
||||
@@ -110,7 +110,7 @@ When using `IEx`, you may pass `--dbg pry` as an option to "stop" the code execu
|
||||
$ iex --dbg pry
|
||||
```
|
||||
|
||||
Or to debug inside a of a project:
|
||||
Or to debug inside of a project:
|
||||
|
||||
```console
|
||||
$ iex --dbg pry -S mix
|
||||
|
||||
@@ -11,36 +11,40 @@ Keyword lists are a data-structure used to pass options to functions. Let's see
|
||||
Imagine you want to split a string of numbers. Initially, we can invoke `String.split/2` passing two strings as arguments:
|
||||
|
||||
```elixir
|
||||
iex> String.split("1 2 3", " ")
|
||||
["1", "2", "3"]
|
||||
iex> String.split("1 2 3 4", " ")
|
||||
["1", "2", "3", "4"]
|
||||
```
|
||||
|
||||
However, what happens if there is an additional space between the numbers:
|
||||
What if you only want to split at most 2 times? The `String.split/3` function allows the `parts` option to be set to the maximum number of entries in the result:
|
||||
|
||||
```elixir
|
||||
iex> String.split("1 2 3", " ")
|
||||
["1", "", "2", "", "3"]
|
||||
iex> String.split("1 2 3 4", " ", [parts: 3])
|
||||
["1", "2", "3 4"]
|
||||
```
|
||||
|
||||
As you can see, there are now empty strings in our results. Luckily, the `String.split/3` function allows the `trim` option to be set to true:
|
||||
As you can see, we got 3 parts, the last one containing the remaining of the input without splitting it.
|
||||
|
||||
Now imagine that some of the inputs you must split on contains additional spaces between the numbers:
|
||||
|
||||
```elixir
|
||||
iex> String.split("1 2 3", " ", [trim: true])
|
||||
["1", "2", "3"]
|
||||
iex> String.split("1 2 3 4", " ", [parts: 3])
|
||||
["1", "", "2 3 4"]
|
||||
```
|
||||
|
||||
We can also use options to limit the splitting algorithm to a maximum number of parts, as shown next:
|
||||
As you can see, the additional spaces lead to empty entries in the output. Luckily, we can also set the `trim` option to `true` to remove them:
|
||||
|
||||
```elixir
|
||||
iex> String.split("1 2 3", " ", [trim: true, parts: 2])
|
||||
["1", "2 3"]
|
||||
iex> String.split("1 2 3 4", " ", [parts: 3, trim: true])
|
||||
["1", "2", " 3 4"]
|
||||
```
|
||||
|
||||
`[trim: true]` and `[trim: true, parts: 2]` are keyword lists. When a keyword list is the last argument of a function, we can skip the brackets and write:
|
||||
Once again we got 3 parts, with the last one containing the leftovers.
|
||||
|
||||
`[parts: 3]` and `[parts: 3, trim: true]` are keyword lists. When a keyword list is the last argument of a function, we can skip the brackets and write:
|
||||
|
||||
```elixir
|
||||
iex> String.split("1 2 3", " ", trim: true, parts: 2)
|
||||
["1", "2 3"]
|
||||
iex> String.split("1 2 3 4", " ", parts: 3, trim: true)
|
||||
["1", "2", " 3 4"]
|
||||
```
|
||||
|
||||
As shown in the example above, keyword lists are mostly used as optional arguments to functions.
|
||||
@@ -48,7 +52,7 @@ As shown in the example above, keyword lists are mostly used as optional argumen
|
||||
As the name implies, keyword lists are simply lists. In particular, they are lists consisting of 2-item tuples where the first element (the key) is an atom and the second element can be any value. Both representations are the same:
|
||||
|
||||
```elixir
|
||||
iex> [{:trim, true}, {:parts, 2}] == [trim: true, parts: 2]
|
||||
iex> [{:parts, 3}, {:trim, true}] == [parts: 3, trim: true]
|
||||
true
|
||||
```
|
||||
|
||||
|
||||
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 ::
|
||||
|
||||
@@ -191,12 +191,19 @@ canonical = System.fetch_env!("CANONICAL")
|
||||
before_closing_body_tag: fn
|
||||
:html ->
|
||||
"""
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/mermaid@10.2.3/dist/mermaid.min.js"></script>
|
||||
<script>
|
||||
function mermaidLoaded() {
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: document.body.className.includes("dark") ? "dark" : "default"
|
||||
});
|
||||
let initialized = false;
|
||||
|
||||
window.addEventListener("exdoc:loaded", () => {
|
||||
if (!initialized) {
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: document.body.className.includes("dark") ? "dark" : "default"
|
||||
});
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
let id = 0;
|
||||
for (const codeEl of document.querySelectorAll("pre code.mermaid")) {
|
||||
const preEl = codeEl.parentElement;
|
||||
@@ -210,9 +217,8 @@ canonical = System.fetch_env!("CANONICAL")
|
||||
preEl.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script async src="https://cdn.jsdelivr.net/npm/mermaid@10.2.3/dist/mermaid.min.js" onload="mermaidLoaded();"></script>
|
||||
"""
|
||||
|
||||
_ ->
|
||||
|
||||
@@ -222,7 +222,7 @@ store_definition(Meta, Kind, CheckClauses, Name, Arity, DefaultsArgs, Guards, Bo
|
||||
[store_definition(none, Kind, Meta, Name, length(DefaultArgs), File,
|
||||
Module, 0, [Default]) || {_, DefaultArgs, _, _} = Default <- Defaults],
|
||||
|
||||
run_on_definition_callbacks(Kind, Module, Name, DefaultsArgs, Guards, Body, E),
|
||||
run_on_definition_callbacks(Meta, Kind, Module, Name, DefaultsArgs, Guards, Body, E),
|
||||
Tuple.
|
||||
|
||||
env_for_expansion(Kind, Tuple, E) when Kind =:= defmacro; Kind =:= defmacrop ->
|
||||
@@ -267,10 +267,13 @@ def_to_clauses(Kind, Meta, Args, Guards, Body, E) ->
|
||||
elixir_errors:file_error(Meta, E, elixir_expand, {missing_option, Kind, [do]})
|
||||
end.
|
||||
|
||||
run_on_definition_callbacks(Kind, Module, Name, Args, Guards, Body, E) ->
|
||||
run_on_definition_callbacks(Meta, Kind, Module, Name, Args, Guards, Body, E) ->
|
||||
{_, Bag} = elixir_module:data_tables(Module),
|
||||
Callbacks = ets:lookup_element(Bag, {accumulate, on_definition}, 2),
|
||||
_ = [Mod:Fun(E, Kind, Name, Args, Guards, Body) || {Mod, Fun} <- lists:reverse(Callbacks)],
|
||||
_ = [begin
|
||||
elixir_env:trace({remote_function, Meta, Mod, Fun, 6}, E),
|
||||
Mod:Fun(E, Kind, Name, Args, Guards, Body)
|
||||
end || {Mod, Fun} <- lists:reverse(Callbacks)],
|
||||
ok.
|
||||
|
||||
store_definition(CheckClauses, Kind, Meta, Name, Arity, File, Module, Defaults, Clauses)
|
||||
@@ -314,7 +317,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);
|
||||
|
||||
@@ -204,7 +204,9 @@ do_expand_import(Result, Meta, Name, Arity, Module, E, Trace) ->
|
||||
{import, Receiver} ->
|
||||
case expand_require(true, Meta, Receiver, Name, Arity, E, Trace) of
|
||||
{macro, _, _} = Response -> Response;
|
||||
error -> {function, Receiver, Name}
|
||||
error ->
|
||||
Trace andalso elixir_env:trace({remote_function, Meta, Receiver, Name, Arity}, E),
|
||||
{function, Receiver, Name}
|
||||
end;
|
||||
false when Module == ?kernel ->
|
||||
case elixir_rewrite:inline(Module, Name, Arity) of
|
||||
|
||||
@@ -92,6 +92,9 @@ elixir_to_erl(Tree, Ann) when is_binary(Tree) ->
|
||||
%% considers a string in a binary to be encoded in latin1, so the bytes
|
||||
%% are not changed in any fashion.
|
||||
{bin, Ann, [{bin_element, Ann, {string, Ann, binary_to_list(Tree)}, default, default}]};
|
||||
elixir_to_erl(Tree, Ann) when is_bitstring(Tree) ->
|
||||
Segments = [elixir_to_erl_bitstring_segment(X, Ann) || X <- bitstring_to_list(Tree)],
|
||||
{bin, Ann, Segments};
|
||||
elixir_to_erl(Tree, Ann) when is_function(Tree) ->
|
||||
case (erlang:fun_info(Tree, type) == {type, external}) andalso
|
||||
(erlang:fun_info(Tree, env) == {env, []}) of
|
||||
@@ -111,6 +114,13 @@ elixir_to_erl(Tree, Ann) ->
|
||||
elixir_to_erl_cons([H | T], Ann) -> {cons, Ann, elixir_to_erl(H, Ann), elixir_to_erl_cons(T, Ann)};
|
||||
elixir_to_erl_cons(T, Ann) -> elixir_to_erl(T, Ann).
|
||||
|
||||
elixir_to_erl_bitstring_segment(Int, Ann) when is_integer(Int) ->
|
||||
{bin_element, Ann, {integer, Ann, Int}, default, [integer]};
|
||||
elixir_to_erl_bitstring_segment(Rest, Ann) when is_bitstring(Rest) ->
|
||||
Size = bit_size(Rest),
|
||||
<<Int:Size>> = Rest,
|
||||
{bin_element, Ann, {integer, Ann, Int}, {integer, Ann, Size}, [integer]}.
|
||||
|
||||
%% Returns a scope for translation.
|
||||
|
||||
scope(_Meta, ExpandCaptures) ->
|
||||
@@ -165,7 +175,12 @@ dynamic_form(#{module := Module, relative_file := RelativeFile,
|
||||
{Def, Defmacro, Macros, Exports, Functions} =
|
||||
split_definition(Definitions, Unreachable, Line, [], [], [], [], {[], []}),
|
||||
|
||||
FilteredOpts = lists:filter(fun({no_warn_undefined, _}) -> false; (_) -> true end, Opts),
|
||||
FilteredOpts = lists:filter(fun(
|
||||
{no_warn_undefined, _}) -> false;
|
||||
(debug_info) -> false;
|
||||
(_) -> true
|
||||
end, Opts),
|
||||
|
||||
Location = {elixir_utils:characters_to_list(RelativeFile), Line},
|
||||
|
||||
Prefix = [{attribute, Line, file, Location},
|
||||
|
||||
@@ -20,7 +20,15 @@ reduce_clauses([], Acc, OldStack, SAcc, _S) ->
|
||||
{lists:reverse(Acc), SAcc#elixir_erl{stacktrace=OldStack}}.
|
||||
|
||||
each_clause({'catch', Meta, Raw, Expr}, S) ->
|
||||
{Match, Guards} = elixir_utils:extract_splat_guards(Raw),
|
||||
{Args, Guards} = elixir_utils:extract_splat_guards(Raw),
|
||||
|
||||
Match =
|
||||
%% Elixir v1.17 and earlier emitted single argument
|
||||
%% and may still be processed via debug_info
|
||||
case Args of
|
||||
[X] -> [throw, X];
|
||||
[X, Y] -> [X, Y]
|
||||
end,
|
||||
|
||||
{{clause, Line, [TKind, TMatches], TGuards, TBody}, TS} =
|
||||
elixir_erl_clauses:clause(?ann(Meta), fun elixir_erl_pass:translate_args/3, Match, Expr, Guards, S),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -293,7 +293,7 @@ expand({'&', Meta, [{'/', ArityMeta, [{super, SuperMeta, Context}, Arity]} = Exp
|
||||
|
||||
case resolve_super(Meta, Arity, E) of
|
||||
{Kind, Name, _} when Kind == def; Kind == defp ->
|
||||
{{'&', Meta, [{'/', ArityMeta, [{Name, SuperMeta, Context}, Arity]}]}, S, E};
|
||||
expand({'&', Meta, [{'/', ArityMeta, [{Name, SuperMeta, Context}, Arity]}]}, S, E);
|
||||
_ ->
|
||||
expand_fn_capture(Meta, Expr, S, E)
|
||||
end;
|
||||
@@ -343,7 +343,9 @@ expand({with, Meta, [_ | _] = Args}, S, E) ->
|
||||
|
||||
expand({super, Meta, Args}, S, E) when is_list(Args) ->
|
||||
assert_no_match_or_guard_scope(Meta, "super", S, E),
|
||||
{Kind, Name, _} = resolve_super(Meta, length(Args), E),
|
||||
Arity = length(Args),
|
||||
{Kind, Name, _} = resolve_super(Meta, Arity, E),
|
||||
elixir_env:trace({local_function, Meta, Name, Arity}, E),
|
||||
{EArgs, SA, EA} = expand_args(Args, S, E),
|
||||
{{super, [{super, {Kind, Name}} | Meta], EArgs}, SA, EA};
|
||||
|
||||
@@ -899,8 +901,7 @@ expand_local(Meta, Name, Args, S, #{function := Function, context := Context} =
|
||||
module_error(Meta, E, ?MODULE, {invalid_local_invocation, elixir_utils:guard_info(S), {Name, Meta, Args}});
|
||||
|
||||
nil ->
|
||||
Arity = length(Args),
|
||||
elixir_env:trace({local_function, Meta, Name, Arity}, E)
|
||||
elixir_env:trace({local_function, Meta, Name, length(Args)}, E)
|
||||
end,
|
||||
|
||||
{EArgs, SA, EA} = expand_args(Args, S, E),
|
||||
|
||||
@@ -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]}]},
|
||||
|
||||
@@ -155,6 +155,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
|
||||
put_compiler_modules([Module | CompilerModules]),
|
||||
{Result, ModuleE, CallbackE} = eval_form(Line, Module, DataBag, Block, Vars, Prune, E),
|
||||
CheckerInfo = checker_info(),
|
||||
BeamLocation = beam_location(ModuleAsCharlist),
|
||||
|
||||
{Binary, PersistedAttributes, Autoload} =
|
||||
elixir_erl_compiler:spawn(fun() ->
|
||||
@@ -177,7 +178,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
|
||||
'Elixir.Module':'__check_attributes__'(E, DataSet, DataBag),
|
||||
|
||||
AfterVerify = bag_lookup_element(DataBag, {accumulate, after_verify}, 2),
|
||||
[elixir_env:trace({remote_function, [], VerifyMod, VerifyFun, 1}, CallbackE) ||
|
||||
[elixir_env:trace({remote_function, [{line, Line}], VerifyMod, VerifyFun, 1}, CallbackE) ||
|
||||
{VerifyMod, VerifyFun} <- AfterVerify],
|
||||
|
||||
%% Ensure there are no errors before we infer types
|
||||
@@ -219,7 +220,7 @@ compile(Meta, Module, ModuleAsCharlist, Block, Vars, Prune, E) ->
|
||||
{Binary, PersistedAttributes, Autoload}
|
||||
end),
|
||||
|
||||
Autoload andalso code:load_binary(Module, beam_location(ModuleAsCharlist), Binary),
|
||||
Autoload andalso code:load_binary(Module, BeamLocation, Binary),
|
||||
put_compiler_modules(CompilerModules),
|
||||
eval_callbacks(Line, DataBag, after_compile, [CallbackE, Binary], CallbackE),
|
||||
elixir_env:trace({on_module, Binary, none}, ModuleE),
|
||||
@@ -294,7 +295,8 @@ validate_on_load_attribute({on_load, Def}, Defs, Bag, Line, E) ->
|
||||
elixir_errors:module_error([{line, Line}], E, ?MODULE, {undefined_function, on_load, Def});
|
||||
{_Def, Kind, _Meta, _Clauses} when Kind == defmacro; Kind == defmacrop ->
|
||||
elixir_errors:module_error([{line, Line}], E, ?MODULE, {bad_macro, on_load, Def});
|
||||
{_Def, Kind, _Meta, _Clauses} ->
|
||||
{{Name, Arity}, Kind, _Meta, _Clauses} ->
|
||||
elixir_env:trace({local_function, [{line, Line}], Name, Arity}, E),
|
||||
(Kind == defp) andalso ets:insert(Bag, {used_private, Def})
|
||||
end;
|
||||
validate_on_load_attribute(false, _Defs, _Bag, _Line, _E) -> ok.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -160,10 +160,19 @@ do_escape(BitString, _) when is_bitstring(BitString) ->
|
||||
end;
|
||||
|
||||
do_escape(Map, Q) when is_map(Map) ->
|
||||
TT = do_quote(lists:sort(maps:to_list(Map)), Q),
|
||||
TT =
|
||||
[if
|
||||
is_reference(V) ->
|
||||
argument_error(<<('Elixir.Kernel':inspect(Map, []))/binary, " contains a reference (",
|
||||
('Elixir.Kernel':inspect(V, []))/binary, ") and therefore it cannot be escaped ",
|
||||
"(it must be defined within a function instead). ", (bad_escape_hint())/binary>>);
|
||||
true ->
|
||||
{do_quote(K, Q), do_quote(V, Q)}
|
||||
end || {K, V} <- lists:sort(maps:to_list(Map))],
|
||||
{'%{}', [], TT};
|
||||
|
||||
do_escape([], _) -> [];
|
||||
do_escape([], _) ->
|
||||
[];
|
||||
|
||||
do_escape([H | T], #elixir_quote{unquote=false} = Q) ->
|
||||
do_quote_simple_list(T, do_quote(H, Q), Q);
|
||||
@@ -195,8 +204,11 @@ do_escape(Other, _) ->
|
||||
|
||||
bad_escape(Arg) ->
|
||||
argument_error(<<"cannot escape ", ('Elixir.Kernel':inspect(Arg, []))/binary, ". ",
|
||||
"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
|
||||
"PIDs and remote functions in the format &Mod.fun/arity">>).
|
||||
(bad_escape_hint())/binary>>).
|
||||
|
||||
bad_escape_hint() ->
|
||||
<<"The supported values are: lists, tuples, maps, atoms, numbers, bitstrings, ",
|
||||
"PIDs and remote functions in the format &Mod.fun/arity">>.
|
||||
|
||||
%% Quote entry points
|
||||
|
||||
|
||||
@@ -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__()]")
|
||||
|
||||
@@ -1517,6 +1517,18 @@ defmodule EnumTest do
|
||||
assert Enum.zip([], []) == []
|
||||
end
|
||||
|
||||
test "zip/2 with infinite streams" do
|
||||
assert Enum.zip([], Stream.cycle([1, 2])) == []
|
||||
assert Enum.zip([], Stream.cycle(1..2)) == []
|
||||
assert Enum.zip(.., Stream.cycle([1, 2])) == []
|
||||
assert Enum.zip(.., Stream.cycle(1..2)) == []
|
||||
|
||||
assert Enum.zip(Stream.cycle([1, 2]), ..) == []
|
||||
assert Enum.zip(Stream.cycle(1..2), ..) == []
|
||||
assert Enum.zip(Stream.cycle([1, 2]), ..) == []
|
||||
assert Enum.zip(Stream.cycle(1..2), ..) == []
|
||||
end
|
||||
|
||||
test "zip/1" do
|
||||
assert Enum.zip([[:a, :b], [1, 2], ["foo", "bar"]]) == [{:a, 1, "foo"}, {:b, 2, "bar"}]
|
||||
|
||||
|
||||
@@ -46,6 +46,12 @@ defmodule ExceptionTest do
|
||||
{:io, :put_chars, [self(), <<222>>],
|
||||
[error_info: %{module: __MODULE__, function: :dummy_error_extras}]}
|
||||
])
|
||||
|
||||
assert %ErlangError{original: {:failed_load_cacerts, :enoent}, reason: ": this is chardata"} =
|
||||
Exception.normalize(:error, {:failed_load_cacerts, :enoent}, [
|
||||
{:pubkey_os_cacerts, :get, 0,
|
||||
[error_info: %{module: __MODULE__, function: :dummy_error_chardata}]}
|
||||
])
|
||||
end
|
||||
|
||||
test "format/2 without stacktrace" do
|
||||
@@ -1026,4 +1032,8 @@ defmodule ExceptionTest do
|
||||
end
|
||||
|
||||
def dummy_error_extras(_exception, _stacktrace), do: %{general: "foo"}
|
||||
|
||||
def dummy_error_chardata(_exception, _stacktrace) do
|
||||
%{general: ~c"this is " ++ [~c"chardata"], reason: ~c"this " ++ [~c"too"]}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
defmodule Dialyzer.ProtocolOpaque do
|
||||
def circus() do
|
||||
duck = Dialyzer.ProtocolOpaque.Duck.new()
|
||||
Dialyzer.ProtocolOpaque.Entity.speak(duck)
|
||||
end
|
||||
end
|
||||
|
||||
defprotocol Dialyzer.ProtocolOpaque.Entity do
|
||||
@fallback_to_any true
|
||||
def speak(entity)
|
||||
end
|
||||
|
||||
defmodule Dialyzer.ProtocolOpaque.Duck do
|
||||
@opaque t :: %__MODULE__{feathers: :white_and_grey}
|
||||
defstruct feathers: :white_and_grey
|
||||
|
||||
@spec new :: t
|
||||
def new(), do: %__MODULE__{}
|
||||
|
||||
defimpl Dialyzer.ProtocolOpaque.Entity do
|
||||
def speak(%Dialyzer.ProtocolOpaque.Duck{}), do: "Quack!"
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Dialyzer.ProtocolOpaque.Entity, for: Any do
|
||||
def speak(_any) do
|
||||
"I can be anything"
|
||||
end
|
||||
end
|
||||
@@ -278,10 +278,15 @@ defmodule Inspect.ListTest do
|
||||
|
||||
test "printable limit" do
|
||||
assert inspect(~c"hello world", printable_limit: 4) == ~s(~c"hell" ++ ...)
|
||||
assert inspect(~c"hello", printable_limit: :infinity) == ~s(~c"hello")
|
||||
# Non printable characters after the limit don't matter
|
||||
assert inspect(~c"hello world" ++ [0], printable_limit: 4) == ~s(~c"hell" ++ ...)
|
||||
# Non printable strings aren't affected by printable limit
|
||||
assert inspect([0, 1, 2, 3, 4], printable_limit: 3) == ~s([0, 1, 2, 3, 4])
|
||||
# Ensure a non-printable value after limit does not crash
|
||||
assert inspect(~c"hello world" ++ [nil], printable_limit: 4) == ~s(~c"hell" ++ ...)
|
||||
# Ensure a non-printable value after default limit does not hang
|
||||
assert String.ends_with?(inspect(List.duplicate(?a, 5000) ++ [nil]), "\" ++ ...")
|
||||
end
|
||||
|
||||
test "keyword" do
|
||||
@@ -306,10 +311,20 @@ defmodule Inspect.ListTest do
|
||||
assert inspect([0], charlists: :infer) == "[0]"
|
||||
end
|
||||
|
||||
test "opt as strings" do
|
||||
test "opt as charlists" do
|
||||
assert inspect(~c"john" ++ [0] ++ ~c"doe", charlists: :as_charlists) == ~s(~c"john\\0doe")
|
||||
assert inspect(~c"john", charlists: :as_charlists) == ~s(~c"john")
|
||||
assert inspect([0], charlists: :as_charlists) == ~s(~c"\\0")
|
||||
assert inspect([nil], charlists: :as_charlists) == "[nil]"
|
||||
assert inspect([0xD800], charlists: :as_charlists) == "[55296]"
|
||||
assert inspect([1_114_112], charlists: :as_charlists) == "[1114112]"
|
||||
assert inspect([[?a]], charlists: :as_charlists) == ~s([~c"a"])
|
||||
|
||||
assert inspect([?a, ?b, ?c | nil], charlists: :as_charlists, printable_limit: 4) ==
|
||||
"[97, 98, 99 | nil]"
|
||||
|
||||
assert inspect([?a, ?b, ?c | nil], charlists: :as_charlists, printable_limit: 3) ==
|
||||
~s(~c"abc" ++ ...)
|
||||
end
|
||||
|
||||
test "opt as lists" do
|
||||
|
||||
@@ -33,8 +33,8 @@ defmodule JSONTest do
|
||||
end
|
||||
|
||||
test "maps" do
|
||||
assert JSON.encode!(%{1 => 2, 3.0 => 4.0, key: :bar}) ==
|
||||
"{\"1\":2,\"3.0\":4.0,\"key\":\"bar\"}"
|
||||
assert JSON.encode!(%{1 => 2, 3.0 => 4.0, ~c"list" => ~c"list", key: :bar}) ==
|
||||
"{\"1\":2,\"3.0\":4.0,\"key\":\"bar\",\"list\":[108,105,115,116]}"
|
||||
end
|
||||
|
||||
test "lists" do
|
||||
@@ -80,8 +80,8 @@ defmodule JSONTest do
|
||||
end
|
||||
|
||||
test "maps" do
|
||||
assert protocol_encode(%{1 => 2, 3.0 => 4.0, key: :bar}) ==
|
||||
"{\"1\":2,\"3.0\":4.0,\"key\":\"bar\"}"
|
||||
assert protocol_encode(%{1 => 2, 3.0 => 4.0, ~c"list" => ~c"list", key: :bar}) ==
|
||||
"{\"1\":2,\"3.0\":4.0,\"key\":\"bar\",\"list\":[108,105,115,116]}"
|
||||
end
|
||||
|
||||
test "lists" do
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -119,23 +119,6 @@ defmodule Kernel.DialyzerTest do
|
||||
assert_dialyze_no_warnings!(context)
|
||||
end
|
||||
|
||||
@tag warnings: [:specdiffs]
|
||||
test "no warnings on protocol calls with opaque types", context do
|
||||
alias Dialyzer.ProtocolOpaque
|
||||
|
||||
copy_beam!(context, ProtocolOpaque)
|
||||
copy_beam!(context, ProtocolOpaque.Entity)
|
||||
copy_beam!(context, ProtocolOpaque.Entity.Any)
|
||||
copy_beam!(context, ProtocolOpaque.Duck)
|
||||
assert_dialyze_no_warnings!(context)
|
||||
|
||||
# Also ensure no warnings after consolidation.
|
||||
Code.prepend_path(context.base_dir)
|
||||
{:ok, binary} = Protocol.consolidate(ProtocolOpaque.Entity, [ProtocolOpaque.Duck, Any])
|
||||
File.write!(Path.join(context.outdir, "#{ProtocolOpaque.Entity}.beam"), binary)
|
||||
assert_dialyze_no_warnings!(context)
|
||||
end
|
||||
|
||||
test "no warnings on and/2 and or/2", context do
|
||||
copy_beam!(context, Dialyzer.BooleanCheck)
|
||||
assert_dialyze_no_warnings!(context)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -517,5 +517,55 @@ defmodule Kernel.LexicalTrackerTest do
|
||||
refute URI in exports
|
||||
refute URI in runtime
|
||||
end
|
||||
|
||||
test "imported functions from quote adds dependencies" do
|
||||
{{compile, exports, runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.QuotedFun do
|
||||
import URI
|
||||
|
||||
defmacro parse_root() do
|
||||
quote do
|
||||
parse("/")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.LexicalTrackerTest.UsingQuotedFun do
|
||||
require Kernel.LexicalTrackerTest.QuotedFun, as: QF
|
||||
QF.parse_root()
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
assert URI in compile
|
||||
refute URI in exports
|
||||
refute URI in runtime
|
||||
end
|
||||
|
||||
test "imported macro from quote adds dependencies" do
|
||||
{{compile, exports, runtime, _}, _binding} =
|
||||
Code.eval_string("""
|
||||
defmodule Kernel.LexicalTrackerTest.QuotedMacro do
|
||||
import Config
|
||||
|
||||
defmacro config_env() do
|
||||
quote do
|
||||
config_env()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Kernel.LexicalTrackerTest.UsingQuotedMacro do
|
||||
require Kernel.LexicalTrackerTest.QuotedMacro, as: QM
|
||||
def fun(), do: QM.config_env()
|
||||
Kernel.LexicalTracker.references(__ENV__.lexical_tracker)
|
||||
end |> elem(3)
|
||||
""")
|
||||
|
||||
assert Config in compile
|
||||
refute Config in exports
|
||||
refute Config in runtime
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,11 +10,15 @@ defmodule Kernel.TracersTest do
|
||||
end
|
||||
|
||||
def trace(event, %Macro.Env{} = env) do
|
||||
send(self(), {event, env})
|
||||
for {pid, _} <- Registry.lookup(__MODULE__, :tracers) do
|
||||
send(pid, {event, env})
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
setup_all do
|
||||
start_supervised!({Registry, keys: :duplicate, name: __MODULE__})
|
||||
Code.put_compiler_option(:tracers, [__MODULE__])
|
||||
|
||||
on_exit(fn ->
|
||||
@@ -22,6 +26,11 @@ defmodule Kernel.TracersTest do
|
||||
end)
|
||||
end
|
||||
|
||||
setup do
|
||||
Registry.register(__MODULE__, :tracers, :unused)
|
||||
:ok
|
||||
end
|
||||
|
||||
test "traces start and stop" do
|
||||
compile_string("""
|
||||
Foo
|
||||
@@ -255,7 +264,7 @@ defmodule Kernel.TracersTest do
|
||||
assert meta[:from_brackets]
|
||||
|
||||
compile_string("""
|
||||
defmodule Foo do
|
||||
defmodule TracerBracketAccess do
|
||||
@foo %{bar: 3}
|
||||
def a() do
|
||||
@foo[:bar]
|
||||
@@ -274,6 +283,75 @@ defmodule Kernel.TracersTest do
|
||||
assert meta[:from_brackets]
|
||||
end
|
||||
|
||||
test "traces on_load" do
|
||||
compile_string("""
|
||||
defmodule TracerOnLoad do
|
||||
@on_load :init
|
||||
def init, do: :ok
|
||||
end
|
||||
""")
|
||||
|
||||
assert_received {{:local_function, meta, :init, 0}, _}
|
||||
assert meta[:line] == 1
|
||||
end
|
||||
|
||||
def __before_compile__(_), do: :ok
|
||||
def __after_compile__(_, _), do: :ok
|
||||
def __after_verify__(_), do: :ok
|
||||
def __on_definition__(_, _, _, _, _, _), do: :ok
|
||||
|
||||
test "traces compile time attributes" do
|
||||
compile_string("""
|
||||
defmodule TracerCompileAttributes do
|
||||
@before_compile Kernel.TracersTest
|
||||
@after_compile Kernel.TracersTest
|
||||
@on_definition Kernel.TracersTest
|
||||
@after_verify Kernel.TracersTest
|
||||
def hello, do: :world
|
||||
end
|
||||
""")
|
||||
|
||||
assert_received {{:remote_function, meta, __MODULE__, :__before_compile__, 1}, _}
|
||||
assert meta[:line] == 1
|
||||
|
||||
assert_received {{:remote_function, meta, __MODULE__, :__after_compile__, 2}, _}
|
||||
assert meta[:line] == 1
|
||||
|
||||
assert_received {{:remote_function, meta, __MODULE__, :__after_verify__, 1}, _}
|
||||
assert meta[:line] == 1
|
||||
|
||||
assert_received {{:remote_function, meta, __MODULE__, :__on_definition__, 6}, _}
|
||||
assert meta[:line] == 6
|
||||
end
|
||||
|
||||
test "traces super" do
|
||||
compile_string("""
|
||||
defmodule TracerOverridable do
|
||||
def local(x), do: x
|
||||
defoverridable [local: 1]
|
||||
def local(x), do: super(x)
|
||||
|
||||
defmacro macro(x), do: x
|
||||
defoverridable [macro: 1]
|
||||
defmacro macro(x), do: super(x)
|
||||
|
||||
def capture(x), do: x
|
||||
defoverridable [capture: 1]
|
||||
def capture(x), do: tap(x, &super/1)
|
||||
|
||||
def capture_arg(x), do: x
|
||||
defoverridable [capture_arg: 1]
|
||||
def capture_arg(x), do: tap(x, &super(&1))
|
||||
end
|
||||
""")
|
||||
|
||||
assert_received {{:local_function, _, :"local (overridable 1)", 1}, _}
|
||||
assert_received {{:local_function, _, :"macro (overridable 1)", 1}, _}
|
||||
assert_received {{:local_function, _, :"capture (overridable 1)", 1}, _}
|
||||
assert_received {{:local_function, _, :"capture_arg (overridable 1)", 1}, _}
|
||||
refute_received {{:local_function, _, _, _}, _}
|
||||
end
|
||||
|
||||
test "does not trace bind quoted twice" do
|
||||
compile_string("""
|
||||
quote bind_quoted: [foo: List.flatten([])] do
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -327,6 +327,12 @@ defmodule ListTest do
|
||||
assert_raise ArgumentError, ~r"cannot convert the given list to a string", fn ->
|
||||
List.to_string([:a, :b])
|
||||
end
|
||||
|
||||
invalid = List.duplicate(?a, 4096) ++ [nil]
|
||||
|
||||
assert_raise ArgumentError, ~r/got:\n\n\[97, 97, .*\.\.\.\]\n\z/s, fn ->
|
||||
List.to_string(invalid)
|
||||
end
|
||||
end
|
||||
|
||||
test "to_charlist/1" do
|
||||
@@ -345,6 +351,12 @@ defmodule ListTest do
|
||||
assert_raise ArgumentError, ~r"cannot convert the given list to a charlist", fn ->
|
||||
List.to_charlist([:a, :b])
|
||||
end
|
||||
|
||||
invalid = List.duplicate(?a, 4096) ++ [nil]
|
||||
|
||||
assert_raise ArgumentError, ~r/got:\n\n\[97, 97, .*\.\.\.\]\n\z/s, fn ->
|
||||
List.to_charlist(invalid)
|
||||
end
|
||||
end
|
||||
|
||||
describe "myers_difference/2" do
|
||||
|
||||
@@ -136,6 +136,12 @@ defmodule MacroTest do
|
||||
test "does not add context to quote" do
|
||||
assert Macro.escape({:quote, [], [[do: :foo]]}) == {:{}, [], [:quote, [], [[do: :foo]]]}
|
||||
end
|
||||
|
||||
test "inspects container when a reference cannot be escaped" do
|
||||
assert_raise ArgumentError, ~r"~r/foo/ contains a reference", fn ->
|
||||
Macro.escape(%{~r/foo/ | re_pattern: make_ref()})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "expand_once/2" do
|
||||
|
||||
@@ -395,6 +395,15 @@ defmodule MapTest do
|
||||
assert quoted == {:%, [], [User, {:%{}, [], [{:foo, 1}]}]}
|
||||
end
|
||||
|
||||
test "structs with bitstring defaults" do
|
||||
defmodule WithBitstring do
|
||||
defstruct bitstring: <<255, 127::7>>
|
||||
end
|
||||
|
||||
info = Macro.struct_info!(WithBitstring, __ENV__)
|
||||
assert info == [%{default: <<255, 127::7>>, field: :bitstring}]
|
||||
end
|
||||
|
||||
test "defstruct can only be used once in a module" do
|
||||
message =
|
||||
"defstruct has already been called for TestMod, " <>
|
||||
|
||||
@@ -101,6 +101,46 @@ defmodule Module.Types.DescrTest do
|
||||
assert union(difference(list(term()), list(integer())), list(integer()))
|
||||
|> equal?(list(term()))
|
||||
end
|
||||
|
||||
test "optimizations" do
|
||||
# The tests are checking the actual implementation, not the semantics.
|
||||
# This is why we are using structural comparisons.
|
||||
# It's fine to remove these if the implementation changes, but breaking
|
||||
# these might have an important impact on compile times.
|
||||
|
||||
# Optimization one: same tags, all but one key are structurally equal
|
||||
assert union(
|
||||
open_map(a: float(), b: atom()),
|
||||
open_map(a: integer(), b: atom())
|
||||
) == open_map(a: union(float(), integer()), b: atom())
|
||||
|
||||
assert union(
|
||||
closed_map(a: float(), b: atom()),
|
||||
closed_map(a: integer(), b: atom())
|
||||
) == closed_map(a: union(float(), integer()), b: atom())
|
||||
|
||||
# Optimization two: we can tell that one map is a trivial subtype of the other:
|
||||
|
||||
assert union(
|
||||
closed_map(a: term(), b: term()),
|
||||
closed_map(a: float(), b: binary())
|
||||
) == closed_map(a: term(), b: term())
|
||||
|
||||
assert union(
|
||||
open_map(a: term()),
|
||||
closed_map(a: float(), b: binary())
|
||||
) == open_map(a: term())
|
||||
|
||||
assert union(
|
||||
closed_map(a: float(), b: binary()),
|
||||
open_map(a: term())
|
||||
) == open_map(a: term())
|
||||
|
||||
assert union(
|
||||
closed_map(a: term(), b: tuple([term(), term()])),
|
||||
closed_map(a: float(), b: tuple([atom(), binary()]))
|
||||
) == closed_map(a: term(), b: tuple([term(), term()]))
|
||||
end
|
||||
end
|
||||
|
||||
describe "intersection" do
|
||||
@@ -163,6 +203,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
|
||||
|
||||
@@ -251,6 +251,10 @@ defmodule Module.Types.PatternTest do
|
||||
assert typecheck!([<<x::utf8>>], x) == integer()
|
||||
end
|
||||
|
||||
test "nested" do
|
||||
assert typecheck!([<<0, <<x::bitstring>>::binary>>], x) == binary()
|
||||
end
|
||||
|
||||
test "error" do
|
||||
assert typeerror!([<<x::binary-size(2), x::float>>], x) == ~l"""
|
||||
incompatible types assigned to "x":
|
||||
|
||||
@@ -428,10 +428,9 @@ end
|
||||
defmodule OptionsParserDeprecationsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@warning ~r[not passing the :switches or :strict option to OptionParser is deprecated]
|
||||
|
||||
def assert_deprecated(fun) do
|
||||
assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~ @warning
|
||||
assert ExUnit.CaptureIO.capture_io(:stderr, fun) =~
|
||||
"not passing the :switches or :strict option to OptionParser is deprecated"
|
||||
end
|
||||
|
||||
test "parses boolean option" do
|
||||
|
||||
@@ -106,15 +106,18 @@ defmodule ProtocolTest do
|
||||
assert Sample.impl_for(%ImplStruct{}) == Sample.ProtocolTest.ImplStruct
|
||||
assert Sample.impl_for(%ImplStructExplicitFor{}) == Sample.ProtocolTest.ImplStructExplicitFor
|
||||
assert Sample.impl_for(%NoImplStruct{}) == nil
|
||||
assert is_nil(Sample.impl_for(%{__struct__: nil}))
|
||||
end
|
||||
|
||||
test "protocol implementation with Any and struct fallbacks" do
|
||||
assert WithAny.impl_for(%NoImplStruct{}) == WithAny.Any
|
||||
# Derived
|
||||
assert WithAny.impl_for(%ImplStruct{}) == ProtocolTest.WithAny.ProtocolTest.ImplStruct
|
||||
assert WithAny.impl_for(%{__struct__: nil}) == WithAny.Any
|
||||
assert WithAny.impl_for(%{__struct__: "foo"}) == WithAny.Map
|
||||
assert WithAny.impl_for(%{}) == WithAny.Map
|
||||
assert WithAny.impl_for(self()) == WithAny.Any
|
||||
|
||||
# Derived
|
||||
assert WithAny.impl_for(%ImplStruct{}) == ProtocolTest.WithAny.ProtocolTest.ImplStruct
|
||||
end
|
||||
|
||||
test "protocol not implemented" do
|
||||
|
||||
@@ -3,41 +3,25 @@ Code.require_file("test_helper.exs", __DIR__)
|
||||
defmodule RegexTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
@re_21_3_little %Regex{
|
||||
re_pattern:
|
||||
{:re_pattern, 1, 0, 0,
|
||||
<<69, 82, 67, 80, 94, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 64, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 131, 0, 20, 29, 99, 133,
|
||||
0, 7, 0, 1, 29, 100, 119, 0, 5, 29, 101, 120, 0, 12, 120, 0, 20, 0>>},
|
||||
re_version: {"8.42 2018-03-20", :little},
|
||||
source: "c(?<foo>d|e)"
|
||||
}
|
||||
|
||||
@re_21_3_big %Regex{
|
||||
re_pattern:
|
||||
{:re_pattern, 1, 0, 0,
|
||||
<<80, 67, 82, 69, 0, 0, 0, 86, 0, 0, 0, 0, 0, 0, 0, 17, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 0, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 56, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 131, 0, 20, 29, 99, 133, 0, 7, 0, 1, 29, 100, 119,
|
||||
0, 5, 29, 101, 120, 0, 12, 120, 0, 20, 0>>},
|
||||
re_version: {"8.42 2018-03-20", :big},
|
||||
source: "c(?<foo>d|e)"
|
||||
}
|
||||
|
||||
@re_19_3_little %Regex{
|
||||
re_pattern:
|
||||
{:re_pattern, 1, 0, 0,
|
||||
<<69, 82, 67, 80, 94, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255,
|
||||
255, 99, 0, 0, 0, 0, 0, 1, 0, 0, 0, 64, 0, 6, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 102, 111, 111, 0, 125, 0, 20, 29, 99, 127,
|
||||
0, 7, 0, 1, 29, 100, 113, 0, 5, 29, 101, 114, 0, 12, 114, 0, 20, 0>>},
|
||||
re_version: {"8.33 2013-05-29", :little},
|
||||
source: "c(?<foo>d|e)"
|
||||
}
|
||||
|
||||
doctest Regex
|
||||
|
||||
if System.otp_release() >= "28" do
|
||||
test "module attribute" do
|
||||
defmodule ModAttr do
|
||||
@regex ~r/example/
|
||||
def regex, do: @regex
|
||||
|
||||
@bare_regex :erlang.term_to_binary(@regex)
|
||||
def bare_regex, do: :erlang.binary_to_term(@bare_regex)
|
||||
|
||||
# We don't rewrite outside of functions
|
||||
assert @regex.re_pattern == :erlang.binary_to_term(@bare_regex).re_pattern
|
||||
end
|
||||
|
||||
assert ModAttr.regex().re_pattern != ModAttr.bare_regex().re_pattern
|
||||
end
|
||||
end
|
||||
|
||||
test "multiline" do
|
||||
refute Regex.match?(~r/^b$/, "a\nb\nc")
|
||||
assert Regex.match?(~r/^b$/m, "a\nb\nc")
|
||||
@@ -68,16 +52,9 @@ defmodule RegexTest do
|
||||
test "literal source" do
|
||||
assert Regex.source(Regex.compile!("foo")) == "foo"
|
||||
assert Regex.source(~r"foo") == "foo"
|
||||
assert Regex.re_pattern(Regex.compile!("foo")) == Regex.re_pattern(~r"foo")
|
||||
|
||||
assert Regex.source(Regex.compile!("\a\b\d\e\f\n\r\s\t\v")) == "\a\b\d\e\f\n\r\s\t\v"
|
||||
assert Regex.source(~r<\a\b\d\e\f\n\r\s\t\v>) == "\\a\\b\\d\\e\\f\\n\\r\\s\\t\\v"
|
||||
|
||||
assert Regex.re_pattern(Regex.compile!("\a\b\d\e\f\n\r\s\t\v")) ==
|
||||
Regex.re_pattern(~r"\x07\x08\x7F\x1B\x0C\x0A\x0D\x20\x09\x0B")
|
||||
|
||||
assert Regex.re_pattern(Regex.compile!("\\a\\b\\d\e\f\\n\\r\\s\\t\\v")) ==
|
||||
Regex.re_pattern(~r"\a\b\d\e\f\n\r\s\t\v")
|
||||
end
|
||||
|
||||
test "Unicode" do
|
||||
@@ -116,16 +93,6 @@ defmodule RegexTest do
|
||||
end
|
||||
end
|
||||
|
||||
test "recompile/1" do
|
||||
new_regex = ~r/foo/
|
||||
{:ok, %Regex{}} = Regex.recompile(new_regex)
|
||||
assert %Regex{} = Regex.recompile!(new_regex)
|
||||
|
||||
old_regex = Map.delete(~r/foo/, :re_version)
|
||||
{:ok, %Regex{}} = Regex.recompile(old_regex)
|
||||
assert %Regex{} = Regex.recompile!(old_regex)
|
||||
end
|
||||
|
||||
test "opts/1" do
|
||||
assert Regex.opts(Regex.compile!("foo", "i")) == [:caseless]
|
||||
assert Regex.opts(Regex.compile!("foo", [:ucp])) == [:ucp]
|
||||
@@ -179,16 +146,6 @@ defmodule RegexTest do
|
||||
assert Regex.run(~r"bar", "foobar", offset: 2, return: :index) == [{3, 3}]
|
||||
end
|
||||
|
||||
test "run/3 with regexes compiled in different systems" do
|
||||
assert Regex.run(@re_21_3_little, "abcd abce", capture: :all_names) == ["d"]
|
||||
assert Regex.run(@re_21_3_big, "abcd abce", capture: :all_names) == ["d"]
|
||||
assert Regex.run(@re_19_3_little, "abcd abce", capture: :all_names) == ["d"]
|
||||
end
|
||||
|
||||
test "run/3 with regexes with options compiled in different systems" do
|
||||
assert Regex.run(%{~r/foo/i | re_version: "bad version"}, "FOO") == ["FOO"]
|
||||
end
|
||||
|
||||
test "scan/2" do
|
||||
assert Regex.scan(~r"c(d|e)", "abcd abce") == [["cd", "d"], ["ce", "e"]]
|
||||
assert Regex.scan(~r"c(?:d|e)", "abcd abce") == [["cd"], ["ce"]]
|
||||
@@ -207,16 +164,6 @@ defmodule RegexTest do
|
||||
assert Regex.scan(~r"^foo", "foobar", offset: 1) == []
|
||||
end
|
||||
|
||||
test "scan/2 with regexes compiled in different systems" do
|
||||
assert Regex.scan(@re_21_3_little, "abcd abce", capture: :all_names) == [["d"], ["e"]]
|
||||
assert Regex.scan(@re_21_3_big, "abcd abce", capture: :all_names) == [["d"], ["e"]]
|
||||
assert Regex.scan(@re_19_3_little, "abcd abce", capture: :all_names) == [["d"], ["e"]]
|
||||
end
|
||||
|
||||
test "scan/2 with regexes with options compiled in different systems" do
|
||||
assert Regex.scan(%{~r/foo/i | re_version: "bad version"}, "FOO") == [["FOO"]]
|
||||
end
|
||||
|
||||
test "split/2,3" do
|
||||
assert Regex.split(~r",", "") == [""]
|
||||
assert Regex.split(~r",", "", trim: true) == []
|
||||
|
||||
@@ -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
|
||||
@@ -385,6 +393,7 @@ defmodule TypespecTest do
|
||||
@type size :: <<_::3>>
|
||||
@type unit :: <<_::_*8>>
|
||||
@type size_and_unit :: <<_::3, _::_*8>>
|
||||
@type size_prod_unit :: <<_::3*8>>
|
||||
end
|
||||
|
||||
assert [
|
||||
@@ -393,17 +402,15 @@ defmodule TypespecTest do
|
||||
type: {:size, {:type, _, :binary, [{:integer, _, 3}, {:integer, _, 0}]}, []},
|
||||
type:
|
||||
{:size_and_unit, {:type, _, :binary, [{:integer, _, 3}, {:integer, _, 8}]}, []},
|
||||
type:
|
||||
{:size_prod_unit,
|
||||
{:type, _, :binary,
|
||||
[{:op, _, :*, {:integer, _, 3}, {:integer, _, 8}}, {:integer, _, 0}]}, []},
|
||||
type: {:unit, {:type, _, :binary, [{:integer, _, 0}, {:integer, _, 8}]}, []}
|
||||
] = types(bytecode)
|
||||
end
|
||||
|
||||
test "@type with invalid binary spec" do
|
||||
assert_raise Kernel.TypespecError, ~r"invalid binary specification", fn ->
|
||||
test_module do
|
||||
@type my_type :: <<_::3*8>>
|
||||
end
|
||||
end
|
||||
|
||||
assert_raise Kernel.TypespecError, ~r"invalid binary specification", fn ->
|
||||
test_module do
|
||||
@type my_type :: <<_::atom()>>
|
||||
@@ -841,6 +848,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",
|
||||
@@ -1196,6 +1216,7 @@ defmodule TypespecTest do
|
||||
quote(do: @type(binary_type1() :: <<_::_*8>>)),
|
||||
quote(do: @type(binary_type2() :: <<_::3>>)),
|
||||
quote(do: @type(binary_type3() :: <<_::3, _::_*8>>)),
|
||||
quote(do: @type(binary_type4() :: <<_::3*8>>)),
|
||||
quote(do: @type(tuple_type() :: {integer()})),
|
||||
quote(do: @type(ftype() :: (-> any()) | (-> integer()) | (integer() -> integer()))),
|
||||
quote(do: @type(cl() :: charlist())),
|
||||
|
||||
@@ -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
|
||||
@@ -1155,6 +1156,7 @@ defmodule ExUnit.Diff do
|
||||
else
|
||||
other
|
||||
|> Map.to_list()
|
||||
|> Enum.map(&escape_pair/1)
|
||||
|> build_map_or_struct(struct)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -707,7 +707,7 @@ defmodule ExUnit.AssertionsTest do
|
||||
rescue
|
||||
error in [ExUnit.AssertionError] ->
|
||||
"foo" = error.left
|
||||
~r{a} = error.right
|
||||
%Regex{} = error.right
|
||||
end
|
||||
end
|
||||
|
||||
@@ -722,7 +722,7 @@ defmodule ExUnit.AssertionsTest do
|
||||
rescue
|
||||
error in [ExUnit.AssertionError] ->
|
||||
"foo" = error.left
|
||||
~r"o" = error.right
|
||||
%Regex{} = error.right
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -178,38 +178,38 @@ defmodule ExUnit.CaptureIOTest do
|
||||
|
||||
test "with get password" do
|
||||
capture_io(fn ->
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("", fn ->
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("abc", fn ->
|
||||
assert :io.get_password() == "abc"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "abc"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("abc\n", fn ->
|
||||
assert :io.get_password() == "abc\n"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "abc\n"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("\n", fn ->
|
||||
assert :io.get_password() == "\n"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "\n"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("a\nb", fn ->
|
||||
assert :io.get_password() == "a\n"
|
||||
assert :io.get_password() == "b"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "a\n"
|
||||
assert :io.get_password(Process.group_leader()) == "b"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
|
||||
capture_io("あい\nう", fn ->
|
||||
assert :io.get_password() == "あい\n"
|
||||
assert :io.get_password() == "う"
|
||||
assert :io.get_password() == :eof
|
||||
assert :io.get_password(Process.group_leader()) == "あい\n"
|
||||
assert :io.get_password(Process.group_leader()) == "う"
|
||||
assert :io.get_password(Process.group_leader()) == :eof
|
||||
end)
|
||||
end
|
||||
|
||||
|
||||
@@ -330,6 +330,8 @@ defmodule ExUnit.DiffTest do
|
||||
"[[[[], \"Hello-,- \"] | \"world\"] | \"!\"]",
|
||||
"[[[[], \"Hello \"] | \"world\"] | \"!\"]"
|
||||
)
|
||||
|
||||
refute_diff(:foo = %{bar: [:a | :b]}, "", "")
|
||||
end
|
||||
|
||||
test "proper lists" do
|
||||
@@ -1133,6 +1135,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
|
||||
|
||||
@@ -503,6 +503,17 @@ defmodule ExUnit.FormatterTest do
|
||||
"""
|
||||
end
|
||||
|
||||
test "formats assertions with nested improper list diffing" do
|
||||
failure = [{:error, catch_assertion(assert :foo = %{bar: [1 | 2]}), []}]
|
||||
|
||||
assert format_test_all_failure(test_module(), failure, 1, :infinity, &diff_formatter/2) =~ """
|
||||
match (=) failed
|
||||
code: assert :foo = %{bar: [1 | 2]}
|
||||
left: :foo
|
||||
right: %{bar: [1 | 2]}
|
||||
"""
|
||||
end
|
||||
|
||||
defmodule BadInspect do
|
||||
defstruct key: 0
|
||||
|
||||
|
||||
@@ -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, "_"),
|
||||
@@ -378,7 +378,8 @@ defmodule IEx.Autocomplete do
|
||||
case Code.Fragment.container_cursor_to_quoted(code) do
|
||||
{:ok, quoted} ->
|
||||
case Macro.path(quoted, &match?({:__cursor__, _, []}, &1)) do
|
||||
[cursor, {:%{}, _, pairs}, {:%, _, [{:__aliases__, _, aliases}, _map]} | _] ->
|
||||
[cursor, {:%{}, _, pairs}, {:%, _, [{:__aliases__, _, aliases = [h | _]}, _map]} | _]
|
||||
when is_atom(h) ->
|
||||
container_context_struct(cursor, pairs, aliases, shell)
|
||||
|
||||
[
|
||||
@@ -386,8 +387,9 @@ defmodule IEx.Autocomplete do
|
||||
pairs,
|
||||
{:|, _, _},
|
||||
{:%{}, _, _},
|
||||
{:%, _, [{:__aliases__, _, aliases}, _map]} | _
|
||||
] ->
|
||||
{:%, _, [{:__aliases__, _, aliases = [h | _]}, _map]} | _
|
||||
]
|
||||
when is_atom(h) ->
|
||||
container_context_struct(cursor, pairs, aliases, shell)
|
||||
|
||||
[cursor, pairs, {:|, _, [{variable, _, nil} | _]}, {:%{}, _, _} | _] ->
|
||||
|
||||
@@ -40,6 +40,7 @@ defmodule IEx.Helpers do
|
||||
* `pid/3` - creates a PID with the 3 integer arguments passed
|
||||
* `port/1` - creates a port from a string
|
||||
* `port/2` - creates a port with the 2 non-negative integers passed
|
||||
* `process_info/1` - returns information about the given process
|
||||
* `pwd/0` - prints the current working directory
|
||||
* `r/1` - recompiles the given module's source file
|
||||
* `recompile/0` - recompiles the current project
|
||||
@@ -817,6 +818,168 @@ defmodule IEx.Helpers do
|
||||
|
||||
defp pad_key(key), do: String.pad_trailing(key, 21, " ")
|
||||
|
||||
@process_info_keys_and_labels [
|
||||
{:initial_call, "Initial call"},
|
||||
{:dictionary, "Dictionary"},
|
||||
{:registered_name, "Registered name"},
|
||||
{:current_function, "Current function"},
|
||||
{:status, "Status"},
|
||||
{:message_queue_len, "Message queue length"},
|
||||
{:trap_exit, "Trap exit"},
|
||||
{:priority, "Priority"},
|
||||
{:group_leader, "Group leader"},
|
||||
{:reductions, "Reductions"},
|
||||
{:links, "Links"},
|
||||
{:monitors, "Monitors"},
|
||||
{:memory, "Memory"},
|
||||
{:total_heap_size, "Total heap size"},
|
||||
{:heap_size, "Heap size"},
|
||||
{:stack_size, "Stack size"},
|
||||
{:current_stacktrace, "Current stacktrace"}
|
||||
]
|
||||
@process_info_keys Enum.map(@process_info_keys_and_labels, fn {key, _} -> key end)
|
||||
@process_info_label_mapping Map.new(@process_info_keys_and_labels)
|
||||
|
||||
@doc """
|
||||
Prints information about the given process.
|
||||
|
||||
Includes a generic overview and details such as the linked and monitored processes,
|
||||
the memory usage and the current stacktrace.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> process_info(self())
|
||||
...
|
||||
iex> process_info({:via, Registry, {MyApp.Registry, :name}})
|
||||
...
|
||||
|
||||
"""
|
||||
@doc since: "1.19.0"
|
||||
def process_info(pid) do
|
||||
with pid when is_pid(pid) <- GenServer.whereis(pid),
|
||||
info when is_list(info) <-
|
||||
:erpc.call(node(pid), :erlang, :process_info, [pid, @process_info_keys]) do
|
||||
info = Map.new(info)
|
||||
|
||||
IO.puts(IEx.color(:eval_result, ["\n# Process ", inspect(pid)]))
|
||||
|
||||
print_process_overview(info)
|
||||
print_process_links(info[:links])
|
||||
print_process_monitors(info[:monitors])
|
||||
print_process_memory(info)
|
||||
print_process_stacktrace(info[:current_stacktrace])
|
||||
else
|
||||
_ ->
|
||||
IO.puts(
|
||||
IEx.color(
|
||||
:eval_error,
|
||||
"Failed to get process info. Either the process was not found or is not alive."
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
dont_display_result()
|
||||
end
|
||||
|
||||
defp print_process_overview(info) do
|
||||
print_pane("Overview")
|
||||
|
||||
for key <- [
|
||||
:initial_call,
|
||||
:current_function,
|
||||
:registered_name,
|
||||
:status,
|
||||
:message_queue_len,
|
||||
:group_leader,
|
||||
:priority,
|
||||
:trap_exit,
|
||||
:reductions
|
||||
] do
|
||||
print_entry(
|
||||
@process_info_label_mapping[key],
|
||||
inspect(info[key], printable_limit: 256, limit: 5)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
defp print_process_links([]), do: :ok
|
||||
|
||||
defp print_process_links(ports_and_pids) do
|
||||
print_pane("Links")
|
||||
|
||||
for link <- ports_and_pids do
|
||||
print_entry(inspect(link), pid_or_port_details(link))
|
||||
end
|
||||
end
|
||||
|
||||
defp print_process_monitors([]), do: :ok
|
||||
|
||||
defp print_process_monitors(monitors) do
|
||||
print_pane("Monitors")
|
||||
|
||||
for {_, pid_or_port} <- monitors do
|
||||
print_entry(inspect(pid_or_port), pid_or_port_details(pid_or_port))
|
||||
end
|
||||
end
|
||||
|
||||
defp print_process_memory(info) do
|
||||
print_pane("Memory")
|
||||
|
||||
for key <- [:memory, :total_heap_size, :heap_size, :stack_size] do
|
||||
print_entry(@process_info_label_mapping[key], format_bytes(info[key]))
|
||||
end
|
||||
end
|
||||
|
||||
defp print_process_stacktrace([]), do: :ok
|
||||
|
||||
defp print_process_stacktrace(stacktrace) do
|
||||
print_pane("Current stacktrace")
|
||||
|
||||
IO.puts(IEx.color(:eval_info, Exception.format_stacktrace(stacktrace)))
|
||||
end
|
||||
|
||||
defp pid_or_port_details(pid) when is_pid(pid), do: to_process_details(pid)
|
||||
defp pid_or_port_details(name) when is_atom(name), do: to_process_details(name)
|
||||
defp pid_or_port_details(port) when is_port(port), do: to_port_details(port)
|
||||
defp pid_or_port_details(reference) when is_reference(reference), do: reference
|
||||
|
||||
defp to_process_details(pid) when is_pid(pid) do
|
||||
case Process.info(pid, [:initial_call, :dictionary, :registered_name]) do
|
||||
[{:initial_call, initial_call}, {:dictionary, dictionary}, {:registered_name, name}] ->
|
||||
initial_call = Keyword.get(dictionary, :"$initial_call", initial_call)
|
||||
|
||||
format_registered_name(name) ||
|
||||
format_process_label(Keyword.get(dictionary, :"$process_label")) ||
|
||||
format_initial_call(initial_call)
|
||||
|
||||
_ ->
|
||||
"-"
|
||||
end
|
||||
end
|
||||
|
||||
defp to_process_details(name) when is_atom(name) do
|
||||
Process.whereis(name)
|
||||
|> to_process_details()
|
||||
end
|
||||
|
||||
defp format_process_label(nil), do: nil
|
||||
defp format_process_label(label) when is_binary(label), do: label
|
||||
defp format_process_label(label), do: inspect(label)
|
||||
|
||||
defp format_registered_name([]), do: nil
|
||||
defp format_registered_name(name), do: inspect(name)
|
||||
|
||||
defp format_initial_call({:supervisor, mod, arity}), do: Exception.format_mfa(mod, :init, arity)
|
||||
defp format_initial_call({m, f, a}), do: Exception.format_mfa(m, f, a)
|
||||
defp format_initial_call(nil), do: nil
|
||||
|
||||
defp to_port_details(port) when is_port(port) do
|
||||
case Port.info(port, :name) do
|
||||
{:name, name} -> name
|
||||
_ -> "-"
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Clears out all messages sent to the shell's inbox and prints them out.
|
||||
"""
|
||||
|
||||
@@ -12,10 +12,18 @@ defmodule IEx.MixListener do
|
||||
|
||||
@doc """
|
||||
Unloads all modules invalidated by external compilations.
|
||||
|
||||
Returns `:noop` if there is no module to purge or if
|
||||
the listener is not running (it may happen when connecting
|
||||
via --remsh to a node that was started without IEx).
|
||||
"""
|
||||
@spec purge :: :ok | :noop
|
||||
def purge do
|
||||
GenServer.call(@name, :purge, :infinity)
|
||||
if Process.whereis(@name) do
|
||||
GenServer.call(@name, :purge, :infinity)
|
||||
else
|
||||
:noop
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user