% This module contains useful helper functions. -module(gradualizer_lib). -export([merge_with/3, top_sort/1, pick_value/1, fold_ast/3, get_ast_children/1]). -export_type([graph/1]). %% Pattern macros -define(type(T), {type, _, T, []}). -define(type(T, A), {type, _, T, A}). %% merge_with for maps. Similar to merge_with for dicts. %% Arguably, this function should be in OTP. merge_with(F, M1, M2) -> case maps:size(M1) < maps:size(M2) of true -> maps:fold(fun (K, V1, M) -> maps:update_with(K, fun (V2) -> F(K, V1, V2) end, V1, M) end, M2, M1); false -> maps:fold(fun (K, V2, M) -> maps:update_with(K, fun (V1) -> F(K, V1, V2) end, V2, M) end, M1, M2) end. %% -- Topological sort -type graph(Node) :: #{Node => [Node]}. %% List of incoming edges (dependencies). %% Topologically sorted strongly-connected components of a graph. -spec top_sort(graph(Node)) -> [{cyclic, [Node]} | {acyclic, Node}]. top_sort(Graph) -> Trees = dfs(Graph, lists:reverse(postorder(dff(reverse_graph(Graph))))), Decode = fun(T) -> case postorder(T) of [I] -> case lists:member(I, maps:get(I, Graph, [])) of true -> {cyclic, [I]}; false -> {acyclic, I} end; Is -> {cyclic, Is} end end, lists:map(Decode, Trees). %% Depth first spanning forest of a graph. dff(Graph) -> dfs(Graph, maps:keys(Graph)). dfs(Graph, Vs) -> {_, Trees} = dfs(Graph, #{}, Vs, []), Trees. dfs(_Graph, Visited, [], Trees) -> {Visited, lists:reverse(Trees)}; dfs(Graph, Visited, [V | Vs], Trees) -> case maps:is_key(V, Visited) of true -> dfs(Graph, Visited, Vs, Trees); false -> {Visited1, Tree} = dfs1(Graph, Visited#{ V => true }, V), dfs(Graph, Visited1, Vs, [Tree | Trees]) end. dfs1(Graph, Visited, V) -> Ws = maps:get(V, Graph, []), {Visited1, Trees} = dfs(Graph, Visited, Ws, []), {Visited1, {V, Trees}}. %% Post-order traversal of a tree/forest. postorder(Tree = {_, _}) -> postorder([Tree]); postorder(Trees) when is_list(Trees) -> postorder(Trees, []). postorder([], Acc) -> Acc; postorder([{V, Trees1} | Trees], Acc) -> postorder(Trees1, [V | postorder(Trees, Acc)]). from_edges(Is, Es) -> lists:foldl(fun({I, J}, G) -> maps:update_with(I, fun(Js) -> lists:umerge([J], Js) end, [J], G) end, maps:from_list([ {I, []} || I <- Is ]), Es). reverse_graph(G) -> from_edges(maps:keys(G), [ {J, I} || {I, Js} <- maps:to_list(G), J <- Js ]). % Given a type, pick a value of that type. % Used in exhaustiveness checking to show an example value % which is not covered by the cases. pick_value(List) when is_list(List) -> [pick_value(Ty) || Ty <- List ]; pick_value(?type(integer)) -> 0; pick_value(?type(char)) -> $a; pick_value(?type(non_neg_integer)) -> 0; pick_value(?type(pos_integer)) -> 1; pick_value(?type(neg_integer)) -> -1; pick_value(?type(float)) -> 0.0; pick_value(?type(atom)) -> a; pick_value({atom, _, A}) -> A; pick_value({ann_type, _, [_, Ty]}) -> pick_value(Ty); pick_value(?type(union, [Ty|_])) -> pick_value(Ty); pick_value(?type(tuple, any)) -> {}; pick_value(?type(tuple, Tys)) -> list_to_tuple([pick_value(Ty) || Ty <- Tys]); pick_value(?type(list)) -> []; pick_value(?type(list,_)) -> []; pick_value(?type(nil)) -> []; pick_value(?type(range, [{_TagLo, _, neg_inf}, {_TagHi, _, Hi}])) -> Hi; pick_value(?type(range, [{_TagLo, _, Lo}, {_TagHi, _, _Hi}])) -> Lo. %% ------------------------------------------------ %% Functions for working with abstract syntax trees %% ------------------------------------------------ %% erl_parse:erl_parse_tree() is documented but not exported :-( -type erl_parse_tree() :: erl_parse:abstract_clause() | erl_parse:abstract_expr() | erl_parse:abstract_form() | erl_parse:abstract_type(). %% Folds a function over an Erlang abstract syntax tree. The fun is applied to %% each node (tuple) in the AST, which is traversed in depth first order. -spec fold_ast(Fun, AccIn, Ast) -> AccOut when Fun :: fun((tuple(), Acc) -> Acc), Ast :: erl_parse_tree() | [erl_parse_tree()], AccIn :: Acc, AccOut :: Acc. fold_ast(Fun, AccIn, [X|Xs]) -> Acc = fold_ast(Fun, AccIn, X), fold_ast(Fun, Acc, Xs); fold_ast(Fun, AccIn, Node) when is_tuple(Node) -> Acc = Fun(Node, AccIn), fold_ast(Fun, Acc, get_ast_children(Node)); fold_ast(_Fun, Acc, _LiteralEtc) -> Acc. %% Returns the children of an AST node get_ast_children({clauses, Clauses}) -> %% This one doesn't have an annotation Clauses; get_ast_children(Node) -> [_Tag, _Anno | Children] = tuple_to_list(Node), Children.