Skip to content

Commit

Permalink
Merge branch 'elixir-lang:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasvegi authored Sep 18, 2023
2 parents 8e4f6e7 + 6510f25 commit 2d44257
Show file tree
Hide file tree
Showing 26 changed files with 763 additions and 155 deletions.
4 changes: 2 additions & 2 deletions lib/elixir/lib/calendar/naive_datetime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,7 @@ defmodule NaiveDateTime do
datetime
|> NaiveDateTime.beginning_of_day()
|> DateTime.from_naive(datetime.timezone)
|> DateTime.from_naive(datetime.time_zone)
Note that the beginning of the day may not exist or be ambiguous
in a given timezone, so you must handle those cases accordingly.
Expand All @@ -1251,7 +1251,7 @@ defmodule NaiveDateTime do
datetime
|> NaiveDateTime.end_of_day()
|> DateTime.from_naive(datetime.timezone)
|> DateTime.from_naive(datetime.time_zone)
Note that the end of the day may not exist or be ambiguous
in a given timezone, so you must handle those cases accordingly.
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir/lib/code.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1595,7 +1595,7 @@ defmodule Code do
to the parser when compiling files. It accepts the same options as
`string_to_quoted/2` (except by the options that change the AST itself).
This can be used in combination with the tracer to retrieve localized
information about events happening during compilation. Defaults to `[]`.
information about events happening during compilation. Defaults to `[columns: true]`.
This option only affects code compilation functions, such as `compile_string/2`
and `compile_file/2` but not `string_to_quoted/2` and friends, as the
latter is used for other purposes beyond compilation.
Expand Down
4 changes: 2 additions & 2 deletions lib/elixir/lib/code/typespec.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ defmodule Code.Typespec do
located by the runtime system. The types will be in the Erlang
Abstract Format.
"""
@spec fetch_specs(module) :: {:ok, [tuple]} | :error
@spec fetch_specs(module | binary) :: {:ok, [tuple]} | :error
def fetch_specs(module) when is_atom(module) or is_binary(module) do
case typespecs_abstract_code(module) do
{:ok, abstract_code} ->
Expand All @@ -142,7 +142,7 @@ defmodule Code.Typespec do
which can be located by the runtime system. The types will be
in the Erlang Abstract Format.
"""
@spec fetch_callbacks(module) :: {:ok, [tuple]} | :error
@spec fetch_callbacks(module | binary) :: {:ok, [tuple]} | :error
def fetch_callbacks(module) when is_atom(module) or is_binary(module) do
case typespecs_abstract_code(module) do
{:ok, abstract_code} ->
Expand Down
220 changes: 207 additions & 13 deletions lib/elixir/lib/exception.ex
Original file line number Diff line number Diff line change
Expand Up @@ -937,40 +937,227 @@ defmodule MismatchedDelimiterError do
- `fn a -> )`
"""

@max_lines_shown 5

defexception [
:file,
:line,
:column,
:end_line,
:end_column,
:opening_delimiter,
:closing_delimiter,
:snippet,
description: "mismatched delimiter error"
]

@impl true
def message(%{
line: _start_line,
column: _start_column,
line: start_line,
column: start_column,
end_line: end_line,
end_column: end_column,
description: description,
opening_delimiter: opening_delimiter,
closing_delimiter: _closing_delimiter,
file: file,
snippet: snippet
}) do
snippet =
:elixir_errors.format_snippet(
{end_line, end_column},
file,
description,
snippet,
:error,
[],
nil
)
start_pos = {start_line, start_column}
end_pos = {end_line, end_column}
lines = String.split(snippet, "\n")
expected_delimiter = :elixir_tokenizer.terminator(opening_delimiter)

snippet = format_snippet(start_pos, end_pos, description, file, lines, expected_delimiter)
format_message(file, end_line, end_column, snippet)
end

defp format_snippet(
{start_line, _start_column} = start_pos,
{end_line, end_column} = end_pos,
description,
file,
lines,
expected_delimiter
)
when start_line < end_line do
max_digits = digits(end_line)
general_padding = max(2, max_digits) + 1
padding = n_spaces(general_padding)

relevant_lines =
if end_line - start_line < @max_lines_shown do
line_range(lines, start_pos, end_pos, padding, max_digits, expected_delimiter)
else
trimmed_inbetween_lines(
lines,
start_pos,
end_pos,
padding,
max_digits,
expected_delimiter
)
end

"""
#{padding}#{red("error:")} #{pad_message(description, padding)}
#{padding}
#{relevant_lines}
#{padding}
#{padding}└─ #{Path.relative_to_cwd(file)}:#{end_line}:#{end_column}\
"""
end

defp format_snippet(
{start_line, start_column},
{end_line, end_column},
description,
file,
lines,
expected_delimiter
)
when start_line == end_line do
max_digits = digits(end_line)
general_padding = max(2, max_digits) + 1
padding = n_spaces(general_padding)

line = Enum.fetch!(lines, end_line - 1)
formatted_line = [line_padding(end_line, max_digits), to_string(end_line), " │ ", line]

mismatched_closing_line =
[
n_spaces(start_column - 1),
red("│"),
mismatched_closing_delimiter(end_column - start_column, expected_delimiter)
]

unclosed_delimiter_line =
[padding, " │ ", unclosed_delimiter(start_column)]

below_line = [padding, " │ ", mismatched_closing_line, "\n", unclosed_delimiter_line]

"""
#{padding}#{red("error:")} #{pad_message(description, padding)}
#{padding}
#{formatted_line}
#{below_line}
#{padding}
#{padding}└─ #{Path.relative_to_cwd(file)}:#{end_line}:#{end_column}\
"""
end

defp line_padding(line_number, max_digits) do
line_digits = digits(line_number)

spacing =
if line_digits == 1 do
max(2, max_digits)
else
max_digits - line_digits + 1
end

n_spaces(spacing)
end

defp n_spaces(n), do: String.duplicate(" ", n)

defp digits(number, acc \\ 1)
defp digits(number, acc) when number < 10, do: acc
defp digits(number, acc), do: digits(div(number, 10), acc + 1)

defp trimmed_inbetween_lines(
lines,
{start_line, start_column},
{end_line, end_column},
padding,
max_digits,
expected_delimiter
) do
start_padding = line_padding(start_line, max_digits)
end_padding = line_padding(end_line, max_digits)
first_line = Enum.fetch!(lines, start_line - 1)
last_line = Enum.fetch!(lines, end_line - 1)

"""
#{start_padding}#{start_line}#{first_line}
#{padding}#{unclosed_delimiter(start_column)}
...
#{end_padding}#{end_line}#{last_line}
#{padding}#{mismatched_closing_delimiter(end_column, expected_delimiter)}\
"""
end

defp line_range(
lines,
{start_line, start_column},
{end_line, end_column},
padding,
max_digits,
expected_delimiter
) do
start_line = start_line - 1
end_line = end_line - 1

lines
|> Enum.slice(start_line..end_line)
|> Enum.zip_with(start_line..end_line, fn line, line_number ->
line_number = line_number + 1
start_line = start_line + 1
end_line = end_line + 1

line_padding = line_padding(line_number, max_digits)

cond do
line_number == start_line ->
[
line_padding,
to_string(line_number),
" │ ",
line,
"\n",
padding,
" │ ",
unclosed_delimiter(start_column)
]

line_number == end_line ->
[
line_padding,
to_string(line_number),
" │ ",
line,
"\n",
padding,
" │ ",
mismatched_closing_delimiter(end_column, expected_delimiter)
]

true ->
[line_padding, to_string(line_number), " │ ", line]
end
end)
|> Enum.intersperse("\n")
end

defp mismatched_closing_delimiter(end_column, expected_closing_delimiter),
do: [
n_spaces(end_column - 1),
red(~s/└ mismatched closing delimiter (expected "#{expected_closing_delimiter}")/)
]

defp unclosed_delimiter(start_column),
do: [n_spaces(start_column - 1), red("└ unclosed delimiter")]

defp pad_message(message, padding), do: String.replace(message, "\n", "\n #{padding}")

defp red(string) do
if IO.ANSI.enabled?() do
[IO.ANSI.red(), string, IO.ANSI.reset()]
else
string
end
end

defp format_message(file, line, column, message) do
location = Exception.format_file_line_column(Path.relative_to_cwd(file), line, column)
"mismatched delimiter found on " <> location <> "\n" <> message
Expand Down Expand Up @@ -1040,7 +1227,14 @@ defmodule TokenMissingError do
"""

defexception [:file, :line, :snippet, :column, description: "expression is incomplete"]
defexception [
:file,
:line,
:snippet,
:column,
:opening_delimiter,
description: "expression is incomplete"
]

@impl true
def message(%{
Expand Down
22 changes: 17 additions & 5 deletions lib/elixir/lib/macro.ex
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,12 @@ defmodule Macro do
the compiler, each variable is identified by the combination of either
`name` and `metadata[:counter]`, or `name` and `context`.
* `:from_brackets` - Used to determine whether a call to `Access.get/3` is from
bracket syntax.
* `:from_interpolation` - Used to determine whether a call to `Access.get/3` is
from interpolation.
* `:generated` - Whether the code should be considered as generated by
the compiler or not. This means the compiler and tools like Dialyzer may not
emit certain warnings.
Expand All @@ -192,28 +198,34 @@ defmodule Macro do
* `:keep` - Used by `quote/2` with the option `location: :keep` to annotate
the file and the line number of the quoted source.
* `:line` - The line number of the AST node.
* `:from_brackets` - Used to determine whether a call to `Access.get/3` is from
bracket syntax or a function call.
* `:line` - The line number of the AST node. Note line information is discarded
from quoted code but can be enabled back via the `:line` option.
The following metadata keys are enabled by `Code.string_to_quoted/2`:
* `:closing` - contains metadata about the closing pair, such as a `}`
in a tuple or in a map, or such as the closing `)` in a function call
with parens. The `:closing` does not delimit the end of expression if
there are `:do` and `:end` metadata (when `:token_metadata` is true)
* `:column` - the column number of the AST node (when `:columns` is true)
* `:column` - the column number of the AST node (when `:columns` is true).
Note column information is always discarded from quoted code.
* `: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)
* `:end` - contains metadata about the `end` location in a function call with
`do`-`end` blocks (when `:token_metadata` is true)
* `:end_of_expression` - denotes when the end of expression effectively
happens. Available for all expressions except the last one inside a
`__block__` (when `:token_metadata` is true)
* `:indentation` - indentation of a sigil heredoc
The following metadata keys are private:
Expand Down
3 changes: 1 addition & 2 deletions lib/elixir/lib/module/parallel_checker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,7 @@ defmodule Module.ParallelChecker do

case :erlang.get(:elixir_code_diagnostics) do
:undefined -> :ok
{tail, true} -> :erlang.put(:elixir_code_diagnostics, {diagnostics ++ tail, true})
{tail, false} -> :erlang.put(:elixir_code_diagnostics, {diagnostics ++ tail, false})
{tail, log?} -> :erlang.put(:elixir_code_diagnostics, {diagnostics ++ tail, log?})
end

diagnostics
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir/lib/process.ex
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ defmodule Process do
defdelegate unlink(pid_or_port), to: :erlang

@doc """
Registers the given `pid_or_port` under the given `name`.
Registers the given `pid_or_port` under the given `name` on the local node.
`name` must be an atom and can then be used instead of the
PID/port identifier when sending messages with `Kernel.send/2`.
Expand Down
2 changes: 1 addition & 1 deletion lib/elixir/pages/getting-started/keywords-and-maps.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ iex> map = %{:name => "John", :age => 23}

As you can see from the printed result above, Elixir also allows you to write maps of atom keys using the same `key: value` syntax as keyword lists.

When the keys are atoms, in particular when working with maps of predefined keys, we can also also access them using the `map.key` syntax:
When the keys are atoms, in particular when working with maps of predefined keys, we can also access them using the `map.key` syntax:

```elixir
iex> map = %{name: "John", age: 23}
Expand Down
4 changes: 2 additions & 2 deletions lib/elixir/scripts/elixir_docs.exs
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ canonical = System.fetch_env!("CANONICAL")
"Getting started": ~r"pages/getting-started/.*\.md$",
Cheatsheets: ~r"pages/cheatsheets/.*\.cheatmd$",
"Anti-patterns": ~r"pages/anti-patterns/.*\.md$",
References: ~r"pages/references/.*\.md$",
"Meta-programming": ~r"pages/meta-programming/.*\.md$",
"Mix & OTP": ~r"pages/mix-and-otp/.*\.md$"
"Mix & OTP": ~r"pages/mix-and-otp/.*\.md$",
References: ~r"pages/references/.*\.md$"
],
groups_for_functions: [
Guards: &(&1[:guard] == true)
Expand Down
Loading

0 comments on commit 2d44257

Please sign in to comment.