diff options
Diffstat (limited to 'lib/dialyzer/src/dialyzer_contracts.erl')
-rw-r--r-- | lib/dialyzer/src/dialyzer_contracts.erl | 123 |
1 files changed, 75 insertions, 48 deletions
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index 7251de8b10..d1ffa07706 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -126,13 +126,19 @@ butlast([H|T]) -> [H|butlast(T)]. constraints_to_string([]) -> ""; -constraints_to_string([{type, _, constraint, [{atom, _, What}, Types]}]) -> - atom_to_list(What) ++ "(" ++ - sequence([erl_types:t_form_to_string(T) || T <- Types], ",") ++ ")"; constraints_to_string([{type, _, constraint, [{atom, _, What}, Types]}|Rest]) -> - atom_to_list(What) ++ "(" ++ - sequence([erl_types:t_form_to_string(T) || T <- Types], ",") - ++ "), " ++ constraints_to_string(Rest). + S = constraint_to_string(What, Types), + case Rest of + [] -> S; + _ -> S ++ ", " ++ constraints_to_string(Rest) + end. + +constraint_to_string(is_subtype, [{var, _, Var}, T]) -> + atom_to_list(Var) ++ " :: " ++ erl_types:t_form_to_string(T); +constraint_to_string(What, Types) -> + atom_to_list(What) ++ "(" + ++ sequence([erl_types:t_form_to_string(T) || T <- Types], ",") + ++ ")". sequence([], _Delimiter) -> ""; sequence([H], _Delimiter) -> H; @@ -161,8 +167,7 @@ process_contract_remote_types(CodeServer) -> dialyzer_codeserver:finalize_contracts(NewContractDict, NewCallbackDict, CodeServer). --type opaques() :: [erl_types:erl_type()] | 'universe'. --type opaques_fun() :: fun((module()) -> opaques()). +-type opaques_fun() :: fun((module()) -> [erl_types:erl_type()]). -type fun_types() :: dict:dict(label(), erl_types:type_table()). @@ -205,10 +210,10 @@ check_contract(Contract, SuccType) -> check_contract(#contract{contracts = Contracts}, SuccType, Opaques) -> try - Contracts1 = [{Contract, insert_constraints(Constraints, dict:new())} + Contracts1 = [{Contract, insert_constraints(Constraints)} || {Contract, Constraints} <- Contracts], - Contracts2 = [erl_types:t_subst(Contract, Dict) - || {Contract, Dict} <- Contracts1], + Contracts2 = [erl_types:t_subst(Contract, Map) + || {Contract, Map} <- Contracts1], GenDomains = [erl_types:t_fun_args(C) || C <- Contracts2], case check_domains(GenDomains) of error -> @@ -272,28 +277,45 @@ check_extraneous_1(Contract, SuccType) -> case [CR || CR <- CRngs, erl_types:t_is_none(erl_types:t_inf(CR, STRng))] of [] -> - CRngList = list_part(CRng), - STRngList = list_part(STRng), - case is_not_nil_list(CRngList) andalso is_not_nil_list(STRngList) of - false -> ok; - true -> - CRngElements = erl_types:t_list_elements(CRngList), - STRngElements = erl_types:t_list_elements(STRngList), - Inf = erl_types:t_inf(CRngElements, STRngElements), - case erl_types:t_is_none(Inf) of - true -> {error, invalid_contract}; - false -> ok - end + case bad_extraneous_list(CRng, STRng) + orelse bad_extraneous_map(CRng, STRng) + of + true -> {error, invalid_contract}; + false -> ok end; CRs -> {error, {extra_range, erl_types:t_sup(CRs), STRng}} end. +bad_extraneous_list(CRng, STRng) -> + CRngList = list_part(CRng), + STRngList = list_part(STRng), + case is_not_nil_list(CRngList) andalso is_not_nil_list(STRngList) of + false -> false; + true -> + CRngElements = erl_types:t_list_elements(CRngList), + STRngElements = erl_types:t_list_elements(STRngList), + Inf = erl_types:t_inf(CRngElements, STRngElements), + erl_types:t_is_none(Inf) + end. + list_part(Type) -> erl_types:t_inf(erl_types:t_list(), Type). is_not_nil_list(Type) -> erl_types:t_is_list(Type) andalso not erl_types:t_is_nil(Type). +bad_extraneous_map(CRng, STRng) -> + CRngMap = map_part(CRng), + STRngMap = map_part(STRng), + (not is_empty_map(CRngMap)) andalso (not is_empty_map(STRngMap)) + andalso is_empty_map(erl_types:t_inf(CRngMap, STRngMap)). + +map_part(Type) -> + erl_types:t_inf(erl_types:t_map(), Type). + +is_empty_map(Type) -> + erl_types:t_is_equal(Type, erl_types:t_from_term(#{})). + %% This is the heart of the "range function" -spec process_contracts([contract_pair()], [erl_types:erl_type()]) -> erl_types:erl_type(). @@ -322,15 +344,15 @@ process_contract({Contract, Constraints}, CallTypes0) -> [erl_types:t_to_string(ContArgsFun), erl_types:t_to_string(CallTypesFun)]), case solve_constraints(ContArgsFun, CallTypesFun, Constraints) of - {ok, VarDict} -> - {ok, erl_types:t_subst(erl_types:t_fun_range(Contract), VarDict)}; + {ok, VarMap} -> + {ok, erl_types:t_subst(erl_types:t_fun_range(Contract), VarMap)}; error -> error end. solve_constraints(Contract, Call, Constraints) -> %% First make sure the call follows the constraints - CDict = insert_constraints(Constraints, dict:new()), - Contract1 = erl_types:t_subst(Contract, CDict), + CMap = insert_constraints(Constraints), + Contract1 = erl_types:t_subst(Contract, CMap), %% Just a safe over-approximation. %% TODO: Find the types for type variables properly ContrArgs = erl_types:t_fun_args(Contract1), @@ -338,7 +360,7 @@ solve_constraints(Contract, Call, Constraints) -> InfList = erl_types:t_inf_lists(ContrArgs, CallArgs), case erl_types:any_none_or_unit(InfList) of true -> error; - false -> {ok, CDict} + false -> {ok, CMap} end. %%Inf = erl_types:t_inf(Contract1, Call), %% Then unify with the constrained call type. @@ -368,23 +390,26 @@ warn_spec_missing_fun({M, F, A} = MFA, Contracts) -> {?WARN_CONTRACT_SYNTAX, WarningInfo, {spec_missing_fun, [M, F, A]}}. %% This treats the "when" constraints. It will be extended, we hope. -insert_constraints([{subtype, Type1, Type2}|Left], Dict) -> +insert_constraints(Constraints) -> + insert_constraints(Constraints, maps:new()). + +insert_constraints([{subtype, Type1, Type2}|Left], Map) -> case erl_types:t_is_var(Type1) of true -> Name = erl_types:t_var_name(Type1), - Dict1 = case dict:find(Name, Dict) of - error -> - dict:store(Name, Type2, Dict); - {ok, VarType} -> - dict:store(Name, erl_types:t_inf(VarType, Type2), Dict) - end, - insert_constraints(Left, Dict1); + Map1 = case maps:find(Name, Map) of + error -> + maps:put(Name, Type2, Map); + {ok, VarType} -> + maps:put(Name, erl_types:t_inf(VarType, Type2), Map) + end, + insert_constraints(Left, Map1); false -> %% A lot of things should change to add supertypes throw({error, io_lib:format("First argument of is_subtype constraint " "must be a type variable: ~p\n", [Type1])}) end; -insert_constraints([], Dict) -> Dict. +insert_constraints([], Map) -> Map. -type types() :: erl_types:type_table(). @@ -454,7 +479,8 @@ initialize_constraints([], _MFA, _RecDict, _ExpTypes, _AllRecords, Acc) -> initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords, Acc) -> case Constr of {type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]} -> - T1 = final_form(Type1, ExpTypes, MFA, AllRecords, dict:new()), + VarTable = erl_types:var_table__new(), + T1 = final_form(Type1, ExpTypes, MFA, AllRecords, VarTable), Entry = {T1, Type2}, initialize_constraints(Rest, MFA, RecDict, ExpTypes, AllRecords, [Entry|Acc]); {type, _, constraint, [{atom,_,Name}, List]} -> @@ -464,8 +490,9 @@ initialize_constraints([Constr|Rest], MFA, RecDict, ExpTypes, AllRecords, Acc) - end. constraints_fixpoint(Constrs, MFA, RecDict, ExpTypes, AllRecords) -> + VarTable = erl_types:var_table__new(), VarDict = - constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, dict:new()), + constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarTable), constraints_fixpoint(VarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords). constraints_fixpoint(OldVarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords) -> @@ -473,11 +500,11 @@ constraints_fixpoint(OldVarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords) -> constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, OldVarDict), case NewVarDict of OldVarDict -> - DictFold = + Fun = fun(Key, Value, Acc) -> [{subtype, erl_types:t_var(Key), Value}|Acc] end, - FinalConstrs = dict:fold(DictFold, [], NewVarDict), + FinalConstrs = maps:fold(Fun, [], NewVarDict), {FinalConstrs, NewVarDict}; _Other -> constraints_fixpoint(NewVarDict, MFA, Constrs, RecDict, ExpTypes, AllRecords) @@ -487,18 +514,18 @@ final_form(Form, ExpTypes, MFA, AllRecords, VarDict) -> from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarDict). from_form_with_check(Form, ExpTypes, MFA, AllRecords) -> - from_form_with_check(Form, ExpTypes, MFA, AllRecords, dict:new()). + VarTable = erl_types:var_table__new(), + from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarTable). from_form_with_check(Form, ExpTypes, MFA, AllRecords, VarDict) -> Site = {spec, MFA}, - erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords, - VarDict), + erl_types:t_check_record_fields(Form, ExpTypes, Site, AllRecords, VarDict), erl_types:t_from_form(Form, ExpTypes, Site, AllRecords, VarDict). constraints_to_dict(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarDict) -> Subtypes = constraints_to_subs(Constrs, MFA, RecDict, ExpTypes, AllRecords, VarDict, []), - insert_constraints(Subtypes, dict:new()). + insert_constraints(Subtypes). constraints_to_subs([], _MFA, _RecDict, _ExpTypes, _AllRecords, _VarDict, Acc) -> Acc; @@ -583,8 +610,8 @@ general_domain(List) -> general_domain(List, erl_types:t_none()). general_domain([{Sig, Constraints}|Left], AccSig) -> - Dict = insert_constraints(Constraints, dict:new()), - Sig1 = erl_types:t_subst(Sig, Dict), + Map = insert_constraints(Constraints), + Sig1 = erl_types:t_subst(Sig, Map), general_domain(Left, erl_types:t_sup(AccSig, Sig1)); general_domain([], AccSig) -> %% Get rid of all variables in the domain. |