Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attach column info #70

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.12.3
elixir 1.13.3
erlang 24.1
9 changes: 0 additions & 9 deletions config/config.exs

This file was deleted.

1 change: 1 addition & 0 deletions examples/simple_app/.tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
elixir 1.11.4
29 changes: 29 additions & 0 deletions examples/simple_app/lib/simple_app/pipe_op.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
defmodule SimpleApp.PipeOp do

use Gradient.TypeAnnotation

@spec int_inc(integer()) :: integer()
def int_inc(int) do
int + 1
end

def easy_pipe do
'1'
|> int_inc()
'1'
|> int_inc()
'1'
|> int_inc()
'1'
|> int_inc()
end

def easy_pipe2 do
int_inc(
{%{a: 1, b: 2, c: 3},
%{a: 1, b: 2, c: 3},
%{a: 1, b: 2, c: 3}})

end

end
4 changes: 1 addition & 3 deletions lib/gradient.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ defmodule Gradient do
alias Gradient.AstSpecifier
alias Gradient.ElixirChecker

require Logger

@type options() :: [{:app_path, String.t()}, {:code_path, String.t()}]

@spec type_check_file(String.t(), options()) :: :ok | :error
Expand Down Expand Up @@ -44,7 +42,7 @@ defmodule Gradient do
_ -> :ok
end
error ->
Logger.error("Can't load file - #{inspect(error)}")
IO.puts(IO.ANSI.format([:red, "Can't load file - #{inspect(error)}"]))
:error
end
end
Expand Down
40 changes: 40 additions & 0 deletions lib/gradient/anno.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
defmodule Gradient.Anno do
@type anno :: keyword()
@type location :: {non_neg_integer(), pos_integer()}
@type line :: non_neg_integer()

@max_col 1000

@spec end_location(anno()) :: location()
def end_location(anno) when is_list(anno) do
case Keyword.fetch(anno, :end_location) do
{:ok, {line, col}} -> {abs_line(line(anno), line), col}
:error -> line(anno)
end
end

def end_location(anno), do: {line(anno), @max_col}

def end_line(anno) when is_list(anno) do
case Keyword.fetch(anno, :end_location) do
{:ok, {line, _}} -> abs_line(line(anno), line)
:error -> line(anno)
end
end

def end_line(anno), do: line(anno)

@spec line(anno()) :: line()
def line(anno), do: :erl_anno.line(:erl_anno.from_term(anno))

@spec location(anno()) :: location()
def location(anno) do
case :erl_anno.location(:erl_anno.from_term(anno)) do
{line, col} -> {line, col}
line -> {line, 1}
end
end

def abs_line(startl, endl) when startl > endl, do: 2 * startl - endl
def abs_line(_, endl), do: endl
end
132 changes: 88 additions & 44 deletions lib/gradient/ast_specifier.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ defmodule Gradient.AstSpecifier do

import Gradient.Tokens

require Logger

alias Gradient.Types

@type token :: Types.token()
Expand All @@ -23,6 +21,8 @@ defmodule Gradient.AstSpecifier do
# Expressions that could have missing location
@lineless_forms [:atom, :char, :float, :integer, :string, :bin, :cons, :tuple]

@ensure_location [:var | @lineless_forms]

# Api

@doc """
Expand Down Expand Up @@ -257,7 +257,7 @@ defmodule Gradient.AstSpecifier do
|> get_tuple(opts)
|> case do
{:tuple, tokens} ->
{anno, opts} = update_line_from_tokens(tokens, anno, opts)
{anno, opts} = update_loc_from_tokens(tokens, anno, opts)
# drop a token that begins tuple
tokens = drop_tokens_while(tokens, fn t -> elem(t, 0) in [:"{"] end)

Expand Down Expand Up @@ -325,17 +325,38 @@ defmodule Gradient.AstSpecifier do
|> pass_tokens(tokens)
end

def mapper({:call, anno, name, args}, tokens, opts) do
def mapper({:call, anno, name, args} = c, tokens, opts) do
# anno has correct line
{:ok, _, anno, opts, _} = get_line(anno, opts)
{:ok, _, _, opts, _} = get_line(anno, opts)
name = remote_mapper(name)

{{:call, anno, _, _}, _tokens} = specify_line(c, tokens, opts)

anno =
if is_integer(anno) do
anno
else
set_end_location(anno, get_closing_paren_loc(tokens))
end

{args, tokens} = call_args_mapper(args, tokens, name, opts)

{:call, anno, name, args}
|> pass_tokens(tokens)
end

def set_end_location(anno, :undefined), do: anno
# def set_end_location(anno, location) when is_integer(anno) do
# [location: anno, end_location: location]
# end
def set_end_location(anno, location) when is_tuple(anno) do
[location: anno, end_location: location]
end

def set_end_location(anno, location) do
Keyword.put(anno, :end_location, location)
end

def mapper({:op, anno, op, left, right}, tokens, opts) do
# anno has correct line
{:ok, _, anno, opts, _} = get_line(anno, opts)
Expand Down Expand Up @@ -385,7 +406,7 @@ defmodule Gradient.AstSpecifier do
end

def mapper({type, anno, value}, tokens, opts)
when type in @lineless_forms do
when type in @ensure_location do
{:ok, line} = Keyword.fetch(opts, :line)
anno = :erl_anno.set_line(line, anno)
anno = :erl_anno.set_generated(Keyword.get(opts, :generated, false), anno)
Expand All @@ -397,23 +418,15 @@ defmodule Gradient.AstSpecifier do
def mapper(skip, tokens, _opts)
when elem(skip, 0) in [
:fun,
:attribute,
:var,
nil,
:atom,
:char,
:float,
:integer,
:string,
:bin
:attribute
] do
# NOTE fun - I skipped here checking &name/arity or &module.name/arity
# skip forms that don't need analysis and do not display warning
pass_tokens(skip, tokens)
end

def mapper(form, tokens, _opts) do
Logger.warn("Not found mapper for #{inspect(form)}")
IO.puts(IO.ANSI.format([:yellow, "Not found mapper for #{inspect(form)}"]))
pass_tokens(form, tokens)
end

Expand Down Expand Up @@ -512,7 +525,7 @@ defmodule Gradient.AstSpecifier do
{[[g] | gs], ts}

gs, {ags, ts} ->
Logger.error("Unsupported guards format #{inspect(gs)}")
IO.puts(IO.ANSI.format([:red, "Unsupported guards format #{inspect(gs)}"]))
{gs ++ ags, ts}
end)
end
Expand Down Expand Up @@ -552,7 +565,7 @@ defmodule Gradient.AstSpecifier do
def cons_mapper({:cons, anno, value, tail}, tokens, opts) do
{:ok, _, anno0, opts0, _} = get_line(anno, opts)

{anno, opts} = update_line_from_tokens(tokens, anno0, opts0)
{anno, opts} = update_loc_from_tokens(tokens, anno0, opts0)
# drop a token that begins list
tokens = drop_tokens_while(tokens, fn t -> elem(t, 0) in [:"["] end)

Expand Down Expand Up @@ -671,59 +684,90 @@ defmodule Gradient.AstSpecifier do
l2 <= l1
end

defp match_token_to_form({:identifier, {l1, _, _}, t_name}, {:var, l2, raw_name}) do
l2 == l1 && String.contains?(to_string(raw_name), <<to_string(t_name)::binary, "@">>)
end

defp match_token_to_form({:paren_identifier, {l1, _, _}, name1}, {:call, l2, name2, _}) do
:erl_anno.line(l2) == l1 && match_remote_names(name1, name2)
end

defp match_token_to_form(_, _) do
false
end

def match_remote_names(name, {:remote, _, _, {:atom, _, name}}), do: true
def match_remote_names(name, {:atom, _, name}), do: true
def match_remote_names(name, name), do: true
def match_remote_names(_, _), do: false

defp literal_loc({line, col_start, _}, literal) do
col_end = col_start + length(to_charlist(literal))
[location: {line, col_start}, end_location: {line, col_end}]
end

@spec take_loc_from_token(token(), form()) :: form()
defp take_loc_from_token({:int, {line, _, _}, _}, {:integer, _, value}) do
{:integer, line, value}
defp take_loc_from_token({:int, loc, _}, {:integer, _, value}) do
{:integer, literal_loc(loc, value), value}
end

defp take_loc_from_token({:char, {line, _, _}, _}, {:integer, _, value}) do
{:integer, line, value}
defp take_loc_from_token({:char, loc, _}, {:integer, _, value}) do
{:integer, literal_loc(loc, value), value}
end

defp take_loc_from_token({:flt, {line, _, _}, _}, {:float, _, value}) do
{:float, line, value}
defp take_loc_from_token({:flt, loc, _}, {:float, _, value}) do
{:float, literal_loc(loc, value), value}
end

defp take_loc_from_token({:atom, {line, _, _}, _}, {:atom, _, value}) do
{:atom, line, value}
defp take_loc_from_token({:atom, loc, _}, {:atom, _, value}) do
{:atom, literal_loc(loc, value), value}
end

defp take_loc_from_token({:alias, {line, _, _}, _}, {:atom, _, value}) do
{:atom, line, value}
defp take_loc_from_token({:alias, loc, _}, {:atom, _, value}) do
{:atom, literal_loc(loc, value), value}
end

defp take_loc_from_token({:kw_identifier, {line, _, _}, _}, {:atom, _, value}) do
{:atom, line, value}
defp take_loc_from_token({:kw_identifier, loc, _}, {:atom, _, value}) do
{:atom, literal_loc(loc, value), value}
end

defp take_loc_from_token({:list_string, {l1, _, _}, _}, {:cons, _, _, _} = charlist) do
charlist_set_loc(charlist, l1)
defp take_loc_from_token({:list_string, loc, v}, {:cons, _, _, _} = charlist) do
charlist_set_loc(charlist, literal_loc(loc, v))
end

defp take_loc_from_token(
{:bin_string, {l1, _, _}, _},
{:bin_string, loc, _},
{:bin, _, [{:bin_element, _, {:string, _, v2}, :default, :default}]}
) do
{:bin, l1, [{:bin_element, l1, {:string, l1, v2}, :default, :default}]}
loc = literal_loc(loc, v2)
{:bin, loc, [{:bin_element, loc, {:string, loc, v2}, :default, :default}]}
end

defp take_loc_from_token({:str, _, _}, {:string, loc, v2}) do
# FIXME missing col
{:string, loc, v2}
end

defp take_loc_from_token({true, {line, _, _}}, {:atom, _, true}) do
{:atom, line, true}
defp take_loc_from_token({true, loc}, {:atom, _, true}) do
{:atom, literal_loc(loc, true), true}
end

defp take_loc_from_token({false, {line, _, _}}, {:atom, _, false}) do
{:atom, line, false}
defp take_loc_from_token({false, loc}, {:atom, _, false}) do
{:atom, literal_loc(loc, true), false}
end

defp take_loc_from_token(_, _), do: nil
defp take_loc_from_token({:identifier, loc, name}, {:var, _, raw_name}) do
{:var, literal_loc(loc, name), raw_name}
end

defp take_loc_from_token({:paren_identifier, {line, col, _}, _}, {:call, anno, name, args}) do
{:call, :erl_anno.set_location({line, col}, anno), name, args}
end

defp take_loc_from_token(t, e) do
IO.puts(IO.ANSI.format([:red, "Cannot take loc from token - #{inspect(t)}, #{inspect(e)}"]))
nil
end

def cons_to_charlist({nil, _}), do: []

Expand All @@ -737,12 +781,12 @@ defmodule Gradient.AstSpecifier do

def charlist_set_loc({nil, _}, loc), do: {nil, loc}

def update_line_from_tokens([token | _], anno, opts) do
line = get_line_from_token(token)
{:erl_anno.set_line(line, anno), Keyword.put(opts, :line, line)}
def update_loc_from_tokens([token | _], anno, opts) do
{line, col} = get_loc_from_token(token)
{:erl_anno.set_location({line, col}, anno), Keyword.put(opts, :line, line)}
end

def update_line_from_tokens(_, anno, opts) do
def update_loc_from_tokens(_, anno, opts) do
{anno, opts}
end

Expand Down Expand Up @@ -772,7 +816,7 @@ defmodule Gradient.AstSpecifier do
end

defp set_form_end_line(opts, form, forms) do
if elem(form, 0) not in [:bin, :cons] do
if elem(form, 0) not in [:bin] do
set_form_end_line_(opts, form, forms)
else
opts
Expand Down
Loading