Skip to content

Commit 0997974

Browse files
authored
Merge branch 'main' into jv-opt-descr
2 parents 229be81 + fcf47f3 commit 0997974

43 files changed

Lines changed: 737 additions & 91 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/codeql.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ jobs:
3232
with:
3333
persist-credentials: false
3434
- name: Initialize CodeQL
35-
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
35+
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
3636
with:
3737
languages: ${{ matrix.language }}
3838
build-mode: ${{ matrix.build-mode }}
3939
- name: Perform CodeQL Analysis
40-
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
40+
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
4141
with:
4242
category: "/language:${{matrix.language}}"
4343

lib/eex/lib/eex/compiler.ex

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,15 @@ defmodule EEx.Compiler do
7979
{:error, message <> code_snippet(state.source, state.indentation, meta), meta}
8080

8181
{:ok, expr, new_line, new_column, rest} ->
82-
{key, expr} =
82+
{key, expr, extra_meta} =
8383
case :elixir_tokenizer.tokenize(expr, 1, file: "eex", check_terminators: false) do
8484
{:ok, _line, _column, _warnings, rev_tokens, []} ->
8585
# We ignore warnings because the code will be tokenized
8686
# again later with the right line+column info
8787
token_key(rev_tokens, expr)
8888

8989
{:error, _, _, _, _} ->
90-
{:expr, expr}
90+
{:expr, expr, %{}}
9191
end
9292

9393
marker =
@@ -102,8 +102,8 @@ defmodule EEx.Compiler do
102102
marker
103103
end
104104

105-
token = {key, marker, expr, %{line: line, column: column}}
106-
trim_and_tokenize(rest, new_line, new_column, state, buffer, acc, &[token | &1])
105+
token = {key, marker, expr, Map.merge(%{line: line, column: column}, extra_meta)}
106+
trim_and_tokenize(rest, new_line, new_column, state, buffer, acc, &merge_token(token, &1))
107107
end
108108
end
109109

@@ -127,6 +127,27 @@ defmodule EEx.Compiler do
127127
tokenize(rest, line, column, state, [{line, column}], fun.(acc))
128128
end
129129

130+
# Merge middle expressions separated only by whitespace so the whitespace is
131+
# part of the Elixir expression, not a separate EEx body.
132+
defp merge_token(
133+
{:middle_expr, ~c"", chars, meta},
134+
[{:text, text, text_meta}, {:middle_expr, ~c"", prev_chars, prev_meta} | acc]
135+
) do
136+
if only_spaces?(text) and clause_block_identifier?(prev_meta) do
137+
[{:middle_expr, ~c"", prev_chars ++ text ++ chars, prev_meta} | acc]
138+
else
139+
[
140+
{:middle_expr, ~c"", chars, meta},
141+
{:text, text, text_meta},
142+
{:middle_expr, ~c"", prev_chars, prev_meta} | acc
143+
]
144+
end
145+
end
146+
147+
defp merge_token(token, acc) do
148+
[token | acc]
149+
end
150+
130151
# Retrieve marker for <%
131152

132153
defp retrieve_marker([marker | t]) when marker in [?=, ?/, ?|] do
@@ -177,31 +198,31 @@ defmodule EEx.Compiler do
177198
defp token_key(rev_tokens, expr) do
178199
case {Enum.reverse(rev_tokens), drop_eol(rev_tokens)} do
179200
{[{:end, _} | _], [{:do, _} | _]} ->
180-
{:middle_expr, expr}
201+
{:middle_expr, expr, %{}}
181202

182203
{_, [{:do, _} | _]} ->
183-
{:start_expr, maybe_append_space(expr)}
204+
{:start_expr, maybe_append_space(expr), %{}}
184205

185-
{_, [{:block_identifier, _, _} | _]} ->
186-
{:middle_expr, maybe_append_space(expr)}
206+
{_, [{:block_identifier, _, identifier} | _]} ->
207+
{:middle_expr, maybe_append_space(expr), %{block_identifier: identifier}}
187208

188209
{[{:end, _} | _], [{:stab_op, _, _} | _]} ->
189-
{:middle_expr, expr}
210+
{:middle_expr, expr, %{}}
190211

191212
{_, [{:stab_op, _, _} | reverse_tokens]} ->
192213
fn_index = Enum.find_index(reverse_tokens, &match?({:fn, _}, &1)) || :infinity
193214
end_index = Enum.find_index(reverse_tokens, &match?({:end, _}, &1)) || :infinity
194215

195216
if end_index > fn_index do
196-
{:start_expr, expr}
217+
{:start_expr, expr, %{}}
197218
else
198-
{:middle_expr, expr}
219+
{:middle_expr, expr, %{}}
199220
end
200221

201222
{tokens, _} ->
202223
case Enum.drop_while(tokens, &closing_bracket?/1) do
203-
[{:end, _} | _] -> {:end_expr, expr}
204-
_ -> {:expr, expr}
224+
[{:end, _} | _] -> {:end_expr, expr, %{}}
225+
_ -> {:expr, expr, %{}}
205226
end
206227
end
207228
end
@@ -476,6 +497,12 @@ defmodule EEx.Compiler do
476497
Enum.all?(chars, &(&1 in @all_spaces))
477498
end
478499

500+
defp clause_block_identifier?(%{block_identifier: identifier}) do
501+
identifier in [:else, :rescue, :catch]
502+
end
503+
504+
defp clause_block_identifier?(_meta), do: false
505+
479506
# Changes placeholder to real expression
480507

481508
defp insert_quoted({:__EEX__, _, [key]}, quoted) do

lib/eex/test/eex/tokenizer_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ defmodule EEx.TokenizerTest do
270270
{:text, ~c"foo ", %{column: 1, line: 1}},
271271
{:start_expr, ~c"", ~c" if true do ", %{column: 5, line: 1}},
272272
{:text, ~c"bar", %{column: 21, line: 1}},
273-
{:middle_expr, ~c"", ~c" else ", %{column: 24, line: 1}},
273+
{:middle_expr, ~c"", ~c" else ", %{block_identifier: :else, column: 24, line: 1}},
274274
{:text, ~c"baz", %{column: 34, line: 1}},
275275
{:end_expr, ~c"", ~c" end ", %{column: 37, line: 1}},
276276
{:eof, %{column: 46, line: 1}}
@@ -286,7 +286,7 @@ defmodule EEx.TokenizerTest do
286286
exprs = [
287287
{:start_expr, ~c"=", ~c" if true do ", %{column: 2, line: 1}},
288288
{:text, ~c"\n TRUE \n", %{column: 20, line: 1}},
289-
{:middle_expr, ~c"", ~c" else ", %{column: 3, line: 3}},
289+
{:middle_expr, ~c"", ~c" else ", %{block_identifier: :else, column: 3, line: 3}},
290290
{:text, ~c"\n FALSE \n", %{column: 13, line: 3}},
291291
{:end_expr, ~c"", ~c" end ", %{column: 3, line: 5}},
292292
{:eof, %{column: 3, line: 7}}

lib/eex/test/eex_test.exs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,37 @@ defmodule EExTest do
262262
assert_eval("foo 1,2,3", "foo <% require Enum, as: E %><%= E.join [1, 2, 3], \",\" %>")
263263
end
264264

265+
test "with expression with else clause split across tags" do
266+
template = """
267+
<%= with {:ok, x} <- @res do %>
268+
<p><%= x %></p>
269+
<% else %>
270+
<% _ -> %>
271+
<p>bad</p>
272+
<% end %>
273+
"""
274+
275+
assert_eval("\n <p>ok</p>\n\n", template, [assigns: [res: {:ok, "ok"}]],
276+
engine: EEx.SmartEngine
277+
)
278+
279+
assert_eval("\n <p>bad</p>\n\n", template, [assigns: [res: :error]],
280+
engine: EEx.SmartEngine
281+
)
282+
end
283+
284+
test "empty clauses separated by whitespace" do
285+
template = """
286+
<%= case x do %>
287+
<% :foo -> %>
288+
<% :bar -> %>
289+
<% end %>
290+
"""
291+
292+
assert_eval("\n \n", template, x: :foo)
293+
assert_eval("\n\n", template, x: :bar)
294+
end
295+
265296
test "with end of token" do
266297
assert_eval("foo bar %>", "foo bar %>")
267298
end
@@ -522,7 +553,7 @@ defmodule EExTest do
522553

523554
assert message |> Exception.message() |> strip_ansi() =~ """
524555
525-
514 │ true && @some[\s
556+
#{line + 2} │ true && @some[\s
526557
│ │ └ missing closing delimiter (expected "]")
527558
│ └ unclosed delimiter
528559
"""

lib/elixir/lib/calendar.ex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# SPDX-FileCopyrightText: 2012 Plataformatec
44

55
defmodule Calendar do
6+
@strftime_max_width 1024
7+
68
@moduledoc """
79
This module defines the responsibilities for working with
810
calendars, dates, times and datetimes in Elixir.
@@ -529,6 +531,7 @@ defmodule Calendar do
529531
* `%`: indicates the start of a formatted section
530532
* `<padding>`: set the padding (see below)
531533
* `<width>`: a number indicating the minimum size of the formatted section
534+
(maximum #{@strftime_max_width})
532535
* `<format>`: the format itself (see below)
533536
534537
### Accepted padding options
@@ -667,9 +670,13 @@ defmodule Calendar do
667670
end
668671

669672
defp parse_modifiers(<<digit, rest::binary>>, width, pad, parser_data) when digit in ?0..?9 do
670-
new_width = (width || 0) * 10 + (digit - ?0)
673+
width = (width || 0) * 10 + (digit - ?0)
674+
675+
if width > @strftime_max_width do
676+
raise ArgumentError, "invalid strftime format: width must be at most #{@strftime_max_width}"
677+
end
671678

672-
parse_modifiers(rest, new_width, pad, parser_data)
679+
parse_modifiers(rest, width, pad, parser_data)
673680
end
674681

675682
# set default padding if none was specified

lib/elixir/lib/code.ex

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ defmodule Code do
314314
:relative_paths
315315
]
316316

317-
@list_compiler_options [:tracers, :parser_options]
317+
@list_compiler_options [:tracers, :parser_options, :erlc_options]
318318

319319
@available_compiler_options @boolean_compiler_options ++
320320
@list_compiler_options ++
@@ -1634,13 +1634,19 @@ defmodule Code do
16341634
nil
16351635

16361636
:proceed ->
1637-
loaded =
1638-
Module.ParallelChecker.verify(fn ->
1639-
:elixir_compiler.string(charlist, file, fn _, _ -> :ok end)
1640-
end)
1641-
1642-
:elixir_code_server.cast({:required, file})
1643-
loaded
1637+
try do
1638+
loaded =
1639+
Module.ParallelChecker.verify(fn ->
1640+
:elixir_compiler.string(charlist, file, fn _, _ -> :ok end)
1641+
end)
1642+
1643+
:elixir_code_server.cast({:required, file})
1644+
loaded
1645+
catch
1646+
kind, reason ->
1647+
:elixir_code_server.call({:release, file})
1648+
:erlang.raise(kind, reason, __STACKTRACE__)
1649+
end
16441650
end
16451651
end
16461652

@@ -1753,6 +1759,10 @@ defmodule Code do
17531759
* `:docs` - when `true`, retains documentation in the compiled module.
17541760
Defaults to `true`.
17551761
1762+
* `:erlc_options` (since v1.21.0) - a list of Erlang compiler options. For example,
1763+
`erlc_options: [:beam_debug_info, :beam_debug_stack]` emits Erlang/OTP
1764+
debug metadata for BEAM debuggers. Defaults to `[]`.
1765+
17561766
* `:ignore_already_consolidated` (since v1.10.0) - when `true`, does not warn
17571767
when a protocol has already been consolidated and a new implementation is added.
17581768
Defaults to `false`.

lib/elixir/lib/code/fragment.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ defmodule Code.Fragment do
204204
| {:local_arity, charlist}
205205
| {:local_call, charlist}
206206
| {:anonymous_call, inside_caller}
207+
| {:capture_arg, charlist}
207208
| {:module_attribute, charlist}
208209
| {:operator, charlist}
209210
| {:operator_arity, charlist}

lib/elixir/lib/exception.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1046,7 +1046,7 @@ defmodule RuntimeError do
10461046
iex> raise "oops!"
10471047
** (RuntimeError) oops!
10481048
1049-
You should use this exceptions sparingly, since most of the time it might be
1049+
You should use this exception sparingly, since most of the time it might be
10501050
better to define your own exceptions specific to your application or library.
10511051
Sometimes, however, there are situations in which you don't expect a condition to
10521052
happen, but you want to give a meaningful error message if it does. In those cases,

lib/elixir/lib/kernel/typespec.ex

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -860,29 +860,29 @@ defmodule Kernel.Typespec do
860860
{{:type, location(meta), :nonempty_string, args}, state}
861861
end
862862

863-
defp typespec({type, _meta, []}, vars, caller, state) when type in [:charlist, :char_list] do
863+
defp typespec({type, meta, []}, vars, caller, state) when type in [:charlist, :char_list] do
864864
if type == :char_list do
865865
warning = "the char_list() type is deprecated, use charlist()"
866866
IO.warn(warning, caller)
867867
end
868868

869-
typespec(quote(do: :elixir.charlist()), vars, caller, state)
869+
remote_typespec(:charlist, meta, [], vars, caller, state)
870870
end
871871

872-
defp typespec({:nonempty_charlist, _meta, []}, vars, caller, state) do
873-
typespec(quote(do: :elixir.nonempty_charlist()), vars, caller, state)
872+
defp typespec({:nonempty_charlist, meta, []}, vars, caller, state) do
873+
remote_typespec(:nonempty_charlist, meta, [], vars, caller, state)
874874
end
875875

876-
defp typespec({:struct, _meta, []}, vars, caller, state) do
877-
typespec(quote(do: :elixir.struct()), vars, caller, state)
876+
defp typespec({:struct, meta, []}, vars, caller, state) do
877+
remote_typespec(:struct, meta, [], vars, caller, state)
878878
end
879879

880-
defp typespec({:as_boolean, _meta, [arg]}, vars, caller, state) do
881-
typespec(quote(do: :elixir.as_boolean(unquote(arg))), vars, caller, state)
880+
defp typespec({:as_boolean, meta, [arg]}, vars, caller, state) do
881+
remote_typespec(:as_boolean, meta, [arg], vars, caller, state)
882882
end
883883

884-
defp typespec({:keyword, _meta, args}, vars, caller, state) when length(args) <= 1 do
885-
typespec(quote(do: :elixir.keyword(unquote_splicing(args))), vars, caller, state)
884+
defp typespec({:keyword, meta, args}, vars, caller, state) when length(args) <= 1 do
885+
remote_typespec(:keyword, meta, args, vars, caller, state)
886886
end
887887

888888
defp typespec({:fun, meta, args}, vars, caller, state) do
@@ -1007,6 +1007,10 @@ defmodule Kernel.Typespec do
10071007
{{:remote_type, location(meta), [remote, name, args]}, state}
10081008
end
10091009

1010+
defp remote_typespec(name, meta, args, vars, caller, state) do
1011+
typespec({{:., meta, [:elixir, name]}, meta, args}, vars, caller, state)
1012+
end
1013+
10101014
defp collect_union({:|, _, [a, b]}), do: [a | collect_union(b)]
10111015
defp collect_union(v), do: [v]
10121016

lib/elixir/lib/module/types/apply.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ defmodule Module.Types.Apply do
3838
end
3939

4040
fas = list(tuple([atom(), integer()]))
41-
struct_info = list(closed_map(default: if_set(term()), field: atom()))
41+
42+
struct_info =
43+
list(closed_map(default: if_set(term()), field: atom(), required: if_set(boolean())))
4244

4345
shared_info = [
4446
attributes: list(tuple([atom(), list(term())])),

0 commit comments

Comments
 (0)