Compare commits

...
31 Commits
Author SHA1 Message Date
José Valim 175c8243b2 Release v1.18.2 2025-01-22 17:38:30 +01:00
José Valim 2c1a836db3 Make --color/--no-color consistent with mix test 2025-01-22 17:33:00 +01:00
José Valim f29e18bca1 Do not precompile regexes on Erlang/OTP 28 2025-01-21 18:51:54 +01:00
José Valim 329442c481 Provide more AST around cursor fragments, closes #14118 2025-01-21 10:36:19 +01:00
José Valim 01a88e7137 Simplify token pruning, closes #14139 2025-01-20 18:57:09 +01:00
Jean Klingler 4def31f8ab Mention the new type system in typespecs doc (#14188) 2025-01-15 20:42:33 +09:00
José Valim 88c75bcbf5 Remove powershell scripts
It current leaves the shell broken after quitting Erlang.

Issues have been reported upstream, so we may be able
to bring it back in future Erlang/OTP versions.
2025-01-13 13:51:05 +01:00
José Valim 786f3ce797 Add --color for easy enabling and disabling of coloring (#14183) 2025-01-13 08:55:12 +01:00
José Valim 98aeee34b6 Simplify bat and ps1 files (#14180) 2025-01-13 08:55:08 +01:00
José Valim 6f7eaf1122 Keep line information in defstruct 2025-01-12 20:09:44 +01:00
Jonatan Kłosko 9593cef2f5 Remove duplicate call to mkdir in concurrency lock (#14175) 2025-01-11 10:53:36 +01:00
Claudio Ortolina c78f426bf5 Improve clarity of Path.safe_relative/2 argument names (#14167)
Switches from `cwd` to `relative_to`, which is more generic and more consistent with possible use cases of the function.
2025-01-10 20:03:32 +01:00
Steffen Deusch ff29628602 Track mix_pubsub and mix_lock folders per user (#14171) 2025-01-10 20:03:31 +01:00
Zach Allaun f5e42ba11a Update task-and-gen-tcp.md (#14166) 2025-01-09 18:10:35 +01:00
José Valim b0dae83353 Clarify the need for better tools in the TCP guide 2025-01-09 16:37:06 +01:00
Jean Klingler 40da7e8857 Fix ExUnit crash when diffing bitstring specifiers (#14161) 2025-01-09 18:27:58 +09:00
Jean Klingler ac910c1cc2 Prevent infinite loop in compiler for some invalid type specs (#14155) 2025-01-09 18:27:55 +09:00
José Valim 5c340e488e Migrate the git repo against the origin branch, closes #14163 2025-01-09 10:03:18 +01:00
Daniel Drexler defef37856 Improve warning for charlists (#14160)
The current warning for single-quoted character lists is confusing
because it appears to be a stylistic note instead of a deprecation
warning. This commit updates the language to make the reasoning clearer
and to more clearly describe what `mix format --migrate` will change
about the users' code.
2025-01-09 09:22:20 +01:00
Guillaume Milan 432d6fa1c2 Fix crashing on autocompleting structs with runtime values (#14150) 2025-01-07 11:40:01 +01:00
José Valim 24714c68a7 Improve error message for invalid default arguments 2025-01-02 18:36:27 +01:00
Jonatan Kłosko f088fc9cdb Print compilation lock waiting message to stderr (#14138) 2025-01-02 09:41:01 +01:00
José Valim dddb8f7b19 Bring kv-observer image back as it is used in guides 2024-12-31 08:53:56 +01:00
José Valim 2da3300bfc Do not wrap literals in variable when expanding in/2 2024-12-31 08:45:54 +01:00
Jonatan Kłosko c7a841baae Add an environment variable to optionally disable compilation locking (#14129) 2024-12-30 17:00:35 +01:00
José Valim f3dfb72c69 Properly handle optional keys in map intersection 2024-12-30 11:39:57 +01:00
José Valim 7e149619f5 Fix expression-tag pairing inside with type checking 2024-12-30 11:07:35 +01:00
José Valim 6dbb932bf3 Properly raise for invalid async value 2024-12-28 09:33:04 +01:00
José Valim 0cb79b0ba1 Add OTP 25 to sign entry 2024-12-28 09:18:59 +01:00
José Valim 265b9aaeb3 Update AST metadata documentation (#14120) 2024-12-27 11:37:54 +01:00
José Valim b9bd0e3dd9 Bring conditional violation reports back 2024-12-26 19:37:25 +01:00
43 changed files with 424 additions and 559 deletions
+6 -6
View File
@@ -115,13 +115,13 @@ jobs:
with:
name: Docs
path: Docs.zip*
sign:
needs: [build]
strategy:
fail-fast: true
matrix:
otp: [26, 27]
otp: [25, 26, 27]
flavor: [windows, linux]
env:
@@ -160,14 +160,14 @@ jobs:
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"
@@ -191,7 +191,7 @@ jobs:
steps:
- uses: actions/download-artifact@v4
with:
pattern: '{sign-*-elixir-otp-*,Docs}'
pattern: "{sign-*-elixir-otp-*,Docs}"
merge-multiple: true
- name: Upload Pre-built
@@ -235,7 +235,7 @@ jobs:
steps:
- uses: actions/download-artifact@v4
with:
pattern: '{sign-*-elixir-otp-*,Docs}'
pattern: "{sign-*-elixir-otp-*,Docs}"
merge-multiple: true
- name: Init purge keys file
+34
View File
@@ -225,6 +225,40 @@ You may also prefer to write using guards:
def foo(x, y, z) when x == y and y == z
## v1.18.2 (2024-01-22)
### 1. Enhancements
#### Elixir
* [CLI] Add `--color`/`--no-color` for enabling and disabling of ANSI colors
* [Code.Fragment] Provide more AST context when invoking `container_cursor_to_quoted` with trailing fragments
* [Regex] Ensure compatibility with Erlang/OTP 28+ new Regex engine
#### Mix
* [mix] Print compilation lock waiting message to stderr
* [mix] Add an environment variable to optionally disable compilation locking
### 2. Bug fixes
#### Elixir
* [CLI] Temporarily remove PowerShell scripts for `elixir`, `elixirc`, and `mix` on Windows, as they leave the shell broken after quitting Erlang
#### ExUnit
* [ExUnit] Fix crash when diffing bitstring specifiers
#### IEx
* [IEx.Autocomplete] Fix crashing whhen autocompleting structs with runtime values
#### Mix
* [mix] Track compilation locks per user to avoid permission errors
* [mix deps.update] Ensure Git dependencies can be upgraded by doing so against the origin
## v1.18.1 (2024-12-24)
### 1. Enhancements
+1 -1
View File
@@ -1 +1 @@
1.18.1
1.18.2
+3 -2
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.18.1
ELIXIR_VERSION=1.18.2
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
@@ -18,6 +18,7 @@ Usage: $(basename "$0") [options] [.exs file] [data]
-pz "PATH" Appends the given path to Erlang code path (*)
-v, --version Prints Erlang/OTP and Elixir versions (standalone)
--color, --no-color Enables or disables ANSI coloring
--erl "SWITCHES" Switches to be passed down to Erlang (*)
--eval "COMMAND" Evaluates the given command, same as -e (*)
--logger-otp-reports BOOL Enables or disables OTP reporting
@@ -111,7 +112,7 @@ while [ $I -le $LENGTH ]; do
C=1
MODE="iex"
;;
-v|--no-halt)
-v|--no-halt|--color|--no-color)
C=1
;;
-e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
+6 -3
View File
@@ -1,6 +1,6 @@
@echo off
set ELIXIR_VERSION=1.18.1
set ELIXIR_VERSION=1.18.2
if ""%1""=="""" if ""%2""=="""" goto documentation
if /I ""%1""==""--help"" if ""%2""=="""" goto documentation
@@ -24,6 +24,7 @@ echo -pa "PATH" Prepends the given path to Erlang code path
echo -pz "PATH" Appends the given path to Erlang code path (*)
echo -v, --version Prints Erlang/OTP and Elixir versions (standalone)
echo.
echo --color, --no-color Enables or disables ANSI coloring
echo --erl "SWITCHES" Switches to be passed down to Erlang (*)
echo --eval "COMMAND" Evaluates the given command, same as -e (*)
echo --logger-otp-reports BOOL Enables or disables OTP reporting
@@ -107,6 +108,8 @@ if ""==!par:-pz=! (shift && goto startloop)
if ""==!par:-v=! (goto startloop)
if ""==!par:--version=! (goto startloop)
if ""==!par:--no-halt=! (goto startloop)
if ""==!par:--color=! (goto startloop)
if ""==!par:--no-color=! (goto startloop)
if ""==!par:--remsh=! (shift && goto startloop)
if ""==!par:--dot-iex=! (shift && goto startloop)
if ""==!par:--dbg=! (shift && goto startloop)
@@ -133,9 +136,9 @@ if not defined useIEx (
set beforeExtra=-noshell -elixir_root "%SCRIPT_PATH%..\lib" -pa "%SCRIPT_PATH%..\lib\elixir\ebin" %beforeExtra%
if defined ELIXIR_CLI_DRY_RUN (
echo "%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
echo "%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
) else (
"%ERTS_BIN%erl.exe" %ext_libs% %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
"%ERTS_BIN%erl.exe" %ELIXIR_ERL_OPTIONS% %parsErlang% %beforeExtra% -extra %*
)
exit /B %ERRORLEVEL%
:end
-304
View File
@@ -1,304 +0,0 @@
#!/usr/bin/env pwsh
$ELIXIR_VERSION = "1.18.1"
$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
}
-35
View File
@@ -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
View File
@@ -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
View File
@@ -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
+23 -11
View File
@@ -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,13 +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?([_ | 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
+35 -36
View File
@@ -1972,7 +1972,7 @@ defmodule Kernel do
defp build_boolean_check(operator, check, true_clause, false_clause) do
annotate_case(
[optimize_boolean: true],
[optimize_boolean: true, type_check: :expr],
quote do
case unquote(check) do
false -> unquote(false_clause)
@@ -2006,7 +2006,7 @@ defmodule Kernel do
assert_no_match_or_guard_scope(__CALLER__.context, "!")
annotate_case(
[optimize_boolean: true],
[optimize_boolean: true, type_check: :expr],
quote do
case unquote(value) do
x when :"Elixir.Kernel".in(x, [false, nil]) -> false
@@ -2020,7 +2020,7 @@ defmodule Kernel do
assert_no_match_or_guard_scope(__CALLER__.context, "!")
annotate_case(
[optimize_boolean: true],
[optimize_boolean: true, type_check: :expr],
quote do
case unquote(value) do
x when :"Elixir.Kernel".in(x, [false, nil]) -> true
@@ -3910,7 +3910,7 @@ defmodule Kernel do
defp build_if(condition, do: do_clause, else: else_clause) do
annotate_case(
[optimize_boolean: true],
[optimize_boolean: true, type_check: :expr],
quote do
case unquote(condition) do
x when :"Elixir.Kernel".in(x, [false, nil]) -> unquote(else_clause)
@@ -4584,6 +4584,9 @@ defmodule Kernel do
defp in_var(true, {atom, _, context} = var, fun) when is_atom(atom) and is_atom(context),
do: fun.(var)
defp in_var(true, var, fun) when is_atom(var) or is_binary(var) or is_number(var),
do: fun.(var)
defp in_var(true, ast, fun) do
quote do
var = unquote(ast)
@@ -5460,33 +5463,21 @@ defmodule Kernel do
use `@type`.
"""
defmacro defstruct(fields) do
header =
quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
{struct, derive, escaped_struct, kv, body} =
Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
quote bind_quoted: [fields: fields, bootstrapped?: bootstrapped?(Enum)] do
{struct, derive, escaped_struct, kv, body} =
Kernel.Utils.defstruct(__MODULE__, fields, bootstrapped?, __ENV__)
case derive do
[] -> :ok
_ -> Protocol.__derive__(derive, __MODULE__, __ENV__)
end
case derive do
[] -> :ok
_ -> Protocol.__derive__(derive, __MODULE__, __ENV__)
end
# We attach the line: 0 to struct functions because we don't want
# the generated callbacks to count towards code coverage and metrics,
# especially since they are often expanded at compile-time.
functions =
quote line: 0, unquote: false do
def __struct__(), do: unquote(escaped_struct)
def __struct__(unquote(kv)), do: unquote(body)
end
def __struct__(), do: unquote(escaped_struct)
def __struct__(unquote(kv)), do: unquote(body)
footer =
quote do
Kernel.Utils.announce_struct(__MODULE__)
struct
end
{:__block__, [], [header, functions, footer]}
Kernel.Utils.announce_struct(__MODULE__)
struct
end
end
@doc ~S"""
@@ -6446,29 +6437,37 @@ defmodule Kernel do
"""
defmacro sigil_r(term, modifiers)
defmacro sigil_r({:<<>>, _meta, [string]}, options) when is_binary(string) do
binary = :elixir_interpolation.unescape_string(string, &regex_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, &regex_unescape_map/1)
compile_regex(binary, options)
end
defmacro sigil_r({:<<>>, meta, pieces}, options) do
binary = {:<<>>, meta, unescape_tokens(pieces, &regex_unescape_map/1)}
quote(do: Regex.compile!(unquote(binary), unquote(:binary.list_to_bin(options))))
tuple = {:<<>>, meta, unescape_tokens(pieces, &regex_unescape_map/1)}
compile_regex(tuple, options)
end
defp regex_unescape_map(:newline), do: true
defp regex_unescape_map(_), do: false
@doc false
defmacro sigil_R({:<<>>, _meta, [string]}, options) when is_binary(string) do
defmacro sigil_R({:<<>>, _meta, [binary]}, options) when is_binary(binary) do
IO.warn(
"~R/.../ is deprecated, use ~r/.../ instead",
Macro.Env.stacktrace(__CALLER__)
)
regex = Regex.compile!(string, :binary.list_to_bin(options))
Macro.escape(regex)
compile_regex(binary, options)
end
defp compile_regex(binary_or_tuple, options) do
case is_binary(binary_or_tuple) and :erlang.system_info(:otp_release) < [?2, ?8] do
true ->
Macro.escape(Regex.compile!(binary_or_tuple, :binary.list_to_bin(options)))
false ->
quote(do: Regex.compile!(unquote(binary_or_tuple), unquote(:binary.list_to_bin(options))))
end
end
@doc ~S"""
+10
View File
@@ -295,6 +295,16 @@ defmodule Kernel.CLI do
parse_argv(t, %{config | commands: [{:parallel_require, h} | config.commands]})
end
defp parse_argv([~c"--color" | t], config) do
Application.put_env(:elixir, :ansi_enabled, true)
parse_argv(t, config)
end
defp parse_argv([~c"--no-color" | t], config) do
Application.put_env(:elixir, :ansi_enabled, true)
parse_argv(t, config)
end
## Compiler
defp parse_argv([~c"-o", h | t], %{mode: :elixirc} = config) do
+7 -4
View File
@@ -938,7 +938,7 @@ defmodule Kernel.Typespec do
## Helpers
# This is a backport of Macro.expand/2 because we want to expand
# This is a modified backport of Macro.expand/2 because we want to expand
# aliases but we don't them to become compile-time references.
defp expand_remote({:__aliases__, meta, list} = alias, env) do
case :elixir_aliases.expand_or_concat(meta, list, env, true) do
@@ -946,9 +946,12 @@ defmodule Kernel.Typespec do
receiver
[head | tail] ->
case Macro.expand_once(head, env) do
head when is_atom(head) -> :elixir_aliases.concat([head | tail])
_ -> alias
case Macro.expand(head, env) do
head when is_atom(head) ->
:elixir_aliases.concat([head | tail])
_ ->
compile_error(env, "unexpected expression in typespec: #{Macro.to_string(alias)}")
end
end
end
+3 -3
View File
@@ -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),
+9 -3
View File
@@ -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.
+18 -5
View File
@@ -785,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) ||
@@ -794,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)}
+4 -5
View File
@@ -1329,11 +1329,10 @@ defmodule Module.Types.Descr do
:maps.next(iterator) |> map_literal_intersection_loop(acc)
_ ->
# If the key is marked as not_set in the open map, we can ignore it.
if type1 == @not_set do
:maps.next(iterator) |> map_literal_intersection_loop(acc)
else
throw(:empty)
# If the key is optional in the open map, we can ignore it
case type1 do
%{optional: 1} -> :maps.next(iterator) |> map_literal_intersection_loop(acc)
_ -> throw(:empty)
end
end
end
+1 -1
View File
@@ -518,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
+3 -3
View File
@@ -884,7 +884,7 @@ defmodule Path do
* A `..` component would make it so that the path would traverse up above
the root of `relative_to`.
* A symbolic link in the path points to something above the root of `cwd`.
* A symbolic link in the path points to something above the root of `relative_to`.
## Examples
@@ -906,10 +906,10 @@ defmodule Path do
"""
@doc since: "1.14.0"
@spec safe_relative(t, t) :: {:ok, binary} | :error
def safe_relative(path, cwd \\ File.cwd!()) do
def safe_relative(path, relative_to \\ File.cwd!()) do
path = IO.chardata_to_string(path)
case :filelib.safe_relative_path(path, cwd) do
case :filelib.safe_relative_path(path, relative_to) do
:unsafe -> :error
relative_path -> {:ok, IO.chardata_to_string(relative_path)}
end
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

@@ -157,7 +157,7 @@ say me
say me
```
Yes, it works! However, does it *scale*?
Yes, it works! However, can it handle more than one client?
Try to connect two telnet clients at the same time. When you do so, you will notice that the second client doesn't echo:
@@ -303,4 +303,8 @@ Luckily, this can be done by using `Supervisor.child_spec/2`, which allows us to
Now we have an always running acceptor that starts temporary task processes under an always running task supervisor.
## Wrapping up
In this chapter, we implemented a basic TCP acceptor while exploring concurrency and fault-tolerance. Our acceptor can manage concurrent connections, but it is still not ready for production. Production-ready TCP servers run a pool of acceptors, each with their own supervisor. Elixir's `PartitionSupervisor` might be used to partition and scale the acceptor, but it is out of scope for this guide. In practice, you will use existing packages tailored for this use-case, such as [Ranch](https://github.com/ninenines/ranch) (in Erlang) or [Thousand Island](https://github.com/mtrudel/thousand_island) (in Elixir).
In the next chapter, we will start parsing the client requests and sending responses, finishing our server.
+14 -2
View File
@@ -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 ::
+1 -1
View File
@@ -314,7 +314,7 @@ unpack_defaults(Kind, Meta, Name, Args, S, E) ->
unpack_expanded(Kind, Meta, Name, [{'\\\\', DefaultMeta, [Expr, _]} | T] = List, VersionOffset, Acc, Clauses) ->
Base = match_defaults(Acc, length(Acc) + VersionOffset, []),
{Args, Invoke} = extract_defaults(List, length(Base) + VersionOffset, [], []),
Clause = {Meta, Base ++ Args, [], {super, [{super, {Kind, Name}} | DefaultMeta], Base ++ Invoke}},
Clause = {Meta, Base ++ Args, [], {super, [{super, {Kind, Name}}, {default, true} | DefaultMeta], Base ++ Invoke}},
unpack_expanded(Kind, Meta, Name, T, VersionOffset, [Expr | Acc], [Clause | Clauses]);
unpack_expanded(Kind, Meta, Name, [H | T], VersionOffset, Acc, Clauses) ->
unpack_expanded(Kind, Meta, Name, T, VersionOffset, [H | Acc], Clauses);
+61 -63
View File
@@ -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) ->
[].
@@ -1335,19 +1335,22 @@ defmodule CodeFragmentTest do
test "do -> end" do
assert cc2q!("if do\nx ->\n", trailing_fragment: "y\nz ->\nw\nend") ==
s2q!("if do\nx ->\n__cursor__()\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__()\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__()\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
+1 -3
View File
@@ -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
+4
View File
@@ -205,6 +205,10 @@ defmodule KernelTest do
defmodule User do
assert is_map(defstruct name: "john")
# Ensure we keep the line information around.
# It is important for debugging tools, ExDoc, etc.
{:v1, :def, anno, _clauses} = Module.get_definition(__MODULE__, {:__struct__, 1})
anno[:line] == __ENV__.line - 4
end
test "struct/1 and struct/2" do
@@ -163,6 +163,9 @@ defmodule Module.Types.DescrTest do
assert intersection(closed_map(a: integer()), open_map(b: not_set())) ==
closed_map(a: integer())
assert intersection(closed_map(a: integer()), open_map(b: if_set(integer()))) ==
closed_map(a: integer())
assert equal?(
intersection(closed_map(a: integer()), closed_map(a: if_set(integer()))),
closed_map(a: integer())
@@ -1139,6 +1139,14 @@ defmodule Module.Types.ExprTest do
test "and does not report on literals" do
assert typecheck!(false and true) == boolean()
end
test "and reports violations" do
assert typeerror!([x = 123], x and true) =~ """
the following conditional expression will always evaluate to integer():
x
"""
end
end
describe "receive" do
@@ -244,6 +244,39 @@ defmodule Module.Types.IntegrationTest do
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" => """
+24 -3
View File
@@ -75,6 +75,14 @@ defmodule TypespecTest do
@type my_type :: %URI.t(){}
end
end
assert_raise Kernel.TypespecError,
~r"unexpected expression in typespec: t\.Foo",
fn ->
test_module do
@type my_type :: t.Foo
end
end
end
test "invalid function specification" do
@@ -120,7 +128,7 @@ defmodule TypespecTest do
test "redefined type" do
assert_raise Kernel.TypespecError,
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:126",
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:134",
fn ->
test_module do
@type foo :: atom
@@ -129,7 +137,7 @@ defmodule TypespecTest do
end
assert_raise Kernel.TypespecError,
~r"type foo/2 is already defined in .*test/elixir/typespec_test.exs:136",
~r"type foo/2 is already defined in .*test/elixir/typespec_test.exs:144",
fn ->
test_module do
@type foo :: atom
@@ -139,7 +147,7 @@ defmodule TypespecTest do
end
assert_raise Kernel.TypespecError,
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:145",
~r"type foo/0 is already defined in .*test/elixir/typespec_test.exs:153",
fn ->
test_module do
@type foo :: atom
@@ -841,6 +849,19 @@ defmodule TypespecTest do
assert [{:atom, _, Keyword}, {:atom, _, :t}, [{:var, _, :value}]] = kw_with_value_args
end
test "@type with macro in alias" do
bytecode =
test_module do
defmacro module() do
quote do: __MODULE__
end
@type my_type :: module().Foo
end
assert [type: {:my_type, {:atom, _, TypespecTest.TypespecSample.Foo}, []}] = types(bytecode)
end
test "@type with a reserved signature" do
assert_raise Kernel.TypespecError,
~r"type required\/1 is a reserved type and it cannot be defined",
+4
View File
@@ -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
+2 -1
View File
@@ -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
+1 -3
View File
@@ -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 \
+6
View File
@@ -1133,6 +1133,12 @@ defmodule ExUnit.DiffTest do
"-<<trap::binary-size(3)>> <> \"baz\"-",
"+\"foobar\"+"
)
refute_diff(
"hello " <> <<_::binary-size(6)>> = "hello world",
"\"hello \" <> -<<_::binary-size(6)>>-",
"\"hello +world+\""
)
end
test "underscore" do
+1 -1
View File
@@ -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, "_"),
+13
View File
@@ -430,6 +430,19 @@ defmodule IEx.AutocompleteTest do
assert {:yes, ~c"ry: ", []} = expand(~c"%URI{var | path: \"foo\", que")
assert {:no, [], []} = expand(~c"%URI{var | path: \"foo\", unkno")
assert {:no, [], []} = expand(~c"%Unknown{var | path: \"foo\", unkno")
eval("var = %URI{}")
assert {:yes, ~c"", entries} = expand(~c"%{var | ")
assert ~c"path:" in entries
assert ~c"query:" in entries
assert {:yes, ~c"", entries} = expand(~c"%{var | path: \"foo\",")
assert ~c"path:" not in entries
assert ~c"query:" in entries
assert {:yes, ~c"ry: ", []} = expand(~c"%{var | path: \"foo\", que")
assert {:no, [], []} = expand(~c"%URI{var | path: \"foo\", unkno")
end
test "completion for map keys in update syntax" do
+5
View File
@@ -352,6 +352,11 @@ defmodule Mix do
* `MIX_INSTALL_DIR` *(since v1.12.0)* - specifies directory where `Mix.install/2` keeps
install cache
* `MIX_OS_CONCURRENCY_LOCK` - when set to `0` or `false`, disables mix compilation locking.
While not recommended, this may be necessary in cases where hard links or TCP sockets are
not available. When opting for this behaviour, make sure to not start concurrent compilations
of the same project.
* `MIX_PATH` - appends extra code paths
* `MIX_PROFILE` - a list of comma-separated Mix tasks to profile the time spent on
+8 -2
View File
@@ -917,7 +917,10 @@ defmodule Mix.Project do
build_path = build_path(config)
on_taken = fn os_pid ->
Mix.shell().info("Waiting for lock on the build directory (held by process #{os_pid})")
Mix.shell().error([
IO.ANSI.reset(),
"Waiting for lock on the build directory (held by process #{os_pid})"
])
end
Mix.Sync.Lock.with_lock(build_path, fun, on_taken: on_taken)
@@ -931,7 +934,10 @@ defmodule Mix.Project do
deps_path = deps_path(config)
on_taken = fn os_pid ->
Mix.shell().info("Waiting for lock on the deps directory (held by process #{os_pid})")
Mix.shell().error([
IO.ANSI.reset(),
"Waiting for lock on the deps directory (held by process #{os_pid})"
])
end
Mix.Sync.Lock.with_lock(deps_path, fun, on_taken: on_taken)
+11 -3
View File
@@ -126,17 +126,17 @@ defmodule Mix.SCM.Git do
update_origin(opts[:git])
# Fetch external data
rev = get_lock_rev(opts[:lock], opts) || get_opts_rev(opts)
lock_rev = get_lock_rev(opts[:lock], opts)
["--git-dir=.git", "fetch", "--force", "--quiet"]
|> Kernel.++(progress_switch(git_version()))
|> Kernel.++(tags_switch(opts[:tag]))
|> Kernel.++(depth_switch(opts[:depth]))
|> Kernel.++(refspec_switch(opts, rev))
|> Kernel.++(refspec_switch(opts, lock_rev || get_opts_rev(opts)))
|> git!()
# Migrate the Git repo
rev = rev || default_branch()
rev = lock_rev || get_origin_opts_rev(opts) || default_branch()
git!(["--git-dir=.git", "checkout", "--quiet", rev])
if opts[:submodules] do
@@ -314,6 +314,14 @@ defmodule Mix.SCM.Git do
opts[:branch] || opts[:ref] || opts[:tag]
end
defp get_origin_opts_rev(opts) do
if branch = opts[:branch] do
"origin/#{branch}"
else
opts[:ref] || opts[:tag]
end
end
defp redact_uri(git) do
case URI.parse(git) do
%{userinfo: nil} -> git
+21 -8
View File
@@ -79,6 +79,10 @@ defmodule Mix.Sync.Lock do
This function can also be called if this process already has the
lock. In such case the function is executed immediately.
When the `MIX_OS_CONCURRENCY_LOCK` environment variable is set to
a falsy value, the lock is ignored and the function is executed
immediately.
## Options
* `:on_taken` - a one-arity function called if the lock is held
@@ -93,12 +97,12 @@ defmodule Mix.Sync.Lock do
opts = Keyword.validate!(opts, [:on_taken])
hash = key |> :erlang.md5() |> Base.url_encode64(padding: false)
path = Path.join([System.tmp_dir!(), "mix_lock", hash])
path = Path.join(base_path(), hash)
pdict_key = {__MODULE__, path}
has_lock? = Process.get(pdict_key)
has_lock? = Process.get(pdict_key, false)
if has_lock? do
if has_lock? or lock_disabled?() do
fun.()
else
lock = lock(path, opts[:on_taken])
@@ -115,6 +119,14 @@ defmodule Mix.Sync.Lock do
end
end
defp base_path do
# We include user in the dir to avoid permission conflicts across users
user = System.get_env("USER", "default")
Path.join(System.tmp_dir!(), "mix_lock_#{Base.url_encode64(user, padding: false)}")
end
defp lock_disabled?(), do: System.get_env("MIX_OS_CONCURRENCY_LOCK") in ~w(0 false)
defp lock(path, on_taken) do
File.mkdir_p!(path)
@@ -198,11 +210,12 @@ defmodule Mix.Sync.Lock do
:invalidated
{:error, reason} ->
raise File.LinkError,
reason: reason,
action: "create hard link",
existing: port_path,
new: lock_path
Mix.raise("""
could not create hard link from #{port_path} to "#{lock_path}: #{:file.format_error(reason)}.
Hard link support is required for Mix compilation locking. If your system \
does not support hard links, set MIX_OS_CONCURRENCY_LOCK=0\
""")
end
end
+7 -1
View File
@@ -274,7 +274,13 @@ defmodule Mix.Sync.PubSub do
defp path(hash) do
hash = Base.url_encode64(hash, padding: false)
Path.join([System.tmp_dir!(), "mix_pubsub", hash])
Path.join(base_path(), hash)
end
defp base_path do
# We include user in the dir to avoid permission conflicts across users
user = System.get_env("USER", "default")
Path.join(System.tmp_dir!(), "mix_pubsub_#{Base.url_encode64(user, padding: false)}")
end
defp recv(socket, size, timeout \\ :infinity) do
+1 -1
View File
@@ -113,7 +113,7 @@ defmodule Mix.Tasks.Test do
and imports (but not local functions). You can press `n` for the next line
and `c` for the next test. This automatically sets `--trace`
* `--color` - enables color in the output
* `--color` - enables color in ExUnit formatting results
* `--cover` - runs coverage tool. See "Coverage" section below