Skip to content

Commit

Permalink
Store position info in gradualizer_db
Browse files Browse the repository at this point in the history
Remove position info when fetching types from gradualizer_db not when importing them.
This means the caller can decide if they want pos info available or removed.
This, in turn, means the check_undefined_types pass can fetch types with this info present,
while the main typechecker pass can discard position info for easy type comparison.
  • Loading branch information
erszcz committed Feb 15, 2022
1 parent fc06c21 commit 81da18b
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 48 deletions.
83 changes: 49 additions & 34 deletions src/gradualizer_db.erl
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

%% API functions
-export([start_link/1,
get_spec/3,
get_type/3, get_exported_type/3, get_opaque_type/3,
get_spec/4,
get_type/4, get_exported_type/4, get_opaque_type/4,
get_record_type/2,
get_modules/0, get_types/1,
save/1, load/1,
Expand Down Expand Up @@ -54,32 +54,36 @@ start_link(Opts) ->
%% "module.erl"
-spec get_spec(M :: module(),
F :: atom(),
A :: arity()) -> {ok, [type()]} | not_found.
get_spec(M, F, A) ->
call({get_spec, M, F, A}).
A :: arity(),
Opts :: [remove_pos]) -> {ok, [type()]} | not_found.
get_spec(M, F, A, Opts) ->
call({get_spec, M, F, A, Opts}).

%% @doc Fetches an exported or unexported user-defined type. Does not expand
%% opaque types.
-spec get_type(Module :: module(),
Type :: atom(),
Params :: [type()]) -> {ok, type()} | opaque | not_found.
get_type(M, T, A) ->
call({get_type, M, T, A}).
Params :: [type()],
Opts :: [remove_pos]) -> {ok, type()} | opaque | not_found.
get_type(M, T, A, Opts) ->
call({get_type, M, T, A, Opts}).

%% @doc Fetches an exported type. Does not expand opaque types.
-spec get_exported_type(Module :: module(),
Type :: atom(),
Params :: [type()]) -> {ok, type()} | opaque |
not_exported | not_found.
get_exported_type(M, T, A) ->
call({get_exported_type, M, T, A}).
Params :: [type()],
Opts :: [remove_pos]) -> {ok, type()} | opaque |
not_exported | not_found.
get_exported_type(M, T, A, Opts) ->
call({get_exported_type, M, T, A, Opts}).

%% @doc Like get_type/3 but also expands opaque types.
-spec get_opaque_type(Module :: module(),
Type :: atom(),
Params :: [type()]) -> {ok, type()} | not_found.
get_opaque_type(M, T, A) ->
call({get_opaque_type, M, T, A}).
Params :: [type()],
Opts :: [remove_pos]) -> {ok, type()} | not_found.
get_opaque_type(M, T, A, Opts) ->
call({get_opaque_type, M, T, A, Opts}).

%% @doc Fetches a record type defined in the module.
-spec get_record_type(Module :: module(),
Expand Down Expand Up @@ -174,25 +178,27 @@ init(Opts0) ->
{ok, State2}.

-spec handle_call(any(), {pid(), term()}, state()) -> {reply, term(), state()}.
handle_call({get_spec, M, F, A}, _From, State) ->
handle_call({get_spec, M, F, A, Opts}, _From, State) ->
RemovePos = proplists:get_bool(remove_pos, Opts),
State1 = autoimport(M, State),
K = {M, F, A},
case State1#state.specs of
#{K := Types} ->
Types1 = [typelib:annotate_user_types(M, Type) || Type <- Types],
Types1 = [ typelib:annotate_user_types(M, remove_pos(RemovePos, Type))
|| Type <- Types ],
{reply, {ok, Types1}, State1};
_NoMatch ->
{reply, not_found, State1}
end;
handle_call({get_exported_type, M, T, Args}, _From, State) ->
handle_call({get_exported_type, M, T, Args, Opts}, _From, State) ->
State1 = autoimport(M, State),
handle_get_type(M, T, Args, true, false, State1);
handle_call({get_type, M, T, Args}, _From, State) ->
handle_get_type(M, T, Args, true, false, Opts, State1);
handle_call({get_type, M, T, Args, Opts}, _From, State) ->
State1 = autoimport(M, State),
handle_get_type(M, T, Args, false, false, State1);
handle_call({get_opaque_type, M, T, Args}, _From, State) ->
handle_get_type(M, T, Args, false, false, Opts, State1);
handle_call({get_opaque_type, M, T, Args, Opts}, _From, State) ->
State1 = autoimport(M, State),
handle_get_type(M, T, Args, false, true, State1);
handle_get_type(M, T, Args, false, true, Opts, State1);
handle_call({get_record_type, M, Name}, _From, State) ->
State1 = autoimport(M, State),
K = {M, Name},
Expand Down Expand Up @@ -327,10 +333,17 @@ call(Request, Timeout) ->
gen_server:call(?name, Request, Timeout).

%% helper for handle_call for get_type, get_exported_type, get_opaque_type.
-spec handle_get_type(module(), Name :: atom(), Params :: [type()],
RequireExported :: boolean(), ExpandOpaque :: boolean(),
state()) -> {reply, {ok, type()} | atom(), state()}.
handle_get_type(M, T, Args, RequireExported, ExpandOpaque, State) ->
-spec handle_get_type(M, T, Args, RequireExported, ExpandOpaque, Opts, State) -> R when
M :: module(),
T :: atom(),
Args :: [type()],
RequireExported :: boolean(),
ExpandOpaque :: boolean(),
Opts :: [remove_pos],
State :: state(),
R :: {reply, {ok, type()} | atom(), state()}.
handle_get_type(M, T, Args, RequireExported, ExpandOpaque, Opts, State) ->
RemovePos = proplists:get_bool(remove_pos, Opts),
K = {M, T, length(Args)},
case State#state.types of
#{K := TypeInfo} ->
Expand All @@ -342,7 +355,7 @@ handle_get_type(M, T, Args, RequireExported, ExpandOpaque, State) ->
#typeinfo{params = Vars,
body = Type0} ->
VarMap = maps:from_list(lists:zip(Vars, Args)),
Type1 = typelib:annotate_user_types(M, Type0),
Type1 = typelib:annotate_user_types(M, remove_pos(RemovePos, Type0)),
Type2 = typelib:substitute_type_vars(Type1, VarMap),
{reply, {ok, Type2}, State}
end;
Expand Down Expand Up @@ -491,7 +504,7 @@ collect_types(Module, Forms) ->
Info = #typeinfo{exported = Exported,
opaque = (Attr == opaque),
params = Params,
body = typelib:remove_pos(Body)},
body = Body},
{Id, Info}
end || Form = {attribute, _, Attr, {Name, Body, Vars}} <- Forms,
Attr == type orelse Attr == opaque,
Expand Down Expand Up @@ -523,9 +536,8 @@ extract_record_defs([{attribute, L, record, {Name, _UntypedFields}},
%% This representation is only used in OTP < 19
extract_record_defs([{attribute, L, record, {Name, Fields}} | Rest]);
extract_record_defs([{attribute, _L, record, {Name, Fields}} | Rest]) ->
TypedFields = [gradualizer_lib:remove_pos_typed_record_field(
absform:normalize_record_field(Field))
|| Field <- Fields],
TypedFields = [ absform:normalize_record_field(Field)
|| Field <- Fields ],
R = {Name, TypedFields},
[R | extract_record_defs(Rest)];
extract_record_defs([_ | Rest]) ->
Expand Down Expand Up @@ -562,8 +574,7 @@ collect_specs(Module, Forms) ->
{F, A} <- Exports,
not sets:is_element({F, A},
SpecedFunsSet)],
[{Key, lists:map(fun typelib:remove_pos/1,
absform:normalize_function_type_list(Types))}
[{Key, absform:normalize_function_type_list(Types)}
|| {Key, Types} <- Specs ++ ImplicitSpecs].

normalize_spec({{Func, Arity}, Types}, Module) ->
Expand Down Expand Up @@ -623,3 +634,7 @@ beam_file_regexp() ->
erl_file_regexp() ->
{ok, RE} = re:compile(<<"([^/.]*)\.erl$">>),
RE.

-spec remove_pos(boolean(), type()) -> type().
remove_pos(true, Type) -> typelib:remove_pos(Type);
remove_pos( _, Type) -> Type.
9 changes: 5 additions & 4 deletions src/gradualizer_lib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,16 @@ reverse_graph(G) ->
-spec get_type_definition(UserTy, Env, Opts) -> {ok, Ty} | opaque | not_found when
UserTy :: gradualizer_type:abstract_type(),
Env :: typechecker:env(),
Opts :: [annotate_user_types],
Opts :: [Opt],
Opt :: annotate_user_types | remove_pos,
Ty :: gradualizer_type:abstract_type().
get_type_definition({remote_type, _Anno, [{atom, _, Module}, {atom, _, Name}, Args]}, _Env, _Opts) ->
gradualizer_db:get_type(Module, Name, Args);
get_type_definition({remote_type, _Anno, [{atom, _, Module}, {atom, _, Name}, Args]}, _Env, Opts) ->
gradualizer_db:get_type(Module, Name, Args, Opts);
get_type_definition({user_type, Anno, Name, Args}, Env, Opts) ->
%% Let's check if the type is a known remote type.
case typelib:get_module_from_annotation(Anno) of
{ok, Module} ->
gradualizer_db:get_type(Module, Name, Args);
gradualizer_db:get_type(Module, Name, Args, Opts);
none ->
%% Let's check if the type is defined in the context of this module.
case maps:get({Name, length(Args)}, maps:get(types, Env#env.tenv), not_found) of
Expand Down
20 changes: 11 additions & 9 deletions src/typechecker.erl
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ normalize_rec({user_type, P, Name, Args} = Type, Env, Unfolded) ->
{type, NormType} -> NormType;
no_type ->
UnfoldedNew = maps:put(mta(Type, Env), {type, Type}, Unfolded),
case gradualizer_lib:get_type_definition(Type, Env, []) of
case gradualizer_lib:get_type_definition(Type, Env, [remove_pos]) of
{ok, T} ->
normalize_rec(T, Env, UnfoldedNew);
opaque ->
Expand All @@ -779,7 +779,7 @@ normalize_rec(T = ?top(), _Env, _Unfolded) ->
%% Don't normalize gradualizer:top().
T;
normalize_rec({remote_type, P, [{atom, _, M}, {atom, _, N}, Args]}, Env, Unfolded) ->
case gradualizer_db:get_exported_type(M, N, Args) of
case gradualizer_db:get_exported_type(M, N, Args, [remove_pos]) of
{ok, T} ->
normalize_rec(T, Env, Unfolded);
opaque ->
Expand Down Expand Up @@ -1724,7 +1724,7 @@ do_type_check_expr(Env, {'fun', P, {function, Name, Arity}}) ->
do_type_check_expr(Env, {'fun', P, {function, M, F, A}}) ->
case {get_atom(Env, M), get_atom(Env, F), A} of
{{atom, _, Module}, {atom, _, Function}, {integer, _, Arity}} ->
case gradualizer_db:get_spec(Module, Function, Arity) of
case gradualizer_db:get_spec(Module, Function, Arity, [remove_pos]) of
{ok, BoundedFunTypeList} ->
Ty = bounded_type_list_to_type(Env, BoundedFunTypeList),
{Ty, #{}, constraints:empty()};
Expand Down Expand Up @@ -2526,7 +2526,7 @@ do_type_check_expr_in(Env, ResTy, Expr = {'fun', P, {function, Name, Arity}}) ->
do_type_check_expr_in(Env, ResTy, Expr = {'fun', P, {function, M, F, A}}) ->
case {get_atom(Env, M), get_atom(Env, F), A} of
{{atom, _, Module}, {atom, _, Function}, {integer, _,Arity}} ->
case gradualizer_db:get_spec(Module, Function, Arity) of
case gradualizer_db:get_spec(Module, Function, Arity, [remove_pos]) of
{ok, BoundedFunTypeList} ->
FunTypeList =
unfold_bounded_type_list(Env, BoundedFunTypeList),
Expand Down Expand Up @@ -3090,7 +3090,7 @@ type_check_fun(Env, {atom, P, Name}, Arity) ->
{Types, #{}, constraints:empty()};
type_check_fun(_Env, {remote, P, {atom,_,Module}, {atom,_,Fun}}, Arity) ->
% Module:function call
case gradualizer_db:get_spec(Module, Fun, Arity) of
case gradualizer_db:get_spec(Module, Fun, Arity, [remove_pos]) of
{ok, Types} -> {Types, #{}, constraints:empty()};
not_found -> throw({call_undef, P, Module, Fun, Arity})
end;
Expand Down Expand Up @@ -3266,7 +3266,7 @@ get_bounded_fun_type_list(Name, Arity, Env, P) ->
error ->
case erl_internal:bif(Name, Arity) of
true ->
{ok, Types} = gradualizer_db:get_spec(erlang, Name, Arity),
{ok, Types} = gradualizer_db:get_spec(erlang, Name, Arity, [remove_pos]),
Types;
false ->
%% If it's not imported, the file doesn't compile.
Expand All @@ -3280,7 +3280,7 @@ get_bounded_fun_type_list(Name, Arity, Env, P) ->
get_imported_bounded_fun_type_list(Name, Arity, Env, P) ->
case maps:find({Name, Arity}, Env#env.imported) of
{ok, Module} ->
case gradualizer_db:get_spec(Module, Name, Arity) of
case gradualizer_db:get_spec(Module, Name, Arity, [remove_pos]) of
{ok, BoundedFunTypeList} ->
{ok, BoundedFunTypeList};
not_found ->
Expand Down Expand Up @@ -4850,14 +4850,16 @@ check_remote_type(?top(), Acc) ->
Acc;
%% Check remote calls, as the remote function specs can also refer to undefined remote types.
check_remote_type({call, _, {remote, _, {atom, _, Module}, {atom, _, Fun}}, Args}, Acc) ->
case gradualizer_db:get_spec(Module, Fun, length(Args)) of
RemovePosOff = [],
case gradualizer_db:get_spec(Module, Fun, length(Args), RemovePosOff) of
{ok, Types} ->
check_undefined_types(Types) ++ Acc;
not_found ->
Acc
end;
check_remote_type({remote_type, P, [{atom, _, M}, {atom, _, N}, Args]}, Acc) ->
case gradualizer_db:get_exported_type(M, N, Args) of
RemovePosOff = [],
case gradualizer_db:get_exported_type(M, N, Args, RemovePosOff) of
{ok, Type} ->
%% A remote type might expand to another remote type, so let's check that, too.
check_undefined_types([Type]) ++ Acc;
Expand Down
2 changes: 1 addition & 1 deletion src/typelib.erl
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pp_type(Type) ->

%% Looks up and prints the type M:N(P1, ..., Pn).
debug_type(M, N, P) ->
case gradualizer_db:get_type(M, N, P) of
case gradualizer_db:get_type(M, N, P, _RemovePosOff = []) of
{ok, T} ->
Params = lists:join($,, lists:map(fun pp_type/1, P)),
io:format("~w:~w(~s) :: ~s.~n",
Expand Down

0 comments on commit 81da18b

Please sign in to comment.