diff options
Diffstat (limited to 'lib/dialyzer/src/dialyzer.erl')
-rw-r--r-- | lib/dialyzer/src/dialyzer.erl | 452 |
1 files changed, 320 insertions, 132 deletions
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index 185c8c9ae6..d4fe064edd 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -280,20 +280,23 @@ cl_check_log(Output) -> format_warning(W) -> format_warning(W, basename). --spec format_warning(raw_warning() | dial_warning(), fopt()) -> string(). - -format_warning({Tag, {File, Line, _MFA}, Msg}, FOpt) -> - format_warning({Tag, {File, Line}, Msg}, FOpt); -format_warning({_Tag, {File, Line}, Msg}, FOpt) when is_list(File), +-spec format_warning(raw_warning() | dial_warning(), + fopt() | proplists:proplist()) -> string(). + +format_warning(RawWarning, FOpt) when is_atom(FOpt) -> + format_warning(RawWarning, [{filename_opt, FOpt}]); +format_warning({Tag, {File, Line, _MFA}, Msg}, Opts) -> + format_warning({Tag, {File, Line}, Msg}, Opts); +format_warning({_Tag, {File, Line}, Msg}, Opts) when is_list(File), is_integer(Line) -> - F = case FOpt of + F = case proplists:get_value(filename_opt, Opts, basename) of fullpath -> File; basename -> filename:basename(File) end, - String = lists:flatten(message_to_string(Msg)), + Indent = proplists:get_value(indent_opt, Opts, ?INDENT_OPT), + String = message_to_string(Msg, Indent), lists:flatten(io_lib:format("~ts:~w: ~ts", [F, Line, String])). - %%----------------------------------------------------------------------------- %% Message classification and pretty-printing below. Messages appear in %% categories and in more or less alphabetical ordering within each category. @@ -301,52 +304,60 @@ format_warning({_Tag, {File, Line}, Msg}, FOpt) when is_list(File), %%----- Warnings for general discrepancies ---------------- message_to_string({apply, [Args, ArgNs, FailReason, - SigArgs, SigRet, Contract]}) -> - io_lib:format("Fun application with arguments ~ts ", [Args]) ++ - call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract); -message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]}) -> + SigArgs, SigRet, Contract]}, I) -> + io_lib:format("Fun application with arguments ~ts ", [a(Args, I)]) ++ + call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract, I); +message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]}, + I) -> io_lib:format("The call ~s:~ts~ts requires that ~ts is of type ~ts not ~ts\n", - [M, F, Args, Culprit, ExpectedType, FoundType]); -message_to_string({bin_construction, [Culprit, Size, Seg, Type]}) -> + [M, F, a(Args, I), c(Culprit, I), + t(ExpectedType, I), t(FoundType, I)]); +message_to_string({bin_construction, [Culprit, Size, Seg, Type]}, I) -> io_lib:format("Binary construction will fail since the ~s field ~s in" - " segment ~s has type ~s\n", [Culprit, Size, Seg, Type]); + " segment ~s has type ~s\n", + [Culprit, c(Size, I), c(Seg, I), t(Type, I)]); message_to_string({call, [M, F, Args, ArgNs, FailReason, - SigArgs, SigRet, Contract]}) -> - io_lib:format("The call ~w:~tw~ts ", [M, F, Args]) ++ - call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract); -message_to_string({call_to_missing, [M, F, A]}) -> + SigArgs, SigRet, Contract]}, I) -> + io_lib:format("The call ~w:~tw~ts ", [M, F, a(Args, I)]) ++ + call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract, I); +message_to_string({call_to_missing, [M, F, A]}, _I) -> io_lib:format("Call to missing or unexported function ~w:~tw/~w\n", [M, F, A]); -message_to_string({exact_eq, [Type1, Op, Type2]}) -> +message_to_string({exact_eq, [Type1, Op, Type2]}, I) -> io_lib:format("The test ~ts ~s ~ts can never evaluate to 'true'\n", - [Type1, Op, Type2]); -message_to_string({fun_app_args, [Args, Type]}) -> + [t(Type1, I), Op, t(Type2, I)]); +message_to_string({fun_app_args, [ArgNs, Args, Type]}, I) -> + PositionString = form_position_string(ArgNs), io_lib:format("Fun application with arguments ~ts will fail" - " since the function has type ~ts\n", [Args, Type]); -message_to_string({fun_app_no_fun, [Op, Type, Arity]}) -> + " since the function has type ~ts," + " which differs in the ~s argument\n", + [a(Args, I), t(Type, I), PositionString]); +message_to_string({fun_app_no_fun, [Op, Type, Arity]}, I) -> io_lib:format("Fun application will fail since ~ts :: ~ts" - " is not a function of arity ~w\n", [Op, Type, Arity]); -message_to_string({guard_fail, []}) -> + " is not a function of arity ~w\n", [Op, t(Type, I), Arity]); +message_to_string({guard_fail, []}, _I) -> "Clause guard cannot succeed.\n"; -message_to_string({guard_fail, [Arg1, Infix, Arg2]}) -> - io_lib:format("Guard test ~ts ~s ~ts can never succeed\n", [Arg1, Infix, Arg2]); -message_to_string({map_update, [Type, Key]}) -> +message_to_string({guard_fail, [Arg1, Infix, Arg2]}, I) -> + io_lib:format("Guard test ~ts ~s ~ts can never succeed\n", + [a(Arg1, I), Infix, a(Arg2, I)]); % a/2 rather than c/2 +message_to_string({map_update, [Type, Key]}, I) -> io_lib:format("A key of type ~ts cannot exist " - "in a map of type ~ts\n", [Key, Type]); -message_to_string({neg_guard_fail, [Arg1, Infix, Arg2]}) -> + "in a map of type ~ts\n", [t(Key, I), t(Type, I)]); +message_to_string({neg_guard_fail, [Arg1, Infix, Arg2]}, I) -> io_lib:format("Guard test not(~ts ~s ~ts) can never succeed\n", - [Arg1, Infix, Arg2]); -message_to_string({guard_fail, [Guard, Args]}) -> - io_lib:format("Guard test ~w~ts can never succeed\n", [Guard, Args]); -message_to_string({neg_guard_fail, [Guard, Args]}) -> - io_lib:format("Guard test not(~w~ts) can never succeed\n", [Guard, Args]); -message_to_string({guard_fail_pat, [Pat, Type]}) -> + [a(Arg1, I), Infix, a(Arg2, I)]); % a/2 rather than c/2 +message_to_string({guard_fail, [Guard, Args]}, I) -> + io_lib:format("Guard test ~s~ts can never succeed\n", [Guard, a(Args, I)]); +message_to_string({neg_guard_fail, [Guard, Args]}, I) -> + io_lib:format("Guard test not(~s~ts) can never succeed\n", + [Guard, a(Args, I)]); +message_to_string({guard_fail_pat, [Pat, Type]}, I) -> io_lib:format("Clause guard cannot succeed. The ~ts was matched" - " against the type ~ts\n", [Pat, Type]); -message_to_string({improper_list_constr, [TlType]}) -> + " against the type ~ts\n", [ps(Pat, I), t(Type, I)]); +message_to_string({improper_list_constr, [TlType]}, I) -> io_lib:format("Cons will produce an improper list" - " since its 2nd argument is ~ts\n", [TlType]); -message_to_string({no_return, [Type|Name]}) -> + " since its 2nd argument is ~ts\n", [t(TlType, I)]); +message_to_string({no_return, [Type|Name]}, _I) -> NameString = case Name of [] -> "The created fun "; @@ -358,135 +369,150 @@ message_to_string({no_return, [Type|Name]}) -> only_normal -> NameString ++ "has no local return\n"; both -> NameString ++ "has no local return\n" end; -message_to_string({record_constr, [RecConstr, FieldDiffs]}) -> +message_to_string({record_constr, [RecConstr, FieldDiffs]}, I) -> io_lib:format("Record construction ~ts violates the" - " declared type of field ~ts\n", [RecConstr, FieldDiffs]); -message_to_string({record_constr, [Name, Field, Type]}) -> + " declared type of field ~ts\n", + [t(RecConstr, I), field_diffs(FieldDiffs, I)]); +message_to_string({record_constr, [Name, Field, Type]}, I) -> io_lib:format("Record construction violates the declared type for #~tw{}" - " since ~ts cannot be of type ~ts\n", [Name, Field, Type]); -message_to_string({record_matching, [String, Name]}) -> + " since ~ts cannot be of type ~ts\n", + [Name, ps(Field, I), t(Type, I)]); +message_to_string({record_matching, [String, Name]}, I) -> io_lib:format("The ~ts violates the" - " declared type for #~tw{}\n", [String, Name]); -message_to_string({record_match, [Pat, Type]}) -> + " declared type for #~tw{}\n", [rec_type(String, I), Name]); +message_to_string({record_match, [Pat, Type]}, I) -> io_lib:format("Matching of ~ts tagged with a record name violates" - " the declared type of ~ts\n", [Pat, Type]); -message_to_string({pattern_match, [Pat, Type]}) -> - io_lib:format("The ~ts can never match the type ~ts\n", [Pat, Type]); -message_to_string({pattern_match_cov, [Pat, Type]}) -> + " the declared type of ~ts\n", [ps(Pat, I), t(Type, I)]); +message_to_string({pattern_match, [Pat, Type]}, I) -> + io_lib:format("The ~ts can never match the type ~ts\n", + [ps(Pat, I), t(Type, I)]); +message_to_string({pattern_match_cov, [Pat, Type]}, I) -> io_lib:format("The ~ts can never match since previous" " clauses completely covered the type ~ts\n", - [Pat, Type]); -message_to_string({unmatched_return, [Type]}) -> + [ps(Pat, I), t(Type, I)]); +message_to_string({unmatched_return, [Type]}, I) -> io_lib:format("Expression produces a value of type ~ts," - " but this value is unmatched\n", [Type]); -message_to_string({unused_fun, [F, A]}) -> + " but this value is unmatched\n", [t(Type, I)]); +message_to_string({unused_fun, [F, A]}, _I) -> io_lib:format("Function ~tw/~w will never be called\n", [F, A]); %%----- Warnings for specs and contracts ------------------- -message_to_string({contract_diff, [M, F, _A, Contract, Sig]}) -> - io_lib:format("Type specification ~w:~tw~ts" - " is not equal to the success typing: ~w:~tw~ts\n", - [M, F, Contract, M, F, Sig]); -message_to_string({contract_subtype, [M, F, _A, Contract, Sig]}) -> - io_lib:format("Type specification ~w:~tw~ts" - " is a subtype of the success typing: ~w:~tw~ts\n", - [M, F, Contract, M, F, Sig]); -message_to_string({contract_supertype, [M, F, _A, Contract, Sig]}) -> - io_lib:format("Type specification ~w:~tw~ts" - " is a supertype of the success typing: ~w:~tw~ts\n", - [M, F, Contract, M, F, Sig]); -message_to_string({contract_range, [Contract, M, F, ArgStrings, Line, CRet]}) -> - io_lib:format("The contract ~w:~tw~ts cannot be right because the inferred" +message_to_string({contract_diff, [M, F, _A, Contract, Sig]}, I) -> + io_lib:format("Type specification ~ts" + " is not equal to the success typing: ~ts\n", + [con(M, F, Contract, I), con(M, F, Sig, I)]); +message_to_string({contract_subtype, [M, F, _A, Contract, Sig]}, I) -> + io_lib:format("Type specification ~ts" + " is a subtype of the success typing: ~ts\n", + [con(M, F, Contract, I), con(M, F, Sig, I)]); +message_to_string({contract_supertype, [M, F, _A, Contract, Sig]}, I) -> + io_lib:format("Type specification ~ts" + " is a supertype of the success typing: ~ts\n", + [con(M, F, Contract, I), con(M, F, Sig, I)]); +message_to_string({contract_range, [Contract, M, F, ArgStrings, Line, CRet]}, + I) -> + io_lib:format("The contract ~ts cannot be right because the inferred" " return for ~tw~ts on line ~w is ~ts\n", - [M, F, Contract, F, ArgStrings, Line, CRet]); -message_to_string({invalid_contract, [M, F, A, Sig]}) -> + [con(M, F, Contract, I), F, a(ArgStrings, I), Line, t(CRet, I)]); +message_to_string({invalid_contract, [M, F, A, Sig]}, I) -> io_lib:format("Invalid type specification for function ~w:~tw/~w." - " The success typing is ~ts\n", [M, F, A, Sig]); -message_to_string({contract_with_opaque, [M, F, A, OpaqueType, SigType]}) -> + " The success typing is ~ts\n", [M, F, A, sig(Sig, I)]); +message_to_string({contract_with_opaque, [M, F, A, OpaqueType, SigType]}, + I) -> io_lib:format("The specification for ~w:~tw/~w" " has an opaque subtype ~ts which is violated by the" - " success typing ~ts\n", [M, F, A, OpaqueType, SigType]); -message_to_string({extra_range, [M, F, A, ExtraRanges, SigRange]}) -> + " success typing ~ts\n", + [M, F, A, t(OpaqueType, I), sig(SigType, I)]); +message_to_string({extra_range, [M, F, A, ExtraRanges, SigRange]}, I) -> io_lib:format("The specification for ~w:~tw/~w states that the function" " might also return ~ts but the inferred return is ~ts\n", - [M, F, A, ExtraRanges, SigRange]); -message_to_string({missing_range, [M, F, A, ExtraRanges, ContrRange]}) -> + [M, F, A, t(ExtraRanges, I), t(SigRange, I)]); +message_to_string({missing_range, [M, F, A, ExtraRanges, ContrRange]}, I) -> io_lib:format("The success typing for ~w:~tw/~w implies that the function" " might also return ~ts but the specification return is ~ts\n", - [M, F, A, ExtraRanges, ContrRange]); -message_to_string({overlapping_contract, [M, F, A]}) -> + [M, F, A, t(ExtraRanges, I), t(ContrRange, I)]); +message_to_string({overlapping_contract, [M, F, A]}, _I) -> io_lib:format("Overloaded contract for ~w:~tw/~w has overlapping domains;" " such contracts are currently unsupported and are simply ignored\n", [M, F, A]); -message_to_string({spec_missing_fun, [M, F, A]}) -> +message_to_string({spec_missing_fun, [M, F, A]}, _I) -> io_lib:format("Contract for function that does not exist: ~w:~tw/~w\n", [M, F, A]); %%----- Warnings for opaque type violations ------------------- -message_to_string({call_with_opaque, [M, F, Args, ArgNs, ExpArgs]}) -> +message_to_string({call_with_opaque, [M, F, Args, ArgNs, ExpArgs]}, I) -> io_lib:format("The call ~w:~tw~ts contains ~ts when ~ts\n", - [M, F, Args, form_positions(ArgNs), form_expected(ExpArgs)]); -message_to_string({call_without_opaque, [M, F, Args, ExpectedTriples]}) -> + [M, F, a(Args, I), form_positions(ArgNs), + form_expected(ExpArgs, I)]); +message_to_string({call_without_opaque, [M, F, Args, ExpectedTriples]}, I) -> io_lib:format("The call ~w:~tw~ts does not have ~ts\n", - [M, F, Args, form_expected_without_opaque(ExpectedTriples)]); -message_to_string({opaque_eq, [Type, _Op, OpaqueType]}) -> + [M, F, a(Args, I), + form_expected_without_opaque(ExpectedTriples, I)]); +message_to_string({opaque_eq, [Type, _Op, OpaqueType]}, I) -> io_lib:format("Attempt to test for equality between a term of type ~ts" - " and a term of opaque type ~ts\n", [Type, OpaqueType]); -message_to_string({opaque_guard, [Arg1, Infix, Arg2, ArgNs]}) -> + " and a term of opaque type ~ts\n", + [t(Type, I), t(OpaqueType, I)]); +message_to_string({opaque_guard, [Arg1, Infix, Arg2, ArgNs]}, I) -> io_lib:format("Guard test ~ts ~s ~ts contains ~s\n", - [Arg1, Infix, Arg2, form_positions(ArgNs)]); -message_to_string({opaque_guard, [Guard, Args]}) -> + [a(Arg1, I), Infix, a(Arg2, I), form_positions(ArgNs)]); +message_to_string({opaque_guard, [Guard, Args]}, I) -> io_lib:format("Guard test ~w~ts breaks the opacity of its argument\n", - [Guard, Args]); -message_to_string({opaque_match, [Pat, OpaqueType, OpaqueTerm]}) -> + [Guard, a(Args, I)]); +message_to_string({opaque_match, [Pat, OpaqueType, OpaqueTerm]}, I) -> Term = if OpaqueType =:= OpaqueTerm -> "the term"; - true -> OpaqueTerm + true -> t(OpaqueTerm, I) end, - io_lib:format("The attempt to match a term of type ~s against the ~ts" - " breaks the opacity of ~ts\n", [OpaqueType, Pat, Term]); -message_to_string({opaque_neq, [Type, _Op, OpaqueType]}) -> + io_lib:format("The attempt to match a term of type ~ts against the ~ts" + " breaks the opacity of ~ts\n", + [t(OpaqueType, I), ps(Pat, I), Term]); +message_to_string({opaque_neq, [Type, _Op, OpaqueType]}, I) -> io_lib:format("Attempt to test for inequality between a term of type ~ts" - " and a term of opaque type ~ts\n", [Type, OpaqueType]); -message_to_string({opaque_type_test, [Fun, Args, Arg, ArgType]}) -> + " and a term of opaque type ~ts\n", + [t(Type, I), t(OpaqueType, I)]); +message_to_string({opaque_type_test, [Fun, Args, Arg, ArgType]}, I) -> io_lib:format("The type test ~ts~ts breaks the opacity of the term ~ts~ts\n", - [Fun, Args, Arg, ArgType]); -message_to_string({opaque_size, [SizeType, Size]}) -> + [Fun, a(Args, I), Arg, t(ArgType, I)]); +message_to_string({opaque_size, [SizeType, Size]}, I) -> io_lib:format("The size ~ts breaks the opacity of ~ts\n", - [SizeType, Size]); -message_to_string({opaque_call, [M, F, Args, Culprit, OpaqueType]}) -> + [t(SizeType, I), c(Size, I)]); +message_to_string({opaque_call, [M, F, Args, Culprit, OpaqueType]}, I) -> io_lib:format("The call ~s:~ts~ts breaks the opacity of the term ~ts :: ~ts\n", - [M, F, Args, Culprit, OpaqueType]); + [M, F, a(Args, I), c(Culprit, I), t(OpaqueType, I)]); %%----- Warnings for concurrency errors -------------------- -message_to_string({race_condition, [M, F, Args, Reason]}) -> - io_lib:format("The call ~w:~tw~ts ~ts\n", [M, F, Args, Reason]); +message_to_string({race_condition, [M, F, Args, Reason]}, I) -> + %% There is a possibly huge type in Reason. + io_lib:format("The call ~w:~tw~ts ~ts\n", [M, F, a(Args, I), Reason]); %%----- Warnings for behaviour errors -------------------- -message_to_string({callback_type_mismatch, [B, F, A, ST, CT]}) -> - io_lib:format("The inferred return type of ~tw/~w (~ts) has nothing in" +message_to_string({callback_type_mismatch, [B, F, A, ST, CT]}, I) -> + io_lib:format("The inferred return type of ~tw/~w ~ts has nothing in" " common with ~ts, which is the expected return type for" - " the callback of the ~w behaviour\n", [F, A, ST, CT, B]); -message_to_string({callback_arg_type_mismatch, [B, F, A, N, ST, CT]}) -> + " the callback of the ~w behaviour\n", + [F, A, t("("++ST++")", I), t(CT, I), B]); +message_to_string({callback_arg_type_mismatch, [B, F, A, N, ST, CT]}, I) -> io_lib:format("The inferred type for the ~s argument of ~tw/~w (~ts) is" " not a supertype of ~ts, which is expected type for this" " argument in the callback of the ~w behaviour\n", - [ordinal(N), F, A, ST, CT, B]); -message_to_string({callback_spec_type_mismatch, [B, F, A, ST, CT]}) -> + [ordinal(N), F, A, t(ST, I), t(CT, I), B]); +message_to_string({callback_spec_type_mismatch, [B, F, A, ST, CT]}, I) -> io_lib:format("The return type ~ts in the specification of ~tw/~w is not a" " subtype of ~ts, which is the expected return type for the" - " callback of the ~w behaviour\n", [ST, F, A, CT, B]); -message_to_string({callback_spec_arg_type_mismatch, [B, F, A, N, ST, CT]}) -> + " callback of the ~w behaviour\n", + [t(ST, I), F, A, t(CT, I), B]); +message_to_string({callback_spec_arg_type_mismatch, [B, F, A, N, ST, CT]}, + I) -> io_lib:format("The specified type for the ~ts argument of ~tw/~w (~ts) is" " not a supertype of ~ts, which is expected type for this" " argument in the callback of the ~w behaviour\n", - [ordinal(N), F, A, ST, CT, B]); -message_to_string({callback_missing, [B, F, A]}) -> + [ordinal(N), F, A, t(ST, I), t(CT, I), B]); +message_to_string({callback_missing, [B, F, A]}, _I) -> io_lib:format("Undefined callback function ~tw/~w (behaviour ~w)\n", [F, A, B]); -message_to_string({callback_info_missing, [B]}) -> +message_to_string({callback_info_missing, [B]}, _I) -> io_lib:format("Callback info about the ~w behaviour is not available\n", [B]); %%----- Warnings for unknown functions, types, and behaviours ------------- -message_to_string({unknown_type, {M, F, A}}) -> +message_to_string({unknown_type, {M, F, A}}, _I) -> io_lib:format("Unknown type ~w:~tw/~w", [M, F, A]); -message_to_string({unknown_function, {M, F, A}}) -> +message_to_string({unknown_function, {M, F, A}}, _I) -> io_lib:format("Unknown function ~w:~tw/~w", [M, F, A]); -message_to_string({unknown_behaviour, B}) -> +message_to_string({unknown_behaviour, B}, _I) -> io_lib:format("Unknown behaviour ~w", [B]). %%----------------------------------------------------------------------------- @@ -494,7 +520,7 @@ message_to_string({unknown_behaviour, B}) -> %%----------------------------------------------------------------------------- call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, - {IsOverloaded, Contract}) -> + {IsOverloaded, Contract}, I) -> PositionString = form_position_string(ArgNs), case FailReason of only_sig -> @@ -502,24 +528,25 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, true -> %% We do not know which argument(s) caused the failure io_lib:format("will never return since the success typing arguments" - " are ~ts\n", [SigArgs]); + " are ~ts\n", [t(SigArgs, I)]); false -> io_lib:format("will never return since it differs in the ~s argument" " from the success typing arguments: ~ts\n", - [PositionString, SigArgs]) + [PositionString, t(SigArgs, I)]) end; only_contract -> case (ArgNs =:= []) orelse IsOverloaded of true -> %% We do not know which arguments caused the failure - io_lib:format("breaks the contract ~ts\n", [Contract]); + io_lib:format("breaks the contract ~ts\n", [sig(Contract, I)]); false -> io_lib:format("breaks the contract ~ts in the ~s argument\n", - [Contract, PositionString]) + [sig(Contract, I), PositionString]) end; both -> io_lib:format("will never return since the success typing is ~ts -> ~ts" - " and the contract is ~ts\n", [SigArgs, SigRet, Contract]) + " and the contract is ~ts\n", + [t(SigArgs, I), t(SigRet, I), sig(Contract, I)]) end. form_positions(ArgNs) -> @@ -534,24 +561,27 @@ form_positions(ArgNs) -> %% We know which positions N are to blame; %% the list of triples will never be empty. -form_expected_without_opaque([{N, T, TStr}]) -> +form_expected_without_opaque([{N, T, TStr}], I) -> case erl_types:t_is_opaque(T) of true -> - io_lib:format("an opaque term of type ~ts as ", [TStr]); + io_lib:format("an opaque term of type ~ts as ", [t(TStr, I)]); false -> - io_lib:format("a term of type ~ts (with opaque subterms) as ", [TStr]) + io_lib:format("a term of type ~ts (with opaque subterms) as ", + [t(TStr, I)]) end ++ form_position_string([N]) ++ " argument"; -form_expected_without_opaque(ExpectedTriples) -> %% TODO: can do much better here +form_expected_without_opaque(ExpectedTriples, _I) -> %% TODO: can do much better here {ArgNs, _Ts, _TStrs} = lists:unzip3(ExpectedTriples), "opaque terms as " ++ form_position_string(ArgNs) ++ " arguments". -form_expected(ExpectedArgs) -> +form_expected(ExpectedArgs, I) -> case ExpectedArgs of [T] -> TS = erl_types:t_to_string(T), case erl_types:t_is_opaque(T) of - true -> io_lib:format("an opaque term of type ~ts is expected", [TS]); - false -> io_lib:format("a structured term of type ~ts is expected", [TS]) + true -> io_lib:format("an opaque term of type ~ts is expected", + [t(TS, I)]); + false -> io_lib:format("a structured term of type ~ts is expected", + [t(TS, I)]) end; [_,_|_] -> "terms of different types are expected in these positions" end. @@ -571,3 +601,161 @@ ordinal(1) -> "1st"; ordinal(2) -> "2nd"; ordinal(3) -> "3rd"; ordinal(N) when is_integer(N) -> io_lib:format("~wth", [N]). + +%% Functions that parse type strings, literal strings, and contract +%% strings. Return strings formatted by erl_pp. + +%% If lib/hipe/cerl/erl_types.erl is compiled with DEBUG=true, +%% the contents of opaque types are showed inside brackets. +%% Since erl_parse:parse_form() cannot handle the bracket syntax, +%% no indentation is done. +%%-define(DEBUG , true). + +-define(IND, 10). + +con(M, F, Src, I) -> + S = sig(Src, I), + io_lib:format("~w:~tw~ts", [M, F, S]). + +sig(Src, false) -> + Src; +sig(Src, true) -> + Str = lists:flatten(io_lib:format("-spec ~w:~tw~ts.", [a, b, Src])), + {ok, Tokens, _EndLocation} = erl_scan:string(Str), + exec(fun() -> + {ok, {attribute, _, spec, {_MFA, Types}}} = + erl_parse:parse_form(Tokens), + indentation(?IND) ++ pp_spec(Types) + end, Src). + +%% Argument(list)s are a mix of types and Erlang code. Note: sometimes +%% (contract_range, call_without_opaque, opaque_type_test), the initial +%% newline is a bit out of place. +a(""=Args, _I) -> + Args; +a(Args, I) -> + t(Args, I). + +c(Cerl, _I) -> + Cerl. + +field_diffs(Src, false) -> + Src; +field_diffs(Src, true) -> + Fields = string:split(Src, " and ", all), + lists:join(" and ", [field_diff(Field) || Field <- Fields]). + +field_diff(Field) -> + [F | Ts] = string:split(Field, "::", all), + F ++ " ::" ++ t(lists:flatten(lists:join("::", Ts)), true). + +rec_type("record "++Src, I) -> + "record " ++ t(Src, I). + +%% "variable"/"pattern" ++ cerl +ps("pattern "++Src, I) -> + "pattern " ++ t(Src, I); +ps("variable "++_=Src, _I) -> + Src; +ps("record field"++Rest, I) -> + [S, TypeStr] = string:split(Rest, "of type ", all), + "record field" ++ S ++ "of type " ++ t(TypeStr, I). + +%% Scan and parse a type or a literal, and pretty-print it using erl_pp. +t(Src, false) -> + Src; +t("("++_=Src, true) -> + ts(Src); +t(Src, true) -> + %% Binary types and products both start with a $<. + try parse_type_or_literal(Src) of + TypeOrLiteral -> + indentation(?IND) ++ pp_type(TypeOrLiteral) + catch + _:_ -> + ts(Src) + end. + +ts(Src) -> + Ind = indentation(?IND), + [C1|Src1] = Src, % $< (product) or $( (arglist) + [C2|RevSrc2] = lists:reverse(Src1), + Src2 = lists:reverse(RevSrc2), + exec(fun() -> + Types = parse_types_and_literals(Src2), + CommaInd = [$, | Ind], + (indentation(?IND-1) ++ + [C1 | lists:join(CommaInd, [pp_type(Type) || Type <- Types])] ++ + [C2]) + end, Src). + +-ifdef(DEBUG). +exec(F, R) -> + try F() catch _:_ -> R end. +-else. +exec(F, _) -> + F(). +-endif. + +indentation(I) -> + [$\n | lists:duplicate(I, $\s)]. + +pp_type(Type) -> + Form = {attribute, erl_anno:new(0), type, {t, Type, []}}, + TypeDef = erl_pp:form(Form, [{quote_singleton_atom_types, true}]), + {match, [S]} = re:run(TypeDef, <<"::\\s*(.*)\\.\\n*">>, + [{capture, all_but_first, list}, dotall]), + S. + +pp_spec(Spec) -> + Form = {attribute, erl_anno:new(0), spec, {{a,b,0}, Spec}}, + Sig = erl_pp:form(Form, [{quote_singleton_atom_types, true}]), + {match, [S]} = re:run(Sig, <<"-spec a:b\\s*(.*)\\.\\n*">>, + [{capture, all_but_first, list}, dotall]), + S. + +parse_types_and_literals(Src) -> + {ok, Tokens, _EndLocation} = erl_scan:string(Src), + [parse_a_type_or_literal(Ts) || Ts <- types(Tokens)]. + +parse_type_or_literal(Src) -> + {ok, Tokens, _EndLocation} = erl_scan:string(Src), + parse_a_type_or_literal(Tokens). + +parse_a_type_or_literal(Ts0) -> + L = erl_anno:new(1), + Ts = Ts0 ++ [{dot,L}], + Tokens = [{'-',L}, {atom,L,type}, {atom,L,t}, {'(',L}, {')',L}, + {'::',L}] ++ Ts, + case erl_parse:parse_form(Tokens) of + {ok, {attribute, _, type, {t, Type, []}}} -> + Type; + {error, _} -> + %% literal + {ok, [T]} = erl_parse:parse_exprs(Ts), + T + end. + +types([]) -> []; +types(Ts) -> + {Ts0, Ts1} = one_type(Ts, [], []), + [Ts0 | types(Ts1)]. + +one_type([], [], Ts) -> + {lists:reverse(Ts), []}; +one_type([{',', _Lc}|Toks], [], Ts0) -> + {lists:reverse(Ts0), Toks}; +one_type([{')', Lrp}|Toks], [], Ts0) -> + {lists:reverse(Ts0), [{')', Lrp}|Toks]}; +one_type([{'(', Llp}|Toks], E, Ts0) -> + one_type(Toks, [')'|E], [{'(', Llp}|Ts0]); +one_type([{'<<', Lls}|Toks], E, Ts0) -> + one_type(Toks, ['>>'|E], [{'<<', Lls}|Ts0]); +one_type([{'[', Lls}|Toks], E, Ts0) -> + one_type(Toks, [']'|E], [{'[', Lls}|Ts0]); +one_type([{'{', Llc}|Toks], E, Ts0) -> + one_type(Toks, ['}'|E], [{'{', Llc}|Ts0]); +one_type([{Rb, Lrb}|Toks], [Rb|E], Ts0) -> + one_type(Toks, E, [{Rb, Lrb}|Ts0]); +one_type([T|Toks], E, Ts0) -> + one_type(Toks, E, [T|Ts0]). |