diff options
Diffstat (limited to 'lib/dialyzer')
5 files changed, 171 insertions, 15 deletions
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index 3469d70a4d..6f83e29a57 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -256,7 +256,7 @@ check_extraneous([C|Cs], SuccType) -> check_extraneous_1(Contract, SuccType) -> CRngs = erl_types:t_elements(erl_types:t_fun_range(Contract)), STRng = erl_types:t_fun_range(SuccType), - %% io:format("CR = ~p\nSR = ~p\n", [CRngs, STRng]), + ?debug("CR = ~p\nSR = ~p\n", [CRngs, STRng]), case [CR || CR <- CRngs, erl_types:t_is_none(erl_types:t_inf(CR, STRng, opaque))] of [] -> ok; CRs -> {error, {extra_range, erl_types:t_sup(CRs), STRng}} @@ -444,7 +444,21 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left], {error, invalid_contract} -> [invalid_contract_warning(MFA, FileLine, Sig, RecDict)|Acc]; {error, {extra_range, ExtraRanges, STRange}} -> - [extra_range_warning(MFA, FileLine, ExtraRanges, STRange)|Acc]; + Warn = + case t_from_forms_without_remote(Contract#contract.forms, + RecDict) of + {ok, NoRemoteType} -> + CRet = erl_types:t_fun_range(NoRemoteType), + erl_types:t_is_subtype(ExtraRanges, CRet); + unsupported -> + true + end, + case Warn of + true -> + [extra_range_warning(MFA, FileLine, ExtraRanges, STRange)|Acc]; + false -> + Acc + end; {error, Msg} -> [{?WARN_CONTRACT_SYNTAX, FileLine, Msg}|Acc]; ok -> @@ -507,26 +521,92 @@ picky_contract_check(CSig0, Sig0, MFA, FileLine, Contract, RecDict, Acc) -> extra_contract_warning({M, F, A}, FileLine, Contract, CSig, Sig, RecDict) -> SigString = lists:flatten(dialyzer_utils:format_sig(Sig, RecDict)), ContractString0 = lists:flatten(dialyzer_utils:format_sig(CSig, RecDict)), - case SigString =:= ContractString0 of + %% The only difference is in record fields containing 'undefined' or not. + IsUndefRecordFieldsRelated = SigString =:= ContractString0, + {IsRemoteTypesRelated, SubtypeRelation} = + is_remote_types_related(Contract, CSig, Sig, RecDict), + case IsUndefRecordFieldsRelated orelse IsRemoteTypesRelated of true -> - %% The only difference is in record fields containing 'undefined' or not. no_warning; false -> ContractString = contract_to_string(Contract), {Tag, Msg} = - case erl_types:t_is_subtype(CSig, Sig) of - true -> + case SubtypeRelation of + contract_is_subtype -> {?WARN_CONTRACT_SUBTYPE, {contract_subtype, [M, F, A, ContractString, SigString]}}; - false -> - case erl_types:t_is_subtype(Sig, CSig) of - true -> - {?WARN_CONTRACT_SUPERTYPE, - {contract_supertype, [M, F, A, ContractString, SigString]}}; - false -> - {?WARN_CONTRACT_NOT_EQUAL, - {contract_diff, [M, F, A, ContractString, SigString]}} - end + contract_is_supertype -> + {?WARN_CONTRACT_SUPERTYPE, + {contract_supertype, [M, F, A, ContractString, SigString]}}; + neither -> + {?WARN_CONTRACT_NOT_EQUAL, + {contract_diff, [M, F, A, ContractString, SigString]}} end, {warning, {Tag, FileLine, Msg}} end. + +is_remote_types_related(Contract, CSig, Sig, RecDict) -> + case erl_types:t_is_subtype(CSig, Sig) of + true -> + {false, contract_is_subtype}; + false -> + case erl_types:t_is_subtype(Sig, CSig) of + true -> + case t_from_forms_without_remote(Contract#contract.forms, RecDict) of + {ok, NoRemoteTypeSig} -> + case blame_remote(CSig, NoRemoteTypeSig, Sig) of + true -> + {true, neither}; + false -> + {false, contract_is_supertype} + end; + unsupported -> + {false, contract_is_supertype} + end; + false -> + {false, neither} + end + end. + +t_from_forms_without_remote([{FType, []}], RecDict) -> + Type0 = erl_types:t_from_form(FType, RecDict), + Map = + fun(Type) -> + case erl_types:t_is_remote(Type) of + true -> erl_types:t_none(); + false -> Type + end + end, + {ok, erl_types:t_map(Map, Type0)}; +t_from_forms_without_remote([{_FType, _Constrs}], _RecDict) -> + %% 'When' constraints + unsupported; +t_from_forms_without_remote(_Forms, _RecDict) -> + %% Lots of forms + unsupported. + +blame_remote(ContractSig, NoRemoteContractSig, Sig) -> + CArgs = erl_types:t_fun_args(ContractSig), + CRange = erl_types:t_fun_range(ContractSig), + NRArgs = erl_types:t_fun_args(NoRemoteContractSig), + NRRange = erl_types:t_fun_range(NoRemoteContractSig), + SArgs = erl_types:t_fun_args(Sig), + SRange = erl_types:t_fun_range(Sig), + blame_remote_list([CRange|CArgs], [NRRange|NRArgs], [SRange|SArgs]). + +blame_remote_list([], [], []) -> + true; +blame_remote_list([CArg|CArgs], [NRArg|NRArgs], [SArg|SArgs]) -> + case erl_types:t_is_equal(CArg, NRArg) of + true -> + case not erl_types:t_is_equal(CArg, SArg) of + true -> false; + false -> blame_remote_list(CArgs, NRArgs, SArgs) + end; + false -> + case erl_types:t_is_subtype(SArg, NRArg) + andalso not erl_types:t_is_subtype(NRArg, SArg) of + true -> false; + false -> blame_remote_list(CArgs, NRArgs, SArgs) + end + end. diff --git a/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options new file mode 100644 index 0000000000..f7197ac30f --- /dev/null +++ b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options @@ -0,0 +1 @@ +{dialyzer_options, [{warnings, [underspecs]}]}. diff --git a/lib/dialyzer/test/underspecs_SUITE_data/results/remote b/lib/dialyzer/test/underspecs_SUITE_data/results/remote new file mode 100644 index 0000000000..1e0cda3bde --- /dev/null +++ b/lib/dialyzer/test/underspecs_SUITE_data/results/remote @@ -0,0 +1,9 @@ + +remotes1.erl:17: The specification for remotes1:foo5/1 states that the function might also return 'ko' but the inferred return is 'ok' +remotes1.erl:20: Type specification remotes1:foo6('ok' | 'ko') -> 'ok' is a supertype of the success typing: remotes1:foo6('ok') -> 'ok' +remotes1.erl:25: The specification for remotes1:foo7/1 states that the function might also return 'ko' but the inferred return is 'ok' +remotes1.erl:28: Type specification remotes1:foo8(local_type_42()) -> 'ok' is a supertype of the success typing: remotes1:foo8('ok') -> 'ok' +remotes1.erl:33: The specification for remotes1:foo9/1 states that the function might also return 'ko' but the inferred return is 'ok' +remotes1.erl:36: Type specification remotes1:foo10(local_and_known_remote_type_42()) -> 'ok' is a supertype of the success typing: remotes1:foo10('ok') -> 'ok' +remotes1.erl:49: Type specification remotes1:foo13('ok') -> local_and_unknown_remote_type_42() is a supertype of the success typing: remotes1:foo13('ok') -> 'ok' +remotes1.erl:52: Type specification remotes1:foo14(local_and_unknown_remote_type_42()) -> 'ok' is a supertype of the success typing: remotes1:foo14('ok') -> 'ok' diff --git a/lib/dialyzer/test/underspecs_SUITE_data/src/remote/remotes1.erl b/lib/dialyzer/test/underspecs_SUITE_data/src/remote/remotes1.erl new file mode 100644 index 0000000000..b722495095 --- /dev/null +++ b/lib/dialyzer/test/underspecs_SUITE_data/src/remote/remotes1.erl @@ -0,0 +1,61 @@ +-module(remotes1). + +-compile(export_all). + +-spec foo1(some_unknown_remote:type42()) -> ok. +foo1(ok) -> ok. + +-spec foo2(ok) -> some_unknown_remote:type42(). +foo2(ok) -> ok. + +-spec foo3(some_known_remote:type42()) -> ok. +foo3(ok) -> ok. + +-spec foo4(ok) -> some_known_remote:type42(). +foo4(ok) -> ok. + +-spec foo5(ok|ko) -> ok|ko. +foo5(ok) -> ok. + +-spec foo6(ok|ko) -> ok. +foo6(ok) -> ok. + +-type local_type_42() :: ok | ko. + +-spec foo7(ok) -> local_type_42(). +foo7(ok) -> ok. + +-spec foo8(local_type_42()) -> ok. +foo8(ok) -> ok. + +-type local_and_known_remote_type_42() :: some_known_remote:type42() | ok | ko. + +-spec foo9(ok) -> local_and_known_remote_type_42(). +foo9(ok) -> ok. + +-spec foo10(local_and_known_remote_type_42()) -> ok. +foo10(ok) -> ok. + +-type local_and_ok_known_remote_type_42() :: some_known_remote:type42() | ok. + +-spec foo11(ok) -> local_and_ok_known_remote_type_42(). +foo11(ok) -> ok. + +-spec foo12(local_and_ok_known_remote_type_42()) -> ok. +foo12(ok) -> ok. + +-type local_and_unknown_remote_type_42() :: some_unknown_remote:type42() | ok | ko. + +-spec foo13(ok) -> local_and_unknown_remote_type_42(). +foo13(ok) -> ok. + +-spec foo14(local_and_unknown_remote_type_42()) -> ok. +foo14(ok) -> ok. + +-type local_and_ok_unknown_remote_type_42() :: some_unknown_remote:type42() | ok. + +-spec foo15(ok) -> local_and_ok_unknown_remote_type_42(). +foo15(ok) -> ok. + +-spec foo16(local_and_ok_unknown_remote_type_42()) -> ok. +foo16(ok) -> ok. diff --git a/lib/dialyzer/test/underspecs_SUITE_data/src/remote/some_known_remote.erl b/lib/dialyzer/test/underspecs_SUITE_data/src/remote/some_known_remote.erl new file mode 100644 index 0000000000..437f1e7826 --- /dev/null +++ b/lib/dialyzer/test/underspecs_SUITE_data/src/remote/some_known_remote.erl @@ -0,0 +1,5 @@ +-module(some_known_remote). + +-export_type([type42/0]). + +-type type42() :: ok | ko. |