Skip to content

Commit 5779f5b

Browse files
authored
Widen stacktrace extra info types (#15798)
Match OTP's stacktrace contract in __STACKTRACE__ inference and the :erlang.raise/3 signature, allowing arbitrary keyword metadata. Assisted-by: Codex:GPT-5.6
1 parent 3389f84 commit 5779f5b

3 files changed

Lines changed: 76 additions & 2 deletions

File tree

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,15 @@ defmodule Module.Types.Apply do
118118

119119
args_or_arity = opt_union(list(term()), integer())
120120
args_or_none = opt_union(list(term()), atom([:none]))
121-
extra_info = kw.(file: list(integer()), line: integer(), error_info: open_map())
121+
custom_info_key = opt_difference(atom(), atom([:file, :line, :error_info]))
122+
123+
extra_info =
124+
list(
125+
tuple([atom([:file]), opt_union(list(integer()), binary())])
126+
|> opt_union(tuple([atom([:line]), integer()]))
127+
|> opt_union(tuple([atom([:error_info]), open_map()]))
128+
|> opt_union(tuple([custom_info_key, term()]))
129+
)
122130

123131
raise_stacktrace =
124132
list(

‎lib/elixir/lib/module/types/expr.ex‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,14 @@ defmodule Module.Types.Expr do
4040

4141
args_or_arity = opt_union(list(term()), integer())
4242

43+
custom_info_key = opt_difference(atom(), atom([:file, :line, :error_info]))
44+
4345
extra_info =
4446
list(
45-
tuple([atom([:file]), list(integer())])
47+
tuple([atom([:file]), opt_union(list(integer()), binary())])
4648
|> opt_union(tuple([atom([:line]), integer()]))
4749
|> opt_union(tuple([atom([:error_info]), open_map()]))
50+
|> opt_union(tuple([custom_info_key, term()]))
4851
)
4952

5053
@stacktrace list(

‎lib/elixir/test/elixir/module/types/expr_test.exs‎

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,21 @@ defmodule Module.Types.ExprTest do
451451
"incompatible types given to Kernel.send/2"
452452
end
453453

454+
test "raises with arbitrary stacktrace extra info" do
455+
assert typecheck!(
456+
:erlang.raise(:error, :oops, [
457+
{__MODULE__, :example, 1,
458+
[line: 1, column: 2, file: "example.ex", custom: {:any, :term}]}
459+
])
460+
) == none()
461+
462+
assert typeerror!(
463+
:erlang.raise(:error, :oops, [
464+
{__MODULE__, :example, 1, [line: :unknown]}
465+
])
466+
) =~ "incompatible types given to :erlang.raise/3"
467+
end
468+
454469
test "undefined function warnings" do
455470
assert typewarn!(URI.unknown("foo")) ==
456471
{dynamic(), "URI.unknown/1 is undefined or private"}
@@ -2967,6 +2982,54 @@ defmodule Module.Types.ExprTest do
29672982
end
29682983

29692984
test "rescue: matches on stacktrace" do
2985+
assert typecheck!(
2986+
try do
2987+
raise "oops"
2988+
rescue
2989+
_ ->
2990+
case __STACKTRACE__ do
2991+
[{_, _, _, [{:line, line} | _]} | _] -> line + 1
2992+
_ -> 0
2993+
end
2994+
end
2995+
) == integer()
2996+
2997+
assert typecheck!(
2998+
try do
2999+
raise "oops"
3000+
rescue
3001+
_ ->
3002+
case __STACKTRACE__ do
3003+
[{_, _, _, [{:file, file} | _]} | _] -> file
3004+
_ -> "unknown"
3005+
end
3006+
end
3007+
) == opt_union(list(integer()), binary())
3008+
3009+
assert typecheck!(
3010+
try do
3011+
raise "oops"
3012+
rescue
3013+
_ ->
3014+
case __STACKTRACE__ do
3015+
[{_, _, _, [{:error_info, error_info} | _]} | _] -> map_size(error_info)
3016+
_ -> 0
3017+
end
3018+
end
3019+
) == integer()
3020+
3021+
assert typecheck!(
3022+
try do
3023+
raise "oops"
3024+
rescue
3025+
_ ->
3026+
case __STACKTRACE__ do
3027+
[{_, _, _, [{:column, column} | _]} | _] -> column
3028+
_ -> 0
3029+
end
3030+
end
3031+
) == term()
3032+
29703033
assert typeerror!(
29713034
try do
29723035
:ok

0 commit comments

Comments
 (0)