diff options
Diffstat (limited to 'lib/dialyzer/src/dialyzer_dataflow.erl')
-rw-r--r-- | lib/dialyzer/src/dialyzer_dataflow.erl | 717 |
1 files changed, 399 insertions, 318 deletions
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index a57d9a96c6..7137dbc036 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -2,7 +2,7 @@ %%-------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2010. All Rights Reserved. +%% Copyright Ericsson AB 2006-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -21,7 +21,7 @@ %%%------------------------------------------------------------------- %%% File : dialyzer_dataflow.erl %%% Author : Tobias Lindahl <[email protected]> -%%% Description : +%%% Description : %%% %%% Created : 19 Apr 2005 by Tobias Lindahl <[email protected]> %%%------------------------------------------------------------------- @@ -30,6 +30,7 @@ -export([get_fun_types/4, get_warnings/5, format_args/3]). +%% Data structure interfaces. -export([state__add_warning/2, state__cleanup/1, state__get_callgraph/1, state__get_races/1, state__get_records/1, state__put_callgraph/2, @@ -38,9 +39,11 @@ %% Debug and test interfaces. -export([get_top_level_signatures/2, pp/1]). +-export_type([state/0]). + -include("dialyzer.hrl"). --import(erl_types, +-import(erl_types, [any_none/1, t_any/0, t_atom/0, t_atom/1, t_atom_vals/1, t_binary/0, t_boolean/0, t_bitstr/0, t_bitstr/2, t_bitstr_concat/1, t_bitstr_match/2, @@ -88,14 +91,15 @@ fun_tab :: dict(), plt :: dialyzer_plt:plt(), opaques :: [erl_types:erl_type()], - races :: dialyzer_races:races(), - records :: dict(), + races = dialyzer_races:new() :: dialyzer_races:races(), + records = dict:new() :: dict(), tree_map :: dict(), warning_mode = false :: boolean(), warnings = [] :: [dial_warning()], work :: {[_], [_], set()}, module :: module(), - behaviour_api_info = [] :: [{atom(),[_]}]}). + behaviour_api_dict = [] :: + dialyzer_behaviours:behaviour_api_dict()}). %% Exported Types @@ -163,20 +167,20 @@ get_top_level_signatures(Code, Records) -> error -> Arity = cerl:fname_arity(V), Type = t_fun(lists:duplicate(Arity, - t_none()), + t_none()), t_none()), dict:store(Label, Type, Acc); {ok, _} -> Acc end end, FunTypes, cerl:module_defs(Tree)), dialyzer_callgraph:delete(Callgraph), - Sigs = [{{cerl:fname_id(V), cerl:fname_arity(V)}, - dict:fetch(get_label(F), FunTypes1)} + Sigs = [{{cerl:fname_id(V), cerl:fname_arity(V)}, + dict:fetch(get_label(F), FunTypes1)} || {V, F} <- cerl:module_defs(Tree)], ordsets:from_list(Sigs). get_def_plt() -> - try + try dialyzer_plt:from_file(dialyzer_plt:get_default_plt()) catch throw:{dialyzer_error, _} -> dialyzer_plt:new() @@ -202,7 +206,7 @@ annotate_module(Code, Plt) -> annotate(Tree, State) -> case cerl:subtrees(Tree) of [] -> set_type(Tree, State); - List -> + List -> NewSubTrees = [[annotate(Subtree, State) || Subtree <- Group] || Group <- List], NewTree = cerl:update_tree(Tree, NewSubTrees), @@ -214,9 +218,9 @@ set_type(Tree, State) -> 'fun' -> Type = state__fun_type(Tree, State), case t_is_any(Type) of - true -> + true -> cerl:set_ann(Tree, delete_ann(typesig, cerl:get_ann(Tree))); - false -> + false -> cerl:set_ann(Tree, append_ann(typesig, Type, cerl:get_ann(Tree))) end; apply -> @@ -224,10 +228,10 @@ set_type(Tree, State) -> unknown -> Tree; ReturnType -> case t_is_any(ReturnType) of - true -> + true -> cerl:set_ann(Tree, delete_ann(type, cerl:get_ann(Tree))); - false -> - cerl:set_ann(Tree, append_ann(type, ReturnType, + false -> + cerl:set_ann(Tree, append_ann(type, ReturnType, cerl:get_ann(Tree))) end end; @@ -236,7 +240,7 @@ set_type(Tree, State) -> end. append_ann(Tag, Val, [X | Xs]) -> - if tuple_size(X) >= 1, element(1, X) =:= Tag -> + if tuple_size(X) >= 1, element(1, X) =:= Tag -> append_ann(Tag, Val, Xs); true -> [X | append_ann(Tag, Val, Xs)] @@ -245,7 +249,7 @@ append_ann(Tag, Val, []) -> [{Tag, Val}]. delete_ann(Tag, [X | Xs]) -> - if tuple_size(X) >= 1, element(1, X) =:= Tag -> + if tuple_size(X) >= 1, element(1, X) =:= Tag -> delete_ann(Tag, Xs); true -> [X | delete_ann(Tag, Xs)] @@ -314,21 +318,21 @@ analyze_loop(#state{callgraph = Callgraph, races = Races} = State) -> {Fun, NewState} -> ArgTypes = state__get_args(Fun, NewState), case any_none(ArgTypes) of - true -> - ?debug("Not handling1 ~w: ~s\n", - [state__lookup_name(get_label(Fun), State), + true -> + ?debug("Not handling1 ~w: ~s\n", + [state__lookup_name(get_label(Fun), State), t_to_string(t_product(ArgTypes))]), analyze_loop(NewState); - false -> + false -> case state__fun_env(Fun, NewState) of - none -> - ?debug("Not handling2 ~w: ~s\n", - [state__lookup_name(get_label(Fun), State), + none -> + ?debug("Not handling2 ~w: ~s\n", + [state__lookup_name(get_label(Fun), State), t_to_string(t_product(ArgTypes))]), analyze_loop(NewState); Map -> - ?debug("Handling fun ~p: ~s\n", - [state__lookup_name(get_label(Fun), State), + ?debug("Handling fun ~p: ~s\n", + [state__lookup_name(get_label(Fun), State), t_to_string(state__fun_type(Fun, NewState))]), NewState1 = state__mark_fun_as_handled(NewState, Fun), Vars = cerl:fun_vars(Fun), @@ -339,19 +343,19 @@ analyze_loop(#state{callgraph = Callgraph, races = Races} = State) -> RaceAnalysis = dialyzer_races:get_race_analysis(Races), NewState3 = case RaceDetection andalso RaceAnalysis of - true -> + true -> NewState2 = state__renew_curr_fun( state__lookup_name(FunLabel, NewState1), FunLabel, NewState1), state__renew_race_list([], 0, NewState2); false -> NewState1 end, - {NewState4, _Map2, BodyType} = + {NewState4, _Map2, BodyType} = traverse(Body, Map1, NewState3), - ?debug("Done analyzing: ~w:~s\n", + ?debug("Done analyzing: ~w:~s\n", [state__lookup_name(get_label(Fun), State), t_to_string(t_fun(ArgTypes, BodyType))]), - NewState5 = + NewState5 = case RaceDetection andalso RaceAnalysis of true -> Races1 = NewState4#state.races, @@ -382,7 +386,7 @@ traverse(Tree, Map, State) -> %% This only happens when checking for illegal record patterns %% so the handling is a bit rudimentary. traverse(cerl:alias_pat(Tree), Map, State); - apply -> + apply -> handle_apply(Tree, Map, State); binary -> Segs = cerl:binary_segments(Tree), @@ -416,7 +420,7 @@ traverse(Tree, Map, State) -> %% By not including the variables in scope we can assure that we %% will get the current function type when using the variables. FoldFun = fun({Var, Fun}, {AccState, AccMap}) -> - {NewAccState, NewAccMap0, FunType} = + {NewAccState, NewAccMap0, FunType} = traverse(Fun, AccMap, AccState), NewAccMap = enter_type(Var, FunType, NewAccMap0), {NewAccState, NewAccMap} @@ -428,7 +432,7 @@ traverse(Tree, Map, State) -> case cerl:unfold_literal(Tree) of Tree -> Type = literal_type(Tree), - NewType = + NewType = case erl_types:t_opaque_match_atom(Type, State#state.opaques) of [Opaque] -> Opaque; _ -> Type @@ -446,8 +450,8 @@ traverse(Tree, Map, State) -> bs_init_writable -> t_from_term(<<>>); Other -> erlang:error({'Unsupported primop', Other}) end, - {State, Map, Type}; - 'receive' -> + {State, Map, Type}; + 'receive' -> handle_receive(Tree, Map, State); seq -> Arg = cerl:seq_arg(Tree), @@ -457,13 +461,13 @@ traverse(Tree, Map, State) -> true -> SMA; false -> - State2 = + State2 = case (t_is_any(ArgType) orelse t_is_simple(ArgType) orelse is_call_to_send(Arg)) of true -> % do not warn in these cases State1; false -> - state__add_warning(State1, ?WARN_UNMATCHED_RETURN, Arg, + state__add_warning(State1, ?WARN_UNMATCHED_RETURN, Arg, {unmatched_return, [format_type(ArgType, State1)]}) end, @@ -481,12 +485,12 @@ traverse(Tree, Map, State) -> var -> ?debug("Looking up unknown variable: ~p\n", [Tree]), case state__lookup_type_for_rec_var(Tree, State) of - error -> + error -> LType = lookup_type(Tree, Map), Opaques = State#state.opaques, case t_opaque_match_record(LType, Opaques) of [Opaque] -> {State, Map, Opaque}; - _ -> + _ -> case t_opaque_match_atom(LType, Opaques) of [Opaque] -> {State, Map, Opaque}; _ -> {State, Map, LType} @@ -506,7 +510,7 @@ traverse_list([Tree|Tail], Map, State, Acc) -> traverse_list(Tail, Map1, State1, [Type|Acc]); traverse_list([], Map, State, Acc) -> {State, Map, lists:reverse(Acc)}. - + %%________________________________________ %% %% Special instructions @@ -518,7 +522,7 @@ handle_apply(Tree, Map, State) -> {State1, Map1, ArgTypes} = traverse_list(Args, Map, State), {State2, Map2, OpType} = traverse(Op, Map1, State1), case any_none(ArgTypes) of - true -> + true -> {State2, Map2, t_none()}; false -> {CallSitesKnown, FunList} = @@ -533,7 +537,7 @@ handle_apply(Tree, Map, State) -> OpType1 = t_inf(OpType, t_fun(Arity, t_any())), case t_is_none(OpType1) of true -> - Msg = {fun_app_no_fun, + Msg = {fun_app_no_fun, [format_cerl(Op), format_type(OpType, State2), Arity]}, State3 = state__add_warning(State2, ?WARN_FAILING_CALL, Tree, Msg), @@ -541,7 +545,7 @@ handle_apply(Tree, Map, State) -> false -> NewArgs = t_inf_lists(ArgTypes, t_fun_args(OpType1)), case any_none(NewArgs) of - true -> + true -> Msg = {fun_app_args, [format_args(Args, ArgTypes, State), format_type(OpType, State)]}, @@ -554,7 +558,7 @@ handle_apply(Tree, Map, State) -> end end; true -> - FunInfoList = [{local, state__fun_info(Fun, State)} + FunInfoList = [{local, state__fun_info(Fun, State)} || Fun <- FunList], handle_apply_or_call(FunInfoList, Args, ArgTypes, Map2, Tree, State1) end @@ -562,7 +566,7 @@ handle_apply(Tree, Map, State) -> handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State) -> None = t_none(), - handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State, + handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State, [None || _ <- ArgTypes], None). handle_apply_or_call([{local, external}|Left], Args, ArgTypes, Map, Tree, State, @@ -577,7 +581,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], Any = t_any(), AnyArgs = [Any || _ <- Args], GenSig = {AnyArgs, fun(_) -> t_any() end}, - {CArgs, CRange} = + {CArgs, CRange} = case Contr of {value, #contract{args = As} = C} -> {As, fun(FunArgs) -> @@ -627,9 +631,9 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], end end, ArgModeMask = [case lists:member(Arg, Opaques) of - true -> opaque; - false -> structured - end || Arg <- ArgTypes], + true -> opaque; + false -> structured + end || Arg <- ArgTypes], NewArgsSig = t_inf_lists_masked(SigArgs, ArgTypes, ArgModeMask), NewArgsContract = t_inf_lists_masked(CArgs, ArgTypes, ArgModeMask), NewArgsBif = t_inf_lists_masked(BifArgs, ArgTypes, ArgModeMask), @@ -637,7 +641,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], NewArgTypes = t_inf_lists_masked(NewArgTypes0, NewArgsBif, ArgModeMask), BifRet = BifRange(NewArgTypes), {TmpArgTypes, TmpArgsContract} = - case (TypeOfApply == remote) andalso (not IsBIF) of + case (TypeOfApply =:= remote) andalso (not IsBIF) of true -> List1 = lists:zip(CArgs, NewArgTypes), List2 = lists:zip(CArgs, NewArgsContract), @@ -648,19 +652,22 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], false -> {NewArgTypes, NewArgsContract} end, ContrRet = CRange(TmpArgTypes), - RetMode = case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of - true -> opaque; - false -> structured - end, - RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, RetMode), SigRange, RetMode), + RetMode = + case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of + true -> opaque; + false -> structured + end, + RetWithoutContr = t_inf(SigRange, BifRet, RetMode), + RetWithoutLocal = t_inf(ContrRet, RetWithoutContr, RetMode), ?debug("--------------------------------------------------------\n", []), ?debug("Fun: ~p\n", [Fun]), ?debug("Args: ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]), ?debug("NewArgsSig: ~s\n", [erl_types:t_to_string(t_product(NewArgsSig))]), - ?debug("NewArgsContract: ~s\n", + ?debug("NewArgsContract: ~s\n", [erl_types:t_to_string(t_product(NewArgsContract))]), ?debug("NewArgsBif: ~s\n", [erl_types:t_to_string(t_product(NewArgsBif))]), ?debug("NewArgTypes: ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]), + ?debug("RetWithoutContr: ~s\n",[erl_types:t_to_string(RetWithoutContr)]), ?debug("RetWithoutLocal: ~s\n", [erl_types:t_to_string(RetWithoutLocal)]), ?debug("BifRet: ~s\n", [erl_types:t_to_string(BifRange(NewArgTypes))]), ?debug("ContrRet: ~s\n", [erl_types:t_to_string(CRange(TmpArgTypes))]), @@ -677,11 +684,11 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], %% respective callback module's function. Module = State#state.module, - BehApiInfo = State#state.behaviour_api_info, + BehApiDict = State#state.behaviour_api_dict, {RealFun, RealArgTypes, RealArgs} = case dialyzer_behaviours:translate_behaviour_api_call(Fun, ArgTypes, Args, Module, - BehApiInfo) of + BehApiDict) of plain_call -> {Fun, ArgTypes, Args}; BehaviourAPI -> BehaviourAPI end, @@ -695,22 +702,39 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], State2 = case FailedConj andalso not (IsFailBif orelse IsFailSig) of true -> - FailedSig = any_none(NewArgsSig), - FailedContract = any_none([CRange(TmpArgsContract)|NewArgsContract]), - FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]), - InfSig = t_inf(t_fun(SigArgs, SigRange), - t_fun(BifArgs, BifRange(BifArgs))), - FailReason = apply_fail_reason(FailedSig, FailedBif, FailedContract), - Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig, - Contr, CArgs, State1, FailReason), - WarnType = case Msg of - {call, _} -> ?WARN_FAILING_CALL; - {apply, _} -> ?WARN_FAILING_CALL; - {call_with_opaque, _} -> ?WARN_OPAQUE; - {call_without_opaque, _} -> ?WARN_OPAQUE; - {opaque_type_test, _} -> ?WARN_OPAQUE - end, - state__add_warning(State1, WarnType, Tree, Msg); + case t_is_none(RetWithoutLocal) andalso + not t_is_none(RetWithoutContr) andalso + not any_none(NewArgTypes) of + true -> + {value, C1} = Contr, + Contract = dialyzer_contracts:contract_to_string(C1), + {M1, F1, A1} = state__lookup_name(Fun, State), + ArgStrings = format_args(Args, ArgTypes, State), + CRet = erl_types:t_to_string(RetWithoutContr), + %% This Msg will be post_processed by dialyzer_succ_typings + Msg = + {contract_range, [Contract, M1, F1, A1, ArgStrings, CRet]}, + state__add_warning(State1, ?WARN_CONTRACT_RANGE, Tree, Msg); + false -> + FailedSig = any_none(NewArgsSig), + FailedContract = + any_none([CRange(TmpArgsContract)|NewArgsContract]), + FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]), + InfSig = t_inf(t_fun(SigArgs, SigRange), + t_fun(BifArgs, BifRange(BifArgs))), + FailReason = + apply_fail_reason(FailedSig, FailedBif, FailedContract), + Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig, + Contr, CArgs, State1, FailReason), + WarnType = case Msg of + {call, _} -> ?WARN_FAILING_CALL; + {apply, _} -> ?WARN_FAILING_CALL; + {call_with_opaque, _} -> ?WARN_OPAQUE; + {call_without_opaque, _} -> ?WARN_OPAQUE; + {opaque_type_test, _} -> ?WARN_OPAQUE + end, + state__add_warning(State1, WarnType, Tree, Msg) + end; false -> State1 end, State3 = @@ -725,15 +749,15 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], remote -> add_bif_warnings(Fun, NewArgTypes, Tree, State2) end, - NewAccArgTypes = + NewAccArgTypes = case FailedConj of true -> AccArgTypes; false -> [t_sup(X, Y) || {X, Y} <- lists:zip(NewArgTypes, AccArgTypes)] end, NewAccRet = t_sup(AccRet, t_inf(RetWithoutLocal, LocalRet, opaque)), - handle_apply_or_call(Left, Args, ArgTypes, Map, Tree, + handle_apply_or_call(Left, Args, ArgTypes, Map, Tree, State3, NewAccArgTypes, NewAccRet); -handle_apply_or_call([], Args, _ArgTypes, Map, _Tree, State, +handle_apply_or_call([], Args, _ArgTypes, Map, _Tree, State, AccArgTypes, AccRet) -> NewMap = enter_type_lists(Args, AccArgTypes, Map), {State, NewMap, AccRet}. @@ -745,13 +769,13 @@ apply_fail_reason(FailedSig, FailedBif, FailedContract) -> true -> both end. -get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, +get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, Sig, Contract, ContrArgs, State, FailReason) -> ArgStrings = format_args(Args, ArgTypes, State), ContractInfo = case Contract of {value, #contract{} = C} -> - {dialyzer_contracts:is_overloaded(C), + {dialyzer_contracts:is_overloaded(C), dialyzer_contracts:contract_to_string(C)}; none -> {false, none} end, @@ -765,7 +789,7 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, {M, F, _A} -> case is_opaque_type_test_problem(Fun, NewArgTypes, State) of true -> - [Opaque] = NewArgTypes, + [Opaque] = NewArgTypes, {opaque_type_test, [atom_to_list(F), erl_types:t_to_string(Opaque)]}; false -> SigArgs = t_fun_args(Sig), @@ -789,7 +813,7 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, {call_without_opaque, [M, F, ArgStrings, ExpectedTriples]}; false -> %% there is a structured term clash in some argument {call, [M, F, ArgStrings, - ArgNs, FailReason, + ArgNs, FailReason, format_sig_args(Sig, State), format_type(t_fun_range(Sig), State), ContractInfo]} @@ -797,8 +821,8 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, end end; Label when is_integer(Label) -> - {apply, [ArgStrings, - ArgNs, FailReason, + {apply, [ArgStrings, + ArgNs, FailReason, format_sig_args(Sig, State), format_type(t_fun_range(Sig), State), ContractInfo]} @@ -826,7 +850,7 @@ is_opaque_type_test_problem(Fun, ArgTypes, State) -> FN =:= is_number; FN =:= is_pid; FN =:= is_port; FN =:= is_reference; FN =:= is_tuple -> [Type] = ArgTypes, - erl_types:t_is_opaque(Type) andalso + erl_types:t_is_opaque(Type) andalso not lists:member(Type, State#state.opaques); _ -> false end. @@ -1043,7 +1067,7 @@ handle_cons(Tree, Map, State) -> Tl = cerl:cons_tl(Tree), {State1, Map1, HdType} = traverse(Hd, Map, State), {State2, Map2, TlType} = traverse(Tl, Map1, State1), - State3 = + State3 = case t_is_none(t_inf(TlType, t_list())) of true -> Msg = {improper_list_constr, [format_type(TlType, State2)]}, @@ -1088,7 +1112,7 @@ handle_let(Tree, Map, #state{callgraph = Callgraph, races = Races} = State) -> case cerl:is_literal(Mod) andalso cerl:concrete(Mod) =:= ets andalso cerl:is_literal(Name) andalso - cerl:concrete(Name) =:= new of + cerl:concrete(Name) =:= new of true -> NewTable = dialyzer_races:get_new_table(State1#state.races), renew_public_tables(Vars, NewTable, @@ -1112,7 +1136,7 @@ handle_module(Tree, Map, State) -> %% By not including the variables in scope we can assure that we %% will get the current function type when using the variables. Defs = cerl:module_defs(Tree), - PartFun = fun({_Var, Fun}) -> + PartFun = fun({_Var, Fun}) -> state__is_escaping(get_label(Fun), State) end, {Defs1, Defs2} = lists:partition(PartFun, Defs), @@ -1143,12 +1167,12 @@ handle_receive(Tree, Map, RaceListSize + 1, State); false -> State end, - {MapList, State2, ReceiveType} = + {MapList, State2, ReceiveType} = handle_clauses(Clauses, ?no_arg, t_any(), t_any(), State1, [], Map, [], []), Map1 = join_maps(MapList, Map), {State3, Map2, TimeoutType} = traverse(Timeout, Map1, State2), - case (t_is_atom(TimeoutType) andalso + case (t_is_atom(TimeoutType) andalso (t_atom_vals(TimeoutType) =:= ['infinity'])) of true -> {State3, Map2, ReceiveType}; @@ -1168,17 +1192,17 @@ handle_try(Tree, Map, State) -> Vars = cerl:try_vars(Tree), Body = cerl:try_body(Tree), Handler = cerl:try_handler(Tree), - {State1, Map1, ArgType} = traverse(Arg, Map, State), + {State1, Map1, ArgType} = traverse(Arg, Map, State), Map2 = mark_as_fresh(Vars, Map1), {SuccState, SuccMap, SuccType} = case bind_pat_vars(Vars, t_to_tlist(ArgType), [], Map2, State1) of {error, _, _, _, _} -> {State1, map__new(), t_none()}; {SuccMap1, VarTypes} -> - %% Try to bind the argument. Will only succeed if + %% Try to bind the argument. Will only succeed if %% it is a simple structured term. SuccMap2 = - case bind_pat_vars_reverse([Arg], [t_product(VarTypes)], [], + case bind_pat_vars_reverse([Arg], [t_product(VarTypes)], [], SuccMap1, State1) of {error, _, _, _, _} -> SuccMap1; {SM, _} -> SM @@ -1212,10 +1236,10 @@ handle_tuple(Tree, Map, State) -> RecFields = t_tuple_args(RecStruct), case bind_pat_vars(Elements, RecFields, [], Map1, State1) of {error, _, ErrorPat, ErrorType, _} -> - Msg = {record_constr, + Msg = {record_constr, [TagVal, format_patterns(ErrorPat), format_type(ErrorType, State1)]}, - State2 = state__add_warning(State1, ?WARN_MATCHING, + State2 = state__add_warning(State1, ?WARN_MATCHING, Tree, Msg), {State2, Map1, t_none()}; {Map2, _ETypes} -> @@ -1224,26 +1248,24 @@ handle_tuple(Tree, Map, State) -> _ -> case state__lookup_record(TagVal, length(Left), State1) of error -> {State1, Map1, TupleType}; - {ok, Prototype} -> - %% io:format("In handle_tuple:\n Prototype = ~p\n", [Prototype]), - InfTupleType = t_inf(Prototype, TupleType), - %% io:format(" TupleType = ~p,\n Inf = ~p\n", [TupleType, InfTupleType]), + {ok, RecType} -> + InfTupleType = t_inf(RecType, TupleType), case t_is_none(InfTupleType) of true -> - Msg = {record_constr, - [format_type(TupleType, State1), TagVal]}, - State2 = state__add_warning(State1, ?WARN_MATCHING, + RecC = format_type(TupleType, State1), + FieldDiffs = format_field_diffs(TupleType, State1), + Msg = {record_constr, [RecC, FieldDiffs]}, + State2 = state__add_warning(State1, ?WARN_MATCHING, Tree, Msg), {State2, Map1, t_none()}; false -> - case bind_pat_vars(Elements, t_tuple_args(Prototype), + case bind_pat_vars(Elements, t_tuple_args(RecType), [], Map1, State1) of {error, bind, ErrorPat, ErrorType, _} -> - %% io:format("error\n", []), - Msg = {record_constr, + Msg = {record_constr, [TagVal, format_patterns(ErrorPat), format_type(ErrorType, State1)]}, - State2 = state__add_warning(State1, ?WARN_MATCHING, + State2 = state__add_warning(State1, ?WARN_MATCHING, Tree, Msg), {State2, Map1, t_none()}; {Map2, ETypes} -> @@ -1305,7 +1327,7 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, handle_clauses([], _Arg, _ArgType, _OrigArgType, #state{callgraph = Callgraph, races = Races} = State, CaseTypes, _MapIn, Acc, ClauseAcc) -> - State1 = + State1 = case dialyzer_callgraph:get_race_detection(Callgraph) andalso dialyzer_races:get_race_analysis(Races) of true -> @@ -1313,7 +1335,7 @@ handle_clauses([], _Arg, _ArgType, _OrigArgType, [dialyzer_races:end_case_new(ClauseAcc)| dialyzer_races:get_race_list(Races)], dialyzer_races:get_race_list_size(Races) + 1, State); - false -> State + false -> State end, {lists:reverse(Acc), State1, t_sup(CaseTypes)}. @@ -1324,7 +1346,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, Body = cerl:clause_body(C), RaceDetection = dialyzer_callgraph:get_race_detection(Callgraph), RaceAnalysis = dialyzer_races:get_race_analysis(Races), - State1 = + State1 = case RaceDetection andalso RaceAnalysis of true -> state__renew_fun_args(Pats, State); @@ -1339,7 +1361,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, true -> {error, bind, Pats, ArgType0, ArgType0}; false -> - ArgTypes = + ArgTypes = case t_is_any(ArgType0) of true -> [ArgType0 || _ <- Pats]; false -> t_to_tlist(ArgType0) @@ -1347,19 +1369,20 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, bind_pat_vars(Pats, ArgTypes, [], Map1, State1) end, case BindRes of - {error, BindOrOpaque, NewPats, Type, OpaqueTerm} -> - ?debug("Failed binding pattern: ~s\nto ~s\n", + {error, ErrorType, NewPats, Type, OpaqueTerm} -> + ?debug("Failed binding pattern: ~s\nto ~s\n", [cerl_prettypr:format(C), format_type(ArgType0, State1)]), case state__warning_mode(State1) of false -> {State1, Map, t_none(), ArgType0}; true -> PatString = - case BindOrOpaque of + case ErrorType of bind -> format_patterns(Pats); + record -> format_patterns(Pats); opaque -> format_patterns(NewPats) end, - {Msg, Force} = + {Msg, Force} = case t_is_none(ArgType0) of true -> PatTypes = [PatString, format_type(OrigArgType, State1)], @@ -1375,13 +1398,13 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, {_, _} -> {{pattern_match_cov, PatTypes}, false} end; false -> - %% Try to find out if this is a default clause in a list + %% Try to find out if this is a default clause in a list %% comprehension and supress this. A real Hack(tm) Force0 = case is_compiler_generated(cerl:get_ann(C)) of true -> case Pats of - [Pat] -> + [Pat] -> case cerl:is_c_cons(Pat) of true -> not (cerl:is_c_var(cerl:cons_hd(Pat)) andalso @@ -1396,13 +1419,15 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, false -> true end, - PatTypes = case BindOrOpaque of + PatTypes = case ErrorType of bind -> [PatString, format_type(ArgType0, State1)]; - opaque -> [PatString, format_type(Type, State1), + record -> [PatString, format_type(Type, State1)]; + opaque -> [PatString, format_type(Type, State1), format_type(OpaqueTerm, State1)] - end, - FailedMsg = case BindOrOpaque of + end, + FailedMsg = case ErrorType of bind -> {pattern_match, PatTypes}; + record -> {record_match, PatTypes}; opaque -> {opaque_match, PatTypes} end, {FailedMsg, Force0} @@ -1410,6 +1435,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, WarnType = case Msg of {opaque_match, _} -> ?WARN_OPAQUE; {pattern_match, _} -> ?WARN_MATCHING; + {record_match, _} -> ?WARN_MATCHING; {pattern_match_cov, _} -> ?WARN_MATCHING end, {state__add_warning(State1, WarnType, C, Msg, Force), @@ -1420,9 +1446,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, case Arg =:= ?no_arg of true -> Map2; false -> - %% Try to bind the argument. Will only succeed if + %% Try to bind the argument. Will only succeed if %% it is a simple structured term. - case bind_pat_vars_reverse([Arg], [t_product(PatTypes)], + case bind_pat_vars_reverse([Arg], [t_product(PatTypes)], [], Map2, State1) of {error, _, _, _, _} -> Map2; {NewMap, _} -> NewMap @@ -1436,11 +1462,11 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, t_subtract(t_product(t_to_tlist(ArgType0)), GenType) end, case bind_guard(Guard, Map3, State1) of - {error, Reason} -> - ?debug("Failed guard: ~s\n", + {error, Reason} -> + ?debug("Failed guard: ~s\n", [cerl_prettypr:format(C, [{hook, cerl_typean:pp_hook()}])]), PatString = format_patterns(Pats), - DefaultMsg = + DefaultMsg = case Pats =:= [] of true -> {guard_fail, []}; false -> @@ -1454,6 +1480,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, false -> WarnType = case Msg of {guard_fail, _} -> ?WARN_MATCHING; + {neg_guard_fail, _} -> ?WARN_MATCHING; {opaque_guard, _} -> ?WARN_OPAQUE end, state__add_warning(State1, WarnType, FailGuard, Msg); @@ -1470,7 +1497,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, bind_subst(Arg, Pats, Map) -> case cerl:type(Arg) of - values -> + values -> bind_subst_list(cerl:values_es(Arg), Pats, Map); var -> [Pat] = Pats, @@ -1499,17 +1526,21 @@ bind_subst_list([], [], Map) -> %% bind_pat_vars(Pats, Types, Acc, Map, State) -> - try + try bind_pat_vars(Pats, Types, Acc, Map, State, false) - catch - throw:Error -> Error % Error = {error, bind | opaque, ErrorPats, ErrorType} + catch + throw:Error -> + %% Error = {error, bind | opaque | record, ErrorPats, ErrorType} + Error end. bind_pat_vars_reverse(Pats, Types, Acc, Map, State) -> - try + try bind_pat_vars(Pats, Types, Acc, Map, State, true) - catch - throw:Error -> Error % Error = {error, bind | opaque, ErrorPats, ErrorType} + catch + throw:Error -> + %% Error = {error, bind | opaque | record, ErrorPats, ErrorType} + Error end. bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> @@ -1520,7 +1551,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> AliasPat = cerl:alias_pat(Pat), Var = cerl:alias_var(Pat), Map1 = enter_subst(Var, AliasPat, Map), - {Map2, [PatType]} = bind_pat_vars([AliasPat], [Type], [], + {Map2, [PatType]} = bind_pat_vars([AliasPat], [Type], [], Map1, State, Rev), {enter_type(Var, PatType, Map2), PatType}; binary -> @@ -1541,18 +1572,18 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> cons -> Cons = t_inf(Type, t_cons()), case t_is_none(Cons) of - true -> + true -> bind_opaque_pats(t_cons(), Type, Pat, Map, State, Rev); false -> - {Map1, [HdType, TlType]} = + {Map1, [HdType, TlType]} = bind_pat_vars([cerl:cons_hd(Pat), cerl:cons_tl(Pat)], - [t_cons_hd(Cons), t_cons_tl(Cons)], + [t_cons_hd(Cons), t_cons_tl(Cons)], [], Map, State, Rev), {Map1, t_cons(HdType, TlType)} end; literal -> Literal = literal_type(Pat), - LiteralOrOpaque = + LiteralOrOpaque = case t_opaque_match_atom(Literal, State#state.opaques) of [Opaque] -> Opaque; _ -> Literal @@ -1564,18 +1595,21 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> end; tuple -> Es = cerl:tuple_es(Pat), - Prototype = + {TypedRecord, Prototype} = case Es of - [] -> t_tuple([]); + [] -> {false, t_tuple([])}; [Tag|Left] -> case cerl:is_c_atom(Tag) of true -> TagAtom = cerl:atom_val(Tag), case state__lookup_record(TagAtom, length(Left), State) of - error -> t_tuple(length(Es)); - {ok, Record} -> Record + error -> {false, t_tuple(length(Es))}; + {ok, Record} -> + [_Head|AnyTail] = [t_any() || _ <- Es], + UntypedRecord = t_tuple([t_atom(TagAtom)|AnyTail]), + {not erl_types:t_is_equal(Record, UntypedRecord), Record} end; - false -> t_tuple(length(Es)) + false -> {false, t_tuple(length(Es))} end end, Tuple = t_inf(Prototype, Type), @@ -1585,10 +1619,10 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> false -> SubTuples = t_tuple_subtypes(Tuple), %% Need to call the top function to get the try-catch wrapper - Results = + Results = case Rev of true -> - [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple), [], + [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple), [], Map, State) || SubTuple <- SubTuples]; false -> @@ -1600,7 +1634,11 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> bind_error([Pat], Tuple, Opaque, opaque); false -> case [M || {M, _} <- Results, M =/= error] of - [] -> bind_error([Pat], Tuple, t_none(), bind); + [] -> + case TypedRecord of + true -> bind_error([Pat], Tuple, Prototype, record); + false -> bind_error([Pat], Tuple, t_none(), bind) + end; Maps -> Map1 = join_maps(Maps, Map), TupleType = t_sup([t_tuple(EsTypes) @@ -1632,12 +1670,12 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> end, %% Must do inf when binding args to pats. Vars in pats are fresh. VarType2 = t_inf(VarType1, Type), - VarType3 = + VarType3 = case Opaques =/= [] of true -> case t_opaque_match_record(VarType2, Opaques) of [OpaqueRec] -> OpaqueRec; - _ -> + _ -> case t_opaque_match_atom(VarType2, Opaques) of [OpaqueAtom] -> OpaqueAtom; _ -> VarType2 @@ -1648,9 +1686,9 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) -> case t_is_none(VarType3) of true -> case t_find_opaque_mismatch(VarType1, Type) of - {ok, T1, T2} -> + {ok, T1, T2} -> bind_error([Pat], T1, T2, opaque); - error -> + error -> bind_error([Pat], Type, t_none(), bind) end; false -> @@ -1745,17 +1783,17 @@ bind_opaque_pats(GenType, Type, Pat, Map, State, Rev) -> bind_guard(Guard, Map, State) -> try bind_guard(Guard, Map, dict:new(), pos, State) of - {Map1, _Type} -> Map1 + {Map1, _Type} -> Map1 catch throw:{fail, Warning} -> {error, Warning}; throw:{fatal_fail, Warning} -> {error, Warning} end. bind_guard(Guard, Map, Env, Eval, State) -> - ?debug("Handling ~w guard: ~s\n", + ?debug("Handling ~w guard: ~s\n", [Eval, cerl_prettypr:format(Guard, [{noann, true}])]), case cerl:type(Guard) of - binary -> + binary -> {Map, t_binary()}; 'case' -> Arg = cerl:case_arg(Guard), @@ -1793,10 +1831,10 @@ bind_guard(Guard, Map, Env, Eval, State) -> var -> ?debug("Looking for var(~w)...", [cerl_trees:get_label(Guard)]), case dict:find(get_label(Guard), Env) of - error -> + error -> ?debug("Did not find it\n", []), Type = lookup_type(Guard, Map), - Constr = + Constr = case Eval of pos -> t_atom(true); neg -> t_atom(false); @@ -1804,7 +1842,7 @@ bind_guard(Guard, Map, Env, Eval, State) -> end, Inf = t_inf(Constr, Type), {enter_type(Guard, Inf, Map), Inf}; - {ok, Tree} -> + {ok, Tree} -> ?debug("Found it\n", []), {Map1, Type} = bind_guard(Tree, Map, Env, Eval, State), {enter_type(Guard, Type, Map1), Type} @@ -1827,7 +1865,7 @@ handle_guard_call(Guard, Map, Env, Eval, State) -> handle_guard_type_test(Guard, F, Map, Env, Eval, State); {erlang, is_function, 2} -> handle_guard_is_function(Guard, Map, Env, Eval, State); - MFA when (MFA =:= {erlang, internal_is_record, 3}) or + MFA when (MFA =:= {erlang, internal_is_record, 3}) or (MFA =:= {erlang, is_record, 3}) -> handle_guard_is_record(Guard, Map, Env, Eval, State); {erlang, '=:=', 2} -> @@ -1840,7 +1878,7 @@ handle_guard_call(Guard, Map, Env, Eval, State) -> handle_guard_or(Guard, Map, Env, Eval, State); {erlang, 'not', 1} -> handle_guard_not(Guard, Map, Env, Eval, State); - {erlang, Comp, 2} when Comp =:= '<'; Comp =:= '=<'; + {erlang, Comp, 2} when Comp =:= '<'; Comp =:= '=<'; Comp =:= '>'; Comp =:= '>=' -> handle_guard_comp(Guard, Comp, Map, Env, Eval, State); _ -> @@ -1866,8 +1904,8 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) -> true -> %% Is this an error-bif? case t_is_none(erl_bif_types:type(M, F, A)) of - true -> signal_guard_fail(Guard, As, State); - false -> signal_guard_fatal_fail(Guard, As, State) + true -> signal_guard_fail(Eval, Guard, As, State); + false -> signal_guard_fatal_fail(Eval, Guard, As, State) end; false -> BifArgs = case erl_bif_types:arg_types(M, F, A) of @@ -1875,7 +1913,7 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) -> List -> List end, Map2 = enter_type_lists(Args, t_inf_lists(BifArgs, As0, Mode), Map1), - Ret = + Ret = case Eval of pos -> t_inf(t_atom(true), BifRet); neg -> t_inf(t_atom(false), BifRet); @@ -1884,7 +1922,7 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) -> case t_is_none(Ret) of true -> case Eval =:= pos of - true -> signal_guard_fail(Guard, As, State); + true -> signal_guard_fail(Eval, Guard, As, State); false -> throw({fail, none}) end; false -> {Map2, Ret} @@ -1892,14 +1930,14 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) -> end. handle_guard_type_test(Guard, F, Map, Env, Eval, State) -> - [Arg] = cerl:call_args(Guard), + [Arg] = cerl:call_args(Guard), {Map1, ArgType} = bind_guard(Arg, Map, Env, dont_know, State), case bind_type_test(Eval, F, ArgType, State) of - error -> + error -> ?debug("Type test: ~w failed\n", [F]), - signal_guard_fail(Guard, [ArgType], State); - {ok, NewArgType, Ret} -> - ?debug("Type test: ~w succeeded, NewType: ~s, Ret: ~s\n", + signal_guard_fail(Eval, Guard, [ArgType], State); + {ok, NewArgType, Ret} -> + ?debug("Type test: ~w succeeded, NewType: ~s, Ret: ~s\n", [F, t_to_string(NewArgType), t_to_string(Ret)]), {enter_type(Arg, NewArgType, Map1), Ret} end. @@ -1930,13 +1968,13 @@ bind_type_test(Eval, TypeTest, ArgType, State) -> end; neg -> case Mode of - opaque -> + opaque -> Struct = erl_types:t_opaque_structure(ArgType), case t_is_none(t_subtract(Struct, Type)) of true -> error; false -> {ok, ArgType, t_atom(false)} end; - structured -> + structured -> Sub = t_subtract(ArgType, Type), case t_is_none(Sub) of true -> error; @@ -1960,21 +1998,22 @@ handle_guard_comp(Guard, Comp, Map, Env, Eval, State) -> true when Eval =:= pos -> {Map, t_atom(true)}; true when Eval =:= dont_know -> {Map, t_atom(true)}; true when Eval =:= neg -> {Map, t_atom(true)}; - false when Eval =:= pos -> signal_guard_fail(Guard, ArgTypes, State); + false when Eval =:= pos -> + signal_guard_fail(Eval, Guard, ArgTypes, State); false when Eval =:= dont_know -> {Map, t_atom(false)}; false when Eval =:= neg -> {Map, t_atom(false)} end; {literal, var} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) -> case bind_comp_literal_var(Arg1, Arg2, Type2, Comp, Map1) of - error -> signal_guard_fail(Guard, ArgTypes, State); + error -> signal_guard_fail(Eval, Guard, ArgTypes, State); {ok, NewMap} -> {NewMap, t_atom(true)} end; {var, literal} when IsInt1 andalso IsInt2 andalso (Eval =:= pos) -> case bind_comp_literal_var(Arg2, Arg1, Type1, invert_comp(Comp), Map1) of - error -> signal_guard_fail(Guard, ArgTypes, State); + error -> signal_guard_fail(Eval, Guard, ArgTypes, State); {ok, NewMap} -> {NewMap, t_atom(true)} end; - {_, _} -> + {_, _} -> handle_guard_gen_fun({erlang, Comp, 2}, Guard, Map, Env, Eval, State) end. @@ -2011,7 +2050,7 @@ handle_guard_is_function(Guard, Map, Env, Eval, State) -> [FunType0, ArityType0] = ArgTypes0, ArityType = t_inf(ArityType0, t_integer()), case t_is_none(ArityType) of - true -> signal_guard_fail(Guard, ArgTypes0, State); + true -> signal_guard_fail(Eval, Guard, ArgTypes0, State); false -> FunTypeConstr = case t_number_vals(ArityType) of @@ -2021,9 +2060,9 @@ handle_guard_is_function(Guard, Map, Env, Eval, State) -> end, FunType = t_inf(FunType0, FunTypeConstr), case t_is_none(FunType) of - true -> + true -> case Eval of - pos -> signal_guard_fail(Guard, ArgTypes0, State); + pos -> signal_guard_fail(Eval, Guard, ArgTypes0, State); neg -> {Map1, t_atom(false)}; dont_know -> {Map1, t_atom(false)} end; @@ -2057,16 +2096,16 @@ handle_guard_is_record(Guard, Map, Env, Eval, State) -> end, Type = t_inf(NewTupleType, RecType, Mode), case t_is_none(Type) of - true -> + true -> case Eval of - pos -> signal_guard_fail(Guard, - [RecType, t_from_term(Tag), + pos -> signal_guard_fail(Eval, Guard, + [RecType, t_from_term(Tag), t_from_term(Arity)], State); neg -> {Map1, t_atom(false)}; dont_know -> {Map1, t_atom(false)} end; - false -> + false -> case Eval of pos -> {enter_type(Rec, Type, Map1), t_atom(true)}; neg -> {Map1, t_atom(false)}; @@ -2079,20 +2118,23 @@ handle_guard_eq(Guard, Map, Env, Eval, State) -> case {cerl:type(Arg1), cerl:type(Arg2)} of {literal, literal} -> case cerl:concrete(Arg1) =:= cerl:concrete(Arg2) of - true -> - if + true -> + if Eval =:= pos -> {Map, t_atom(true)}; - Eval =:= neg -> throw({fail, none}); + Eval =:= neg -> + ArgTypes = [t_from_term(cerl:concrete(Arg1)), + t_from_term(cerl:concrete(Arg2))], + signal_guard_fail(Eval, Guard, ArgTypes, State); Eval =:= dont_know -> {Map, t_atom(true)} end; false -> - if + if Eval =:= neg -> {Map, t_atom(false)}; Eval =:= dont_know -> {Map, t_atom(false)}; - Eval =:= pos -> + Eval =:= pos -> ArgTypes = [t_from_term(cerl:concrete(Arg1)), t_from_term(cerl:concrete(Arg2))], - signal_guard_fail(Guard, ArgTypes, State) + signal_guard_fail(Eval, Guard, ArgTypes, State) end end; {literal, _} when Eval =:= pos -> @@ -2137,17 +2179,20 @@ handle_guard_eqeq(Guard, Map, Env, Eval, State) -> {literal, literal} -> case cerl:concrete(Arg1) =:= cerl:concrete(Arg2) of true -> - if Eval =:= neg -> throw({fail, none}); + if Eval =:= neg -> + ArgTypes = [t_from_term(cerl:concrete(Arg1)), + t_from_term(cerl:concrete(Arg2))], + signal_guard_fail(Eval, Guard, ArgTypes, State); Eval =:= pos -> {Map, t_atom(true)}; Eval =:= dont_know -> {Map, t_atom(true)} end; false -> if Eval =:= neg -> {Map, t_atom(false)}; Eval =:= dont_know -> {Map, t_atom(false)}; - Eval =:= pos -> + Eval =:= pos -> ArgTypes = [t_from_term(cerl:concrete(Arg1)), t_from_term(cerl:concrete(Arg2))], - signal_guard_fail(Guard, ArgTypes, State) + signal_guard_fail(Eval, Guard, ArgTypes, State) end end; {literal, _} when Eval =:= pos -> @@ -2161,7 +2206,7 @@ handle_guard_eqeq(Guard, Map, Env, Eval, State) -> bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) -> {Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State), {Map2, Type2} = bind_guard(Arg2, Map1, Env, dont_know, State), - ?debug("Types are:~s =:= ~s\n", [t_to_string(Type1), + ?debug("Types are:~s =:= ~s\n", [t_to_string(Type1), t_to_string(Type2)]), Inf = t_inf(Type1, Type2), case t_is_none(Inf) of @@ -2169,7 +2214,7 @@ bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) -> case Eval of neg -> {Map2, t_atom(false)}; dont_know -> {Map2, t_atom(false)}; - pos -> signal_guard_fail(Guard, [Type1, Type2], State) + pos -> signal_guard_fail(Eval, Guard, [Type1, Type2], State) end; false -> case Eval of @@ -2196,29 +2241,29 @@ bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) -> end. bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) -> - %% Assumes positive evaluation + Eval = dont_know, case cerl:concrete(Arg1) of true -> {_, Type} = MT = bind_guard(Arg2, Map, Env, pos, State), case t_is_atom(true, Type) of true -> MT; - false -> - {_, Type0} = bind_guard(Arg2, Map, Env, dont_know, State), - signal_guard_fail(Guard, [Type0, t_atom(true)], State) + false -> + {_, Type0} = bind_guard(Arg2, Map, Env, Eval, State), + signal_guard_fail(Eval, Guard, [Type0, t_atom(true)], State) end; - false -> + false -> {Map1, Type} = bind_guard(Arg2, Map, Env, neg, State), case t_is_atom(false, Type) of true -> {Map1, t_atom(true)}; - false -> - {_, Type0} = bind_guard(Arg2, Map, Env, dont_know, State), - signal_guard_fail(Guard, [Type0, t_atom(true)], State) + false -> + {_, Type0} = bind_guard(Arg2, Map, Env, Eval, State), + signal_guard_fail(Eval, Guard, [Type0, t_atom(false)], State) end; Term -> LitType = t_from_term(Term), - {Map1, Type} = bind_guard(Arg2, Map, Env, dont_know, State), + {Map1, Type} = bind_guard(Arg2, Map, Env, Eval, State), case t_is_subtype(LitType, Type) of - false -> signal_guard_fail(Guard, [Type, LitType], State); + false -> signal_guard_fail(Eval, Guard, [Type, LitType], State); true -> case cerl:is_c_var(Arg2) of true -> {enter_type(Arg2, LitType, Map1), t_atom(true)}; @@ -2233,97 +2278,118 @@ handle_guard_and(Guard, Map, Env, Eval, State) -> pos -> {Map1, Type1} = bind_guard(Arg1, Map, Env, Eval, State), case t_is_atom(true, Type1) of - false -> throw({fail, none}); + false -> signal_guard_fail(Eval, Guard, [Type1, t_any()], State); true -> {Map2, Type2} = bind_guard(Arg2, Map1, Env, Eval, State), case t_is_atom(true, Type2) of - false -> throw({fail, none}); + false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State); true -> {Map2, t_atom(true)} end end; neg -> - {Map1, Type1} = + {Map1, Type1} = try bind_guard(Arg1, Map, Env, neg, State) catch throw:{fail, _} -> bind_guard(Arg2, Map, Env, pos, State) end, - {Map2, Type2} = - try bind_guard(Arg1, Map, Env, neg, State) - catch throw:{fail, _} -> bind_guard(Arg2, Map, Env, pos, State) + {Map2, Type2} = + try bind_guard(Arg2, Map, Env, neg, State) + catch throw:{fail, _} -> bind_guard(Arg1, Map, Env, pos, State) end, case t_is_atom(false, Type1) orelse t_is_atom(false, Type2) of true -> {join_maps([Map1, Map2], Map), t_atom(false)}; - false -> throw({fail, none}) + false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State) end; dont_know -> - True = t_atom(true), {Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State), - case t_is_none(t_inf(Type1, t_boolean())) of - true -> throw({fail, none}); + {Map2, Type2} = bind_guard(Arg2, Map, Env, dont_know, State), + Bool1 = t_inf(Type1, t_boolean()), + Bool2 = t_inf(Type2, t_boolean()), + case t_is_none(Bool1) orelse t_is_none(Bool2) of + true -> throw({fatal_fail, none}); false -> - {Map2, Type2} = bind_guard(Arg2, Map1, Env, Eval, State), - case t_is_none(t_inf(Type2, t_boolean())) of - true -> throw({fail, none}); - false -> {Map2, True} - end + NewMap = join_maps([Map1, Map2], Map), + NewType = + case {t_atom_vals(Bool1), t_atom_vals(Bool2)} of + {['true'] , ['true'] } -> t_atom(true); + {['false'], _ } -> t_atom(false); + {_ , ['false']} -> t_atom(false); + {_ , _ } -> t_boolean() + end, + {NewMap, NewType} end end. handle_guard_or(Guard, Map, Env, Eval, State) -> [Arg1, Arg2] = cerl:call_args(Guard), case Eval of - pos -> - {Map1, Bool1} = + pos -> + {Map1, Bool1} = try bind_guard(Arg1, Map, Env, pos, State) - catch + catch throw:{fail,_} -> bind_guard(Arg1, Map, Env, dont_know, State) end, - {Map2, Bool2} = + {Map2, Bool2} = try bind_guard(Arg2, Map, Env, pos, State) - catch + catch throw:{fail,_} -> bind_guard(Arg2, Map, Env, dont_know, State) end, case ((t_is_atom(true, Bool1) andalso t_is_boolean(Bool2)) - orelse + orelse (t_is_atom(true, Bool2) andalso t_is_boolean(Bool1))) of true -> {join_maps([Map1, Map2], Map), t_atom(true)}; - false -> throw({fail, none}) + false -> signal_guard_fail(Eval, Guard, [Bool1, Bool2], State) end; neg -> {Map1, Type1} = bind_guard(Arg1, Map, Env, neg, State), - case t_is_atom(true, Type1) of - false -> throw({fail, none}); + case t_is_atom(false, Type1) of + false -> signal_guard_fail(Eval, Guard, [Type1, t_any()], State); true -> {Map2, Type2} = bind_guard(Arg2, Map1, Env, neg, State), - case t_is_atom(true, Type2) of - false -> throw({fail, none}); + case t_is_atom(false, Type2) of + false -> signal_guard_fail(Eval, Guard, [Type1, Type2], State); true -> {Map2, t_atom(false)} end end; dont_know -> - {Map1, Bool1} = bind_guard(Arg1, Map, Env, dont_know, State), - {Map2, Bool2} = bind_guard(Arg2, Map, Env, dont_know, State), - case t_is_boolean(Bool1) andalso t_is_boolean(Bool2) of - true -> {join_maps([Map1, Map2], Map), t_sup(Bool1, Bool2)}; - false -> throw({fail, none}) + {Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State), + {Map2, Type2} = bind_guard(Arg2, Map, Env, dont_know, State), + Bool1 = t_inf(Type1, t_boolean()), + Bool2 = t_inf(Type2, t_boolean()), + case t_is_none(Bool1) orelse t_is_none(Bool2) of + true -> throw({fatal_fail, none}); + false -> + NewMap = join_maps([Map1, Map2], Map), + NewType = + case {t_atom_vals(Bool1), t_atom_vals(Bool2)} of + {['false'], ['false']} -> t_atom(false); + {['true'] , _ } -> t_atom(true); + {_ , ['true'] } -> t_atom(true); + {_ , _ } -> t_boolean() + end, + {NewMap, NewType} end end. handle_guard_not(Guard, Map, Env, Eval, State) -> [Arg] = cerl:call_args(Guard), case Eval of - neg -> + neg -> {Map1, Type} = bind_guard(Arg, Map, Env, pos, State), case t_is_atom(true, Type) of true -> {Map1, t_atom(false)}; - false -> throw({fail, none}) + false -> + {_, Type0} = bind_guard(Arg, Map, Env, Eval, State), + signal_guard_fail(Eval, Guard, [Type0], State) end; - pos -> + pos -> {Map1, Type} = bind_guard(Arg, Map, Env, neg, State), case t_is_atom(false, Type) of true -> {Map1, t_atom(true)}; - false -> throw({fail, none}) + false -> + {_, Type0} = bind_guard(Arg, Map, Env, Eval, State), + signal_guard_fail(Eval, Guard, [Type0], State) end; - dont_know -> + dont_know -> {Map1, Type} = bind_guard(Arg, Map, Env, dont_know, State), Bool = t_inf(Type, t_boolean()), case t_is_none(Bool) of @@ -2346,23 +2412,31 @@ bind_guard_list([G|Gs], Map, Env, Eval, State, Acc) -> bind_guard_list([], Map, _Env, _Eval, _State, Acc) -> {Map, lists:reverse(Acc)}. --spec signal_guard_fail(cerl:c_call(), [erl_types:erl_type()], state()) -> - no_return(). +-type eval() :: 'pos' | 'neg' | 'dont_know'. -signal_guard_fail(Guard, ArgTypes, State) -> +-spec signal_guard_fail(eval(), cerl:c_call(), [erl_types:erl_type()], + state()) -> no_return(). + +signal_guard_fail(Eval, Guard, ArgTypes, State) -> Args = cerl:call_args(Guard), F = cerl:atom_val(cerl:call_name(Guard)), MFA = {cerl:atom_val(cerl:call_module(Guard)), F, length(Args)}, Msg = case is_infix_op(MFA) of - true -> + true -> [ArgType1, ArgType2] = ArgTypes, [Arg1, Arg2] = Args, - {guard_fail, [format_args_1([Arg1], [ArgType1], State), - atom_to_list(F), - format_args_1([Arg2], [ArgType2], State)]}; + Kind = + case Eval of + neg -> neg_guard_fail; + pos -> guard_fail; + dont_know -> guard_fail + end, + {Kind, [format_args_1([Arg1], [ArgType1], State), + atom_to_list(F), + format_args_1([Arg2], [ArgType2], State)]}; false -> - mk_guard_msg(F, Args, ArgTypes, State) + mk_guard_msg(Eval, F, Args, ArgTypes, State) end, throw({fail, {Guard, Msg}}). @@ -2377,26 +2451,31 @@ is_infix_op({erlang, '>=', 2}) -> true; is_infix_op({M, F, A}) when is_atom(M), is_atom(F), is_integer(A), 0 =< A, A =< 255 -> false. --spec signal_guard_fatal_fail(cerl:c_call(), [erl_types:erl_type()], state()) -> - no_return(). +-spec signal_guard_fatal_fail(eval(), cerl:c_call(), [erl_types:erl_type()], + state()) -> no_return(). -signal_guard_fatal_fail(Guard, ArgTypes, State) -> - Args = cerl:call_args(Guard), +signal_guard_fatal_fail(Eval, Guard, ArgTypes, State) -> + Args = cerl:call_args(Guard), F = cerl:atom_val(cerl:call_name(Guard)), - Msg = mk_guard_msg(F, Args, ArgTypes, State), + Msg = mk_guard_msg(Eval, F, Args, ArgTypes, State), throw({fatal_fail, {Guard, Msg}}). -mk_guard_msg(F, Args, ArgTypes, State) -> +mk_guard_msg(Eval, F, Args, ArgTypes, State) -> FArgs = [F, format_args(Args, ArgTypes, State)], case any_has_opaque_subtype(ArgTypes) of true -> {opaque_guard, FArgs}; - false -> {guard_fail, FArgs} + false -> + case Eval of + neg -> {neg_guard_fail, FArgs}; + pos -> {guard_fail, FArgs}; + dont_know -> {guard_fail, FArgs} + end end. - + bind_guard_case_clauses(Arg, Clauses, Map, Env, Eval, State) -> Clauses1 = filter_fail_clauses(Clauses), {GenMap, GenArgType} = bind_guard(Arg, Map, Env, dont_know, State), - bind_guard_case_clauses(GenArgType, GenMap, Arg, Clauses1, Map, Env, Eval, + bind_guard_case_clauses(GenArgType, GenMap, Arg, Clauses1, Map, Env, Eval, t_none(), [], State). filter_fail_clauses([Clause|Left]) -> @@ -2413,7 +2492,7 @@ filter_fail_clauses([Clause|Left]) -> filter_fail_clauses([]) -> []. -bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], +bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], Map, Env, Eval, AccType, AccMaps, State) -> Pats = cerl:clause_pats(Clause), {NewMap0, ArgType} = @@ -2427,7 +2506,7 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], false -> bind_guard(ArgExpr, Map, Env, neg, State); _ -> {GenMap, GenArgType} end - catch + catch throw:{fail, _} -> {none, GenArgType} end; false -> @@ -2457,7 +2536,7 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], NewGenArgType = t_subtract(GenArgType, GenPatType), case (NewMap1 =:= none) orelse t_is_none(GenArgType) of true -> - bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, + bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, Eval, AccType, AccMaps, State); false -> {NewAccType, NewAccMaps} = @@ -2467,15 +2546,15 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], true -> throw({fail, none}); false -> ok end, - {NewMap3, CType} = bind_guard(cerl:clause_body(Clause), NewMap2, + {NewMap3, CType} = bind_guard(cerl:clause_body(Clause), NewMap2, Env, Eval, State), case Eval of - pos -> + pos -> case t_is_atom(true, CType) of true -> ok; false -> throw({fail, none}) end; - neg -> + neg -> case t_is_atom(false, CType) of true -> ok; false -> throw({fail, none}) @@ -2487,10 +2566,10 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left], catch throw:{fail, _What} -> {AccType, AccMaps} end, - bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, + bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env, Eval, NewAccType, NewAccMaps, State) end; -bind_guard_case_clauses(_GenArgType, _GenMap, _ArgExpr, [], Map, _Env, _Eval, +bind_guard_case_clauses(_GenArgType, _GenMap, _ArgExpr, [], Map, _Env, _Eval, AccType, AccMaps, _State) -> case t_is_none(AccType) of true -> throw({fail, none}); @@ -2576,7 +2655,7 @@ enter_type(Key, Val, {Map, Subst} = MS) -> enter_subst(Key, Val, {Map, Subst} = MS) -> KeyLabel = get_label(Key), case cerl:is_literal(Val) of - true -> + true -> NewMap = dict:store(KeyLabel, literal_type(Val), Map), {NewMap, Subst}; false -> @@ -2598,13 +2677,13 @@ enter_subst(Key, Val, {Map, Subst} = MS) -> end end. -lookup_type(Key, {Map, Subst}) -> +lookup_type(Key, {Map, Subst}) -> lookup(Key, Map, Subst, t_none()). lookup(Key, Map, Subst, AnyNone) -> case cerl:is_literal(Key) of true -> literal_type(Key); - false -> + false -> Label = get_label(Key), case dict:find(Label, Subst) of {ok, NewKey} -> lookup(NewKey, Map, Subst, AnyNone); @@ -2669,7 +2748,7 @@ get_label(T) -> t_is_simple(ArgType) -> t_is_atom(ArgType) orelse t_is_number(ArgType) orelse t_is_port(ArgType) - orelse t_is_pid(ArgType) orelse t_is_reference(ArgType) + orelse t_is_pid(ArgType) orelse t_is_reference(ArgType) orelse t_is_nil(ArgType). %% t_is_structured(ArgType) -> @@ -2687,8 +2766,8 @@ is_call_to_send(Tree) -> Mod = cerl:call_module(Tree), Name = cerl:call_name(Tree), Arity = cerl:call_arity(Tree), - cerl:is_c_atom(Mod) - andalso cerl:is_c_atom(Name) + cerl:is_c_atom(Mod) + andalso cerl:is_c_atom(Name) andalso (cerl:atom_val(Name) =:= '!') andalso (cerl:atom_val(Mod) =:= erlang) andalso (Arity =:= 2) @@ -2714,7 +2793,7 @@ filter_match_fail([Clause] = Cls) -> filter_match_fail([H|T]) -> [H|filter_match_fail(T)]; filter_match_fail([]) -> - %% This can actually happen, for example in + %% This can actually happen, for example in %% receive after 1 -> ok end []. @@ -2731,22 +2810,22 @@ determine_mode(Type, Opaques) -> %%% =========================================================================== state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) -> + Opaques = erl_types:module_builtin_opaques(Module) ++ + erl_types:t_opaque_from_records(Records), TreeMap = build_tree_map(Tree), Funs = dict:fetch_keys(TreeMap), - FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt), + FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt, Opaques), Work = init_work([get_label(Tree)]), Env = dict:store(top, map__new(), dict:new()), - Opaques = erl_types:module_builtin_opaques(Module) ++ - erl_types:t_opaque_from_records(Records), #state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques, plt = Plt, races = dialyzer_races:new(), records = Records, warning_mode = false, warnings = [], work = Work, tree_map = TreeMap, - module = Module, behaviour_api_info = BehaviourTranslations}. + module = Module, behaviour_api_dict = BehaviourTranslations}. state__mark_fun_as_handled(#state{fun_tab = FunTab} = State, Fun0) -> Fun = get_label(Fun0), case dict:find(Fun, FunTab) of - {ok, {not_handled, Entry}} -> + {ok, {not_handled, Entry}} -> State#state{fun_tab = dict:store(Fun, Entry, FunTab)}; {ok, {_, _}} -> State @@ -2800,7 +2879,7 @@ state__add_warning(State, Tag, Tree, Msg) -> state__add_warning(#state{warning_mode = false} = State, _, _, _, _) -> State; -state__add_warning(#state{warnings = Warnings, warning_mode = true} = State, +state__add_warning(#state{warnings = Warnings, warning_mode = true} = State, Tag, Tree, Msg, Force) -> Ann = cerl:get_ann(Tree), case Force of @@ -2848,7 +2927,7 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab, {Name, Contract} = case dialyzer_callgraph:lookup_name(FunLbl, Callgraph) of error -> {[], none}; - {ok, {_M, F, A} = MFA} -> + {ok, {_M, F, A} = MFA} -> {[F, A], dialyzer_plt:lookup_contract(Plt, MFA)} end, case t_is_none(Ret) of @@ -2866,19 +2945,19 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab, case classify_returns(Fun) of no_match -> Msg = {no_return, [no_match|Name]}, - state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, + state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, Fun, Msg); only_explicit -> Msg = {no_return, [only_explicit|Name]}, - state__add_warning(AccState, ?WARN_RETURN_ONLY_EXIT, + state__add_warning(AccState, ?WARN_RETURN_ONLY_EXIT, Fun, Msg); only_normal -> Msg = {no_return, [only_normal|Name]}, - state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, + state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, Fun, Msg); both -> Msg = {no_return, [both|Name]}, - state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, + state__add_warning(AccState, ?WARN_RETURN_NO_RETURN, Fun, Msg) end; false -> @@ -2916,10 +2995,10 @@ state__lookup_name(Fun, #state{callgraph = Callgraph}) -> state__lookup_record(Tag, Arity, #state{records = Records}) -> case erl_types:lookup_record(Tag, Arity, Records) of - {ok, Fields} -> + {ok, Fields} -> {ok, t_tuple([t_atom(Tag)| [FieldType || {_FieldName, FieldType} <- Fields]])}; - error -> + error -> error end. @@ -2942,15 +3021,15 @@ build_tree_map(Tree) -> end, cerl_trees:fold(Fun, dict:new(), Tree). -init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt) -> +init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> NewDict = dict:store(top, {not_handled, {[], t_none()}}, Dict), - init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt); -init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt) -> + init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques); +init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) -> Arity = cerl:fun_arity(dict:fetch(Fun, TreeMap)), FunEntry = case dialyzer_callgraph:is_escaping(Fun, Callgraph) of true -> - Args = lists:duplicate(Arity, t_any()), + Args = lists:duplicate(Arity, t_any()), case lookup_fun_sig(Fun, Callgraph, Plt) of none -> {Args, t_unit()}; {value, {RetType, _}} -> @@ -2962,8 +3041,8 @@ init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt) -> false -> {lists:duplicate(Arity, t_none()), t_unit()} end, NewDict = dict:store(Fun, {not_handled, FunEntry}, Dict), - init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt); -init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt) -> + init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques); +init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt, _Opaques) -> Dict. state__update_fun_env(Tree, Map, #state{envs = Envs} = State) -> @@ -2990,7 +3069,7 @@ state__all_fun_types(#state{fun_tab = FunTab}) -> dict:map(fun(_Fun, {Args, Ret}) -> t_fun(Args, Ret)end, Tab1). state__fun_type(Fun, #state{fun_tab = FunTab}) -> - Label = + Label = if is_integer(Fun) -> Fun; true -> get_label(Fun) end, @@ -3001,10 +3080,10 @@ state__fun_type(Fun, #state{fun_tab = FunTab}) -> t_fun(A, R) end. -state__update_fun_entry(Tree, ArgTypes, Out0, +state__update_fun_entry(Tree, ArgTypes, Out0, #state{fun_tab=FunTab, callgraph=CG, plt=Plt} = State)-> Fun = get_label(Tree), - Out1 = + Out1 = if Fun =:= top -> Out0; true -> case lookup_fun_sig(Fun, CG, Plt) of @@ -3016,15 +3095,15 @@ state__update_fun_entry(Tree, ArgTypes, Out0, case dict:find(Fun, FunTab) of {ok, {ArgTypes, OldOut}} -> case t_is_equal(OldOut, Out) of - true -> - ?debug("Fixpoint for ~w: ~s\n", - [state__lookup_name(Fun, State), + true -> + ?debug("Fixpoint for ~w: ~s\n", + [state__lookup_name(Fun, State), t_to_string(t_fun(ArgTypes, Out))]), State; false -> NewEntry = {ArgTypes, Out}, - ?debug("New Entry for ~w: ~s\n", - [state__lookup_name(Fun, State), + ?debug("New Entry for ~w: ~s\n", + [state__lookup_name(Fun, State), t_to_string(t_fun(ArgTypes, Out))]), NewFunTab = dict:store(Fun, NewEntry, FunTab), State1 = State#state{fun_tab = NewFunTab}, @@ -3033,8 +3112,8 @@ state__update_fun_entry(Tree, ArgTypes, Out0, {ok, {NewArgTypes, _OldOut}} -> %% Can only happen in self-recursive functions. Only update the out type. NewEntry = {NewArgTypes, Out}, - ?debug("New Entry for ~w: ~s\n", - [state__lookup_name(Fun, State), + ?debug("New Entry for ~w: ~s\n", + [state__lookup_name(Fun, State), t_to_string(t_fun(NewArgTypes, Out))]), NewFunTab = dict:store(Fun, NewEntry, FunTab), State1 = State#state{fun_tab = NewFunTab}, @@ -3053,9 +3132,9 @@ state__add_work_from_fun(Tree, #state{callgraph = Callgraph, MFAList -> LabelList = [dialyzer_callgraph:lookup_label(MFA, Callgraph) || MFA <- MFAList], - %% Must filter the result for results in this module. + %% Must filter the result for results in this module. FilteredList = [L || {ok, L} <- LabelList, dict:is_key(L, TreeMap)], - ?debug("~w: Will try to add:~w\n", + ?debug("~w: Will try to add:~w\n", [state__lookup_name(get_label(Tree), State), MFAList]), lists:foldl(fun(L, AccState) -> state__add_work(L, AccState) @@ -3086,15 +3165,15 @@ state__fun_info(external, #state{}) -> external; state__fun_info({_, _, _} = MFA, #state{plt = PLT}) -> {MFA, - dialyzer_plt:lookup(PLT, MFA), + dialyzer_plt:lookup(PLT, MFA), dialyzer_plt:lookup_contract(PLT, MFA), t_any()}; state__fun_info(Fun, #state{callgraph = CG, fun_tab = FunTab, plt = PLT}) -> {Sig, Contract} = case dialyzer_callgraph:lookup_name(Fun, CG) of - error -> + error -> {dialyzer_plt:lookup(PLT, Fun), none}; - {ok, MFA} -> + {ok, MFA} -> {dialyzer_plt:lookup(PLT, MFA), dialyzer_plt:lookup_contract(PLT, MFA)} end, LocalRet = @@ -3122,18 +3201,18 @@ state__find_apply_return(Tree, #state{callgraph = Callgraph} = State) -> forward_args(Fun, ArgTypes, #state{work = Work, fun_tab = FunTab} = State) -> {OldArgTypes, OldOut, Fixpoint} = case dict:find(Fun, FunTab) of - {ok, {not_handled, {OldArgTypes0, OldOut0}}} -> + {ok, {not_handled, {OldArgTypes0, OldOut0}}} -> {OldArgTypes0, OldOut0, false}; {ok, {OldArgTypes0, OldOut0}} -> - {OldArgTypes0, OldOut0, + {OldArgTypes0, OldOut0, t_is_subtype(t_product(ArgTypes), t_product(OldArgTypes0))} end, case Fixpoint of true -> State; - false -> + false -> NewArgTypes = [t_sup(X, Y) || {X, Y} <- lists:zip(ArgTypes, OldArgTypes)], NewWork = add_work(Fun, Work), - ?debug("~w: forwarding args ~s\n", + ?debug("~w: forwarding args ~s\n", [state__lookup_name(Fun, State), t_to_string(t_product(NewArgTypes))]), NewFunTab = dict:store(Fun, {NewArgTypes, OldOut}, FunTab), @@ -3248,7 +3327,7 @@ get_file([_|Tail]) -> get_file(Tail). is_compiler_generated(Ann) -> lists:member(compiler_generated, Ann) orelse (get_line(Ann) < 1). --spec format_args([term()], [erl_types:erl_type()], state()) -> +-spec format_args([cerl:cerl()], [erl_types:erl_type()], state()) -> nonempty_string(). format_args([], [], _State) -> @@ -3256,9 +3335,6 @@ format_args([], [], _State) -> format_args(ArgList, TypeList, State) -> "(" ++ format_args_1(ArgList, TypeList, State) ++ ")". --spec format_args_1([term(),...], [erl_types:erl_type(),...], state()) -> - string(). - format_args_1([Arg], [Type], State) -> format_arg(Arg) ++ format_type(Type, State); format_args_1([Arg|Args], [Type|Types], State) -> @@ -3291,6 +3367,11 @@ format_arg(Arg) -> format_type(Type, #state{records = R}) -> t_to_string(Type, R). +-spec format_field_diffs(erl_types:erl_type(), state()) -> string(). + +format_field_diffs(RecConstruction, #state{records = R}) -> + erl_types:record_field_diffs_to_string(RecConstruction, R). + -spec format_sig_args(erl_types:erl_type(), state()) -> string(). format_sig_args(Type, #state{records = R}) -> @@ -3298,12 +3379,12 @@ format_sig_args(Type, #state{records = R}) -> case SigArgs of [] -> "()"; [SArg|SArgs] -> - lists:flatten("(" ++ t_to_string(SArg, R) + lists:flatten("(" ++ t_to_string(SArg, R) ++ ["," ++ t_to_string(T, R) || T <- SArgs] ++ ")") end. format_cerl(Tree) -> - cerl_prettypr:format(cerl:set_ann(Tree, []), + cerl_prettypr:format(cerl:set_ann(Tree, []), [{hook, dialyzer_utils:pp_hook()}, {noann, true}, {paper, 100000}, %% These guys strip @@ -3366,7 +3447,7 @@ find_terminals(Tree) -> true -> M = cerl:concrete(M0), F = cerl:concrete(F0), - case (erl_bif_types:is_known(M, F, A) + case (erl_bif_types:is_known(M, F, A) andalso t_is_none(erl_bif_types:type(M, F, A))) of true -> {true, false}; false -> {false, true} @@ -3381,12 +3462,12 @@ find_terminals(Tree) -> letrec -> find_terminals(cerl:letrec_body(Tree)); literal -> {false, true}; primop -> {false, false}; %% match_fail, etc. are not explicit exits. - 'receive' -> + 'receive' -> Timeout = cerl:receive_timeout(Tree), Clauses = cerl:receive_clauses(Tree), case (cerl:is_literal(Timeout) andalso (cerl:concrete(Timeout) =:= infinity)) of - true -> + true -> if Clauses =:= [] -> {false, true}; %% A never ending receive. true -> find_terminals_list(Clauses) end; @@ -3454,11 +3535,11 @@ find_rec_warnings_tuple(Tree, State) -> TagVal = cerl:atom_val(Tag), case state__lookup_record(TagVal, length(Left), State) of error -> State; - {ok, Prototype} -> + {ok, Prototype} -> InfTupleType = t_inf(Prototype, TupleType), case t_is_none(InfTupleType) of true -> - Msg = {record_matching, + Msg = {record_matching, [format_patterns([Tree]), TagVal]}, state__add_warning(State, ?WARN_MATCHING, Tree, Msg); false -> @@ -3476,7 +3557,7 @@ find_rec_warnings_tuple(Tree, State) -> %%---------------------------------------------------------------------------- -ifdef(DEBUG_PP). -debug_pp(Tree, true) -> +debug_pp(Tree, true) -> io:put_chars(cerl_prettypr:format(Tree, [{hook, cerl_typean:pp_hook()}])), io:nl(), ok; |