Skip to content

Commit

Permalink
do not infer special forms as local calls
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszsamson committed Sep 26, 2024
1 parent c5c0437 commit 6aca656
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
48 changes: 48 additions & 0 deletions lib/elixir_sense/core/type_inference.ex
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,54 @@ defmodule ElixirSense.Core.TypeInference do
{:list, list |> Enum.map(&type_of(&1, context))}
end

# block expressions
def type_of({:__block__, _meta, exprs}, context) do
case List.last(exprs) do
nil -> nil
last_expr -> type_of(last_expr, context)
end
end

# anonymous functions
def type_of({:fn, _meta, _clauses}, _context), do: nil

# special forms
# for case/cond/with/receive/for/try we have no idea what the type is going to be
# we don't support binaries
# TODO guard?
# other are not worth handling
def type_of({form, _meta, _clauses}, _context)
when form in [
:case,
:cond,
:try,
:receive,
:for,
:with,
:quote,
:unquote,
:unquote_splicing,
:import,
:alias,
:require,
:__aliases__,
:__cursor__,
:__DIR__,
:super,
:<<>>,
:"::"
],
do: nil

# __ENV__ is already expanded to map
def type_of({form, _meta, _clauses}, _context) when form in [:__CALLER__] do
{:struct, [], {:atom, Macro.Env}, nil}
end

def type_of({:__STACKTRACE__, _meta, _clauses}, _context) do
{:list, nil}
end

# local call
def type_of({var, _, args}, context) when is_atom(var) and is_list(args) do
{:local_call, var, Enum.map(args, &type_of(&1, context))}
Expand Down
55 changes: 55 additions & 0 deletions test/elixir_sense/core/type_inference_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -488,5 +488,60 @@ defmodule ElixirSense.Core.TypeInferenceTest do
assert type_of("\"asd\"") == nil
assert type_of("1.23") == nil
end

test "__STACKTRACE__ returns {:list, nil}" do
assert type_of("__STACKTRACE__") == {:list, nil}
end

test "anonymous function returns nil" do
assert type_of("fn -> a end") == nil
assert type_of("fn x -> x + 1 end") == nil
assert type_of("fn x, y -> x * y end") == nil
end
end

describe "block expressions" do
test "non-empty block returns type of last expression" do
assert type_of("(a = 1; b = 2; c = 3)") == {:integer, 3}

assert type_of("""
(
a = 1
b = 2
c = 3
)
""") == {:integer, 3}
end

test "empty block returns nil" do
assert type_of("( )") == nil
end

test "__CALLER__ returns {:struct, [], {:atom, Macro.Env}, nil}" do
assert type_of("__CALLER__") == {:struct, [], {:atom, Macro.Env}, nil}
end
end

describe "special forms" do
special_forms = [
"case a do\n :ok -> 1\n :error -> 2\nend",
"cond do\n a -> 1\n b -> 2\nend",
"try do\n risky_operation()\nrescue\n e -> handle(e)\nend",
"receive do\n {:msg, msg} -> process(msg)\nend",
"for x <- list, do: x * 2",
"with {:ok, a} <- fetch_a(), {:ok, b} <- fetch_b(a), do: a + b",
"quote do: a + b",
"unquote(expr)",
"unquote_splicing(expr)",
"import Module",
"alias Module.SubModule",
"require Module"
]

for form <- special_forms do
test "special form: #{inspect(form)} returns nil" do
assert type_of(unquote(form)) == nil
end
end
end
end

0 comments on commit 6aca656

Please sign in to comment.